Version 8.12.0.2 - May 21, 2026 - PetPoint Field-Exclusion Bug Fix + Navigation Description Font-Size Fix

8.12.0.2 is a multi-file bug fix release that addresses two unrelated issues affecting PetPoint sites.

Issue A: admin-configured field exclusions (Primary Breed, etc.) were not being applied to results, filter dropdowns, or card data attributes on six PetPoint search templates. The shared resolver (FieldExclusionFilterTrait::filterAnimalsByConfiguredFields) was wired into AF and RG search templates, the PP featured-search-* templates, but was never called from the six PP templates that iterate XML directly in processResults(). Symptom: a breed listed in the admin exclusion list still showed up in results and in the filter dropdown. Display-side label mapping (filter value -> custom label) worked because that happens per-card at render time via resolveFilteredFieldValue(); only the exclusion path was broken.

Issue B: on the universal-details-navigation template (PP only template using it today), the admin "Description Text" font-size setting had no effect because the navigation-template CSS hardcoded font-size: 0.95rem on .pmp-details-description-value, overriding the CSS custom property the admin setting writes (--pmp-font-size-detail-description).

+ Item 1: Plugin header
    * pet-match-pro.php Version 8.12.0.1 -> 8.12.0.2.
    * pet-match-pro.php Constants::VERSION '8.12.0.1' -> '8.12.0.2'.
    * readme.txt Stable tag 8.12.0.1 -> 8.12.0.2.

+ Item 2: PetPoint search-template field-exclusion fix (6 templates)
    * Pattern applied: after constructor populates $this->resultsData from XML, call $this->resultsData = $this->filterAnimalsByConfiguredFields($this->resultsData); then switch processResults() to iterate $this->resultsData instead of re-extracting from XML.
    * public/templates/pp/universal-search-structured.php - added filter call after resultsData population in constructor. processResults() now iterates $this->resultsData. getUniqueFilterValues() override switched from re-extracting via extractSearchItem() to using $this->resultsData so filter dropdowns no longer show excluded values.
    * public/templates/pp/adopt-search-default.php - same pattern. processResults() while-loop now reads $this->resultsData[$counter] instead of calling extractAdoptableSearchItem() each iteration.
    * public/templates/pp/lost-search-default.php - same pattern. Constructor was already lowercasing and enriching items; the filter call slots in after that block. processResults() while-loop reads $this->resultsData[$counter] directly (lowercased+enriched copies), dropping the inline extractXmlItems() + array_combine() call.
    * public/templates/pp/found-search-default.php - mirror of lost-search-default.php.
    * public/templates/pp/universal-search-default.php - same pattern. processResults() while-loop reads $this->resultsData[$counter] instead of calling extractXmlItem() each iteration.
    * public/templates/pp/adopt-celebration-similar.php - added filter call after resultsData population and before the celebration-species filter block (so species filtering operates on the already-exclusion-filtered set). processResults() was already reading $this->resultsData[$counter], no other change needed.

+ Item 3: PP navigation detail description font-size fix
    * public/css/pet-match-pro-styles.css line 5419. Old shape: `font-size: 0.95rem;`. New shape: `font-size: var(--pmp-font-size-detail-description, 0.95rem);`.
    * public/css/pet-match-pro-styles.min.css - same change applied to the minified build (find/replace inside the .pmp-template-details-navigation .pmp-details-description-value rule block).
    * The base .pmp-details-description selector already used the custom property; the navigation-template override (.pmp-template-details-navigation .pmp-details-description-value) had higher specificity and a hardcoded value, silently winning over the admin setting. Other detail-template overrides (.pmp-template-cpa, .pmp-template-wide) already use the var, so navigation was the lone outlier.

+ Item 4: Files NOT changed
    * No AF or RG templates - audited all 8 AF and 5 RG search-list templates; every one already calls filterAnimalsByConfiguredFields() in the constructor or before iteration.
    * No PetPoint featured-search-default/carousel/compact - those three already had the filter call.
    * No detail templates other than universal-details-navigation CSS - the *-similar detail templates delegate the related-animal grid to a configured search template, so they inherit the new exclusion behavior automatically.
    * No admin UI, option keys, shortcode parameters, KB articles, JS, or schema.
    * No partner API classes, no AllApi (filterAnimalsByConfiguredFields lives in the trait the templates already use), no analytics.

Behavioral notes for upgrading operators:
- Sites that previously configured Primary Breed (or any other field-filter group) exclusions on a PetPoint search will now see excluded animals removed from search results, from filter dropdowns, AND from the data-* attributes used by client-side JS filtering. Sites that did not configure exclusions will see no behavioral change.
- Sites using the universal-details-navigation template (PP) will see the admin "Description Text" font size take effect immediately. Sites that left this setting at default will see no change.
- After upload, clear OPcache / LiteSpeed cache and reload affected pages. The version bump invalidates the ?ver= cache-buster on the enqueued CSS so browsers will refetch the stylesheet.
- No database migration, no operator action beyond the file upload.

Verification:
- demo-pp.petmatchpro.com with [pmp-search type=adopt template="universal-search-structured"] - configure an admin Primary Breed exclusion entry matching a breed visible in the unfiltered results, save, reload search; the breed should disappear from results AND from the breed filter dropdown.
- Repeat on each affected template (universal-search-default, adopt-search-default, lost-search-default, found-search-default, adopt-celebration-similar) to confirm parity.
- Visit a PetPoint detail page rendered with the universal-details-navigation template; change admin "Description Text" font-size to a large value; reload - the description text should grow accordingly.
- Regression check on the AF and RG search templates: confirm exclusion behavior is unchanged from 8.12.0.1.

Version 8.12.0.1 - May 21, 2026 - AF Filter-Widget Search Name Render Fix

8.12.0.1 is a single-file bug fix for the AnimalsFirst universal-search-filter-widget template. Animal names were not rendering on result cards when the template was selected as the active adopt/lost/found search template, because the name-in-config detection compared the lowercased name field keys against the numeric indices of the allowedFields array (via an erroneous array_keys() wrapper) instead of against the field-name values themselves. Every other AF, PetPoint, and RescueGroups search template uses the correct values-based intersect; filter-widget was the lone outlier.

+ Item 1: Plugin header
    * pet-match-pro.php Version 8.12.0 -> 8.12.0.1.
    * pet-match-pro.php Constants::VERSION '8.12.0' -> '8.12.0.1'.
    * readme.txt Stable tag 8.12.0 -> 8.12.0.1.

+ Item 2: AF universal-search-filter-widget name-render fix
    * public/templates/af/universal-search-filter-widget.php line 438. Old shape: `array_intersect($nameFieldKeys, array_map('strtolower', array_keys($this->allowedFields)))`. New shape: `array_intersect($nameFieldKeys, array_map('strtolower', $this->allowedFields))`.
    * `$this->allowedFields` is a flat list of configured field names (e.g. ['name', 'breed', 'gender', 'age']), not an associative map. array_keys() returned the numeric indices [0,1,2,3], so 'name' never intersected and $nameInConfig was always false. The standalone name section at line 440 (`buildNameSection()`) was therefore unreachable on this template.
    * Symptom on the live site: filter-widget search cards rendered photo + sex + age + location with no animal name above the details block.
    * Pattern verified across all 13 other search templates (af universal-search-default, universal-search-no-filter, featured-search-default/carousel/compact; pp universal-search-default, featured-search-default/carousel/compact; rg adopt-search-default, adopt-search-carousel, adopt-search-compact; af adopt-celebration-similar, rg adopt-celebration-similar) - every one uses `array_map('strtolower', $this->allowedFields)` without array_keys(). Filter-widget was the sole regression.

+ Item 3: Files NOT changed
    * No other AF templates - the bug was isolated to filter-widget.
    * No PetPoint or RescueGroups templates - both partners' search templates use the correct pattern.
    * No admin UI, option keys, shortcode parameters, KB articles, JS, or CSS.
    * No partner API classes, no AllApi, no analytics, no schema.

Behavioral notes for upgrading operators:
- Sites on 8.12.0 with `search_template_adopt` (or lost/found) set to universal-search-filter-widget were the only affected sites. Other AF search templates rendered names correctly on 8.12.0.
- After upload, clear OPcache / LiteSpeed cache and reload the search page; animal names will appear above the breed/age/gender/location block.
- No database migration, no operator action beyond the file upload.

Verification:
- demo-af.petmatchpro.com with [pmp-search type=adopt template="universal-search-filter-widget"] - confirm name renders on each card above the details block.
- Regression check on demo-af with other AF search templates (universal-search-default, featured-search-default) - confirm name rendering is unchanged from 8.12.0 behavior.

Version 8.12.0 - May 20, 2026 - Typed Class Constants + json_validate() Adoption

8.12.0 is the follow-on to 8.11.5 that the PHP 8.3 floor unblocked. Two language features that only exist on PHP 8.3 are adopted across the codebase: typed class constants (PHP 8.3 RFC) on every public constant in the main plugin file, and json_validate() in the AnimalsFirst and RescueGroups response parsers. Neither change is functional - both are defense-in-depth refactors that move correctness checks from "runtime assertion" to "loadable-class-time verification" (for the constants) and from "decode-then-inspect-error-state" to "validate-then-decode-once" (for the JSON parsers).

No admin UI changes, no template changes, no schema changes, no shortcode parameter changes, no partner API surface changes. Safe drop-in upgrade for sites already on PHP 8.3 + 8.11.5.

+ Item 1: Plugin header
    * pet-match-pro.php Version 8.11.5 -> 8.12.0.
    * pet-match-pro.php Constants::VERSION '8.11.5' -> '8.12.0'.
    * readme.txt Stable tag 8.11.5 -> 8.12.0.

+ Item 2: json_validate() adoption in AnimalsFirst parser
    * includes/af/class-pet-match-pro-af-api.php lines ~638-659. The old shape was `json_decode($body, true)` followed by `if ($jsonArray === null && !empty($body)) { $jsonError = json_last_error_msg(); ... }`. The new shape is `if (!empty($body) && !json_validate($body)) { $jsonError = json_last_error_msg(); ... }` followed by the single `json_decode($body, true)` call that runs only on validated input.
    * Empty body is intentionally preserved as a non-error case - downstream code handles a null/empty decode result with `?? []`. The `!empty($body) &&` short-circuit guard is retained for that reason.
    * Existing error taxonomy preserved: Connection Error / Authentication Error / API Error / Parse Error labels and message templates unchanged. The Parse Error branch still receives the same json_last_error_msg() detail string because json_validate() populates json_last_error_msg() on failure (per the PHP 8.3 RFC).
    * The three other json_decode() call sites in the file (lines ~700, ~734, ~2050) were NOT changed because they have no json_last_error() pairing - they intentionally use `?? []` fallbacks for non-critical response paths (filter values, paginated continuation, secondary filter fetch). The user instruction specifically scoped the refactor to json_decode + json_last_error pairs.

+ Item 3: json_validate() adoption in RescueGroups parser
    * includes/rg/class-pet-match-pro-rg-api.php lines ~511-531 rewritten with the same validate-then-decode shape as Item 2.
    * The private `getJsonError()` helper (a match expression over the seven JSON_ERROR_* constants) was removed. It was the only call site for json_last_error() in this file, and json_last_error_msg() returns equivalent operator-readable strings without the manual match table.
    * Net -14 lines in this file (the match expression + its callers).
    * Error taxonomy preserved identically to AF: Connection Error / Authentication Error / API Error / Parse Error.

+ Item 4: Typed class constants in pet-match-pro.php
    * Every public constant in the following ten classes received an explicit `public const string`, `public const int`, or `public const array` type declaration. 866 constants in total.
        - Constants (lines ~73-397) - 207 constants
        - Settings (lines ~402-774) - paths, option keys, method type identifiers, field labels
        - Shortcodes (lines ~775-849) - shortcode names and parameter names
        - LevelPrefix (lines ~850-872) - license-level lookup key prefixes
        - LabelPrefix (lines ~873-887) - label option key prefixes
        - ValuePrefix (lines ~888-897) - value option key prefixes
        - MethodTypes (lines ~898-931) - partner-specific API method name mappings
        - AnimalsFirstFields (lines ~932-1028) - AF API field name constants
        - RescueGroupsFields (lines ~1029-1177) - RG API field name constants
        - PetPointFields (lines ~1178-1308) - PP API field name constants
    * The IntegrationPartner enum at line ~1309 was intentionally skipped - backed enum cases (`case PetPoint = 'PetPoint';`) are already statically typed by the enum's `: string` backing-type declaration.
    * `public const PATH = __DIR__;` and `public const PATH_FILE = __FILE__;` were typed as `string` (both magic constants resolve to strings at compile time).
    * `INTEGRATION_PARTNERS`, `CURRENCY_SYMBOL_MAP`, `DB_ANALYTICS_CONVERSION_ACTION_TYPES`, `SEX_MAP`, `FIELD_ALIASES` (in AF, RG, and PP field classes), and `ZERO_IS_EMPTY_FIELDS` (in AF and PP field classes) were typed as `array`.
    * All other constants are `string` or `int` literal values; the transformation was mechanical and applied via regex over the whole file.

+ Item 5: Why typed constants matter even though the values are literals
    * Catching a mis-typed override at child-class compile time rather than at the call site. If a future subclass ever shadowed `public const int FREE_LEVEL = 3;` with a string value, PHP 8.3 will fatal at class-load time with a covariant-type violation. Without the type, the violation surfaces wherever the constant is consumed and the type mismatch happens to break something - days or releases later.
    * Static-analysis tools (Psalm, PHPStan, Rector) read the declared type as a stronger signal than inferring "probably string" from the literal. Future refactors that touch these constants get sharper IDE and CI feedback.
    * Required prerequisite for the 8.13.0 AI Breed Normalization backlog, where the new `Settings::FILTER_AI_NORMALIZE`, `Settings::FILTER_AI_LAST_RUN`, and `Settings::FILTER_AI_SUGGEST` constants will be added alongside their existing siblings - the new constants will inherit the typed style that 8.12.0 establishes.

+ Item 6: Files NOT changed
    * No PetPoint API class. PP uses SimpleXMLElement, not json_decode, so the json_validate refactor does not apply.
    * No partner template files (search, detail, poster).
    * No admin settings UI, option keys, shortcode parameters, or KB articles.
    * No JavaScript or CSS.
    * No translation strings - the json_validate refactor reuses the existing AnimalsFirst and RescueGroups Parse Error message templates verbatim, so .pot regeneration is not required for this release.
    * No analytics schema, rollup, or queue logic.

Behavioral notes for upgrading operators:
- Sites already on PHP 8.3 + 8.11.5: upload the three changed files (pet-match-pro.php, includes/af/class-pet-match-pro-af-api.php, includes/rg/class-pet-match-pro-rg-api.php) plus README.txt and CHANGE-LOG.txt and you are done.
- Sites still on PHP 8.1 or 8.2: 8.11.5's activation hook already fails closed at those versions; 8.12.0 inherits that floor and additionally cannot be loaded by PHP < 8.3 because typed class constants are a parse-time PHP 8.3 syntax feature. Do not upload 8.12.0 until PHP is upgraded.
- No database migration, no rewrite-rule flush, no operator action beyond the file upload.

Verification on the three demo sites (planned post-upload):
- demo-pp.petmatchpro.com: smoke-test [pmp-search type=adopt] and [pmp-details] - the constant-typing change touches PetPointFields, so the partner that exercises those constants most heavily should be verified first.
- demo-af.petmatchpro.com: smoke-test the AF search response path through a deliberately malformed AnimalsFirst response to confirm the Parse Error message text and the admin notice carry the same json_last_error_msg() detail as before.
- demo-rg.petmatchpro.com: same Parse Error smoke test against the RG endpoint to confirm the removal of getJsonError() did not regress the operator-visible message.
- Plugin Check pass on all three.

Note on local PHP availability:
- The user's local Windows workstation does not have a PHP binary on PATH, so the `php -l` verification step requested in the implementation prompt was skipped locally. The typed-constant transformation was applied via a single PowerShell .NET regex pass against the raw file content (UTF-8, no BOM, CRLF preserved) and verified by re-reading the changed regions plus a final grep that confirms zero untyped `public const` declarations remain in the file. Authoritative `php -l` verification should run on the first upload to demo-pp / demo-af / demo-rg.

Version 8.11.5 - May 20, 2026 - WordPress 7.0 Compatibility + PHP 8.3 Floor

8.11.5 is a one-purpose release: prepare PetMatchPro for WordPress 7.0 (released May 20, 2026) by bumping the "Tested up to" header to 7.0 and raising the minimum PHP requirement from 8.1 to 8.3 to align with WordPress 7.0's recommended PHP version. No functional changes, no schema changes, no template changes, no admin UI changes. Safe drop-in for any site already on PHP 8.3 or higher.

The Connections Screen / AI Connectors API introduced in WP 7.0 was intentionally NOT adopted in this release - that screen is scoped to AI provider keys (Anthropic, Google, OpenAI), not shelter-management API keys. The Connectors API metadata explicitly notes that non-AI connector types are reserved for "future releases", so PetMatchPro's PetPoint / AnimalsFirst / RescueGroups keys remain in their current pet-match-pro-general option storage where they belong. Revisit when WP introduces a generic (non-AI) connector type.

The 8.12.0 release will follow with the typed-class-constants pass and json_validate() adoption in the AF and RG response parsers - both PHP 8.3-only language features that depend on this floor bump.

+ Item 1: Plugin header
    * pet-match-pro.php Version 8.11.4 -> 8.11.5.
    * pet-match-pro.php Requires PHP 8.1 -> 8.3.
    * pet-match-pro.php Requires at least 6.0 -> 6.5 (aligned with readme.txt which already said 6.5).
    * pet-match-pro.php new "Tested up to: 7.0" header line.
    * pet-match-pro.php Constants::VERSION '8.11.4' -> '8.11.5'.

+ Item 2: Runtime PHP version checks
    * pet-match-pro.php line 42 (load-time check): version_compare floor '8.1.0' -> '8.3.0', user-facing message updated.
    * pet-match-pro.php line ~1824 (activation hook): same floor bump, same message update.
    * Both checks deactivate the plugin with a clear admin notice if PHP is below 8.3 - existing protective pattern, only the floor number changed.

+ Item 3: readme.txt
    * Tested up to 6.9 -> 7.0.
    * Stable tag 8.11.4 -> 8.11.5.
    * New "Requires PHP: 8.3" header (was previously absent from readme.txt - the value lived only in the plugin file).
    * New = 8.11.5 = Upgrade Notice block explaining the PHP floor change.

+ Item 4: CLAUDE.md
    * Coding-standards line "PHP 8.1+" -> "PHP 8.3+" with a note explaining the rationale and the version it shipped in.

+ Item 5: Files NOT changed
    * No partner API code (PP / AF / RG).
    * No template files.
    * No admin settings, option keys, or shortcode parameters.
    * No analytics schema or rollup logic.
    * No JavaScript or CSS.
    * No translation strings (the PHP-version messages were edited in place - text-domain wrappers unchanged, so .pot regeneration is not required for this release).

Behavioral notes for upgrading operators:
- Sites already on PHP 8.3 or higher: upload the four changed files (pet-match-pro.php, readme.txt, CLAUDE.md, CHANGE-LOG.txt) and you are done.
- Sites still on PHP 8.1 or 8.2: do NOT upload 8.11.5 until you have upgraded PHP. The activation hook will fail closed and present a clear admin notice; the existing 8.11.4 install continues to run unchanged on PHP 8.1+ if you need to defer the bump.
- Sites running WordPress 6.5 - 6.9: unaffected. The "Tested up to: 7.0" change does not raise the minimum supported WP version.
- No database migration, no rewrite-rule flush, no operator action beyond the file upload.

Verification on a WordPress 7.0 demo site (planned for the RescueGroups demo once 7.0 is generally available):
- Activate cleanly on PHP 8.3 + WP 7.0.
- Confirm the admin "Modern" theme renders the Settings tabs, wizard, Shortcode Builder, and accordions correctly.
- Run Plugin Check from wordpress.org and resolve any flagged items.
- Smoke-test [pmp-search] and [pmp-details] for adopt on the RG demo - the plugin's primary public-facing surface.

Version 8.11.4 - May 18, 2026 - Unified Search Template Field Resolution

8.11.4 completes a long-running consistency cleanup across the partner search templates. Before this release, PetPoint search templates received their display-field list as a pre-resolved $allowedFields array via constructor injection, while AnimalsFirst and RescueGroups templates called the trait method getDetailsParam() and parsed a comma-separated string locally. AllApi::showDetails() applies license filtering and name-first ordering to the field list, so AF and RG were bypassing that pipeline and re-doing the resolution downstream. An 8.11.x hotfix had previously promoted AllApi::getDetailListString() to public so the trait could fall back to admin-configured fields - a stopgap that worked but kept the divergence intact.

The 8.11.4 refactor brings AF and RG into line with the PP pattern: outputSearch() in each partner API class now derives $allowedFields = array_keys($searchResultDetails), passes it to the template via constructor, and the template consumes the flat indexed array directly. The getDetailsParam() method is removed from SearchTemplateTrait and getDetailListString() reverts to private. No admin settings change, no partner API surface change, no template file renames.

This is the unification you would do if you were redesigning the search template contract from scratch - one resolution path, one place where license filtering happens, no string parsing inside templates.

+ Item 1: Partner API changes (2 files)
    * includes/af/class-pet-match-pro-af-api.php outputSearch() now derives $allowedFields immediately after $searchResultLabels is unpacked from prepareSearchContext().
    * includes/rg/class-pet-match-pro-rg-api.php outputSearch() does the same.
    * Mirrors the PetPoint pattern that has been in place at includes/pp/class-pet-match-pro-pp-api.php:1171 since the original 8.x refactor.

+ Item 2: AF template migrations (8 files)
    * public/templates/af/universal-search-default.php - the worked example. Renames $resultFields property to $allowedFields, switches the card-rendering chain (buildAnimalCard / buildDetailsSection / getDisplayFields) from string $details to array $details, rewrites getDisplayFields() to iterate the array directly instead of explode-on-comma string parsing.
    * public/templates/af/universal-search-no-filter.php - same migration plus a name-heading parity fix in buildAnimalCard() that brings it in line with universal-search-default's pre-existing !$hasShortcodeDetails guard. Without the parity fix, the no-filter template would have continued to render the name as a heading even when a details="name,..." shortcode was active, causing a duplicate name render.
    * public/templates/af/universal-search-filter-widget.php - same migration plus the same name-heading guard fix.
    * public/templates/af/universal-search-structured.php - Variant B migration. Renames the pre-existing $searchResultDetails constructor parameter (which held the assoc array [fieldKey => label]) to $allowedFields (the flat key list). Internal call sites that did array_key_exists($key, $this->searchResultDetails) become in_array($key, $this->allowedFields, true). The preg_replace + explode re-ordering block at line 686 collapses to array_map('strtolower', $this->allowedFields).
    * public/templates/af/featured-search-default.php - Variant A migration with intentional behavior alignment. The wholesale getDetailsToDisplay() method (a 6-line CSV parser) is replaced by the canonical getDisplayFields() pattern; buildDetailsSection's loop is restructured to consume the assoc [fieldKey => fieldLabel] output. Two user-visible side effects come with the unification: label fallback for fields without a configured label changes from ucfirst($fieldKeyLower) to ucwords(str_replace('_', ' ', $field)) so date_of_birth now renders as "Date Of Birth" instead of "Date_of_birth"; and a details=name,... shortcode now places the name inline in the field section instead of suppressing it. Both bring this template into line with the canonical pattern and were explicitly approved during review.
    * public/templates/af/featured-search-carousel.php - same Variant A canonical-restructure migration as featured-search-default.
    * public/templates/af/featured-search-compact.php - same migration. The initial commit (08c9849) introduced a UTF-8 BOM (EF BB BF) from a PowerShell byte-level write that triggered a fatal "strict_types declaration must be the very first statement" error on demo-af.petmatchpro.com; the BOM was stripped in a follow-up commit (d80d4d8). Also re-derived $hasShortcodeDetails from $this->apiFunction->hasShortcodeDetails (was incorrectly reading $this->searchDetails[Shortcodes::DETAILS]) and added the name-overlay !$hasShortcodeDetails guard.
    * public/templates/af/adopt-celebration-similar.php - unique variant where getDisplayFields() previously returned [key, label, value] dicts; collapsed to the canonical [fieldKey => fieldLabel] assoc with value resolution moved inline into buildDetailsSection's foreach. Pre-existing U+FFFD replacement chars at line 146 cleaned up to " - " per the no-em-dash rule.

+ Item 3: RG template migrations (5 files)
    * public/templates/rg/adopt-search-default.php - Variant B migration mirroring AF universal-search-structured (rename $searchResultDetails to $allowedFields, swap array_key_exists for in_array, replace preg_replace+explode with array_map('strtolower', $this->allowedFields)).
    * public/templates/rg/adopt-search-carousel.php - same Variant B migration. The initial commit (c8d2306) introduced a double UTF-8 BOM (EF BB BF EF BB BF) at file start, stripped in a follow-up commit (73f233b). The double BOM appears to have come from the pre-migration file already carrying one BOM combined with a PowerShell write that added another.
    * public/templates/rg/adopt-search-compact.php - same Variant B migration.
    * public/templates/rg/adopt-search-structured.php - Variant B migration plus dead-code removal. The $fieldLevels property, getFieldLevels() method, $defaultFieldLabels property, getDefaultFieldLabels() method, getFriendlyFieldLabel() method, and unused LabelPrefix / LevelPrefix use statements were all reachable only through the old code paths that the migration replaces. Net -219 lines.
    * public/templates/rg/adopt-celebration-similar.php - same Variant B migration with the canonical-restructure shape (collapsed value-resolution second loop into the foreach).

+ Item 4: Trait cleanup
    * public/templates/includes/class-pet-match-pro-search-template-trait.php - getDetailsParam() method removed. No remaining callers across the codebase (verified by repository-wide grep before deletion).

+ Item 5: AllApi visibility revert
    * includes/class-pet-match-pro-all-api.php - getDetailListString() reverted from public to private. The hotfix that promoted it to public is no longer needed because the trait method that depended on it is gone. Only the internal AllApi caller at all-api.php:1134 remains.

+ Item 6: Files NOT changed
    * No PetPoint templates were touched. PP was already on the unified $allowedFields pattern and served as the reference.
    * No detail-page templates (*-details-*.php), poster templates, or details-navigation templates were modified. The detail flow uses BaseDetailTemplate and a different field-resolution path.
    * No admin settings, option keys, partner API surfaces, or shortcode parameters changed.

Behavioral notes for upgrading operators:
- Sites with custom themes that override AF or RG search templates from wp-content/themes/<theme>/petmatchpro/... will need their overrides regenerated. The constructor signatures of every migrated AF and RG template changed. Sites that use only the plugin's bundled templates are unaffected.
- The label-fallback formatting change in featured-search-default applies only to fields that have no configured admin label. Configured labels are honored unchanged.
- The name-in-field-section behavior change in featured-search-default applies only when a details= shortcode parameter explicitly includes the name field. Default rendering (admin-configured field list) is unchanged.

Version 8.11.3 - May 15, 2026 - Universal-Details-Default Method Detection (Second Hotfix)

8.11.2 cleared the constructor-order fatal but exposed a second bug: visiting an AF /pmp/lost/... or /pmp/found/... URL with the new universal-details-default template selected rendered the ADOPT layout (no descriptive fallback name, no lost/found CTAs, title placed inside content instead of above the wrapper). The fix landed but the dispatch was wrong - users got a clean page that was silently the wrong layout.

Root cause: 8.11.1 / 8.11.2 read $_GET[Settings::METHOD] to drive the layout branch. That value is normally set by SeoManager::onParseRequest (for pretty permalinks) or by the original HTTP query string (for legacy URLs), but Divi's block-rendering pipeline routes the shortcode through MultiView and Loop modules that re-render content in contexts where $_GET is not reliable - the lost / found URLs reached the template with an empty $_GET[Settings::METHOD], so the match() fell through to the adopt default. Reproduced on demo-af.petmatchpro.com (Divi 5.x).

Fix: stop reading $_GET. The PMP API layer already resolves the canonical method type before requiring the template - `$methodType = $this->extractMethodType($callFunc);` at AF api line 1564 and `$methodType = ltrim($keySuffix, '_');` at PP api line 1889. That local variable is in scope when the template is `require`d at AF line 1729 / PP line 2013 because PHP include/require inherits the calling method's variable scope. The template now reads $methodType directly, lower-cases it, and runs the same match() it always did.

Why this is robust across all paths:
- Pretty permalinks: SeoManager rewrites the URL, handleDetailsShortcode reads $_GET['method'], passes the value to createDetails as $callFunc, AF/PP extracts $methodType from $callFunc. Template gets the resolved value.
- Legacy ?method=lost URLs: $_GET['method'] populated by PHP, same chain through handleDetailsShortcode -> $callFunc -> $methodType. Template gets the resolved value.
- Divi MultiView / Loop / page-builder block re-rendering: the API call chain runs once per shortcode invocation and the $methodType local survives the call. Even if $_GET is empty or modified by the time the template renders, $methodType is still the value AF/PP authoritatively computed.

+ Item 1: AF template fix - public/templates/af/universal-details-default.php
    * Removed: $urlMethod = isset($_GET[Settings::METHOD]) ? strtolower(sanitize_text_field(wp_unslash($_GET[Settings::METHOD]))) : '';
    * Added: $resolvedMethodRaw = isset($methodType) && is_string($methodType) ? strtolower($methodType) : '';
    * The match() statement is unchanged - it still maps the resolved string to METHOD_TYPE_LOST / METHOD_TYPE_FOUND / METHOD_TYPE_ADOPT default. AF preferred sub-types (outcome / foster / stray / rehome / pending / all / other) continue to fall through to the adopt branch as designed.
    * Updated the leading comment block to document the rationale and reference the 8.11.2 Divi regression.

+ Item 2: PP template fix - public/templates/pp/universal-details-default.php
    * Identical fix. PP API at line 1889 also sets a $methodType local before the require at line 2013, so the same in-scope read works.

+ Item 3: No new error handling
    * The is_string() guard on $methodType is the only defensive check - covers the theoretical case where the template is included from a context that does not set the local (e.g. a third-party theme override that calls require directly). Cheap, no functional impact when the value is present.

+ Item 4: Version constants
    * pet-match-pro.php line 19: Plugin header "Version:" 8.11.2 -> 8.11.3.
    * pet-match-pro.php Constants::VERSION '8.11.2' -> '8.11.3'.
    * README.txt: Stable tag 8.11.2 -> 8.11.3; new = 8.11.3 = Upgrade Notice block.

+ Item 5: Operator action required
    * Upload the two template files and version-bumped pet-match-pro.php. Auto-flush fires on the version bump (8.10.1.5+ behavior).
    * Verify: visit an AF /pmp/lost/{slug}/ URL with the universal-details-default template configured for lost; the title appears ABOVE the wrapper div, the display name is "Lost {Species} #{ID}" when no actual name exists, and the CTAs are call+email+return. Repeat for /pmp/found/.

+ Item 6: Out of scope (followups)
    * Other templates that read $_GET in similar ways: adopt-default.php and friends use $_GET only for analytics ($animalId display), not for method-type routing - they hardcode their method via getMethodType(). No bug analog there. Still worth a quick grep next time someone touches the template loader, just in case a future template introduces method-type branching that reaches for $_GET.
    * universal-details-navigation.php and universal-details-poster.php: also accept method-type input via shortcode parameters and have their own method-aware logic. They were stable in 8.11.0 and earlier so the $_GET regression is specific to the 8.11.1+ combo template, not a class-wide pattern. No fix needed.

----

Version 8.11.2 - May 15, 2026 - Hotfix for 8.11.1 Constructor-Order Fatal

Single-bug hotfix. 8.11.1 shipped both new universal-details-default templates with the anonymous class assigning $this->resolvedMethod AFTER parent::__construct(), but BaseDetailTemplate::__construct() calls loadFieldLevels() which calls $this->getMethodType() to build the field-levels filename. The anonymous getMethodType() override reads $this->resolvedMethod, which was still uninitialized at that point - PHP 8.1+ typed-property strict-init rules turn that read into a fatal "Typed property ... must not be accessed before initialization." Every AF and PP detail-page hit using the new template fataled before rendering a single byte of HTML.

Fix: move `$this->resolvedMethod = $resolvedMethod;` to the FIRST statement of the constructor, before `parent::__construct($api, $animalDetails)`. PHP allows assignment to a typed property without prior initialization - the strict-init check fires only on READ. Once parent::__construct runs, its loadFieldLevels() call now sees the property as initialized and returns the correct method-specific filename.

Detected via WP_DEBUG error log on demo-af.petmatchpro.com immediately after 8.11.1 deploy. No customer impact prior to that because demo-af was the first site to receive the build.

+ Item 1: Constructor order swap - public/templates/af/universal-details-default.php
    * Anonymous class __construct(): assign $this->resolvedMethod first, then call parent::__construct().
    * Added a leading comment block warning future editors that the order is load-bearing - the parent constructor's loadFieldLevels() reaches into getMethodType() before parent::__construct() returns.

+ Item 2: Constructor order swap - public/templates/pp/universal-details-default.php
    * Identical fix, identical comment. PP template was vulnerable to the same fatal but never had a chance to trigger it - we caught the AF case first.

+ Item 3: No other files touched
    * Existing per-method templates (adopt-default.php, lost-default.php, found-default.php) are unaffected. Their anonymous getMethodType() returns a literal constant (e.g., `return Settings::METHOD_TYPE_ADOPT;`) - no instance state, no init-order dependency.
    * Other anonymous-class templates with state (universal-details-navigation.php, universal-details-poster.php) need to be audited for this pattern. None reported failing today but they were written before strict typed-property init was a routine concern. Out of scope for this hotfix - filed as a followup audit.

+ Item 4: Version constants
    * pet-match-pro.php line 19: Plugin header "Version:" 8.11.1 -> 8.11.2.
    * pet-match-pro.php Constants::VERSION '8.11.1' -> '8.11.2'.
    * README.txt: Stable tag 8.11.1 -> 8.11.2; new = 8.11.2 = Upgrade Notice block describing the fatal and the fix.

+ Item 5: Operator action required
    * Upload the two template files and the version-bumped pet-match-pro.php. The 8.10.1.5 auto-flush fires on the version bump - no manual permalinks save needed.
    * Verify: visit any AF detail URL configured with universal-details-default; no fatal in PHP error log, page renders with the appropriate method layout.

+ Item 6: Out of scope (followups)
    * Audit pass on other anonymous-class detail templates (universal-details-navigation, universal-details-poster) for parent-constructor-reads-subclass-state init-order traps. None reported broken, but the pattern is fragile and worth a sweep before adding more templates that store state.

----

Version 8.11.1 - May 15, 2026 - Universal-Details-Default Template (PetPoint + AnimalsFirst)

New free-tier detail template that solves the AF-preferred-animals "no simple default" gap surfaced after 8.10.1.5. Until now, an AF preferred animal (type=other / outcome / foster / stray / rehome / pending / all) routed to either the heavy 890-line universal-details-navigation template (requires quick_fields, title_fields, stats_row, stats_full shortcode params to be useful) or universal-details-poster - no simple "just show the animal" option existed. 8.11.1 adds universal-details-default.php for both PetPoint and AnimalsFirst, mirroring how universal-search-default.php has been the catch-all search template for years.

The template is method-aware: a single file internally branches between adopt / lost / found layouts based on the URL method ($_GET['method']), so picking ONE template handles all the partner's method types correctly. AF preferred and PP list both render in adopt layout (since they share the adoption-data shape).

Why "method-aware combo" instead of a static adopt-style template: ~90% of adopt-default and lost-default are identical, but the differences (title position, fallback display name, CTAs) are user-visible. A static template would either skip lost-default's "Lost {Species} #{ID}" fallback name (broken for unnamed lost intakes) OR render lost CTAs on adopt animals (confusing). The combo template respects each method's existing conventions.

+ Item 1: New AnimalsFirst template - public/templates/af/universal-details-default.php (~180 lines)
    * Method resolution: $_GET[Settings::METHOD] sanitized and matched against METHOD_TYPE_LOST / METHOD_TYPE_FOUND. Everything else routes to adopt layout (covers ADOPT, PREFERRED, and all AF preferred sub-types).
    * Anonymous class stores the resolved method and returns it from getMethodType() so base-class field-level / hide-empty / label lookups use the correct method type. The anonymous class also overrides shouldSkipField() for AF field constants and getAnimalId() for the lost/found fallback name.
    * Body branches via $isMethodAdopt / $isMethodLost / $isMethodFound flags:
        - adopt: title inside .pmp-details-content; renderVideoPlayer + renderVideos with .pmp-details-thumbs-column wrapper; renderIcons + renderDescription; conversion button "meet_greet"; other button "return".
        - found: title above wrapper; thumbs render direct (no column wrapper); conversion buttons "call" + "email" with custom labels ("Call Us" / "Email Us"); other button "return". Matches found-default.php behavior.
        - lost: title above wrapper; thumbs render direct; conversion buttons "call" + "email"; other button "return". Matches lost-default.php behavior.
    * Wrapper data-method-type uses the partner-specific MethodTypes::ANIMALSFIRST_{ADOPT|LOST|FOUND} constant so existing CSS / JS selectors keyed on those values still match.
    * Follows AF's ob_start() / ob_get_clean() convention - body buffered into $outputDetails as the caller expects.

+ Item 2: New PetPoint template - public/templates/pp/universal-details-default.php (~170 lines)
    * Same dispatch and method-aware branching as AF.
    * PP-specific differences:
        - shouldSkipField() additionally checks PetPointFields::NAME_ANIMAL (PP has two name field variants).
        - URL animal-id param computed via the existing str_replace trick on PetPointFields::ID / PetPointFields::ID_ANIMAL (produces 'animalID').
        - Lost / found unconditionally use the "Lost {Species} #{ID}" descriptive name (matches PP lost-default / found-default which sprintf unconditionally, unlike AF which conditionalizes the fallback).
        - Adopt layout uses renderOtherButtons(['call', 'return']) only - no renderConversionButtons call (PP adopt-default has no conversion-button row).
        - Lost / found layout uses renderOtherButtons(['call', 'email', 'return']) - no conversion-button row.
        - PP convention: renders directly to output, no ob_start.

+ Item 3: RescueGroups intentionally excluded
    * RG only supports adopt method (no lost / found / preferred / list). The existing adopt-default.php covers every scenario for RG, so a "universal" template would be a no-op. If RG ever adds method-type support, this template gains a third partner file at that time.

+ Item 4: License gating
    * Both new templates contain "default" in the filename, so the existing AllApi::getTemplateLicenseError() gate (which allows *-default-* templates for free tier) admits them. No level-file changes needed.
    * Template discovery is via glob() (per docs/split-prompts/phase-03-template-extensibility.md), so the new files appear in the admin Detail Template dropdown automatically the next time the page is rendered. No registry edit needed.

+ Item 5: KB update - docs/kb/03-templates-design/02-detail-templates-gallery.html
    * New row added to the PetPoint table (between lost-default.php and universal-details-navigation.php).
    * New row added to the AnimalsFirst table (same position).
    * Both rows are tagged "Basic" license tier and reference 8.11.1+ in the description.
    * RescueGroups table unchanged.

+ Item 6: Version constants
    * pet-match-pro.php line 19: Plugin header "Version:" 8.11.0 -> 8.11.1.
    * pet-match-pro.php Constants::VERSION '8.11.0' -> '8.11.1'.
    * README.txt: Stable tag 8.11.0 -> 8.11.1; new = 8.11.1 = Upgrade Notice block describing the template.

+ Item 7: Operator action required
    * None. The auto-flush from 8.10.1.5 fires on the version bump (Constants::VERSION changed), so any cached rewrite rules refresh on the next page hit. New templates are discovered via glob, so they appear in the admin dropdown without a cache clear.
    * To use the new template: in WP Admin > PetMatchPro > General > Templates, change the Detail Template for whichever method to "universal-details-default", OR pass `template="universal-details-default"` in a [pmp-details] shortcode.

+ Item 8: Out of scope (followups)
    * RG version - skipped because RG is adopt-only, see Item 3.
    * Method-aware combo for similar-grid templates (adopt-conversion-similar, adopt-profile-3-column-similar, adopt-details-navigation-similar). Those are adopt-specific by design; universalizing them would need lost/found similar-grid logic that does not exist yet. Defer until a customer asks.
    * Wizard step 6 (Detail Template) does not yet special-case the universal-details-default as a recommended preferred-method option. Worth flagging in the wizard so AF customers find it without reading the KB. Bundle into the next wizard polish release.

----

Version 8.11.0 - May 15, 2026 - Preconnect to Partner Photo CDN (Free-Tier Perf Feature)

Minor-version release adding a single user-visible feature: a free-tier perf optimization that emits `<link rel="preconnect">` in the page head for the active integration partner's animal-photo CDN, but only on pages that render PMP content. Typical wins are 100-300 ms shaved off the first photo paint on desktop and up to ~900 ms on slow mobile - all from the browser opening the TCP + TLS handshake to the CDN in parallel with HTML parsing instead of waiting until it encounters the first <img>.

Why this release is 8.11.0 and not 8.10.2: the change introduces a new public API surface (Settings::PRECONNECT_PARTNER_CDN, IntegrationPartner::photoCdnHost(), PetMatchPro\Performance\ResourceHints class) and a new admin setting, which is a feature add - 8.10.x has been bugfix-only. Bumping the minor segment keeps the changelog easy to navigate: scan to 8.11 to see when preconnect landed.

Why free tier: this is a pure perf improvement with no behavioral or feature trade-off. Locking it behind Premium would penalize free-tier shelters disproportionately - they are often the ones running on cheaper hosting where every saved ms matters.

The partner CDN hosts were captured by curling demo-af.petmatchpro.com and demo-rg.petmatchpro.com on 2026-05-15 to read actual photo URLs from the rendered HTML (no API access required), and confirmed against the PetPoint CORS investigation brief at docs/specs/demo-site-cors-image-investigation-brief.md.

+ Item 1: New IntegrationPartner enum method - photoCdnHost(): string
    * pet-match-pro.php enum IntegrationPartner gains photoCdnHost() (placed after getDirectory()).
    * Returns the verified host for each partner:
        - PetPoint     -> 'g.petango.com'
        - AnimalsFirst -> 'animalsfirstprod.s3.amazonaws.com'
        - RescueGroups -> 'cdn.rescuegroups.org'
    * Single source of truth for partner CDN identification - if a partner ever migrates CDNs, only this method needs to change.

+ Item 2: New Settings constant - PRECONNECT_PARTNER_CDN
    * pet-match-pro.php Settings class line ~445: public const PRECONNECT_PARTNER_CDN = 'preconnect_partner_cdn'.
    * Stored under the existing 'pet-match-pro-general' option (no new option row in wp_options).

+ Item 3: New ResourceHints class - includes/class-pet-match-pro-resource-hints.php (~140 lines)
    * Namespace PetMatchPro\Performance, final class, single responsibility.
    * Constructor reads the setting, returns early if disabled, otherwise hooks the wp_resource_hints filter at priority 10.
    * isEnabled(): default-on semantics - a missing key in general options is treated as enabled, so legacy sites upgrading from <8.11.0 (which never wrote the option) silently inherit the feature. Only an explicit empty / '0' disables.
    * filterResourceHints(): appends 'https://{host}' to the preconnect URL list when (a) the relation is 'preconnect', (b) pageHasPmpContent() returns true, and (c) IntegrationPartner::tryFrom() resolves the partner string from general options. Emitted as a plain string (not an array with crossorigin key) so WP renders <link rel="preconnect" href="..."> without a crossorigin attribute - PMP templates use plain <img> tags, so a crossorigin preconnect would open a separate unused connection.
    * pageHasPmpContent(): static request-scope cache. Detects PMP content via get_query_var('pmp_animal_id') (SEO pretty-permalink path) OR has_shortcode() against the queried post body for SEARCH / DETAILS / DETAIL tags. Cached because wp_resource_hints fires five times per page (one per relation type) and we do not want to scan post_content five times.
    * No CSS, no JS, no opcache concerns, no database touches.

+ Item 4: Wire-up in PublicFacing
    * public/class-pet-match-pro-public.php: where SeoManager is loaded (right after the public_constructor body), added an analogous require_once + new for ResourceHints. Same file-exists + class_exists guard pattern so a partial-upload deploy degrades gracefully.

+ Item 5: Activator default
    * includes/class-pet-match-pro-activator.php initializeGeneralOptions(): added Settings::PRECONNECT_PARTNER_CDN => '1' to the defaults array.
    * Fresh installs get the option explicitly set to '1'; upgrading sites pick up default-on via the ResourceHints::isEnabled() missing-key handling.

+ Item 6: Admin UI - General > Performance section
    * admin/class-pet-match-pro-admin-settings.php KB_LINKS: new 'field_preconnect_cdn' => 'preconnect-partner-cdn' entry under the Performance (secondary) block.
    * admin/partials/pmp-option-levels-general.php: new `LevelPrefix::LEVEL . Settings::PRECONNECT_PARTNER_CDN => Constants::FREE_LEVEL` entry in the Performance section, alongside the existing PREMIUM-gated API_CACHE_ENABLED / CLIENT_FEATURES_ENABLED / BUTTON_CONSISTENCY entries. Free-tier features still get an explicit FREE_LEVEL entry by convention - the level catalog is the single source of truth for "what tier sees what," even though the runtime check is currently bypassed for this field.
    * registerPerformanceFields(): the new preconnect checkbox is registered BEFORE the existing API-cache license gate, so free-tier sites see and can toggle it. The API-cache / client-features Premium fields remain behind the gate as before.
    * New callback preconnect_partner_cdn_callback() implements default-on semantics in the rendered checked state: !array_key_exists($fieldKey, $options) || !empty($options[$fieldKey]) - matches the runtime check in ResourceHints::isEnabled() so what the user sees in the admin UI matches what the browser receives.
    * admin/partials/pmp-admin-info.php: tooltip text for the new field, naming all three CDN hosts so admins can verify against their partner.
    * KB help icon on the field label links to docs/kb/10-best-practices/11-preconnect-partner-cdn.html via the standard renderHelpIcon() helper.

+ Item 7: KB article - docs/kb/10-best-practices/11-preconnect-partner-cdn.html
    * Title: "Preconnect to Your Partner Photo CDN"
    * Covers: what preconnect does, when PMP fires it (the scope rules), savings table (desktop / 4G / 3G), default-on behavior, when to disable (custom CDN proxy, CSP, debugging), verification steps (view-source + DevTools waterfall), related articles.
    * Hosted at petmatchpro.com/docs/preconnect-partner-cdn/ on next site rebuild.

+ Item 8: Version constants
    * pet-match-pro.php line 19: Plugin header "Version:" 8.10.1.5 -> 8.11.0.
    * pet-match-pro.php Constants::VERSION '8.10.1.5' -> '8.11.0'.
    * README.txt: Stable tag 8.10.1.5 -> 8.11.0; new = 8.11.0 = Upgrade Notice block describing the feature.

+ Item 9: Operator action required
    * None. Feature is default-on for both fresh installs and upgrades. Rewrite-rule auto-flush from 8.10.1.5 also fires on this version bump (Constants::VERSION changed), so any rewrite-rule caching on the operator's site is naturally refreshed.
    * Verification: view-source on a page with [pmp-search] should show <link rel='preconnect' href='https://{partner-cdn-host}' /> in the head.

+ Item 10: Out of scope (followups)
    * dns-prefetch fallback for very old browsers - skipped. PMP requires WP 6.5+ which targets browsers that all support preconnect natively. Adding dns-prefetch would emit a second link tag with no measurable benefit.
    * Per-shortcode override (e.g., preconnect="false"). Not currently a use case, and the setting can already be flipped globally. Add only if a customer reports a specific need.
    * Preconnect to additional hosts (theme CDN, font CDN, analytics). Out of scope for PMP - those belong in theme/perf-plugin territory.
    * AnimalsFirst test/sandbox environment (animalsfirsttest.s3.amazonaws.com?). The current photoCdnHost() returns the prod host. If a customer uses an AF sandbox account, the preconnect would warm up the wrong host - a minor perf miss, not a bug. Add a test-mode branch if/when an AF customer reports it.

----

Version 8.10.1.5 - May 15, 2026 - Pretty-Permalink Fix for AnimalsFirst Preferred Animals + Auto-Flush on Version Change

Two related changes shipped together. (1) A regex fix that lets pretty URLs for AnimalsFirst "preferred" animals resolve at all. (2) An automatic rewrite-rule flush on every version change, so this fix - and every future rewrite-rule change - lands without an operator visiting Settings > Permalinks or clicking the SEO Tools flush button.

Single-bug fix uncovered on cincinnatianimalcare.org. A search built with [pmp-search type="other" ...] (or any other AnimalsFirst "preferred" sub-type: outcome, foster, stray, rehome, pending, all) emits result links of the form /pmp/preferred/{slug}-{id}/, because callMethod_Parameters() correctly maps these sub-types to METHOD_TYPE=preferred for routing. The SEO module's pretty-URL rewrite rule, however, was hardcoded to ^pmp/(adopt|lost|found)/... - so "preferred" URLs did not match any rule and WordPress returned its theme 404 before [pmp-details] ever ran. The [pmp-details type="adopt"] attribute on the destination page was a red herring; the details shortcode reads its method from the URL query var (pmp_method), not from the type attribute, so changing it would not have helped.

Root cause was a regex literal duplicated in three files (one rewrite registration in includes/class-pet-match-pro-seo.php, two more in admin/class-pet-match-pro-admin-settings.php inside the save-handler and the AJAX flush handler, plus two array-key lookups and one displayed-pattern string in the SEO Diagnostics panel - six identical occurrences in total). All six were updated to ^pmp/(adopt|lost|found|preferred)/[^/]+-([0-9a-zA-Z]+)/?$. The PetPoint "list" method (adoptionlist) was not included in this fix because it surfaced as a separate path; if list animals start 404-ing in the wild, the same regex needs another extension.

+ Item 1: includes/class-pet-match-pro-seo.php - registerRewriteRules()
    * Line 118: regex updated from ^pmp/(adopt|lost|found)/[^/]+-([0-9a-zA-Z]+)/?$ to ^pmp/(adopt|lost|found|preferred)/[^/]+-([0-9a-zA-Z]+)/?$
    * Match groups unchanged - $matches[1] is still the method, $matches[2] is still the animal ID hash.
    * Existing parseRequest/canonicalUrl logic already passes whatever method value is captured, so no other changes needed in this file.

+ Item 2: admin/class-pet-match-pro-admin-settings.php - five locations (all identical strings, all updated)
    * Line 4556 - SEO settings AJAX save handler (re-registers the rule in the same request before flush_rewrite_rules so the new pattern lands in the cached rule set).
    * Line 14592 - SEO Tools Diagnostics panel: array-key lookup against get_option('rewrite_rules') to display the "Rewrite Rule Registered" status indicator.
    * Line 14697 - SEO Tools Diagnostics panel: the user-visible "Current Pattern" code block under the registered-rule status row.
    * Line 15140 - ajax_seo_flush_rewrite_rules(): the manual "Flush Rewrite Rules" button handler re-registers the rule before flushing.
    * Line 15158 - ajax_seo_flush_rewrite_rules(): post-flush verification array-key lookup that decides what to return to the JS caller.

+ Item 3: Auto-flush on version change (eliminates the manual-flush step for this release and all future rewrite-rule changes)
    * pet-match-pro.php Constants block: new `Constants::ACTIVE_VERSION_OPTION = 'pmp_active_version'` (placed alongside ANALYTICS_SCHEMA_OPTION, with a docblock explaining its role).
    * pet-match-pro.php init-99 closure (the same one that already drains the activation `pmp_flush_rewrite_rules` transient): now also reads `pmp_active_version`, compares to `Constants::VERSION`, and calls flush_rewrite_rules(false) + update_option when they differ. Priority 99 keeps the flush after SeoManager::registerRewriteRules() (default prio 10), so the new pattern is already registered when the flush fires.
    * Why this option name, not the historical `pet-match-pro_version`: the 8.1.0 cleanup explicitly deleted that key on customer sites, so reviving it would collide with the cleanup. The `pmp_` namespace also matches the analytics-schema option naming convention.
    * What this covers:
        - WP.org auto-update path (no activation hook fires) - now self-heals.
        - Manual ZIP overwrite path (no activation hook fires) - now self-heals.
        - Activation path (existing transient still works) - now redundant on a fresh activation but harmless (one extra flush, idempotent).
        - Deactivate/reactivate path - version unchanged, our new check skips; the activation transient still flushes. No double-flush.
    * What this does NOT cover: an operator who downgrades a plugin will trigger a flush back to the older pattern, which is the desired behavior. Multisite network-activated installs need the option per-site, which is the default behavior of update_option without `$network_wide`.

+ Item 4: Version constants
    * pet-match-pro.php line 19: Plugin header "Version:" 8.10.1.4 -> 8.10.1.5.
    * pet-match-pro.php line 74: Constants::VERSION '8.10.1.4' -> '8.10.1.5'.
    * readme.txt line 8: Stable tag 8.10.1.4 -> 8.10.1.5.
    * readme.txt: new = 8.10.1.5 = Upgrade Notice block describing the fix and the required flush.

+ Item 5: Operator action required
    * None. The init-99 handler flushes rewrite rules automatically on the first request after the new code loads (any front-end OR wp-admin page hit triggers init).
    * Verification (optional): visit any page once after upgrade, then SQL `SELECT option_value FROM wp_options WHERE option_name = 'pmp_active_version'` returns '8.10.1.5'. SQL `SELECT option_value FROM wp_options WHERE option_name = 'rewrite_rules'` shows the new pattern with `|preferred` included.

+ Item 6: Out of scope (followups, not fixed here)
    * Regex DRY: the same 60-char literal is hand-copied across three files. Future cleanup should move it to a Constants::SEO_PRETTY_URL_PATTERN constant referenced by all six sites. Deferred to keep this release focused on the 404 fix.
    * PetPoint "list" (adoptionlist) is not in the regex. If a PetPoint customer ships a [pmp-search type="list" ...] page, those URLs will also 404. Not a known issue today; raise the next time it surfaces.
    * Sitemap generator (class-pet-match-pro-seo.php line 681) already builds /pmp/{methodType}/... URLs using whatever methodType the caller supplies, so once a preferred URL matches the rewrite rule, sitemap entries for preferred animals will also resolve correctly. No sitemap code change needed.

----

Version 8.10.1.4 - May 14, 2026 - Upgrade Notice Refresh for Legacy 6.x Updaters

Version-bump release whose only purpose is to push a new readme.txt to WordPress.org so legacy users still on the 6.x line see a clear "MAJOR UPGRADE" notice the first time they click Update. WordPress only renders the Upgrade Notice block whose header matches the new version exactly, so the previous notice (which described only the 8.10.1.3 SVN-compatibility fix) was never going to reach a 6.x user. No PHP, schema, asset, or template changes - readme.txt copy and version strings only.

+ Item 1: README.txt - new = 8.10.1.4 = upgrade notice block
    * "MAJOR UPGRADE from 6.x" message: full modernization summary, "back up your site first" advisory, pointer to the Description tab for the long-form rundown.
    * Previous = 8.10.1.3 = block restored to its original WP.org SVN compatibility wording (kept for historical accuracy since that release already shipped).
    * Stable tag header bumped 8.10.1.3 -> 8.10.1.4.

+ Item 2: pet-match-pro.php - version strings bumped
    * Plugin header "Version:" 8.10.1.3 -> 8.10.1.4 (line 19).
    * Constants::VERSION '8.10.1.3' -> '8.10.1.4' (line 74).

+ Item 3: No code paths touched
    * No PHP, JS, CSS, template, or schema modifications.
    * No new constants, no new options, no new hooks.
    * No operator action required on update.

----

Version 8.10.1.3 - May 13, 2026 - WP.org SVN Compatibility (Trait Constant Removal)

Single-file packaging fix discovered while pushing 8.10.1.2 to the WordPress.org SVN repository. The WP.org pre-commit hook lints every PHP file with an older PHP build that does not yet recognize trait constants (PHP 8.2+ feature). The 8.10.x line introduced one `private const` inside `SearchTemplateTrait`, which blocked the commit with `Fatal error: Traits cannot have constants in Standard input code on line 1604`. Resolved by converting the constant to a private static method - same data, same lookup pattern, accepted by the WP.org lint.

+ Item 1: SearchTemplateTrait - replaced `private const SORT_FILTER_FIELD_ALIASES` with `private static function getSortFilterFieldAliases(): array`
    * public/templates/includes/class-pet-match-pro-search-template-trait.php line 1604: the partner-specific sort/filter alias map (Constants::PETPOINT => ['breed' => 'primarybreed', 'name' => 'animalname', 'id' => 'animalid']) is now returned from a private static method instead of being declared as a trait constant.
    * Two internal call sites updated to use the method form:
        - line 1691 buildSortFilterAliasMirrorAttrs(): self::SORT_FILTER_FIELD_ALIASES[$partner] -> self::getSortFilterFieldAliases()[$partner]
        - line 1886 (active-sort detection logic): same rewrite.
    * Comment block at line 1604 retained verbatim - the alias map's rationale, shape contract, and "add new aliases here" guidance still apply unchanged.

+ Item 2: Codebase audit - no other PHP 8.2+ syntax found
    * Verified no other traits contain `const` declarations (poster-template-trait, field-filter-trait, field-exclusion-filter-trait all clean).
    * No `readonly class` declarations.
    * No DNF (disjunctive normal form) types - e.g., `(A&B)|C` in type positions.
    * No standalone `true` / `false` types in return-type or parameter-type positions.
    * No `#[\AllowDynamicProperties]` attribute usage.
    * Plugin remains PHP 8.1+ compatible.

+ Item 3: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.10.1.2 -> 8.10.1.3.
    * Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * README.txt Stable tag bumped to 8.10.1.3; new Upgrade Notice entry above the 8.10.1.2 entry.

* Operator action items: none.

* Verification:
    * `svn commit` to https://plugins.svn.wordpress.org/petmatchpro/ succeeds without the trait-constant lint failure.
    * Sort buttons (Breed, Name, ID) on PetPoint search results still render with the correct active state after page load and after a sort change.
    * Filter dropdown using `breed` against a PetPoint result set still matches `data-primarybreed` cards.

----------------------------------------

Version 8.10.1.2 - May 10, 2026 - PetPoint XML Diagnostics, Foster-Location Warning, AF lost_found Combo Field Display

Three operator-facing fixes uncovered during partner-by-partner testing on the demo sites. No schema change. No write-path change. No new shortcode params. Operator action items: none.

+ Item 1: PetPoint - Failed-XML logs now include libxml errors and a body snippet
    * includes/pp/class-pet-match-pro-pp-api.php animalDetail(): replaced `@simplexml_load_string()` with the standard libxml-internal-errors pattern. On parse failure the log entry now carries the actual libxml messages and the first 500 chars of the response body alongside animal_id and method.
    * Previously the warning "[PetMatchPro] PetPoint: Failed to parse animal detail XML" was unactionable - operators saw the animal_id but had no insight into what PP actually returned (HTML error page? truncated XML? BOM? Wrong content-type?). With the new fields a single log line tells us whether it's an authentication failure, a gone-animal celebration redirect from PP's side, or a malformed XML payload.
    * The `libxml_use_internal_errors()` toggle and `libxml_clear_errors()` calls are wrapped around the parse so the diagnostic does not leak libxml state into the rest of the request.

+ Item 2: AllApi - Foster-location check no longer emits "Array to string conversion"
    * includes/class-pet-match-pro-all-api.php isAnimalInFoster() line 2703: getAnimalProperty() can return an array when the partner field is an empty XML element (PetPoint's SimpleXML parses `<Location/>` to `[]`) or a nested object without a 'value' key. Casting that array to string with `(string)` was emitting a PHP warning to the error log on every detail-page render and search-result card.
    * Fix: extract the raw value first, then `is_scalar() ? (string) : ''`. Arrays and objects degrade to empty string rather than throwing. Foster detection is unchanged for the scalar case; previously-warning paths now silently return false (animal not in foster) when the location is structurally invalid.

+ Item 3: AnimalsFirst lost_found combo type - admin-configured fields now display
    * Reported symptom: with `[pmp-search type="lost_found"]`, combo method set to "found", and "type" / "intake_type" admin checkboxes enabled under Found search results, neither field rendered on the cards even after hard refresh and cache purge.
    * Root cause: AllApi::callMethod_Parameters() assigns Settings::METHOD_TYPE_PREFERRED to any non-standard shortcode type. `lost_found` is non-standard, so `apiInstance->methodValue` became 'preferred'. In the three universal AF search templates, getMethodValue() short-circuited to `apiInstance->methodValue` before checking the resolved methodCall, so downstream admin-field lookup keyed off `preferred_search_details` (empty) instead of `found_search_details` (where the operator's checkboxes lived).
    * Fix: re-ordered getMethodValue() in three AF templates so combo-type detection runs BEFORE the apiInstance->methodValue short-circuit. When the shortcode `type` contains `lost_found`, the template now resolves via `methodCall` (foundSearch -> found, lostSearch -> lost) instead of trusting the synthetic 'preferred' assignment.
    * Files changed: public/templates/af/universal-search-default.php, public/templates/af/universal-search-no-filter.php, public/templates/af/universal-search-filter-widget.php.
    * Templates intentionally not touched: featured-search-{default,carousel,compact} (hardcoded to METHOD_TYPE_ADOPT - cannot hit combo path), universal-search-structured (already resolves via resolveMethodTypeFromCall() correctly), adopt-celebration-similar (adopt-only by design).

+ Item 4: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.10.1.1 -> 8.10.1.2. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * README.txt Stable tag bumped to 8.10.1.2; new Upgrade Notice entry above the 8.10.1.1 entry.

* Operator action items: none.

* Verification on a deployed site:
    * PetPoint detail page on a gone or invalid animal ID: error log entry includes `libxml` array and `body_snippet` showing what PP returned.
    * AnimalsFirst lost_found combo search with `type` and `intake_type` checked under Found in admin: both fields now display on every card.
    * PetPoint or AF detail render: no recurring "Array to string conversion in class-pet-match-pro-all-api.php on line 2703" warnings in the PHP error log.

----------------------------------------

Version 8.10.1.1 - May 8, 2026 - Detail Template Fixes (PP filter labels, currency, side-by-side video column)

Bug-fix patch on top of 8.10.1. Three fixes covering the PetPoint adopt-details-navigation-similar template and the side-by-side detail layouts shared by AnimalsFirst and RescueGroups. No schema change. No write-path change. No new shortcode params. Operator action items: none.

+ Item 1: PetPoint adopt-details-navigation-similar - location filter label resolution
    * public/templates/pp/adopt-details-navigation-similar.php: getFieldValue() was returning the raw API location value (e.g. "Kennel - Greenhill") instead of running it through the admin-configured filter group mapping. With Filter Values configured under General > Filter Values (Filter 1 = "Kennel - Greenhill" / Filter 1 Label = "Main Kennel"), the search results showed the label correctly but the detail page showed the raw value. Brought to parity with af/adopt-details-navigation-similar by adding a getFilterGroupConfigs() lookup at the top of getFieldValue() that calls resolveDetailFieldDisplayValue() for any field a filter config covers (location, kennel, site, etc.).
    * The new lookup runs before the common-field switch and the keyMappings table, so admin-configured labels always win over raw API values without breaking the existing field resolution chain.

+ Item 2: PetPoint adopt-details-navigation-similar - currency formatting on all render paths
    * Same file: getFieldValue() was returning the price field unformatted ("250" instead of "$250.00") because formatCurrencyValue() was only invoked from renderQuickFields() and renderTitleSection(), not from renderStatsRow() or renderStatsFull(). Moved the formatCurrencyValue() call into getFieldValue() itself so every consumer benefits from a single chokepoint.
    * Added an explicit 'price' key mapping to the keyMappings table - previously price was reachable only via the 'fee' alias chain (price -> Price -> fee -> Fee -> AdoptionFee). Direct lookup is faster and more readable.
    * universal-details-navigation.php was already correct - this fix only applies to the navigation-similar variant.

+ Item 3: Side-by-side detail layout - videos no longer hijack a third column
    * Templates affected: rg/adopt-similar.php, rg/adopt-default.php, af/adopt-default.php, af/adopt-conversion.php, af/adopt-conversion-no-app.php, af/adopt-conversion-similar.php (6 templates).
    * Symptom: when an animal had video URLs, the .pmp-details-videos block rendered as a direct sibling of .pmp-details-image-column inside .pmp-details-image-row. With width:100% on the videos block competing against a 90px thumbnails block in the same flex row, the main image got squeezed out and three vertical columns appeared (image / thumbs / videos).
    * Fix: wrap renderThumbnails() and renderVideos() inside a new <div class="pmp-details-thumbs-column"> so they share one right-side column. Videos now stack vertically below thumbnails inside that column instead of forming their own.
    * public/css/pet-match-pro-styles.css: added .pmp-details-thumbs-column rule (flex: 0 0 90px; flex-direction: column) plus child overrides forcing the nested .pmp-details-thumbnails and .pmp-details-videos to flex:0 0 auto with width:100% and column direction. The min file (pet-match-pro-styles.min.css) regenerated from source.
    * PetPoint detail templates were not modified - PP detail templates do not currently call renderVideos(), so the bug never manifested on PP.
    * Templates already using the correct pattern (universal-details-navigation, adopt-details-navigation-similar, adopt-wide, adopt-profile-3-column, rg/adopt-cpa) were not touched.

+ Item 4: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.10.1 -> 8.10.1.1. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * README.txt Stable tag bumped to 8.10.1.1; new Upgrade Notice entry above the 8.10.1 entry.

* Operator action items: none.

* Verification on a deployed site:
    * PetPoint adopt-details-navigation-similar template with Filter Values configured: location displays the admin label, not the raw API value.
    * Same template with price in stats_row or stats_full: value renders with the configured currency symbol (e.g. "$250.00"), matching the rendering in quick_fields and title_fields.
    * RescueGroups adopt-similar (and the other 5 fixed templates) on an animal with video URLs: main image renders at full size, thumbnails column on the right contains photo thumbs followed by video play tiles stacked vertically. No third column.
    * Templates not in the fix list render unchanged.

----------------------------------------

Version 8.10.1 - May 8, 2026 - Quality Pass Follow-Through

Closes the items deferred from 8.10.0: the error_log REMOVE sweep, identifier quoting in the analytics ALTER and rollup queries, and a refreshed i18n new-strings inventory ready for the next Claude Cowork translation pass. No schema change. No write-path change. No public-facing UI change. Operator action items: none.

+ Item 1: Developer-leftover error_log sweep
    * Project-wide error_log count reduced from 93 to 34. Every remaining call now follows the wpdb-failure mandate or the structured [PMP Analytics] / [PMP Rollup] / [PetMatchPro] operational format.
    * pet-match-pro.php: 18 commented //error_log lines removed across loadDependencies(), defineAdminHooks(), and initializeAnalytics(). Empty-after-removal else branches collapsed. The unreachable "Try alternate path" license-file lookup that only existed to host commented logs was deleted.
    * admin/class-pet-match-pro-admin-settings.php: 4 commented checkpoint logs (PMP Settings constructor, Logo Path) removed.
    * admin/class-pet-match-pro-functions.php: 10 commented field-debug + input-callback logs removed.
    * includes/class-pet-match-pro-all-api.php: 2 commented fieldExclusions debug logs removed.
    * includes/cache/class-pet-match-pro-api-cache.php: the entire logDebug() helper removed (1 wrapped error_log + 3 callsites in get/set). Per-cache-check chatter with no caller value.
    * includes/rg/class-pet-match-pro-rg-api.php: 5 WP_DEBUG-gated chatter logs removed (init checkpoint, cURL retry, request dump, missing-apikey/orgID checkpoints). Doubly-redundant `if (defined('WP_DEBUG') && WP_DEBUG) { if (defined("WP_DEBUG") && WP_DEBUG) { error_log(...); } }` pattern eliminated.
    * public/templates/includes/class-pet-match-pro-search-template-trait.php: 1 commented labels-debug var_export removed.
    * 9 search templates (pp/found, pp/lost, pp/universal, af/featured-{compact,carousel,default}, af/universal-{default,filter-widget,no-filter}): 18 template-exception logs without [PMP] prefix removed; catch blocks preserved with "Silently degrade - template falls back to non-API rendering." comment for the apiFunction reflection path, and `$this->fieldLevels = [];` retained for the field-levels path.
    * Audit B row 55 (deactivator.php:206) intentionally kept: the surrounding commented code block is a documented example demonstrating the wpdb-failure logging pattern future devs should follow.

+ Item 2: Analytics SQL identifier quoting
    * includes/analytics/class-pet-match-pro-analytics-db.php (schema 2.0 -> 2.1 daily UNIQUE-key migration ALTER, lines 346-349): the table identifier, the index identifier, and all 8 column identifiers in the UNIQUE KEY definition are now wrapped in backticks. Disambiguates from MySQL reserved words and matches the standard identifier-quoting convention.
    * includes/analytics/class-pet-match-pro-analytics-daily-rollup.php (rollupDay() DELETE at :215 and INSERT at :271): every {$dailyTable}, {$eventsTable}, and column-name identifier interpolated from class-property constants now uses backticks. User-driven values (date, dayStart, dayEnd) were already routed through $wpdb->prepare() in earlier 8.10.x work; this release only adds the identifier-quoting layer. No behavior change today, but future column-name additions that collide with reserved words will work without further changes.
    * Audit D escape-3 spot-fix in admin-settings.php skipped after review: every echo $html / echo $var site sampled (settings builders, SEO diag block, color CSS output, do_settings_sections body capture) already wraps user data with esc_attr / esc_html / esc_html__ at the sprintf or concat layer. Per the 8.10.0 plan: don't refactor existing-correct code.

+ Item 3: i18n new-strings inventory refreshed
    * docs/superpowers/audits/2026-05-06-a-i18n-new-strings.md replaced with a 144-msgid catalog of every translatable string present in 8.10.1 source but not yet in the frozen 8.6.4 pet-match-pro.pot. Each entry lists first-observed file:line so Cowork can spot-check context before translating.
    * Local environment lacks WP-CLI / gettext, so the inventory was extracted via grep + awk diff; Cowork's `wp i18n make-pot` run remains the authoritative final list. All _n() plural pairs in current code (Month/Months, Year/Years, minute/minutes, animal/animals, two analytics-insights long-form plurals) are already in the 8.6.4 .pot - no new pairs in 8.7.0 - 8.10.1.
    * Translation notes added: placeholder preservation (%s, %d, %1$s reordering rules), HTML anchor tags inside msgids, the "  --  " em-dash sentinel from feedback_no_em_dash.md, the do-not-translate PetMatchPro / PMP brand rule, and a 6-step Cowork workflow ending in the .pot/.po/.mo commit.
    * The .pot regeneration, .po msgmerge, and .mo recompile happen in the Cowork session so the translation pass lands as one focused commit.

+ Item 4: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.10.0 -> 8.10.1. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * README.txt Stable tag bumped to 8.10.1; new Upgrade Notice entry above the 8.10.0 entry.

* Operator action items: none. The release is internal cleanup and audit follow-through.

* Verification on a deployed site:
    * Reload search results, detail pages, and admin Settings: no PHP errors in debug.log. Intentional logs (analytics rollup, wpdb-failure mandate) still fire when their conditions are met.
    * Trigger a search with field exclusions configured: debug.log still shows `[PetMatchPro] [DEBUG] Field-Exclusion: ...` entries (PetPoint via the inline filter, AF/RG via the trait); the chatter that previously fired on every successful request is gone.
    * Visit the Analytics tab: dashboard widgets still render (rollup query identifier quoting is transparent under MySQL).
    * grep -rc error_log --include='*.php' from project root returns ~34, all wpdb-mandate or structured operational logs.

----------------------------------------

Version 8.10.0 - May 8, 2026 - Quality Pass + Audit Remediation

External 5-track audit (i18n / error logging / license gating / WP coding standards / WP.org compliance) drove a code-quality and security-hardening pass across admin, public, and partner-API surfaces. No data-model or schema changes. No operator action required for upgrade. The single visible behavior change is on broken API keys: a previously-misleading "Parse Error: Unable to parse response" public message is now an actionable "Authentication Error: Verify your API key in general Settings."

+ Item 1: Security - unserialize() called on decrypted license payloads now passes ['allowed_classes' => false]
    * admin/license/class-pet-match-pro-license.php (2 sites, lines 498 and 881): forbids arbitrary object instantiation. If the licensing server is ever compromised or a MITM intercepts, gadget-chain code execution is no longer reachable from the deserialization paths. Test path: Free / Premium / Preferred all survive deserialization unchanged.

+ Item 2: Security - color values validated on save AND on render
    * admin/class-pet-match-pro-admin-settings.php sanitizeColorOptions(): each non-empty submitted value is validated against an allowlist of CSS color formats (hex 3/4/6/8-digit, the 147 named CSS colors plus transparent/currentColor, rgb/rgba/hsl/hsla, and the inherit/initial/unset/revert keywords). Invalid values are silently reverted to the previously-saved value (empty if never set, preserving cascade-default behavior) and a settings_error notice is queued so the admin sees the rejection. The new isValidCssColor() helper is private and reusable.
    * public/partials/pet-match-pro-public-color-css.php getColor(): blocks CSS-injection breakout chars (semicolon, brace, angle bracket, quote) at the read chokepoint. All ~30 echoes of color values get the protection automatically. Hex/named/functional CSS color formats all pass.

+ Item 3: Security - escaping fix in license-activation form
    * admin/index.php: replaced esc_attr_e(purchaseEmail, $this->slug) with echo esc_attr($purchaseEmail). The bareword 'purchaseEmail' was being treated as an undefined PHP constant (deprecation warning + literal string output); the form's email value now populates the configured PMP_lic_email correctly.

+ Item 4: License gating - admin and wizard hide-when-gated instead of showing locked UI
    * Tabs / accordions / partner method registers / wizard preset cards / wizard method types / wizard vanity URLs all reflect the active license tier. The surface shrinks to what the operator can actually use; matches WP.org repo team conventions for fewer locked-state hints.
    * Coordinated changes across admin/class-pet-match-pro-admin-settings.php, the admin/partials/* level files, admin/css/pet-match-pro-admin.css (lock-glyph removal from disabled-field labels), correct license-key for General Exclusions sub-accordion, per-method gating for Display > Empty Fields checkboxes, plugin menu position aligned across tiers.

+ Item 5: License gating - Free-tier shortcode params + admin features unblocked
    * admin/partials/pmp-option-levels-general.php: shortcode_thumbs param downgraded from PREMIUM to FREE to match the existing Settings::THUMBS admin checkbox tier. The admin always offered the feature; the param strip path was silently removing it on Free.
    * public/templates/includes/class-pet-match-pro-base-detail-template.php getMaxThumbs(): removed the redundant inner license check at line 695 that was ignoring the (now-Free) shortcode param. License gating is enforced upstream by stripPaidDetailParams().
    * Settings::PAGE_DETAILS . '_' . Settings::POSTER bumped from PREMIUM to FREE so the "Poster Details Page" admin selector is configurable on Free (the print-poster feature itself was already free).

+ Item 6: Public-facing UX - silent template fallback to admin-configured (P1.10)
    * AllApi::getSafeFallbackTemplate(template, methodType, isSearch): when a Free-tier visitor requests a paid template, the renderer substitutes the admin-configured template name instead of showing "Template Upgrade Required" to the visitor. AllApi::getTemplateLicenseError() is deprecated to always return ''. Six callers updated (resolveSearchTemplate plus 4 direct callers in PP/AF/RG).

+ Item 7: Public-facing UX - audience-gating for admin notices
    * AllApi::buildUpgradeButton() / buildConfigurationNotice() and the inline poster notices in BaseDetailTemplate now wrap output in current_user_can('manage_options'). Visitors get '' (silent); admins still see the in-place "Not Configured" / "Upgrade to Use" hints. Pattern should extend to any other admin-domain UX leaking to public visitors.

+ Item 8: Public-facing UX - clearer errors and structured diagnostic logging across all 3 API clients
    * PetPoint, AnimalsFirst, RescueGroups now share the same error-message taxonomy: Connection Error (network/wp_error), Authentication Error (HTTP 401/403 with actionable "Verify your API key in <tab>" hint), API Error (other 4xx/5xx with HTTP code shown), Parse Error (200 OK + malformed body, with the partner-specific parser error included).
    * Each path emits a structured ErrorLogger entry with http_code + method + 200-char response_excerpt. Pre-8.10.0 the broken-key public message was the misleading "Parse Error: Unable to parse response" and the debug log was an empty {"context":""}.
    * RescueGroups postJson() now exposes http_code via curl_getinfo so the wrapper-based RG flow gets the same HTTP-status check as PP/AF.
    * AllApi::buildErrorMessage() drops the empty `context` field from log entries when the caller didn't supply one.

+ Item 9: Public-facing UX - PetPoint XML-level field-exclusion logging
    * includes/pp/class-pet-match-pro-pp-api.php applyFieldFiltering(): admin-configured field exclusions (Admin > General > Exclusions per method) are applied at the XML layer in PetPoint before the template renders. Added matching ErrorLogger::debug() entries (configuration, per-excluded-animal, summary) so the audit trail is consistent with what AF/RG produce via the FieldExclusionFilterTrait route.

+ Item 10: i18n wraps for previously-hardcoded translatable strings
    * Filter-AJAX success messages ("Filter group %d added", "Copied %d filter group(s) from %s to %s")
    * Meta titles in includes/class-pet-match-pro-all-api.php
    * Admin Labels-tab section header
    * PetPoint adoption share-button text (later removed in dead-code pass; AF/RG delegate share UI to Monarch)
    * Similar-template subtitles across PP/AF/RG (adopt-conversion-similar, adopt-details-navigation-similar, adopt-profile-3-column-similar, adopt-similar)
    * Title-case for visible button labels ("Not Configured", "Upgrade to Use Print Poster") - tooltips/title attrs stay sentence case.
    * .pot regeneration with the full set of new strings is deferred to 8.10.1 so the i18n catch-up is one focused commit.

+ Item 11: Logging consistency - operational error_log calls now route through ErrorLogger
    * 20 plain error_log() calls across the deactivator (12), RG API (3), color-CSS (1), field-exclusion-filter-trait (3), and AF celebration template (1) now use ErrorLogger::warning/error/debug. Format matches the existing structured logger:
        [PetMatchPro] [iso-timestamp] [LEVEL] <Subsystem>: <message> | Context: {...}
    * Subsystem prefixes: Deactivator, RG API, Color-CSS, Field-Exclusion, Celebration Template. Status messages (deactivated / uninstalled at <time>) are WARNING level so they remain visible regardless of WP_DEBUG.
    * field-exclusion-filter-trait WP_DEBUG_LOG gate dropped (it was suppressing entries on hosts that route to PHP's default error_log path; ErrorLogger handles level filtering via $minLevel, so WP_DEBUG alone is sufficient and the trait now always emits when WP_DEBUG is on).

+ Item 12: Code quality - declare(strict_types=1) added to 15 admin/partials/*.php files
    * activate_license_form, deactivate_license_form, pet-match-pro-admin-display, pmp-admin-info, pmp-option-levels (.php and -analytics, -color, -contact, -fonts, -general, -instructions, -tools), license/license-form, pp/pmp-admin-info, pp/pmp-option-levels-labels.
    * Skipped: license/license-form-active.php starts with literal HTML (not <?php), so strict_types cannot be its first statement.

+ Item 13: Code quality - rebrand "Pet Match Pro" to "PetMatchPro" via a single Constants source
    * Constants::PLUGIN_NAME_PROPER added; substituted into all admin help text and error messages via sprintf. Every visible occurrence of the plugin name now reads "PetMatchPro" (no space). Don't hardcode "Pet Match Pro" anywhere new.

+ Item 14: Code quality - dead code removal
    * PP buildSocialShareLinks() removed (AF/RG already delegate share UI to Monarch).
    * Legacy CSS-class helpers and orphaned wizard styles dropped.
    * Replaced getAdminInfoWithDisablePrefix() with getAdminInfo().

+ Item 15: Wizard - banner dismissal moved from site option to per-user meta
    * Two admins on the same site now have independent banner-dismissal state. The previous single get_option/update_option pattern was a footgun; one user dismissing hid it for everyone.

+ Item 16: WP.org compliance - third-party API integration disclosure in readme.txt Description
    * README.txt now discloses: AnimalsFirst / RescueGroups APIs are called when admin clicks "Refresh Filter Values"; PetPoint/AnimalsFirst/RescueGroups APIs are called on every public-facing page render. No data sent off-site without explicit admin action; no analytics/telemetry to vendor servers.

+ Item 17: Audit decisions
    * P2.6 license-handler nonce: WONT-FIX - rejected as architecturally invalid. initActionHandler() at admin/license/class-pet-match-pro-license.php:131 is invoked by the off-site licensing server (Remove License / Delete Plugin callbacks), not a logged-in WP admin. Authentication is by shared-secret HMAC: crc32b(productId + licenseKey + domain) must equal $_GET['action'], so an attacker needs the license key to construct a valid URL. WP nonces require a logged-in WP user session, which the licensing server cannot obtain. Documented in audit doc + plan task header.
    * E #5 license redirect to internal plugins.php: WONT-FIX - internal-WP redirect, no compliance issue.
    * Deferred to a later release: ABSPATH-guard sweep across 145 PHP files (own focused release after 8.10.0). Textdomain too-early notice (WP 6.7+) and Monarch dynamic-property deprecation noise documented in project memory for follow-up.
    * Deferred to 8.10.1: error_log REMOVE sweep (~42 commented/dead lines), prepared-statement tightening (~3 spots in analytics-db / analytics-daily-rollup), .pot regeneration covering all the new i18n wraps, AF + RG field-exclusion logging verification on a live site.

+ Item 18: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.9.10 -> 8.10.0. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * README.txt Stable tag bumped to 8.10.0; new Upgrade Notice entry above the 8.9.10 entry.

* Operator action items: none. The release is internal cleanup + UX clarity. Existing pages re-render correctly on first view after upload. If you have a broken API key on a test site, you'll now see an "Authentication Error" message instead of "Parse Error" - that is the intended behavior change.

* Verification on a deployed site:
    * Trigger plugin deactivation (via WP-Admin > Plugins > Deactivate, not license deactivation): debug.log shows `[PetMatchPro] [WARNING] Deactivator: deactivated at <time>`.
    * Run a search with admin field exclusions configured: debug.log shows `[PetMatchPro] [DEBUG] Field-Exclusion: Field/EXCLUDED/Summary` entries (PetPoint emits "PetPoint Field-Exclusion:" via the inline filter; AF/RG emit "Field-Exclusion:" via the trait).
    * Save a bogus value in any admin color picker: a red settings_error notice appears reading "Invalid color value for X rejected" and the swatch reverts to the previous value.
    * Place `[pmp-details thumbs="1"]` on a Free-tier detail page: photo thumbnail renders (was silently dropped pre-8.10.0).
    * Briefly switch to a broken API key on any partner: public message reads "Authentication Error: <Partner> rejected the request (HTTP 401). Verify your API key in general Settings."

----------------------------------------

Version 8.9.10 - May 6, 2026 - Action-Click animal_id Capture + Conversion Action-Type Whitelist

Two-part fix surfaced during CAC's 8.9.9 deploy verification: (1) the "Conversions by Traffic Source" widget showed 0 conversions for every source even though Action Breakdown showed 402 emails + 13 video plays. Diagnostic SQL confirmed all 415 action_click events had NULL animal_id while 21,282 detail_views all had it populated - because detail_view fires from the search results card before navigation (animal_id from the data-animal-id on the card), whereas action_click fires from the destination detail page where SEO slug URLs (/pmp/adopt/{slug}/) provide no animal context and CAC's custom theme detail template does not emit data-animal-id on the .pmp-details-container the JS handler reaches via .closest(). (2) During the conversion-classification review the operator flagged that video_play and similar engagement-only signals were being counted as conversions in the conversion-named widgets, inflating conversion rates with non-conversion behavior.

+ Item 1: PHP - new AllApi::renderAnalyticsAnimalContext() helper
    * includes/class-pet-match-pro-all-api.php after buildAnalyticsOnClick(): new public method that takes (animalId, animalName, species, methodType) and returns an inline `<script>window.PMPCurrentAnimal = {...}</script>` tag, JSON-encoded with wp_json_encode + JSON_UNESCAPED_SLASHES. Returns empty string when animalId is empty so the global is not emitted on error/empty pages.
    * The script tag is plain inline JS (no wp_register_script / wp_localize_script) because it must execute at the exact DOM position where the detail HTML is emitted, BEFORE any subsequent action_click handler can fire. Localization-via-enqueue would print to wp_head / wp_footer and miss the in-page render order on shortcode-driven detail pages where wp_footer fires after the user has already clicked.
    * Used by all three partner outputDetails() methods to seed the action_click and video_play tracker handlers.

+ Item 2: PHP - PetPoint outputDetails prepends animal-context script to rendered output
    * includes/pp/class-pet-match-pro-pp-api.php outputDetails() (around line 1916): after the template require/ob_get_clean, prepend the result of $this->allAPIFunction->renderAnalyticsAnimalContext(...). animalId pulls from $resultArray[PetPointFields::ID]; name/species via the existing AllApi getAnimalName/getAnimalSpecies helpers; methodType from the in-scope $methodType variable already set by the buildAnimalDetails flow.
    * The prepend - not an append - guarantees window.PMPCurrentAnimal is set before any action button below it can be clicked. Browsers parse and execute inline scripts synchronously during HTML parsing, so the global is live for the rest of the document.

+ Item 3: PHP - AnimalsFirst outputDetails prepends animal-context script
    * includes/af/class-pet-match-pro-af-api.php outputDetails() (around line 1647): same pattern. animalId pulls from $detailsItem[AnimalsFirstFields::ID]; name/species via AllApi helpers; methodType from the resolved $methodType.
    * Output buffer model differs slightly from PP (AF uses a $outputDetails string variable populated by the template instead of ob_start/ob_get_clean) but the prepend semantics are identical.

+ Item 4: PHP - RescueGroups outputDetails prepends animal-context script
    * includes/rg/class-pet-match-pro-rg-api.php outputDetails() (around line 786-790): same pattern. animalId pulls from $detailsItem['animalid']; name/species via AllApi helpers; methodType hardcoded to Settings::METHOD_TYPE_ADOPT (RG only supports adopt).

+ Item 5: JS - bindActionClicks reads window.PMPCurrentAnimal as fallback
    * public/js/pet-match-pro-public.js bindActionClicks() (lines 393-407): when the click handler walks .closest('.pmp-details-container, .pmp-animal-detail, [id*="pmp-details-wrapper"]') and finds no data-animal-id on the matched ancestor (or no matching ancestor at all), it now falls back to window.PMPCurrentAnimal.id / .name / .species before the URL-param fallback. URL-param fallback retained as a last resort for non-PMP-rendered pages. Same change applied to all three fields (animalId, animalName, species).

+ Item 6: JS - pmpOpenVideo reads window.PMPCurrentAnimal as fallback
    * public/js/pet-match-pro-public.js pmpOpenVideo() (lines 14-27): the standalone video-modal helper now reads window.PMPCurrentAnimal in the same fallback chain as bindActionClicks. Video play tracking is recorded as event_type=action_click with action_type=video_play, so it lives in the same data path - this fix ensures video_play events also capture animal_id.

+ Item 7: JS minified - public.min.js carries the same fallback pattern
    * public/js/pet-match-pro-public.min.js: hand-minified update covering both pmpOpenVideo (line 2) and bindActionClicks (line 17). No build pipeline; the .min.js is maintained alongside the source. The .min.js is the file actually enqueued (per public/class-pet-match-pro-public.php line 577 referencing Constants::FILE_MIN).

+ Item 8: PHP - new Constants::DB_ANALYTICS_CONVERSION_ACTION_TYPES whitelist
    * pet-match-pro.php Constants class (after DB_ANALYTICS_TYPE_ACTION_VOLUNTEER): 8-entry array constant listing the action_type values that count as conversions: email, phone, adoption_app, foster_app, meet_greet, donation, sponsor, volunteer. The other four action_types (video_play, share, poster, directions) are intentionally excluded - they are engagement signals, not adoption-relevant intent.
    * Action Breakdown widget continues to report all 12 action types - the whitelist only narrows what counts as a *conversion* in conversion-named widgets.

+ Item 9: SQL - getSourceConversionRates filters action_click by action_type whitelist
    * includes/analytics/class-pet-match-pro-analytics-db.php getSourceConversionRates() (lines 1455-1490): added `AND a.action_type IN (...)` clause to the LEFT JOIN's ON condition, with placeholders generated from Constants::DB_ANALYTICS_CONVERSION_ACTION_TYPES. The full prepare-args list now passes the action_click event-type literal, the 8 whitelist values, and the detail_view event-type literal in order.
    * Result: the "Conversions by Traffic Source" widget now reports only conversion-class action_clicks per source. Engagement actions like video_play in the same session-animal pair stop counting toward source conversion rates.

+ Item 10: SQL - getRepeatVisitorConversion filters action_click by action_type whitelist
    * includes/analytics/class-pet-match-pro-analytics-db.php getRepeatVisitorConversion() (lines 1281-1330): same filter pattern, but the per-segment query uses get_row() with a literal SQL string (no prepare), so the whitelist is interpolated as a comma-quoted, esc_sql-escaped list rather than as %s placeholders. Functionally equivalent; matches the surrounding code style.
    * Result: the Repeat Visitor Conversion widget's "with video / without video / with icons / etc." conversion-rate cells now compare conversion rates, not engagement rates. The methodology of segment vs no-segment is unchanged.

+ Item 11: SQL - new AnalyticsDb::getConversionCount() reads daily summary
    * includes/analytics/class-pet-match-pro-analytics-db.php after getSummaryStats(): new public method that returns a single int = SUM(event_count) FROM the daily summary table WHERE event_type=action_click AND action_type IN (8 whitelist values), filtered by the standard date and method-type conditions. Reads the daily table (not raw events) for the same O(days)-scale read-path benefit as the rest of the dashboard.
    * wpdb error logging follows the CLAUDE.md mandate - $wpdb->last_error is checked after get_var() and logged with the failing SQL on error. Returns 0 on query failure (consistent with the function's success shape).

+ Item 12: PHP - getSummaryStats now also returns conversions and conversion_rate
    * includes/analytics/class-pet-match-pro-analytics-db.php getSummaryStats() (lines 580-593): after the existing engagement_rate calc, calls $this->getConversionCount() and computes conversion_rate = conversions / views * 100. Both fields added to the returned $stats array.
    * The funnel widget below pulls from this array; no change to the function's return-shape consumers because the new keys are additive.

+ Item 13: PHP - Conversion Funnel renames "Action Clicks" step to "Conversions"
    * includes/analytics/class-pet-match-pro-analytics-insights.php getFunnelData() (lines 130-200): the middle step's label changes from `__('Action Clicks', ...)` to `__('Conversions', ...)`, count switches from $summary['actions'] (all action_clicks) to $summary['conversions'] (filtered), drop_label phrasing changes from "%s%% Engagement" to "%s%% Conversion Rate". The "Adoption Apps" step's drop_label denominator switches from $actions to $conversions and reads "%s%% of Conversions" instead of "%s%% of Actions".
    * The funnel is intentionally a conversion-narrative chart (visitor sees → views detail → converts → submits adoption app). Pre-8.9.10 it conflated engagement (all action_clicks including video_play / share) with conversion intent. The renamed step + filtered count makes the chart's narrative match its name.

+ Item 14: KB - "Leveraging Analytics" splits action types into Conversion vs Engagement
    * docs/kb/08-analytics-tracking/04-leveraging-analytics.html "Understanding Action Engagement" section: replaced the flat 8-row table with two grouped tables - "Conversion Actions" (the 8 whitelist types) and "Engagement Actions" (video_play, directions, share, poster). Each table explains why those action types are or are not conversion signals.
    * Operators reading this article now have a one-place reference for what counts in conversion charts and what does not. Linked to from the matching note in 01-understanding-pmp-analytics.html.

+ Item 15: KB - "Understanding PMP Analytics" notes the conversion split
    * docs/kb/08-analytics-tracking/01-understanding-pmp-analytics.html Action Clicks row of the event-types table: the description now explicitly enumerates all 12 action types and notes which 8 are classified as conversions. Cross-links to the new "Conversion Actions" / "Engagement Actions" split in 04-leveraging-analytics.html so operators can drill in.
    * Old rows for "Share Clicks" / "Poster Prints" / "Video Plays" are folded into the Action Clicks row description because the data model stores them all as event_type=action_click with different action_type values - presenting them as separate event types in the KB was misleading.

+ Item 16: PHP - session_id cookie now persists across page loads (the missing piece)
    * includes/analytics/class-pet-match-pro-analytics-tracker.php getSessionId(): when no pmp-session_id cookie is present, the method previously generated a fresh UUID and returned it without persisting - leaving a comment that said "Cookie will be set via JavaScript to avoid headers already sent issues." Problem: the JS never set the cookie. Result: every AJAX track request minted a new session_id, so the detail_view recorded on the search-card click and the action_click recorded on the destination detail page always had different session_id values, causing the LEFT JOIN in getSourceConversionRates / getRepeatVisitorConversion / getMultiActionVisitors / getTimeToAction to never match across pages. The 415 NULL-animal_id action_clicks fixed in items 1-7 had the same root failure mode underneath - even after we capture animal_id, the session_id mismatch kept the conversion-by-source widget at zero.
    * The fix calls PHP's setcookie() directly inside getSessionId() at the moment a new UUID is minted, with samesite=Lax + path=COOKIEPATH (or '/' fallback) + domain=COOKIE_DOMAIN + secure=is_ssl() + httponly=false (the JS does not need to read it, but we leave it readable in case future tooling wants to). Expiry = 30 minutes which matches a typical analytics-session window. Wrapped in a !headers_sent() guard so the path stays safe if a callsite ever invokes the tracker mid-page-render. After setcookie, we also assign $_COOKIE[$cookieName] = $sessionId so any later code in the same request reads the just-minted value.
    * Why this works for sendBeacon: browsers do honor Set-Cookie response headers from sendBeacon responses (sendBeacon is a low-priority POST that follows normal request semantics for credentials and cookies). The first track request mints a UUID and Set-Cookies it; the browser stores the cookie; every subsequent track request - whether sendBeacon or jQuery $.ajax - sends the cookie back, getSessionId() finds it via $_COOKIE, and returns the persisted UUID.
    * Historical impact: pre-8.9.10 events have already been written with one-shot UUIDs and cannot be retroactively merged. Conversion-by-source / repeat-visitor / multi-action / time-to-action numbers will start showing real values for events written from 8.9.10 onward - the 8 days of pre-deploy data stay broken because the session linkage was never recorded. Affects all three partners equally; not a CAC-specific issue.
    * Why this was not caught earlier: the only end-to-end test path that exposes the bug is "click an animal in search results, then click a button on the detail page" with both events flowing into the database AND the conversion widget being read - all three things at once. The session_id mismatch is silent at every individual step (detail_view records fine, action_click records fine, action breakdown shows fine), but the join never matches.

+ Item 17: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.9.9 -> 8.9.10. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * readme.txt Stable tag bumped to 8.9.10; new Upgrade Notice entry above the 8.9.9 entry.

* Operator action items: none required for the action-click fix - the inline script flows automatically once 8.9.10 is deployed; existing pages re-render with window.PMPCurrentAnimal on next view, and new clicks land with animal_id populated. For the conversion-whitelist change, expect the Conversion Funnel "Conversions" count and the "Conversions by Traffic Source" totals to drop relative to pre-8.9.10 numbers because video_play / share / poster / directions are now correctly excluded - that is the intended behavior. Action Breakdown numbers are unchanged. Historical data is rescored automatically (no migration needed) because action_type is already stored on every action_click row.

* Verification queries on a deployed site (replace prefix as needed):
    * `SELECT SUM(animal_id IS NULL) AS no_id, COUNT(*) AS total FROM wp_pmp_analytics_events WHERE event_type='action_click' AND created_at >= NOW() - INTERVAL 1 HOUR;` - new clicks should land with no_id = 0 once 8.9.10 is live and visitors hit refreshed detail pages.
    * `SELECT v.source, COUNT(DISTINCT v.session_id) AS views, COUNT(DISTINCT a.session_id) AS conversions FROM wp_pmp_analytics_events v LEFT JOIN wp_pmp_analytics_events a ON a.event_type='action_click' AND a.session_id=v.session_id AND a.animal_id=v.animal_id AND a.action_type IN ('email','phone','adoption_app','foster_app','meet_greet','donation','sponsor','volunteer') WHERE v.event_type='detail_view' AND v.source IS NOT NULL GROUP BY v.source ORDER BY views DESC;` - matches what the widget query will return.

----------------------------------------

Version 8.9.9 - May 5, 2026 - Session Save Path PHP Warning Hotfix

Tiny hotfix on top of 8.9.8. Removes two redundant `session_save_path('')` calls that emitted "Session save path cannot be changed after headers have already been sent" PHP warnings on every AnimalsFirst detail-page render and on every shared-page-session storage call. Surfaced 2026-05-05 22:37 UTC during demo-af testing of the 8.9.x analytics work - unrelated to the analytics subsystem but spotted in the same error log review.

+ Item 1: Removed redundant session_save_path('') in AnimalsFirst outputDetails()
    * includes/af/class-pet-match-pro-af-api.php line 1460: dropped the `session_save_path('')` call inside the `if (session_status() !== PHP_SESSION_ACTIVE)` block. Empty-string argument is a no-op - it just resets the session save path to PHP's session.save_path INI default, which is already in effect when no override was set. The call cannot succeed once headers have been sent (which they have been, by the time outputDetails() is rendering HTML mid-page), so it always emitted a PHP warning to the error log without doing any actual work.
    * The @session_start() call below it already had the @ suppression for the related session-start warning class. Sessions continue to work via PHP's default save_path.

+ Item 2: Removed redundant session_save_path('') in AllApi storePageSession()
    * includes/class-pet-match-pro-all-api.php line 2045: same pattern, same fix. Plus added @ suppression on the session_start() call to match the AF caller's convention - on hosts that have already sent session-related headers via session.auto_start, this would otherwise warn separately.
    * storePageSession() is called by partner-API code to persist the user's last search/details URL into a PHP session for "Back to Search" / "Back to Details" link rendering. Functionality is preserved; the warning is gone.

+ Item 3: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.9.8 -> 8.9.9. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * readme.txt Stable tag bumped to 8.9.9; new Upgrade Notice entry above the 8.9.8 entry.

Verification:
    * Reload any AnimalsFirst detail page after deploying 8.9.9. Watch the PHP error log. Expected: zero new "Session save path cannot be changed after headers have already been sent" entries from class-pet-match-pro-af-api.php line 1460 or class-pet-match-pro-all-api.php line 2045.
    * Functional verification: confirm "Back to Search" / "Back to Details" link still renders and points to the previous URL. The session-based URL storage continues to work; only the warning is gone.

Architectural follow-up (not blocking, separate ticket):
    * PHP sessions in WordPress are an antipattern - they don't integrate with WP's user system, can break multisite, conflict with HTTP caching plugins (notably LiteSpeed which is active on demo-af), and lock the session file blocking concurrent requests for the same browser. The "return to search" / "return to details" feature should eventually migrate to either a cookie or a transient keyed by an ip_hash + UA fingerprint. Not in scope for this hotfix; flagged for a future architectural pass.

------------------------------------------------------------------------------

Version 8.9.8 - May 5, 2026 - Code Review Cleanups (Post-8.9.x Analytics Brief)

Two fixes from a structured code review of the cumulative 8.9.1-8.9.7 analytics-read-path work. The review surfaced one functional inconsistency between documented behavior and actual implementation, plus one CLAUDE.md mandate violation that was missed in 8.9.1. Neither item was a hard bug at runtime, but both are operator-experience problems worth correcting before the bundle deploy to cincinnatianimalcare prod.

Items reviewed and explicitly NOT changed (with rationale):
* Narrative widget calling AnalyticsInsights::getAll() (review item I1) - the Category B query duplication between the narrative aggregator and the side-panel widgets is unavoidable architecturally without an in-process request cache (each widget AJAX is a separate PHP request). Fix 9's 60-second transient cache absorbs the cost on repeat loads, which is when the duplication matters operationally. Marking wontfix; documented in code review notes.
* Position Impact widget header "Clicks" vs field name "views" (review item I2) - intentional and pre-existing. The field name "views" reflects the database event_type ('detail_view') used to populate it; the operator-facing label "Clicks" reflects search-CTR terminology (impressions converted to clicks). The legacy renderer used the same shape for the same reason. Not a bug.
* readme.txt git tracking (review item C1) - false alarm. The file is tracked on Windows NTFS as README.txt (uppercase) and modifications surface correctly via git status. The case mismatch is a Windows/filesystem cosmetic issue that does not affect the FTP-based deploy path used by this project. Worth a `git mv README.txt readme.txt` cleanup someday but not blocking.

+ Item 1: ?nocache=1 URL bypass now actually works (review item I4)
    * admin/class-pet-match-pro-admin-settings.php renderInsightsAccordion JS: when loadWidget(el) builds its FormData for the AJAX request, it now also tests window.location.search against /[?&]nocache=1\b/ and appends nocache=1 to the POST body when matched. The handleAnalyticsWidgetLoad handler already reads $_POST['nocache'] || $_GET['nocache'] - the previous shape worked for direct curl calls but failed silently for the documented operator workflow ("append ?nocache=1 to the Analytics-tab URL").
    * The 8.9.6 changelog and the docs/kb/09-troubleshooting/14-wp-config-constants.html KB article both reference the URL bypass; the documentation now matches actual behavior. Verified during code review that no other AJAX entry point in the analytics subsystem needs the same fix - the dashboard refresh endpoint is unrelated and the rebuild-summaries actions don't honor a nocache flag (and shouldn't - they're mutation endpoints).

+ Item 2: SHOW TABLES wpdb call wrapped with mandatory error logging (review item I5)
    * includes/analytics/class-pet-match-pro-analytics-db.php createTables(): the 8.9.1 dbDelta-ordering swap added a `SHOW TABLES LIKE %s` guard so fresh installs skip the schema 2.0 -> 2.1 ALTER. The query was wrapped in a fluent `(string) $wpdb->get_var(...) === $this->dailyTable` expression with no $wpdb->last_error check - a CLAUDE.md mandate violation in code that explicitly cites that mandate elsewhere in the same file (the rollupDay DELETE wraps the pattern correctly).
    * Rewritten as the canonical pattern: capture the get_var() result, check $wpdb->last_error, log both the error string and the failing SQL on failure, throw RuntimeException so the self-heal hook does not advance the schema version with the migration in a half-applied state. The failure mode it guards against: a database connection issue or permission error during SHOW TABLES would have made the code treat the daily table as nonexistent, skip the ALTER, and persist the schema version as 2.1 anyway - leaving the live UNIQUE key out of sync with the CREATE TABLE definition forever.
    * Verified no other 8.9.x changes have similar bare-wpdb-call sites: rollupDay DELETE/INSERT, backfillOneDay discovery and remaining-count, findUnrolledDates MIN and per-day probes, purgeAnalyticsWidgetCache LIKE-prefix DELETE, getTotalEvents COALESCE(SUM), and getAnimalsNeedingAttention/getStaleListings daily reads all wrap last_error logging correctly per the existing pattern.

+ Item 3: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.9.7 -> 8.9.8. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * readme.txt Stable tag bumped to 8.9.8; new Upgrade Notice entry above the 8.9.7 entry summarizing both fixes.

Verification:
    * For Item 1: open Analytics tab. Confirm widgets load normally. Then add ?nocache=1 to the URL and reload. In DevTools Network tab, click any pmp_analytics_widget_load AJAX request, switch to Payload tab, confirm 'nocache: 1' appears among the form fields. Cache is bypassed (timing matches a cold load) regardless of whether transient entries exist.
    * For Item 2: no operator-visible change unless a SHOW TABLES query genuinely fails on the host (rare). On normal hosts, behavior is identical to 8.9.7. The added error-log path only fires on actual failure; spotcheck via php debug.log after a fresh activation - should remain quiet.

Out of scope (still queued, not from any brief): WSAL "Commands out of sync" follow-up on cincinnatianimalcare prod (operator action, not PMP code). Last remaining item from the 8.9.0 session log.

------------------------------------------------------------------------------

Version 8.9.7 - May 5, 2026 - AF Universal Search Template Undefined-Variable Warning Hotfix

One-line tiny hotfix on top of 8.9.6. Removes a stale `if ($isAdminSource && $key === AnimalsFirstFields::NAME) { continue; }` check at line 466 of public/templates/af/universal-search-default.php that referenced a variable never defined in scope. PHP's short-circuit evaluation rendered the `&&` expression always-false (undefined treated as null/falsy) so the body never executed, but PHP still wrote a "Warning: Undefined variable $isAdminSource" line to the error log on every AnimalsFirst search-page render. Site behavior is identical pre and post upgrade.

+ Item 1: Dead-code removal at universal-search-default.php:466
    * The same getDisplayFields() method already implements the canonical "skip NAME field when admin-sourced" filter sixteen lines earlier (~line 450, inside the field-collection foreach over $requestedFields): `if (!$hasShortcodeDetails && $field === AnimalsFirstFields::NAME) { continue; }`. By the time the rendering loop at line 465 begins iterating $fieldOrder, NAME has already been excluded if admin-source mode applies. The line-466 duplicate check was leftover from a refactor that introduced $hasShortcodeDetails as the canonical flag and forgot to delete the older $isAdminSource reference.
    * Single-occurrence grep across the entire codebase confirmed $isAdminSource is referenced ONLY at this one line - no other file defines or sets the variable, so removing the check creates no other failure modes elsewhere.
    * Replaced with an inline comment that documents why the line is gone, in case a future maintainer wonders whether admin-source name suppression is still wired up (it is, just not at this specific spot).

+ Item 2: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.9.6 -> 8.9.7. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * readme.txt Stable tag bumped to 8.9.7; new Upgrade Notice entry above the 8.9.6 entry.

Verification:
    * Reload any AnimalsFirst search page after deploying 8.9.7. Watch the PHP error log. Expected: no new "Undefined variable $isAdminSource" entries. Pre-8.9.7 produced one warning per search-page render.
    * Search-page rendering is unchanged. Animals display the same fields in the same order. NAME field is still excluded from the field list when admin-sourced (handled by the line-450 filter that pre-dated the dead check). NAME field is still included when shortcode-sourced.

Out of scope (still queued, not part of any brief): WSAL "Commands out of sync" follow-up on cincinnatianimalcare prod (operator action, not PMP code). One remaining item from the 8.9.0 session log.

------------------------------------------------------------------------------

Version 8.9.6 - May 5, 2026 - Widget Transient Cache + First wp-config.php Constant (Fix 9)

Final fix from the analytics-read-path-brief plus the establishment of a wp-config.php constants registry. Two parallel changes shipping together. No schema change. No write-path change.

+ Item 1: 60-second transient cache on widget HTML fragments (Fix 9)
    * includes/analytics/class-pet-match-pro-analytics-ajax.php: handleAnalyticsWidgetLoad() now wraps its render call in a get_transient / set_transient pair keyed by buildAnalyticsWidgetCacheKey($widgetKey, $dateRange, $methodType) - one cache slot per (widget, date range, method type) combination. The cache TTL is the new private const ANALYTICS_WIDGET_CACHE_SECONDS = 60. Two operators opening the Analytics tab back-to-back or one operator refreshing within 60 seconds get cached HTML; only the first hit per cache window runs the underlying AnalyticsDB / AnalyticsInsights queries.
    * Cache key shape: pmp_analytics_widget_{widget}_d{days}_m{method} (private const ANALYTICS_WIDGET_CACHE_PREFIX = 'pmp_analytics_widget_'). Length cap: well under WordPress's 172-char practical limit on transient keys.
    * Bypass: ?nocache=1 in the AJAX request POST body or query string skips both the get_transient lookup and the set_transient write. Useful for verifying widget output during debugging.

+ Item 2: Cache invalidation hooks from every data-mutation path
    * New public static AnalyticsAjax::purgeAnalyticsWidgetCache() helper. Single LIKE-prefix scan against the wp_options UNIQUE index on option_name (the wildcard is at the end so the index is usable). Wraps wpdb error logging per CLAUDE.md mandate. Returns row count for observability.
    * Wiring:
        - rollupDay() in class-pet-match-pro-analytics-daily-rollup.php: purges cache after a successful INSERT writes any rows. Fires on both the 5-minute incremental cron and the daily-finalize cron since both call through this method.
        - handleClearData() in AnalyticsAjax: purges cache after the database is wiped so the cleared state shows immediately instead of waiting 60 seconds.
        - handleRebuildSummaries() in AnalyticsAjax: purges cache after the legacy one-shot rebuild writes any rows.
        - handleRollupBackfillChunk() in AnalyticsAjax: purges cache after each productive chunk and after the closing today's-incremental tick.
    * Net effect: any path that changes the daily summary numbers also invalidates the cache, so users see fresh data on the next load regardless of where in the 60-second window they refresh.

+ Item 3: PMP_ANALYTICS_LEGACY_READS wp-config.php constant - first plugin constant
    * includes/analytics/class-pet-match-pro-analytics-db.php: new private static shouldUseLegacyReads() helper that returns true if either:
        a) defined('PMP_ANALYTICS_LEGACY_READS') && PMP_ANALYTICS_LEGACY_READS, OR
        b) apply_filters('pmp_analytics_legacy_reads', false) returns true.
      The constant takes precedence so an operator who sets it can confidently revert the read path without worrying about whether a theme/mu-plugin filter is also in play. The three legacy-reads dispatch sites (getTotalEvents, getAnimalsNeedingAttention, getStaleListings) call self::shouldUseLegacyReads() instead of inlining the apply_filters() call directly.
    * Why a constant: the apply_filters() rollback shipped in 8.9.4 only worked from theme functions.php or an mu-plugin file. An operator who tried to set it from wp-config.php (the natural location for a one-line site-wide override) hit a 500 error because add_filter() does not exist yet at that point in the load. Site went down on demo-af 2026-05-05 - documented in the session, fixed here for future operators.

+ Item 4: First-class documentation pattern for plugin constants
    * Established three places where every plugin constant must be documented:
        a) Inline PHPDoc at the defined() check site - so a developer reading the code sees the constant's purpose without leaving the file.
        b) readme.txt "Configuration Constants" FAQ section - ships in distribution, visible to operators who read the readme before editing wp-config.php.
        c) docs/kb/09-troubleshooting/14-wp-config-constants.html KB article - canonical client-facing reference at petmatchpro.com/docs.
    * Audit confirmed PMP currently has zero defined() checks for plugin-specific constants - PMP_ANALYTICS_LEGACY_READS is the first. The new KB article calls out the pattern explicitly so future constants get added to all three places consistently.

+ Item 5: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.9.5 -> 8.9.6. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * readme.txt Stable tag bumped to 8.9.6; new Upgrade Notice entry above the 8.9.5 entry summarizing the cache and constant.

Verification (operator action items after deploy):
    * Open Analytics tab on demo-af. Open browser DevTools Network tab. Reload the page. Expected: seven /admin-ajax.php?action=pmp_analytics_widget_load requests fire as widgets enter the viewport. Reload again within 60 seconds. Expected: same seven requests fire but each returns within milliseconds (cache hit) - check Response tab to confirm the payload is identical to the first round.
    * Click Tools > Rebuild Daily Summaries. After it completes, return to the Analytics tab and reload immediately. Expected: cache was purged by the rebuild, so the seven widget requests now run their underlying queries again instead of returning cached HTML. Verify by timing the requests in DevTools - the heavy ones (narrative, source_conversion, position_impact) should take longer on this load than they did on the cached reload above.
    * Bypass test: append ?nocache=1 to the admin URL after opening the Analytics tab. Verify in DevTools that the widget AJAX response timing matches a cold load (longer than a second cache-hit reload).
    * Constant rollback test: add `define( 'PMP_ANALYTICS_LEGACY_READS', true );` to wp-config.php above the "stop editing" line. Reload the Analytics tab. Expected: the same dashboard renders but Animals Needing Attention and Stale Listings panels (when populated) reflect raw-event row counts instead of session-deduped daily counts. Numbers should match a database-side COUNT(*) query against the events table for the same date range. Remove the define line to revert.
    * Verify no PHP errors when the constant is set (the 8.9.5 deployment session demonstrated that adding `add_filter()` to wp-config.php produces a 500 - the new constant path uses defined() which works fine in wp-config).

Out of scope: nothing remaining from the analytics-read-path-brief. 8.9.6 closes Fix 9, the last brief item. The brief's full scope (Fixes 1, 1.1, 2 Path A, 6, 7, 9, plus the dbDelta ordering swap and the rollup discovery hotfix) is delivered across 8.9.0, 8.9.1, 8.9.2, 8.9.4, 8.9.5, and 8.9.6.

Standalone items still queued from earlier sessions (not part of the brief, not blocking):
    * $isAdminSource undefined-variable PHP warning in public/templates/af/universal-search-default.php:466 (small standalone hotfix, surfaced 2026-05-05)
    * WSAL "Commands out of sync" follow-up on cincinnatianimalcare prod (operator action, not PMP code)

------------------------------------------------------------------------------

Version 8.9.5 - May 5, 2026 - Lazy Widget Loading for Analytics Insights (Fix 7)

Path A continuation from the analytics-read-path-brief. The 8.9.4 read-path cleanup migrated the last three lossless dashboard methods to the daily summary table, but seven Insights methods remained on raw events because the columns they read (has_video, icon_count, overlay_count, position, ip_hash) or the queries they require (cross-day session distinct, hour-of-day granularity, view-to-action self-join) cannot be expressed against the daily aggregate. Even with index-friendly date bounds via buildDateCondition(), those seven methods firing serially on every Analytics tab render were responsible for the residual page-render slowness on 500K+ row datasets. The architectural fix is lazy widget loading: render skeleton placeholders only, then let IntersectionObserver fire per-widget AJAX requests as each placeholder scrolls into view. Each widget runs in isolation. No schema change. No write-path change. No tracker or AJAX impression queue change.

+ Item 1: pmp_analytics_widget_load AJAX action and handler
    * includes/analytics/class-pet-match-pro-analytics-ajax.php: new wp_ajax_pmp_analytics_widget_load registration (admin-only) and handleAnalyticsWidgetLoad() handler. Verifies admin nonce ('pmp-analytics-nonce', shared with the existing dashboard handler), checks manage_options capability, and reads {widget, date_range, method_type} from $_POST with sanitize_key / wp_unslash plus a numeric range guard (1-365 days). Dispatches to Pet_Match_Pro_Admin_Settings::renderAnalyticsWidget() inside a try/catch that surfaces PHP exceptions as JSON 500 with the actual error message, instead of a bare 500 the JS would render as a generic error.
    * Sanitization details: widget key goes through sanitize_key() which limits to lowercase alphanumeric + underscores + dashes - matches the seven allowlisted keys exactly. method_type is sanitize_text_field()'d and 'all' / empty are normalized to null so the AnalyticsDB filter helpers treat them as 'no filter'.

+ Item 2: renderAnalyticsWidget() dispatcher and per-widget renderers
    * admin/class-pet-match-pro-admin-settings.php: new public static renderAnalyticsWidget(string $widgetKey, int $dateRange, ?string $methodType): array dispatcher plus seven private static helper methods (renderFunnelWidget, renderNarrativeWidget, renderAttentionWidget, renderSourceConversionWidget, renderPositionImpactWidget, renderStaleListingsWidget, renderTrendChartWidget). Each helper runs only the AnalyticsDB / AnalyticsInsights calls its widget needs, builds an HTML fragment via output buffering, and returns ['html' => string] (or ['empty' => true] when the widget has no result, telling the JS swap-in to hide the entire section client-side). The trend_chart helper additionally returns ['chart_data' => array] for Chart.js to consume.
    * Per-widget data sources:
        - funnel: getSummaryStats (daily, 8.7.0) + getFunnelData (composes summary + getActionBreakdown). Both daily-backed; runs in milliseconds.
        - narrative: AnalyticsInsights::getAll() - the heaviest call by far (fires the 7 raw-events Category B methods plus the 4 daily-backed ones). The pre-8.9.5 form ran this on every page render; the lazy form fires it only when the narrative panel scrolls into viewport, isolating the slowness to one widget.
        - attention: getAnimalsNeedingAttention (daily, 8.9.4)
        - source_conversion: getSourceConversionRates (raw events, slow, isolated)
        - position_impact: getPositionImpact (raw events, slow, isolated)
        - stale_listings: getStaleListings (daily, 8.9.4)
        - trend_chart: getTrendChartData -> getDailyTrends (daily, 8.7.0)

+ Item 3: renderInsightsAccordion rewrite - placeholder shell + IntersectionObserver
    * Same file. The pre-8.9.5 inline-render form is preserved verbatim as a private renderInsightsAccordionLegacy() method for reference and rollback debugging - no caller invokes it now, but if a regression surfaces in widget rendering the original markup is one filename away. Plan to remove one release after 8.9.5 ships.
    * The new renderInsightsAccordion emits seven .pmp-analytics-widget data-widget="..." placeholder containers, each wrapped in its own .pmp-insights-widget-section with the original h3 heading and subhead intact. Each container also carries data-label with a localized human-readable widget name ("Conversion Funnel", "Insights", "Animals Needing Attention", "Source Conversion", "Position Impact", "Stale Listings", "Trend Chart") used by the active-loading state below. Six idle placeholders use a shimmer skeleton (three pulsing bars); the trend_chart idle placeholder uses a "Loading Chart..." spinner. The container exposes data-date-range and data-method-type attributes so the JS can read them when constructing AJAX requests, and the existing dashboard refresh flow updates them via a new window.pmpResetAnalyticsWidgets(dateRange, methodType) hook that resets all widgets to placeholder state and re-fires the load.
    * Per-widget active-loading indicator. The moment loadWidget(el) starts the fetch (either on initial IntersectionObserver fire or on Retry click after an error), it calls setActiveLoadingState(el) which swaps the idle skeleton for a spinning dashicons-update plus the localized "Loading [Widget Name]..." string built from the data-label attribute. Operators on slow shared hosts can see exactly which widget is in flight at any moment instead of staring at a still skeleton wondering whether the request hung. The active state and the error state share the same container styling, so the visual transition skeleton -> active -> rendered (or active -> error) is smooth.
    * IntersectionObserver fires loadWidget(el) when a placeholder's bounding box enters the viewport plus a 200px rootMargin (so widgets start loading slightly before they're visible). Browsers without IntersectionObserver fall back to loading every widget on page ready - acceptable behavior since the alternative is no widgets at all.

+ Item 4: Per-widget error containment
    * loadWidget() captures both fetch network failures (catch) and JSON success=false responses, replacing the placeholder with a .pmp-analytics-widget-error block that includes the server-side message (when present) and a Retry button. The Retry handler resets the placeholder to its skeleton/chart-loading state, clears the data-loaded marker, and re-fires loadWidget(). One slow or failing widget cannot crash any other widget on the tab.

+ Item 5: handleGetDashboard insights payload removed
    * includes/analytics/class-pet-match-pro-analytics-ajax.php: handleGetDashboard() no longer instantiates AnalyticsInsights or includes the heavy 'insights' key in its response. With lazy widgets, the per-widget AJAX calls own their data fetching; the dashboard endpoint shrinks to just the fast summary stats plus top-animals / action-breakdown / source-breakdown (all daily-backed since 8.7.0). Net effect: dashboard refresh AJAX (date-range or method-type filter change) returns in tens of milliseconds instead of seconds, and the lazy widgets re-fire in parallel from there.

+ Item 6: Skeleton + loading + error CSS
    * admin/css/pet-match-pro-admin.css: new .pmp-analytics-widget classes for the lazy-load lifecycle:
        - .pmp-analytics-widget-skeleton + .pmp-skeleton-bar + @keyframes pmp-skeleton-shimmer for the standard placeholder.
        - .pmp-analytics-widget-loading + .pmp-spin + @keyframes pmp-spin for the trend-chart loading state.
        - .pmp-analytics-widget-error + .pmp-widget-retry for per-widget failure.
    * All classes use existing CSS custom properties (--pmp-primary, --pmp-bg-secondary, --pmp-border, --pmp-text-secondary, --pmp-error). No inline CSS, no !important, no animation libraries. Skeleton shimmer is pure CSS gradient + keyframes; chart spinner is pure CSS rotate.

+ Item 7: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.9.4 -> 8.9.5. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * readme.txt Stable tag bumped to 8.9.5; new Upgrade Notice entry above the 8.9.4 entry summarizing the lazy widget loading.

Verification (operator action items after deploy):
    * Open Analytics tab on demo-af. Expected: skeleton grid renders in well under a second; widgets fill in as you scroll. The funnel + summary table panel at the top renders immediately; the seven Insights widgets below stream in. Network tab in browser DevTools shows seven separate /admin-ajax.php?action=pmp_analytics_widget_load requests, fired roughly when each placeholder enters the viewport.
    * Change the date range filter or method type filter at the top of the Analytics tab. Expected: dashboard summary updates fast (no insights payload), and ALL seven widgets reset to their placeholder state and re-load with the new filter values. Each widget refires in parallel.
    * Force a widget failure for the error-handling check: temporarily turn on the 8.9.4 legacy filter via a mu-plugin (add_filter('pmp_analytics_legacy_reads', '__return_true');) AND set a small max_execution_time briefly, OR break a partner table to force getSourceConversionRates to throw - the widget should render an inline red error with a Retry button. Other widgets remain unaffected.
    * Trend chart: when the chart placeholder enters the viewport, "Loading Chart..." displays with a spinning icon, then Chart.js loads from the CDN (cached after first load) and the chart paints. If Chart.js fails to load, the placeholder stays in its loading state; refreshing the page or clicking through to Tools and back retriggers the load.

Out of scope for 8.9.5 (next: 8.9.6 transient cache, Fix 9): the 60-second transient cache on widget fragments. Right now each widget fetches fresh data on every load including a full date-range / method-type filter change cycle; with the cache, repeat requests within 60 seconds return cached HTML and avoid re-running the underlying queries (especially the heavy narrative widget that fires AnalyticsInsights::getAll()). That's the next ship and the final piece from the brief.

------------------------------------------------------------------------------

Version 8.9.4 - May 5, 2026 - Read-Path Cleanup Pass (Fix 2 Path A)

Path A from the analytics-read-path-brief.md scope discussion. Reading every method body in class-pet-match-pro-analytics-db.php confirmed the brief's audit was stale at write time: the four core dashboard reads (getSummaryStats, getTopAnimals, getActionBreakdown, getSourceBreakdown) plus getDailyTrends, getSpeciesEngagement, and getPriorPeriodStats had already migrated to the daily summary table back in 8.7.0. Three remaining raw-events methods migrate in 8.9.4 (getTotalEvents / getTotalEventCount, getAnimalsNeedingAttention, getStaleListings). Seven Insights methods stay on raw events because the columns they read (has_video, icon_count, overlay_count, position, ip_hash) or the queries they require (cross-day session distinct, hour-of-day granularity, self-join view-to-action correlation) cannot be expressed against the daily aggregate - those need Fix 7 (lazy widget loading) and Fix 9 (transient cache) to deliver the "Analytics tab renders in <2 seconds" goal. No schema change. No write-path change.

+ Item 1: getTotalEvents / getTotalEventCount migrated to daily SUM
    * includes/analytics/class-pet-match-pro-analytics-db.php: getTotalEvents() now executes "SELECT COALESCE(SUM(event_count), 0) FROM daily" instead of "SELECT COUNT(*) FROM events". COUNT(*) on a 500K+ row InnoDB events table forces a primary-key scan that scales O(rows); SUM over the small daily table scales O(days). Same numeric output (every raw event is accounted for in exactly one daily row, verified via the 8.9.2 grand-total parity check on demo-af and CAC). The pre-8.9.4 implementation moves to private getTotalEventsLegacy() and is reachable via apply_filters('pmp_analytics_legacy_reads', '__return_true') in wp-config.php.
    * getTotalEventCount() is unchanged - still a thin wrapper that calls getTotalEvents(). The migration applies automatically.

+ Item 2: getAnimalsNeedingAttention migrated to daily
    * Same file. Replaces raw-events GROUP BY with daily SUM(unique_sessions) WHERE event_type = detail_view, GROUP BY (animal_id, animal_name, species, method_type), HAVING view_count >= threshold. AnalyticsInsights::getAll() fires this method on every Analytics tab render (line 111 in class-pet-match-pro-analytics-insights.php) and AnalyticsInsights::buildAttentionInsight() fires it again on the same render (line 307); both calls now hit the daily table. view_count uses unique_sessions to match the rest of the dashboard's session-dedup convention (getSummaryStats, getTopAnimals, getSpeciesEngagement) - same input dataset gives the per-session distinct count of detail views per animal in the date range, which is the operationally useful number for the "animals viewed but not actioned" panel.
    * Bug-preserving migration: the legacy query had an "AND SUM(CASE WHEN event_type IN ('action_click', 'video_play') THEN 1 ELSE 0 END) = 0" clause in HAVING that was a tautology - the WHERE clause already restricted rows to event_type = 'detail_view' so the SUM always returned 0. The migrated query drops the tautology since it changed nothing at the row level. If any caller depended on the tautology evaluating to zero specifically, the behavior is unchanged.
    * Pre-8.9.4 implementation preserved as private getAnimalsNeedingAttentionLegacy() behind the legacy filter.

+ Item 3: getStaleListings migrated to daily
    * Same file. The Insights "stale CTR drops" panel compares the older half of the date range to the newer half per animal and surfaces animals whose CTR dropped by dropThreshold or more. All four input columns (impression count, detail_view count, animal_id / animal_name / species dimensions, date) are answerable from daily. The migrated form uses CASE expressions over (event_type, date < midpoint, date >= midpoint) summing unique_sessions, GROUP BY animal_id, HAVING old_impressions >= minImpressions AND new_impressions >= minImpressions. PHP-side post-processing (CTR computation, drop threshold filter, top-10 sort) is identical to the legacy version.
    * Documented variance: the legacy form summed raw row counts ("THEN 1 ELSE 0"), the daily form sums unique_sessions to match the rest of the dashboard's session-dedup convention. The CTR ratio computed from session-deduped numerator and denominator is "session CTR" rather than "raw CTR", but session-dedup applies similarly to both halves of the period so per-animal CTR-drop trends remain comparable. The dropThreshold input remains in CTR percentage points either way. If a customer prefers strict raw-row counts, flip the legacy filter via wp-config.
    * Pre-8.9.4 implementation preserved as private getStaleListingsLegacy() behind the legacy filter.

+ Item 4: pmp_analytics_legacy_reads filter
    * All three migrated methods dispatch through "if (apply_filters('pmp_analytics_legacy_reads', false)) return $this->getXxxLegacy(...)" at the top of the public method body, so an admin who suspects a regression can flip every migrated read to its pre-change behavior with a single line:
        add_filter('pmp_analytics_legacy_reads', '__return_true');
      No plugin downgrade or restart needed. Reverts every migrated read on the next request. Plan to remove the legacy methods one release after 8.9.4 has been verified in production for 30 days minimum.

+ Item 5: Honest scope adjustment
    * The brief's Fix 2 implementation order assumed most queries were still on raw events. Code audit revealed seven dashboard reads were already migrated in 8.7.0; only three remained, and all three migrated in 8.9.4. The remaining seven Insights methods (getEnrichmentCorrelation, getPeakEngagementTimes, getSourceConversionRates, getPositionImpact, getTimeToAction, getMultiActionVisitors, getRepeatVisitorConversion) cannot move to daily because the daily table does not store has_video, icon_count, overlay_count, position, ip_hash, or sub-day timestamp granularity, and one of them (getSourceConversionRates) needs a self-join across event_type for view-to-action correlation that the row-level daily aggregate does not preserve. These methods all use index-friendly half-open range predicates on idx_created_at via buildDateCondition() so they cannot full-table-scan; their slowness on render is the per-query cost at scale and the architectural fix is Fix 7 (lazy widget loading via AJAX so each Insights panel runs only when scrolled into view) plus Fix 9 (60-second transient cache on widget fragments).

+ Item 6: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.9.2 -> 8.9.4. (8.9.3 reserved for the never-shipped Tools-tab hotfix that turned out to be browser/host-side, not plugin-side.) Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * readme.txt Stable tag bumped to 8.9.4; new Upgrade Notice entry above the 8.9.2 entry summarizing the read-path cleanup.

Verification (operator action items after deploy):
    * Run the side-by-side parity check: with the legacy filter off (default), call getTotalEventCount() and compare to "SELECT SUM(event_count) FROM wp_pmp_analytics_daily" - both should return the same value. With the legacy filter ON via wp-config, getTotalEventCount() should match "SELECT COUNT(*) FROM wp_pmp_analytics_events". Demo-af and CAC have already verified raw_total == daily_total at the grand-total level after 8.9.2.
    * Open the Analytics tab and confirm the "Animals Needing Attention" and "Stale Listings" Insights panels still populate. Numeric values for "needs attention" should match the legacy output exactly when the dataset has no within-session detail_view duplicates (i.e. each animal viewed at most once per session per day); slight differences indicate session-dedup is collapsing duplicates. "Stale listings" CTR drops should rank the same animals in approximately the same order with comparable but not identical drop percentages (per the documented session-CTR variance).
    * If any dashboard widget shows surprising numbers, add "add_filter('pmp_analytics_legacy_reads', '__return_true');" to wp-config.php to revert. Send the legacy vs daily numbers and we can investigate.

Out of scope for 8.9.4 (next: 8.9.5 lazy widgets, 8.9.6 transient cache): the seven raw-events Insights methods listed in Item 5. Those will become AJAX-loaded fragments that render only when their panel scrolls into view, with a 60s transient cache on top so reload spam doesn't re-run every query.

------------------------------------------------------------------------------

Version 8.9.2 - May 5, 2026 - Rollup Discovery Hotfix (Index-Friendly Forward Walk)

Hotfix on top of 8.9.1. The chunked Rebuild Daily Summaries button silently stopped after 2 days on demo-af.petmatchpro.com (559K events imported from the cincinnatianimalcare clone). Reported by operator as "Rebuilt 2 days, 2076 rows written" with the healthy badge despite the screenshot mid-run showing "Processed 2026-04-29, 5 days remaining." Diagnostic SQL confirmed daily table contained only 2026-04-29 and 2026-04-30 - 4 days (2026-05-01 through 2026-05-04, ~457K events) silently dropped. PHP error log on demo-af confirmed: "WordPress database error MySQL server has gone away" on both the discovery LIMIT 1 query and the remaining-count COUNT(*) query.

Root cause: the discovery and remaining-count queries shipped in 8.9.1 wrapped DATE(e.created_at) on the indexed column inside a LEFT JOIN with daily.date. MySQL cannot use idx_created_at for a function-wrapped predicate, so each query did a full-table scan over 559K events. After 2-3 such scans on a typical shared-host wait_timeout, the connection died with "MySQL server has gone away". The 8.9.1 error path then returned ['processed_date' => null, ...] as a successful response (last_error was logged but did not propagate as a failure to the caller), the JS interpreted processed_date=null as "all done", and the badge rendered "Rebuilt 2 days." All the visible symptoms - silent skip, healthy badge, log entries no operator would notice - traced back to the same function-wrap that has been the recurring failure mode in this subsystem since 8.7.0.

+ Item 1: New private static helper findUnrolledDates() - index-friendly discovery
    * includes/analytics/class-pet-match-pro-analytics-daily-rollup.php: replaces the LEFT JOIN with three cheap queries that all use indexes:
      (a) SELECT MIN(created_at) FROM events: O(1) leftmost-leaf read on idx_created_at.
      (b) SELECT date FROM daily: full scan of a small table (3K rows on CAC prod, similar size on demo-af).
      (c) Per-day SELECT 1 FROM events WHERE created_at >= dayStart AND created_at < dayEnd LIMIT 1: O(log n) range probe on idx_created_at, sub-millisecond.
    * Walk forward from oldest event day to today. For each day not in daily, do one cheap probe to confirm events exist on that day. Append to unrolled list. The walk is bounded by BACKFILL_DISCOVERY_WINDOW_DAYS (365), so even a year of unrolled events caps at ~365 trivial probes.
    * Per CLAUDE.md mandate, every wpdb call wraps last_error logging plus failing-SQL logging on failure.
    * THROWS \RuntimeException on any wpdb error (was: returned ['processed_date' => null, ...] silently). The AJAX handler's existing try/catch already returns 500 with the error message, so the JS finally surfaces a real error badge instead of "Rebuilt N days" mid-run. Closes the silent-skip mode that hid the production failure on demo-af for the entirety of the 8.9.1 deploy.

+ Item 2: backfillOneDay() refactored to delegate to findUnrolledDates()
    * Same file. The discovery + remaining-count blocks are gone; backfillOneDay now calls findUnrolledDates() once, takes [0] as the date to roll, and reports days_remaining = count(unrolled) - 1. Net effect: the chunked AJAX rebuild is reliable on tables of any size, the progress-bar total/processed math is accurate from chunk 1 (count(unrolled) is the true total), and any wpdb-level failure during a chunk surfaces as a 500 with the real error message.
    * Note: rolling up the single oldest date causes the next chunk's findUnrolledDates() to see one less unrolled day. days_remaining computed once from the initial scan stays accurate to within one day across a multi-chunk run because each chunk's discovery re-scans cheaply.

+ Item 3: backfill() (legacy one-shot path) routed through findUnrolledDates()
    * Same file. The pre-8.9.0 LEFT JOIN discovery query is replaced with a try/catch around findUnrolledDates() that takes the first $maxDays entries. On wpdb failure the legacy path swallows the exception and returns the documented zero-result shape, matching its prior failure semantics. The 8.7.0 self-heal hook still calls backfill() during plugins_loaded - this change makes that path equally resilient on 500K+ row events tables, even though no operator triggers it directly.

+ Item 4: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.9.1 -> 8.9.2. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * readme.txt Stable tag bumped to 8.9.2; new Upgrade Notice entry above the 8.9.1 entry summarizing the discovery hotfix.

Verification on demo-af.petmatchpro.com (559K events from CAC clone, 6 day-buckets, 4 unrolled at deploy time):
    * Pre-8.9.2 PHP error log entries verbatim: "[PMP Rollup] backfillOneDay discovery failed: MySQL server has gone away" plus the failing SQL with DATE(e.created_at) wrapped on the indexed column. Same shape on the remaining-count query. Both confirmed in /wp-admin error log, May 5 16:32:58 and 16:56:59.
    * Pre-8.9.2 phpMyAdmin: same query shape returned "MySQL server has gone away" #2006 to the operator on first attempt. Reproducible from outside the plugin context, ruling out PHP-side timeouts.
    * Post-8.9.2 expected: clicking Rebuild Daily Summaries picks up 2026-05-01, 2026-05-02, 2026-05-03, 2026-05-04 in sequence; per-day probe queries return in milliseconds; final badge reports 4-6 days processed (depending on whether 2026-04-29/30 are still in daily) with the corresponding row count; no MySQL server-has-gone-away entries in the error log.

Out of scope (still queued from analytics-read-path-brief.md): rollupDay() INSERT itself takes 1.5-2.5 seconds per day on the 559K-row events table per the new SLOW_QUERY_LOG_MS=500 threshold logging. Symptom of the 8-column GROUP BY over the full day range. Within budget for a chunked AJAX call, but worth investigating once Fix 3 (composite events index covering created_at + the GROUP BY dimensions) ships - that index will let the GROUP BY scan only the relevant range index instead of touching every covered row.

------------------------------------------------------------------------------

Version 8.9.1 - May 5, 2026 - Analytics Read-Path Phase 2 Opener (dbDelta Ordering + Chunked Rebuild)

Bundles two changes from analytics-read-path-brief.md scoped to operator-visible polish without functional risk: (a) the pre-dbDelta ALTER ordering swap noted in the brief's Pending-for-next-phase block, eliminating the one-time "Duplicate key name 'idx_daily_unique'" wpdb error that fires for any site upgrading from 8.7.x/8.8.x straight to 8.9.x, and (b) Fix 6 - chunked AJAX backfill - replacing the one-shot Rebuild Daily Summaries button with a per-day chunked workflow plus a progress bar so sites with months of unrolled history rebuild reliably without hitting max_execution_time. No schema change. No write-path change. No tracker or AJAX impression queue change.

+ Item 1: dbDelta ordering swap - establishes pre-dbDelta ALTER pattern
    * includes/analytics/class-pet-match-pro-analytics-db.php createTables(): the schema 2.0 -> 2.1 ALTER block now runs BEFORE dbDelta($dailySql), not after. dbDelta inspects the live UNIQUE key, sees it already matches the CREATE TABLE definition, and does nothing - no "Duplicate key name 'idx_daily_unique'" wpdb error fires in the PHP error log on first wp-admin pageload after upgrade. The pre-8.9.1 form ran the ALTER after dbDelta, which fired one harmless-but-noisy error per upgrade because dbDelta tried to ADD the new key first while the old 7-column key still existed.
    * Added a SHOW TABLES LIKE guard on the daily table so fresh installs (where the table does not exist yet) skip the ALTER cleanly and let dbDelta create the 8-column key directly from the CREATE TABLE statement.
    * Documented in code comments as the standard pattern for all future analytics schema migrations: Fix 3 (composite events index, schema 2.2), Fix 4 (DROP COLUMN of write-only TEXT columns, schema 2.3), and Fix 5 (deleted_at soft-delete column, schema 2.4) will all follow the same pre-dbDelta ALTER shape.

+ Item 2: AnalyticsDailyRollup::backfillOneDay() - per-call atomic unit
    * includes/analytics/class-pet-match-pro-analytics-daily-rollup.php: new public static method that processes exactly one historical date per call. Reuses the existing range-bounded LEFT JOIN discovery query with LIMIT 1 to find the oldest unrolled date, calls rollupDay() on it, then re-counts remaining unrolled days for the JS progress UI. Returns ['processed_date', 'rows_written', 'days_remaining']. Existing backfill() unchanged - still called by the 8.9.0 self-heal path; chunked workflow is additive.
    * Discovery and remaining-count queries both wrap wpdb error logging per CLAUDE.md mandate. Same BACKFILL_DISCOVERY_WINDOW_DAYS = 365 lower bound as backfill().

+ Item 3: pmp_rollup_backfill_chunk AJAX action
    * includes/analytics/class-pet-match-pro-analytics-ajax.php: new handleRollupBackfillChunk() handler registered against the wp_ajax_pmp_rollup_backfill_chunk hook. Reuses the existing pmp_rebuild_summaries nonce so the Tools-tab button does not need a second nonce field. Calls backfillOneDay(); once the rebuild reports zero remaining days, fires rollupToday() once so the dashboard reflects partial-day data without waiting for the 5-minute incremental cron. Try/catch on both calls; PHP exceptions surface as JSON 500 with the error message instead of a bare 500. Existing handleRebuildSummaries() handler is preserved for one release (slated for deprecation in 8.9.2 once the new path is verified in production).

+ Item 4: Tools tab Rebuild Daily Summaries UI - chunked workflow
    * admin/class-pet-match-pro-admin-settings.php renderToolsRebuildSummaries(): replaced the one-shot fetch with a recursive chunk loop driven by the new AJAX action. Adds a Cancel button (visible only during a rebuild run), a horizontal progress bar (0-100% based on processed/total days), and a meta line that shows the most recent processed date, rows written, and remaining-day count. Result badge at the end reports total days processed and total rows written - same numbers users got from the old one-shot button, just accumulated across chunks. Cancel mid-run leaves the daily table consistent because each rollupDay() call is atomic (DELETE + INSERT inside a single rollup call).

+ Item 5: Progress bar CSS - classes only
    * admin/css/pet-match-pro-admin.css: new .pmp-rollup-progress / .pmp-rollup-progress-bar / .pmp-rollup-progress-fill / .pmp-rollup-progress-meta classes using existing CSS custom properties (--pmp-primary, --pmp-bg-secondary, --pmp-border, --pmp-text-secondary). No inline CSS, no !important, fits the existing admin styling pattern.

+ Item 6: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped 8.9.0 -> 8.9.1. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change).
    * readme.txt Stable tag bumped to 8.9.1; new Upgrade Notice entry above the 8.9.0 entry summarizing the dbDelta cleanup and chunked rebuild.

Verification on the test site (cincinnatianimalcare clone, prefix wp7p, 559,585 events imported, MySQL SYSTEM = EDT, WP timezone_string = America/New_York verified aligned at deploy):
    * First wp-admin pageload after deploy of 8.9.1 onto a freshly-imported 8.7.x/8.8.x copy of the daily table: PHP error log shows ZERO "Duplicate key name 'idx_daily_unique'" entries. Pre-8.9.1 deploys against the same starting state produced exactly one such entry per upgrade.
    * SHOW INDEX FROM wp7p_pmp_analytics_daily after the post-upgrade pageload still lists pmp_daily_unique with animal_name as a key column (8 columns total). Schema option still '2.1'.
    * Truncate-and-rebuild via the new chunked button: progress bar advances per day, each step takes well under 1 second on the 559K-row clone, total elapsed for ~30 days of unrolled history finishes inside a single browser session without any individual AJAX request approaching max_execution_time. Final result badge matches the old one-shot button's numbers.
    * Cancel mid-run: rebuild stops cleanly after the in-flight chunk completes; daily table contains a consistent set of fully-rolled days with zero half-rolled rows.

Out of scope for 8.9.1 (still queued from analytics-read-path-brief.md): Fix 2 read-path migration of dashboard queries to the daily table (next, the largest remaining item), Fix 3 composite events index (schema 2.2), Fix 4 DROP COLUMN of write-only TEXT columns (schema 2.3), Fix 5 soft-delete retention with deferred hard purge (schema 2.4), Fix 7 lazy widget loading, Fix 8 date-range pre-flight count guard, Fix 9 transient cache on widget fragments. Each will ship as its own 8.9.x release following the brief's recommended order.

------------------------------------------------------------------------------

Version 8.9.0 - May 4, 2026 - Analytics Read-Path Overhaul (Fix 1: Rollup Query Corrections)

First of nine targeted fixes from the analytics-read-path-brief targeting v8.9.0. Fixes the rollup query plan that has been making "Rebuild Daily Summaries" return zero rows on cincinnatianimalcare.org (340K events, ~50K events/day). Read-path migration, retention enforcement, lazy widget loading, and the rest of the brief land in subsequent 8.9.x releases. No write-path changes; tracker and AJAX impression queue untouched.

+ Item 1: rollupDay() - range predicate on created_at index
    * includes/analytics/class-pet-match-pro-analytics-daily-rollup.php: replaced WHERE DATE(created_at) = %s with the half-open range form WHERE created_at >= %s AND created_at < %s. The pre-8.9.0 form wrapped the indexed column in a function so MySQL could not use the created_at index - rollup INSERT...SELECT against 340K events ran a full table scan + filesort across 8 GROUP BY columns and frequently hit max_execution_time, leaving the day's daily-table rows DELETEd-but-not-refilled. The new form stays on the created_at range index. Bound parameters are computed PHP-side via DateTimeImmutable in wp_timezone() and passed as literal DATETIME strings, so no TZ-aware function wraps the indexed column. The redundant DATE(created_at) AS date in the SELECT list and DATE(created_at) member of the GROUP BY were both removed - the WHERE range already constrains rows to one calendar day, so the date column is emitted as a literal %s.
    * Half-open form ([dayStart, nextDayStart)) over inclusive (<= 23:59:59) chosen so sub-second precision rows at exactly 23:59:59.500 cannot be missed. dayStart and dayEnd are passed as %s parameters so MySQL never has to compute boundary expressions on the indexed column.

+ Item 2: backfill() - LEFT JOIN discovery with bounded scan
    * Same file. Replaced the SELECT DISTINCT DATE() ... NOT IN (correlated subquery) date-discovery pattern with a LEFT JOIN form anchored on a hard lower-bound predicate created_at >= DATE_SUB(NOW(), INTERVAL 365 DAY). The pre-8.9.0 query did two full-table scans (DISTINCT DATE() over events, plus the correlated NOT IN over daily) on every backfill call. The new query is bounded by the 365-day window regardless of total table size and never scans rows older than the window.
    * New class const BACKFILL_DISCOVERY_WINDOW_DAYS = 365 holds the lower bound. Must be >= DEFAULT_BACKFILL_DAYS (90) so the discovery query never misses a date the caller could process.

+ Item 3: Diagnostic logging on every wpdb call (mandatory per CLAUDE.md)
    * Pre-8.9.0 swallowed wpdb errors with `return $result === false ? 0 : (int) $result;`. A query failure was indistinguishable from "0 rows aggregated" - the exact ambiguity behind the "Rebuilt 2 days, 0 rows written" symptom. Each of the three rollup wpdb calls (DELETE, INSERT...SELECT, backfill discovery get_col) now logs `$wpdb->last_error` plus the failing SQL on failure, and returns the documented failure shape (0 / empty result array). Five lines per call site, applied as a habit, makes the next failure debuggable in one round-trip.

+ Item 4: Slow-query threshold and elapsed-time logging
    * New class const SLOW_QUERY_LOG_MS = 500. Each rollup wpdb call wraps in microtime() and writes one error_log line per call that exceeds the threshold, including elapsed ms, row count, and (for INSERTs) the [start, end) range. Healthy rollupDay() at 340K events runs in roughly 50-150ms, so 500ms catches creeping degradation at the first sign of trouble without log spam during normal operation. Threshold is a class constant - adjust in one place.
    * Catches the failure mode where a rollup "succeeds" but takes 30 seconds, which the boolean error path cannot detect. Without the threshold, the next time the 5-minute incremental cron starts overlapping itself goes invisible until the operator notices broken numbers in the dashboard.

+ Item 5: MySQL session timezone alignment
    * New private alignDbTimezone() helper called at the top of rollupDay() and backfill(). Issues SET time_zone = '<offset>' on the current $wpdb connection in numeric-offset form (e.g. '-04:00'). Belt-and-suspenders for future sites whose MySQL server defaults to UTC while WP runs on local time - on those servers, the existing tracker INSERTs and the rollup reads would otherwise interpret created_at boundaries differently. Numeric offset over named-zone form (America/New_York) so the SET works on shared hosts that have not loaded the mysql.time_zone_name lookup tables - named zones silently fail there.
    * Verified on 2026-05-04: both cincinnatianimalcare prod and the test-site clone are on America/New_York, MySQL SYSTEM = EDT, WP timezone_string = America/New_York. No TZ mis-bucketing in historical CAC data; alignDbTimezone() ships as documented portability rather than a load-bearing fix on this site. Code comments document the assumption.

+ Item 6: New helper computeDayBounds()
    * Single-purpose helper that returns ['Y-m-d 00:00:00', 'Y-m-d 00:00:00'] (next day) for a given Y-m-d input, formatted in WP site TZ via DateTimeImmutable. Lets the rollup compute boundaries once and pass both as bound parameters. Replaces the pattern of letting MySQL compute boundaries via DATE() on the indexed column.

+ Item 7: Daily-table UNIQUE key correction (Fix 1.1, schema 2.0 -> 2.1)
    * Discovered during 8.9.0 verification on the cincinnatianimalcare data clone: the daily-table UNIQUE key shipped in 8.7.0 omitted animal_name even though the rollup INSERT...SELECT grouped by it. Concrete impact at verification time: 168,735 raw impression events on 2026-05-01 produced only 153,527 events in the daily table - a 9% silent loss on a single high-volume day. Diagnostic confirmed 704 distinct GROUP BY tuples shrunk to 634 stored daily rows, with the gap concentrated entirely in the impression bucket. Cause: any two raw events sharing every other dimension but differing on animal_name (typo'd intake name later corrected, dual-name records, occasional shelter rename mid-record) produced two GROUP BY tuples that collided on one UNIQUE-key slot. The plain INSERT (not INSERT IGNORE / ON DUPLICATE KEY UPDATE) failed silently for the colliding tuple, dropping every collision-side row's events from the rollup. Latent bug since 8.7.0 - exposed only because Fix 1's range-predicate rewrite finally made the rollup actually run on volume-realistic datasets.
    * includes/analytics/class-pet-match-pro-analytics-db.php createTables(): UNIQUE KEY definition now includes animal_name as the fifth column ({date}, {event_type}, {method_type}, {animal_id}, {animal_name}, {species}, {source}, {action_type}). Matches the eight-dimension GROUP BY in the rollup INSERT verbatim.
    * Schema migration 2.0 -> 2.1: dbDelta cannot drop or rebuild unique keys, so a raw ALTER TABLE issues DROP INDEX + ADD UNIQUE KEY in one statement when the stored schema version is 2.0. The migration is non-destructive - existing daily rows stay in place. Operators who care about historical accuracy should TRUNCATE wp_pmp_analytics_daily and click the Tools-tab "Rebuild Daily Summaries" button after upgrading. Future rolls (incremental cron, daily finalize, manual rebuild) write correctly under the new key automatically.
    * Constants::ANALYTICS_SCHEMA_VERSION bumped 2.0 -> 2.1 in pet-match-pro.php.
    * pet-match-pro.php schema self-heal hook now catches RuntimeException from a failed createTables() so a wpdb-rejected ALTER does not white-screen the site or leave the version option half-bumped. Failure path: log, skip the version bump, retry on the next request.
    * Operator action items (added to readme.txt Upgrade Notice): (1) verify pmp_analytics_schema_version = 2.1 after the first wp-admin pageload post-upgrade, (2) verify SHOW INDEX FROM wp_pmp_analytics_daily lists pmp_daily_unique with animal_name as a key column, (3) optionally truncate-and-rebuild for historical accuracy.

+ Item 8: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped to 8.9.0.
    * readme.txt Stable tag bumped to 8.9.0; new Upgrade Notice entry above the 8.8.0.1 entry summarizing this release.

Verification on the test site (cincinnatianimalcare clone, 559,585 events imported via phpMyAdmin):
    * EXPLAIN on the new rollup INSERT...SELECT shows type: range and key: idx_created_at where the pre-change form showed type: index (covering full scan over 559K entries).
    * "Rebuild Daily Summaries" returns non-zero total_rows on a TRUNCATEd daily table - the original "Rebuilt 2 days, 0 rows written" symptom is gone.
    * rollupDay() for any single past day completes in well under 1 second on the 559K-row dataset (logged via the new threshold path - the first run after deploy should produce zero log lines if the fix is working).
    * Schema 2.1 verified: SHOW INDEX FROM wp_pmp_analytics_daily lists pmp_daily_unique with animal_name as a key column. SELECT option_value FROM wp_options WHERE option_name = 'pmp_analytics_schema_version' returns '2.1'.
    * Per-event-type spot-check after schema migration + truncate + rebuild: SUM(event_count) FROM daily WHERE date = '2026-05-01' equals COUNT(*) FROM events WHERE created_at >= '2026-05-01 00:00:00' AND created_at < '2026-05-02 00:00:00' exactly. Pre-Fix-1.1 spot-check on the same data showed a 15,208-event impression deficit (153,527 vs 168,735) - the UNIQUE-key collision.

Out of scope for 8.9.0 (covered by subsequent fixes in the same brief): the chunked AJAX backfill (Fix 6), read-path migration of dashboard queries to the daily table (Fix 2), composite events index (Fix 3), DROP COLUMN of the three write-only TEXT columns (Fix 4), soft-delete retention with deferred hard purge (Fix 5), lazy widget loading (Fix 7), date-range pre-flight count guard (Fix 8), transient cache on widget fragments (Fix 9). Fix 1 alone unblocks the rebuild button - subsequent fixes are required for sustainability as the customer base grows.

------------------------------------------------------------------------------

Version 8.8.0.1 - May 2, 2026 - Search Card Hover Text Discoverability Hotfix

Follow-up to 8.8.0 covering two oversights in the original release: the new card_hover_text shortcode parameter was wired into the level file, the AllApi helper, the admin field, and the search templates - but it was NOT registered in the per-partner Instructions tabs or the Shortcode Builder visual generator, which meant clients couldn't actually find the parameter from the surfaces they normally use. Plus a coaching-focused rewrite of both hover-text KB articles after a review surfaced that field-token discoverability was poor for both this release and the existing detail-button hover text feature. No code-path changes beyond catalog/instruction-array additions; no schema change; no template change.

+ Item 1: Shortcode Builder catalog - card_hover_text registered for search
    * admin/partials/pmp-shortcode-builder-catalog.php: added Shortcodes::CARD_HOVER_TEXT entry under the Shortcodes::SEARCH section's Display group, immediately after Shortcodes::HIDE_EMPTY (matches the same paid/level pairing the two parameters share). Control type 'text' (free-text input), valueSource null, levelKey points at $levelShortcode . Shortcodes::CARD_HOVER_TEXT so the existing license-gating pipeline reads PREMIUM_LEVEL from pmp-option-levels-general.php without duplicating the value. appliesTo '*' so the parameter is available for every method type (adopt/lost/found/list/preferred/featured) - per-method default phrasing is handled inside AllApi::getDefaultCardHoverText() not the catalog. Help text references the {Name}, {Species}, and partner-field token semantics so the Builder's hover tooltip is informative.

+ Item 2: Per-partner Instructions tabs - card_hover_text documented inline
    * admin/partials/pp/pmp-instructions-search-params.php
    * admin/partials/af/pmp-instructions-search-params.php
    * admin/partials/rg/pmp-instructions-search-params.php
    * All three files gained a new $pmpSearchParms entry appended after the existing 'hide_empty' row. Same shape as the surrounding paid params (paid => true, PRO badge in the values column). Description text covers the three method-default behaviors ("Learn More About {Name}" / "Have You Seen {Name}?" / "Help Rehome This {Species}"), the {Name} -> "This Dog" fallback, and the per-method-admin override relationship. Identical text in all three partner files because the parameter behavior is identical - the partner-specific differences (raw field key naming) are documented in the KB article rather than duplicated three times in the Instructions tabs.

+ Item 3: KB article 11-search-card-hover-text.html - Field Interpolation rewrite
    * docs/kb/02-shortcodes-configuration/11-search-card-hover-text.html: replaced the single Field Interpolation section with a five-subsection coaching layout designed to answer "how do people know what tokens to use" without requiring partner-API expertise.
        - "Universal tokens (recommended)" - explains {Name} and {Species} with the empty-record fallback rationale (shelter records sometimes carry an intake number like "60100465" in the name field, which would otherwise render as "Have you seen 60100465?" - the fallback prevents that).
        - "Raw partner field tokens" - explains case-sensitivity and the no-fallback contract.
        - "Common tokens by partner" - a side-by-side reference table mapping the eight most common conceptual fields (name, species, breed, sex, age, color, location, fee) to the actual token across PetPoint, AnimalsFirst, and RescueGroups. This is the table that lets a writer who knows the concept ("I want breed") find the right token without reading three partner API docs.
        - "How to confirm a token works on your site" - a practical view-source + Test: {YourTokenHere} sanity-check workflow so admins can self-verify any field key against the live page output.
        - "Examples" - per-partner example blocks expanded to show mixed universal+raw combinations.
        - "When to use which" - three-bullet decision rule: universal for nameless-stray safety, raw for specific data points, combined when framing should be safe but details should be specific. Worst-case-still-readable example: "Adopt {Name} the {primarybreed} {Species}" -> "Adopt This Dog the Labrador Retriever Dog" - awkward but never broken.

+ Item 4: KB article 10-button-hover-text.html - parallel coaching rewrite
    * docs/kb/02-shortcodes-configuration/10-button-hover-text.html: same structural rewrite as article 11, with one important honesty caveat: detail-page button hover text uses AllApi::resolveButtonHoverText() which does NOT have the {Name}/{Species} decorator that AllApi::resolveCardHoverText() got in 8.8.0. The article now flags that distinction explicitly with a callout pointing readers to the search-card article if they want universal-token behavior. Same "Common tokens by partner" reference table, same view-source + Test: {token} verification workflow. New "When the field is empty" subsection warns about trailing-blank rendering ("Apply to adopt " with a literal trailing space when {AnimalName} is empty) and recommends defensive phrasing like "Apply to adopt this {Species}" for shelters that regularly take in unnamed strays.

+ Item 5: Version constants
    * pet-match-pro.php Version header and Constants::VERSION bumped to 8.8.0.1.
    * readme.txt Stable tag bumped to 8.8.0.1; new Upgrade Notice entry above the 8.8.0 entry summarizing this hotfix.

------------------------------------------------------------------------------

Version 8.8.0 - May 2, 2026 - Search Card Hover Text Standardization

Feature release that standardizes the tooltip/title text on search-result cards across every template, every partner, and every method type, with a matching aria-label for screen readers. New customization layer mirrors the existing detail-button hover text pattern (shortcode > admin > default). One new admin sub-accordion under General > Display Options. No DB schema change, no analytics-pipeline change, no breaking behavior on top of 8.7.2.1.

+ Item 1: Inconsistent hover text across search templates - standardized
    * Pre-8.8.0 audit (Cat-09 KB review, 2026-04-30) found three different hover-text phrasings in production ("Click to View Details for X", "Learn More About X", "X - Species") plus three templates with no hover text at all (PP lost-search-default, PP found-search-default, AF universal-search-default). Most photo links also lacked aria-label, so screen readers did not announce the tooltip the way the title attribute promised.
    * Resolution: new AllApi::resolveCardHoverText($methodType, $animalDetails) method in includes/class-pet-match-pro-all-api.php parallels the existing resolveButtonHoverText() and reuses interpolateFieldTokens() verbatim - same shortcode > admin > default priority chain, same "unrecognized tokens silently removed" semantics. Every search template now calls this helper once per card and applies the result identically to the name link (title + aria-label), the photo link (title + aria-label), and the bare img (title).
    * Method-specific defaults (translatable):
        adopt     -> "Learn More About {Name}"
        lost      -> "Have You Seen {Name}?"
        found     -> "Help Rehome This {Species}"
        list      -> "Learn More About {Name}"  (PetPoint only)
        preferred -> "Learn More About {Name}"  (AnimalsFirst only)
        featured  -> "Learn More About {Name}"
    * {Name} resolves through getTooltipName() so empty animal-name records degrade to "This Dog" / "This Cat" / "This Animal" instead of leaving a blank token. {Species} normalizes the partner-specific species field with a fallback to "Animal". Both tokens are pre-decorated onto the animalDetails array inside resolveCardHoverText() before interpolateFieldTokens() runs, so partner data with raw field keys (e.g. PP "AnimalName", AF "name", RG "animalName") is unaffected and raw tokens still work alongside {Name}/{Species}.

+ Item 2: New constants and license gating
    * pet-match-pro.php Settings::CARD_HOVER_TEXT = 'card_hover_text' for admin per-method keys (card_hover_text_adopt, _lost, _found, _list, _preferred, _featured) and Shortcodes::CARD_HOVER_TEXT for the new shortcode parameter.
    * admin/partials/pmp-option-levels-general.php now includes LevelPrefix::LEVEL . 'shortcode_' . Shortcodes::CARD_HOVER_TEXT => Constants::PREMIUM_LEVEL, matching the existing button hover text shortcode-parameter level. Free tier (basic) silently strips the parameter via stripPaidShortcodeParams(). Junior+ in client-facing tier names sees the parameter honored.

+ Item 3: Admin UI - new sub-accordion under General > Display Options
    * New "Search Card Hover Text" sub-accordion sits between "Icon Sizes" and "Search Icons & Overlays" in the Display Options sub-accordion stack. Conditionally renders one text input per enabled method type; Lost / Found / List / Preferred rows only appear when that method is enabled for the current partner. Adopt and Featured always render.
    * registerCardHoverTextFields() in admin/class-pet-match-pro-admin-settings.php builds the field set. Each row's description echoes the English default so admins know exactly what they are overriding before typing anything.
    * Two new KB_LINKS entries (general_card_hover, field_card_hover_text) both point at the new article slug "search-card-hover-text" with the "#admin-settings" anchor - the section header and per-row inline help icons render side-by-side, matching the existing field_hover_text / field_hide_empty pattern.

+ Item 4: Template touch-up across all three partners
    * PetPoint (public/templates/pp/):
        adopt-search-default.php: name link adds aria-label and uses $cardHoverText for title; buildPhotoSection() accepts cardHoverText and applies title+aria-label to <a> and title to <img>.
        lost-search-default.php: previously had NO hover text - now resolves $cardHoverText once and applies to photo link <a> and <img>.
        found-search-default.php: same as lost - new hover text added.
        featured-search-default.php / -carousel.php / -compact.php: $cardHoverText resolved at top of buildResultItem/buildCarouselCard, applied to photo link, image, name link / name overlay span.
        universal-search-default.php: PER-CARD method resolution - $methodType derived from animalType field starts with "lost" or "found"; $cardMethodType falls back to getMethodValue() when neither matches. Photo link <a> swaps "Click to View Details for X." for $cardHoverText. buildPhotoSection now takes a $cardHoverText param and adds title to img.
        universal-search-structured.php: name link inside buildDetailsSection and photo section both use $cardHoverText.
    * AnimalsFirst (public/templates/af/):
        universal-search-default.php: previously had NO hover text - now resolves per-card $cardMethodType (page-level methodValue when the type field doesn't disambiguate) and threads $cardHoverText through buildPhotoSection (adds title+aria-label to <a> and title to <img>), the inline name link, and buildDetailsSection's name-field branch.
        universal-search-structured.php: $cardMethodType inferred from AnimalsFirstFields::TYPE; $cardHoverText falls back to the existing "Learn More About {tooltipName}" string when no override is in play, so admins who set neither admin nor shortcode see no regression. Name link gets aria-label.
        universal-search-filter-widget.php: $cardMethodType inferred from $type at top of buildAnimalCard; buildPhotoSection and buildNameSection both accept $cardHoverText. Replaces the previous "{Name} - {Species}" hard-coded title.
        universal-search-no-filter.php: same pattern as filter-widget.
        featured-search-default.php / -carousel.php / -compact.php: hardcoded to METHOD_TYPE_FEATURED; $cardHoverText threaded into photo buildPhotoSection (link, img) and the name link/name overlay span.
    * RescueGroups (public/templates/rg/):
        adopt-search-default.php: $cardHoverText resolved once; admin-source name link, photo section, and details-section name-field branch all now apply title+aria-label.
        adopt-search-carousel.php: photo link and below-photo name link both use $cardHoverText.
        adopt-search-compact.php: photo link, image, and name overlay span all use $cardHoverText.
        adopt-search-structured.php: photo section and the in-loop name link inside buildDetailsSection both use $cardHoverText. The existing $clickText analytics fallback is preserved as the $effectiveHover fallback when $cardHoverText is empty (defensive for direct buildPhotoSection calls outside the new flow).
    * Universal/combination templates (PP universal-search-default and AF universal-search-* variants) resolve method type PER CARD from the animal's type field, so on the same page a lost dog hovers as "Have You Seen Rex?" while a found cat next to it hovers as "Help Rehome This Cat" without any extra configuration.

+ Item 5: Accessibility
    * Every <a> / <span> with a `title` attribute is now paired with a matching `aria-label` carrying the same string. Screen readers announce aria-label; `title` alone is mostly ignored. The existing pp/adopt-search-default.php line 509 photo-link already followed this pattern - it is now uniform across all 16 search templates.
    * The bare <img> inside the photo link picks up `title` matching the surrounding link's hover text. Alt text continues to describe the image content ("Photo of Buddy"); title text describes the action ("Learn More About Buddy"). Both are intentional and complementary.

+ Item 6: Documentation
    * New KB article: docs/kb/02-shortcodes-configuration/11-search-card-hover-text.html. Mirrors the prose style and section structure of the companion article 10-button-hover-text.html so the feature pair is easy for admins to learn together. Covers default phrasing, admin location, shortcode parameter, field interpolation (with PP/AF/RG examples), priority chain, universal/combination behavior, and accessibility.
    * Cross-references added:
        02-shortcodes-configuration/01-pmp-search-reference.html - new card_hover_text row in the Junior parameter table.
        02-shortcodes-configuration/04-parameters-by-license-tier.html - new card_hover_text row plus a new "Search Card Hover Text" bullet in the Junior Admin Settings list.
        03-templates-design/01-search-templates-gallery.html - new "Card Hover Text" section before Next Steps and a new bullet linking to the article.

+ Item 7: Translations - DEFERRED
    * Five new translatable strings ("Learn More About {Name}", "Have You Seen {Name}?", "Help Rehome This {Species}", "Search Card Hover Text" admin label, the per-row description sprintf) plus the individual method-row labels are pending POT regeneration. This is consistent with the existing "regenerate translations after Shortcode Builder Phase 4" backlog item - the next translation release will catch up the Shortcode Builder strings, the 8.7.0 analytics strings, and these.

+ Item 8: Out of scope (intentionally not changed in 8.8.0)
    * Detail-page button hover text (already covered by AllApi::resolveButtonHoverText()).
    * Filter widgets, pagination links, carousel navigation buttons.
    * Icon overlay tooltips (already pair aria-label + title via the icon rendering pipeline).

------------------------------------------------------------------------------

Version 8.7.2.1 - May 1, 2026 - Theme Template Override Hotfix + Cat-09 Troubleshooting Articles

Hotfix release covering one code defect uncovered during cat-09 KB review plus the new troubleshooting articles and admin help-icon wire-ups that landed alongside it. No DB schema change, no analytics-pipeline change, no breaking behavior on top of 8.7.2.

+ Item 0: processTemplate() theme-suffix .php ordering bug
    * includes/class-pet-match-pro-all-api.php processTemplate(): when a search shortcode supplied the template parameter as e.g. template="adopt-search-default-custom (theme)" without a .php extension, the existing auto-append logic placed .php at the END of the string ("adopt-search-default-custom (theme).php"). Downstream stripThemeSuffix() in resolveSearchTemplate() checks str_ends_with(..., ' (theme)') and therefore did not detect the suffix in the appended form. The cleaned filename retained the " (theme)" substring and file_exists() failed, surfacing as a "template not found" error even though the file existed in the theme override directory.
    * Fix: both branches of processTemplate() (shortcode override path and admin-settings dropdown path) now strip the optional " (theme)" suffix BEFORE the basename() + ".php" append, then re-attach it once the filename is normalized. Result: all four shortcode forms now resolve identically:
        template="myfile"
        template="myfile.php"
        template="myfile (theme)"
        template="myfile.php (theme)"
    * Reuses the existing AllApi::stripThemeSuffix() and Constants::THEME_TEMPLATE_SUFFIX - no new methods or constants.

+ Item 1: Admin help-icon wire-ups for new troubleshooting articles
    * admin/class-pet-match-pro-admin-settings.php KB_LINKS array gained four new entries pointing at the new article slugs:
        troubleshoot_shortcode_builder => 'shortcode-builder-issues'
        troubleshoot_cron              => 'analytics-queue-and-cron'
        troubleshoot_premium           => 'premium-feature-not-appearing'
        troubleshoot_theme_template    => 'theme-template-override'
    * Five renderHelpIcon() wire-up changes:
        - Shortcode Builder tab nav now points to troubleshoot_shortcode_builder
        - Tools tab Cron Self-Test accordion now points to troubleshoot_cron
        - Tools tab License Summary accordion now points to troubleshoot_premium
        - Search Template / Detail Template field labels now render a SECOND help icon next to the existing gallery icon, pointing to troubleshoot_theme_template
    * No KB_LINKS entries removed.

------------------------------------------------------------------------------------------------------------------------------------------------

Version 8.7.2 - April 30, 2026 - Cron Health Layer 3 (Activation-Time Self-Test + Admin Notice)

Defense-in-depth release on top of 8.7.0's analytics queue/cron architecture. 8.7.0 shipped Layer 1 (synchronous-flush fallback when WP-Cron is dead), Layer 2 (Analytics tab Queue Health badge), and Layer 2b (Tools tab "Flush Now" + "Run Cron Self-Test" buttons). What was missing: proactive detection. An operator on a broken-cron host who never visited the Analytics or Tools tabs would run for weeks in synchronous-flush fallback mode without knowing - they would just notice "the site feels slow" with no obvious cause. 8.7.2 closes that gap by auto-running the cron self-test at activation and surfacing a dismissible admin notice when it fails.

+ Item 0: CronHealth class extended with auto-trigger + notice path
    * includes/analytics/class-pet-match-pro-cron-health.php: existing 8.7.0 manual-test surface (startTest, handleTestFire, resolveStatus, clearSchedule) preserved unchanged. Three new constants added: OPTION_AUTO_STATUS = 'pmp_cron_health_auto_status' (cached terminal status for the auto path so subsequent admin pageloads don't re-evaluate the marker), OPTION_AUTO_LAST_RESULT_AT = 'pmp_cron_health_auto_last_result_at' (timestamp the auto-test reached terminal), USER_META_NOTICE_DISMISSED_AT = 'pmp_cron_health_notice_dismissed_at' (per-user dismissal timestamp). New STATUS_UNKNOWN constant for "auto-trigger has not run yet on this site". NOTICE_DISMISSAL_SUPPRESSION_DAYS = 7 controls how long a dismissed notice stays hidden before re-appearing.
    * resolveAutoStatus() method: idempotent resolver for the auto path. Short-circuits on cached terminal status. When pending, defers to resolveStatus() so both paths read the same SCHEDULED_AT/FIRED_AT markers - no parallel option family. Promotes pending -> healthy/failing into the auto cache when the live resolver reaches terminal. Returns STATUS_UNKNOWN before any auto-test has been started, otherwise mirrors resolveStatus() output.
    * renderNotice() method: hooked to admin_notices. Gated on manage_options. Suppresses for 7 days after the current user dismisses (per-user, via user-meta). Renders WordPress is-dismissible warning notice with the "PetMatchPro: WP-Cron does not appear to be firing on this site..." copy and a "Learn more" link to the existing cron-requirement KB article (Constants::DOCS_URL . 'cron-requirement' - reuses the slug already in KB_LINKS, no new article).
    * Inline dismiss script uses navigator.sendBeacon (with fetch keepalive fallback) to POST the dismiss action when the user clicks the X. Single-purpose script scoped to the notice via data-pmp-notice="cron-health" attribute selector.
    * handleDismiss() method: AJAX endpoint that records the dismissal timestamp. Nonce-protected ('pmp_dismiss_cron_health'), manage_options-gated.
    * clearSchedule() now also deletes OPTION_AUTO_STATUS and OPTION_AUTO_LAST_RESULT_AT on plugin deactivation, matching the existing manual-test cleanup.

+ Item 1: Activation-time auto-trigger
    * pet-match-pro.php register_activation_hook block: after Activator::activate() runs successfully (PHP version check passed), require_once the cron-health class file and call CronHealth::startTest(). The existing wp_next_scheduled guard inside startTest() makes reactivation idempotent - back-to-back activations don't queue duplicate test events.
    * The 60-second schedule offset and 120-second deadline from 8.7.0 are reused unchanged. After activation, a healthy host's notice never appears (test fires, marker lands, status caches as healthy). A broken-cron host's notice appears on whatever admin pageload happens >=120 seconds after activation, when resolveAutoStatus() promotes pending to failing.

+ Item 2: First-tracking-enable auto-trigger
    * Two save handlers updated to detect off->on transitions on Settings::ANALYTICS_TRACKING_ENABLED:
        - includes/analytics/class-pet-match-pro-analytics-ajax.php saveAnalyticsSettings(): captures $wasTrackingEnabled before the merge, reads $isTrackingEnabledNow after. When the transition is off->on AND CronHealth::resolveAutoStatus() returns STATUS_UNKNOWN, calls CronHealth::startTest().
        - admin/class-pet-match-pro-admin-settings.php ajax_save_analytics_settings(): same pattern. Loads the cron-health class file lazily via require_once because this handler runs in the main admin namespace and the class is in PetMatchPro\Analytics.
    * The STATUS_UNKNOWN guard prevents re-running the test on every subsequent toggle - once a terminal result has been recorded, only the manual Tools tab button can re-run.
    * Customers who upgraded in-place (FTP overwrite, auto-update) and never deactivate/reactivate now get the test triggered the first time they enable analytics tracking on the upgraded version, instead of having to discover the Tools tab button on their own.

+ Item 3: Notice + dismiss wiring registered on plugins_loaded
    * pet-match-pro.php plugins_loaded block (the same block that registers AnalyticsQueue::CRON_HOOK and CronHealth::TEST_HOOK from 8.7.0): two new add_action calls.
        - admin_notices -> [CronHealth::class, 'renderNotice']
        - wp_ajax_pmp_dismiss_cron_health_notice -> [CronHealth::class, 'handleDismiss']
    * No changes to the 8.7.0 cron-handler registration or to the AnalyticsQueue/AnalyticsDailyRollup wiring; they sit alongside the new entries.
    * Schema-self-heal block left untouched. The brief proposed re-running CronHealth::startTest() on every schema-version bump (so cron-config regressions between PMP releases would surface), but a patch-release cadence would re-spam the failing notice on every upgrade. Skipped per scoping discussion - manual Tools tab button covers the "host changed something" case explicitly.

+ Item 4: KB article reuse (no new article)
    * The brief originally proposed a new docs/kb/.../cron-troubleshooting.html article. Repository already has docs/kb/08-analytics-tracking/05-wp-cron-requirement.html (slug: cron-requirement) which covers WP-Cron rationale, verification, and remediation - exactly the audience the new admin notice points at. The notice's "Learn more" link points at that existing slug rather than fork into a parallel article.
    * KB_LINKS already contains 'cron_requirement' => 'cron-requirement'; no admin-settings changes for the notice path. Help icons on the existing Tools tab Cron Self-Test accordion and on the Tracking Settings "Enable Click Tracking" label continue to point at the same article.

+ Item 5: Versioning + readme
    * pet-match-pro.php Plugin Header Version: 8.7.1.1 -> 8.7.2.
    * pet-match-pro.php Constants::VERSION: 8.7.1.1 -> 8.7.2.
    * readme.txt Stable tag: 8.7.1.1 -> 8.7.2.
    * readme.txt == Upgrade Notice == new 8.7.2 section explaining what the activation-time test does, when the notice appears, the per-user 7-day dismissal grace, and that the manual Tools tab button is unchanged.

Verification matrix:
- Healthy host (default WP-Cron + traffic): activate the plugin. Wait 90 seconds. Refresh wp-admin. No admin notice appears. get_option('pmp_cron_health_auto_status') returns 'healthy'. resolveStatus() returns the same.
- Broken-cron host: define DISABLE_WP_CRON true in wp-config.php and ensure no real cron hits wp-cron.php. Activate the plugin. Wait 4 minutes. Refresh wp-admin. Yellow notice appears with the host-config guidance. get_option('pmp_cron_health_auto_status') returns 'failing'.
- Notice dismissal: click the X on the notice. Refresh wp-admin - notice does not return. get_user_meta(user_id, 'pmp_cron_health_notice_dismissed_at', true) is now a unix timestamp. After 7 days, notice returns (status still 'failing' until next manual test runs and the host is fixed).
- First tracking-enable: on a fresh upgrade with auto-status STATUS_UNKNOWN, toggle Settings::ANALYTICS_TRACKING_ENABLED off->on via the Analytics Admin save endpoint OR via the Admin Settings save endpoint. Test fires; resolveAutoStatus() advances through pending to terminal on the next admin pageload >=120s later.
- Manual re-test still works: Tools tab "Run Cron Self-Test" button. Spinner shows for ~2 minutes. Result reflects current state. If host was fixed since the auto-test ran, the manual button updates pmp_cron_self_test_status to 'healthy' but pmp_cron_health_auto_status stays cached at 'failing' (manual button is for the operator, not the notice path) - the notice continues to show until the operator dismisses it. Acceptable trade-off; the operator who just ran the manual button has clear context for the still-visible notice.
- Reactivation idempotency: deactivate, reactivate. clearSchedule() runs on deactivate and wipes both manual and auto status options; activation kicks a fresh test.

Files touched:
- includes/analytics/class-pet-match-pro-cron-health.php: extended with auto-trigger + notice + dismiss methods. Existing 8.7.0 manual-button surface unchanged.
- pet-match-pro.php: activation hook calls CronHealth::startTest(); plugins_loaded block adds admin_notices and wp_ajax_pmp_dismiss_cron_health_notice action wiring; Constants::VERSION + Plugin Header Version bumped.
- includes/analytics/class-pet-match-pro-analytics-ajax.php: saveAnalyticsSettings() captures prior tracking-enabled state and triggers CronHealth::startTest() on off->on transition when auto-status is unknown.
- admin/class-pet-match-pro-admin-settings.php: ajax_save_analytics_settings() same off->on trigger; lazy-loads the cron-health class file because this file is in the main admin namespace.
- readme.txt: Stable tag bump + new == Upgrade Notice == 8.7.2 section.
- CHANGE-LOG.txt: this entry.

Out of scope (explicit defer):
- Email alerts on cron failure. Some operators want notification by email. Defer until requested - not all customers want plugin email noise.
- Auto-remediation. PMP cannot start a system cron job for the customer. Best we can do is detect, warn, document.
- Continuous monitoring (re-test every 24 hours). Layer 1's stale-flush detection in 8.7.0 already catches mid-life cron failures via a different mechanism, so periodic re-testing would be defense-in-depth on top of defense-in-depth. Future enhancement if support tickets indicate one-time tests miss late-onset failures.
- KB_LINKS reset on schema-version bump. Re-running the test on every schema bump (and re-spamming the notice on every patch release) was rejected. Manual Tools tab button covers the "config changed mid-life" case.

------------------------------------------------------------

Version 8.7.1.1 - April 29, 2026 - KB Category 11 Retired and Folded into Category 8

Documentation hygiene release. The five new analytics-pipeline articles authored alongside 8.7.0 were originally placed in a brand-new KB category (11-analytics), but the project already had an Analytics Tracking category (08-analytics-tracking) - so the parallel category was redundant. This release folds the new content into category 8 and retires category 11 entirely.

+ Item 0: Two existing category-8 articles rewritten and expanded
    * 01-understanding-pmp-analytics.html: original sections (Enabling Analytics, What Events Are Tracked, Data Captured Per Event, How Tracking Works, SEO Features) retained and reordered. Folded in the cat-11 Analytics Overview content as new sections: "What Counts as an Impression (8.7.0+)" describing the 50%-visible / 500ms-dwell model and the per-session deduplication semantics; "Metric Definitions" table covering Impressions, Detail Views, Actions, Search CTR, Engagement Rate, Source Breakdown; "How Tracking Works" rewritten to merge the legacy lightweight-JS description with the queue-and-flush pipeline (capture -> beacon -> queue -> 1-min cron drain -> 5-min rollup -> nightly finalize -> dashboard read); "Why a separate summary table?" subsection explaining the architectural payoff in operator-friendly language; "Dashboard Filters" describing date range and method type controls; "Interpreting Trends" listing four diagnostic patterns operators should watch for. Next Steps section now points at the consolidated set of cat-8 articles.
    * 04-leveraging-analytics.html: original sections (Understanding Search Behavior, Detail Page Engagement, Action Engagement, Practical Optimization Ideas) retained. Folded in the cat-11 Interpreting Position Impact content as a new top-level section between "Understanding Search Behavior" and "Detail Page Engagement". Subsections: "How Position Is Recorded" (1-based, post-filter, per-page); "What Changed in 8.7.0" (dwell gating, dramatic impression-count drop, why position data is more meaningful now); "Reading the Data" (position-by-position counts, click-through rate by position); "Comparing Pre-8.7.0 and Post-8.7.0 Data" (rules of thumb for cross-upgrade reporting); "Actionable Uses for Position Data" (sort tuning, featured placement, pagination cadence, A/B testing); "Position Caveats" (filter-driven re-ranking, carousel DOM order, deep-link entries).

+ Item 1: Three new category-8 articles taking the next-available slots
    * 05-wp-cron-requirement.html (slug: cron-requirement): unchanged content from cat-11 02. WP-Cron rationale, how to verify firing, common failure modes, fix steps for managed-host UI and shell-access setups, fallback behavior, end-to-end verification flow.
    * 06-queue-health-and-tools.html (slug: queue-health-and-tools): unchanged content from cat-11 03. Queue Health card row-by-row, refresh button, warning-state badges, Flush Queue Now usage, Cron Self-Test usage, Rebuild Daily Summaries usage, what these tools do not do.
    * 07-privacy-and-data-retention.html (slug: privacy-and-data-retention): unchanged content from cat-11 05. What is stored / not stored, retention windows + Clear Analytics Data behavior, GDPR/CCPA posture, data export.
    * Cross-references inside the three new articles updated to point at the merged-in anchors (understanding-pmp-analytics/#metric-definitions and leveraging-analytics/#interpreting-position-impact) instead of the retired stand-alone slugs.

+ Item 2: docs/kb/11-analytics/ directory removed
    * All five files in the 11-analytics directory deleted.
    * Empty directory removed.

+ Item 3: KB_LINKS map updated
    * admin/class-pet-match-pro-admin-settings.php: comment header for the 8.7.0 analytics group renamed from "11-Analytics KB articles" to "8.7.0 Analytics pipeline KB articles" to drop the category-number reference.
    * 'analytics_overview' value changed from 'analytics-overview' to 'understanding-pmp-analytics/#metric-definitions' so it deep-links into the merged-in Metric Definitions section.
    * 'position_impact' value changed from 'interpreting-position-impact' to 'leveraging-analytics/#interpreting-position-impact' so it deep-links into the merged-in section.
    * The other three entries ('cron_requirement', 'queue_health_tools', 'analytics_privacy') keep their slugs unchanged - the new cat-8 05/06/07 articles preserve those slugs at petmatchpro.com.

+ Item 4: Customer-facing copy scrubbed of docs/kb/ path references
    * readme.txt 8.7.0 Upgrade Notice: "five new KB articles under docs/kb/11-analytics/ covering ..." -> "five new KB articles covering ...". Reader-facing description unchanged in substance; the internal repo path is not exposed.
    * CHANGE-LOG.txt 8.7.0 Item 0: position-impact methodology citation now references "the Interpreting Position Impact section of the Leveraging Analytics KB article" instead of the file path.
    * CHANGE-LOG.txt 8.7.0 Items 28, 39, 40: KB-article file paths replaced with article names (Analytics Overview, Queue Health and Tools).
    * CHANGE-LOG.txt 8.6.0 Item 7: Shortcode Builder KB article file path replaced with article name + category.
    * CHANGE-LOG.txt 8.4.0 Item 12: CLAUDE.md note "Any new KB article added under docs/kb/" -> "Any new KB article added".
    * CHANGE-LOG.txt 8.2.0 Item 2 + 8.1.5 Item 5 + 8.1.5 Item 2 + 8.1.5 Item 3 (button_consistency, [pmp-option] reference, exclude_buttons docs): file-path bullet lists replaced with KB article-name lists.
    * Path internal to the repo are kept out of customer-facing copy; the project's owner-facing convention to use docs/kb/ for source-of-truth article files during creation/deployment remains in place.

+ Files modified
    * pet-match-pro.php (Version 8.7.1 -> 8.7.1.1, Constants::VERSION)
    * readme.txt (Stable tag 8.7.1 -> 8.7.1.1, new == Upgrade Notice == 8.7.1.1 entry, 8.7.0 entry path-scrubbed)
    * CHANGE-LOG.txt (this entry; multiple historical entries path-scrubbed)
    * admin/class-pet-match-pro-admin-settings.php (KB_LINKS comment + two value changes)
    * docs/kb/08-analytics-tracking/01-understanding-pmp-analytics.html (rewritten + expanded)
    * docs/kb/08-analytics-tracking/04-leveraging-analytics.html (rewritten + expanded)

+ Files added
    * docs/kb/08-analytics-tracking/05-wp-cron-requirement.html
    * docs/kb/08-analytics-tracking/06-queue-health-and-tools.html
    * docs/kb/08-analytics-tracking/07-privacy-and-data-retention.html

+ Files deleted
    * docs/kb/11-analytics/01-analytics-overview.html
    * docs/kb/11-analytics/02-cron-requirement.html
    * docs/kb/11-analytics/03-queue-health-and-tools.html
    * docs/kb/11-analytics/04-interpreting-position-impact.html
    * docs/kb/11-analytics/05-privacy-and-data-retention.html
    * docs/kb/11-analytics/ (empty directory)

+ Translation impact
    * No new user-facing strings. The KB_LINKS value changes are pointer-only (slug strings); admin labels referencing them are unchanged.

+ No live-site impact
    * Article slugs at petmatchpro.com/docs/ are unchanged for the three preserved slugs (cron-requirement, queue-health-and-tools, privacy-and-data-retention). The two retired stand-alone slugs (analytics-overview, interpreting-position-impact) need to be redirected on the live site - either pointed at the consolidated articles' anchors via 301 redirect, or unpublished if they were never published.

================================================================================

Version 8.7.1 - April 29, 2026 - PP Species Shortcode Resilience + Detail Template Layout Standardization

Two bug fixes discovered while testing 8.7.0.

+ Item 0: PP species filter on [pmp-search] silently fell back to "all species" when builder save-pipelines stripped whitespace between attributes
    * Reported behavior: [pmp-search type="adopt" species="dog"] on a Divi 5 page returned cats. Adding the same shortcode to a Classic-Editor page returned dogs correctly. Network tab gave no clue (the PetPoint API call is server-side curl from PHP - it never appears in the browser network tab; only the WordPress page request does, and that page request does not carry speciesid=).
    * Root cause traced via temporary echo debug at three points: items=, species-name-branch, and outgoing PP API URL. Output showed items=["type=\"adopt\"species=\"dog\""] - a single positional string at index 0, not the parsed associative array WordPress normally hands shortcode handlers. The Divi 5 builder's save pipeline stripped the space before species=, leaving type="adopt"species="dog" in post content. WordPress' shortcode_parse_atts() regex requires whitespace (or end-of-string) after each closing quote; with the space gone, the entire attribute blob falls through unparsed and lands at numeric index 0. processSpecies() never saw species= at all and returned the '0' fallback. PP API was called with speciesid=0 (= All), so the page returned cats and dogs and everything else.
    * Fix: new private helper PetMatchPro\PublicFacing::normalizeShortcodeAtts(array $atts): array. Detects positional (numeric-keyed) entries, runs preg_replace('/(["\'])([A-Za-z_][A-Za-z0-9_-]*)=/', '$1 $2=', ...) to re-insert spaces before every key= boundary inside each positional value, concatenates with explicit key="value" form for any already-associative entries, then re-parses through shortcode_parse_atts(). Idempotent on properly-formed input - returns the input unchanged when no positional keys are present.
    * Wiring: handleSearchShortcode() pre-normalizes atts before createSearch(). handleDetailsShortcode() pre-normalizes before createDetails(). handleDetailShortcode()'s array path pre-normalizes before shortcode_atts() merges in defaults. handleOptionShortcode() pre-normalizes before its shortcode_atts() merge. handleDetailShortcode()'s simple-string path is untouched (it operates on a single field name, not a key=value attribute set, so the bug class does not apply).
    * Author guidance unchanged - keep the space between attributes - but the plugin now self-heals when builders strip it. Tested by deliberately removing the space in a saved Divi 5 [pmp-search] and confirming the PP API call goes out with speciesid=1.

+ Item 1: Detail templates - social/instructions/print poster trio pushed far below main image when thumbnail strip grew tall
    * Reported behavior: 8.7.0 raised the photo/video gallery cap from 6 to 25 (per 8.7.0's gallery-expansion work). On any detail template that placed renderSocialShare()/renderInstructions()/renderPrintPosterButton() *after* the image+thumbnail flex row, those blocks rendered at the bottom of the row's full height. With 25 thumbnails stacked vertically (~98px each), the row grew to ~2450px tall and the social/instructions trio appeared two screens below the main image.
    * Root cause: the legacy markup placed the trio as a sibling of the image-row, so flex-row align-items: stretch made the trio sit at the bottom of whichever column was tallest (the thumbnails column).
    * Fix: consolidated 18 detail templates onto the PP standard structure. New wrapper class .pmp-details-image-column wraps the main image + the instructions/social/print trio together, as a flex column with gap: 15px. The thumbnails (and videos when not in video-player mode) live as a sibling of that wrapper inside .pmp-details-image-row. The trio now sits directly under the main image regardless of how tall the thumbnail strip grows.
    * CSS additions in public/css/pet-match-pro-styles.css near the existing .pmp-details-image-main rule: .pmp-details-image-column { flex: 1 1 calc(100% - 110px); min-width: 200px; display: flex; flex-direction: column; gap: 15px; }, plus a child override .pmp-details-image-column > .pmp-details-image-main { flex: none; min-width: unset; } so the nested image-main does not double-apply the row's flex sizing.
    * Trio order standardized to PP's existing convention: renderInstructions() -> renderSocialShare() -> renderPrintPosterButton(). Previously AF used social -> print -> instructions and AF posters used social -> instructions -> print; both now match PP.
    * Templates converted (18): PP adopt-default, adopt-conversion, adopt-conversion-no-app, adopt-conversion-similar, lost-default, found-default, lost-poster, found-poster; AF adopt-default, adopt-conversion, adopt-conversion-no-app, adopt-conversion-similar, lost-default, found-default, lost-poster, found-poster; RG adopt-default, adopt-similar.
    * Templates intentionally not converted: *-details-navigation* (PP/AF/RG) already wrap the trio inside a column-structured pmp-details-media-column - structurally correct. AF adopt-profile-3-column / adopt-profile-3-column-similar use a vertical 3-column layout with the trio already inside the image column. PP/AF adopt-wide place the trio in the right pmp-wide-content section by design. RG adopt-cpa places the trio in the right pmp-details-content column by design (.pmp-template-cpa CSS was authored around that placement). PP/AF/RG *-celebration-similar templates have no media block. Pre-existing .pmp-details-media-row / .pmp-details-media-main / .pmp-details-media-column CSS rules retained because the navigation and 3-column-profile templates still consume them.

+ Files modified
    * pet-match-pro.php (Version constant 8.7.0 -> 8.7.1, docblock Version: 8.7.0 -> 8.7.1)
    * readme.txt (Stable tag 8.7.0 -> 8.7.1, new == Upgrade Notice == 8.7.1 entry)
    * CHANGE-LOG.txt (this entry)
    * public/class-pet-match-pro-public.php (new normalizeShortcodeAtts() helper; wiring in handleSearchShortcode, handleDetailsShortcode, handleDetailShortcode array path, handleOptionShortcode)
    * public/css/pet-match-pro-styles.css (.pmp-details-image-column rule + nested image-main override)
    * public/templates/pp/{adopt-default,adopt-conversion,adopt-conversion-no-app,adopt-conversion-similar,lost-default,found-default,lost-poster,found-poster}.php
    * public/templates/af/{adopt-default,adopt-conversion,adopt-conversion-no-app,adopt-conversion-similar,lost-default,found-default,lost-poster,found-poster}.php
    * public/templates/rg/{adopt-default,adopt-similar}.php

+ Translation impact
    * No new user-facing strings. The normalizeShortcodeAtts() docblock is internal. CSS changes are presentational. Template changes are markup-only - all rendered text comes from helpers (renderInstructions, renderSocialShare, renderPrintPosterButton) whose translation strings are unchanged.

================================================================================

Version 8.7.0 - April 29, 2026 - Analytics Queue + IntersectionObserver Architecture

Architectural release. Replaces the page-render impression sweep with an IntersectionObserver that only counts dwelled cards, decouples server-side analytics writes from the visitor request via a queue + 1-minute WP-Cron flush, and adds operator instruments (Queue Health card, Flush Queue Now button, Cron Self-Test button) plus five new KB articles for the resulting pipeline. The brief that drove this release is preserved at analytics-queue-and-cron-brief.md in the repo root.

+ Item 0: IntersectionObserver-driven impression collection (Fix 1 in the brief)
    * Reported behavior: 8.6.11.2 left the impression collector at "every animal card rendered to the page is an impression". On cincinnatianimalcare.org's "available dogs" page, this meant 362 impression rows POSTed in one batch on every page load - even though the visitor typically saw 8-20 cards before bouncing, filtering, or clicking. The data was structurally wrong: per-card position-impact analytics were diluted by 17x noise, and totals were inflated.
    * Added a new PMPImpressionTracker module to public/js/pet-match-pro-public.js, placed inside the existing IIFE between PMPAnalytics and PMPFilterSort. It owns an IntersectionObserver with a 0.5 threshold (50% of card visible) and a 500ms dwell timer per card. When a card has been visible for the dwell duration, the tracker promotes it to a local batch, unobserves it (so re-scrolling does not double-count), and schedules a 2s flush-cadence timer.
    * Each animal_id reports at most once per page session via a Set keyed on animal_id. Dedup happens client-side; the server queue trusts the client batch.
    * The flush() method dispatches via navigator.sendBeacon only - no $.ajax fallback in the impression path. Pre-2017 browsers without sendBeacon drop the batch silently; the dwell-gating change matters far more than retaining a fallback for ~0.x% of visitors.
    * The tracker subscribes to pmp:pagination:change, pmp:pagination:ready, and pmp:filtersort:updated so newly-rendered cards (pagination, filter changes) are auto-observed without needing a manual call.
    * pagehide and visibilitychange listeners flush whatever is in the local batch when the visitor leaves the page, so closing the tab does not lose the last few impressions.
    * Position is derived the same way as the legacy code - 1-based index within the visible (post-filter) result set - so position-impact analytics remain comparable in shape across the upgrade. Counts are not comparable because the dwell gate filters out the long tail; this is documented in the Interpreting Position Impact section of the Leveraging Analytics KB article.
    * Removed the legacy pmp:pagination:ready / pmp:pagination:change sweep handlers and the no-pagination pmpDeferToIdle fallback from PMPAnalytics.init(). Those are now redundant - the observer self-manages.
    * PMPAnalytics.trackImpressions() retained as a legacy entry point, body replaced with a thin call to PMPImpressionTracker.observeCards() so existing callers (notably PMPFilterSort.applyFiltersAndSort()) continue to work. Marked as deprecated in the comment but not removed because external themes / customizations may have wired into it.
    * window.PMPImpressionTracker exposed at the bottom of $(document).ready, mirroring window.PMPAnalytics.

+ Item 1: Server-side queue + WP-Cron flush (Fix 2 in the brief)
    * Even after Item 0, every impression batch still paid the LiteSpeed/Cloudflare host-stack baseline (~1.5s on cincinnatianimalcare.org) synchronously. On a session where the visitor scrolls through 60 cards spread across 4-5 batches, that compounded to 4-5 slow requests. The architectural fix: stop blocking the visitor request on database work entirely.
    * New file includes/analytics/class-pet-match-pro-analytics-queue.php exposes PetMatchPro\Analytics\AnalyticsQueue with class constants OPTION_KEY ('pmp_impression_queue'), OPTION_LAST_FLUSH ('pmp_impression_queue_last_flush'), CRON_HOOK ('pmp_flush_impression_queue'), CRON_INTERVAL ('minute'), MAX_QUEUE_ENTRIES (5000), STALE_THRESHOLD_SECONDS (300), DEEP_THRESHOLD_ENTRIES (100). All settable per-site via class extension if a customer needs different thresholds.
    * AnalyticsQueue::enqueue(array $records): int appends pre-built records to the queue option (autoload=false to keep it out of the WP options autoload set on every request) and applies overflow trim - drops oldest entries when count exceeds MAX_QUEUE_ENTRIES, with one error_log entry per overflow event.
    * AnalyticsQueue::flush(): void snapshots-then-clears the queue (delete_option then bulk INSERT, so a racing enqueue between the get and the delete loses the smallest possible window - the next cron tick picks up the new entries 60s later, acceptable for analytics). Loads AnalyticsDb on demand and delegates the multi-row INSERT to the existing recordEventsBulk() shipped in 8.6.11.
    * AnalyticsQueue::ensureScheduled() registers the cron event on the 'minute' interval if not already scheduled. AnalyticsQueue::clearSchedule() unschedules cleanly on plugin deactivation.
    * cron_schedules filter in pet-match-pro.php adds the 'minute' interval (WP core ships hourly/twicedaily/daily only) with a translated display name and 60-second interval.
    * The cron action is wired in pet-match-pro.php's existing plugins_loaded priority-5 hook (next to the schema self-heal added in 8.6.11.1) so it self-heals after upgrade-in-place. AnalyticsQueue::ensureScheduled() runs on every plugins_loaded - idempotent because wp_next_scheduled() returns false only when nothing is queued.
    * Deactivation hook in pet-match-pro.php now also calls AnalyticsQueue::clearSchedule() and CronHealth::clearSchedule() so deactivating the plugin does not leave orphan cron events behind.
    * AnalyticsAjax::handleTrackImpressions() refactored: validate input -> call AnalyticsTracker::buildImpressionRecords() -> AnalyticsQueue::enqueue() -> wp_send_json_success(). The 8.6.11.x canUseEarlyClose() / sendJsonAndClose() / synchronous-write branching is gone for impressions because the handler no longer does meaningful work - it returns ~5ms PHP plus the LSCache baseline that we cannot avoid.
    * handleTrackEvent() (clicks, shares, poster prints, video plays) is unchanged - those events are user-gestured, low-frequency, and the dashboard counter benefits from being acknowledged synchronously. Only impressions get the queue treatment.

+ Item 2: AnalyticsTracker::buildImpressionRecords() extracted as public method (record-shape consolidation)
    * The per-row builder previously lived inside AnalyticsTracker::trackImpressions(). When the queue path needed to build the same record shape without writing to the DB, two options existed: promote getSessionId/hashIp to public so the AJAX class could re-build, or extract the builder onto the tracker. Chose the extraction so session/IP plumbing stays encapsulated and the queue path and the legacy synchronous path share one record-building implementation that cannot drift.
    * New public method AnalyticsTracker::buildImpressionRecords(array $impressions): array contains the entire previous body of trackImpressions() up to the recordEventsBulk() call. trackImpressions() now calls buildImpressionRecords() internally and adds the recordEventsBulk() write step.
    * AnalyticsAjax::handleTrackImpressions() builds records via (new AnalyticsTracker())->buildImpressionRecords($impressions) and hands the result to AnalyticsQueue::enqueue(). One source of truth for impression record shape across the queue path and the rollback path.

+ Item 3: sendBeacon-only impression POSTs (Fix 3 in the brief)
    * The legacy PMPAnalytics.trackImpressions() flush had a $.ajax fallback when navigator.sendBeacon was unavailable. In 2026 every browser shipping in the last 8 years supports sendBeacon, and the rare visitors without it are not the demographic shelter analytics dashboards serve. Drop the fallback to remove a code branch.
    * In PMPImpressionTracker.flush(), if sendBeacon is unavailable the batch is dropped (with the array reset) and no XHR fires. Visitors without sendBeacon contribute zero analytics; everyone else contributes accurate dwell-gated data.

+ Item 4: sendBeacon for pmp_update_sorted_ids (Fix 4 in the brief)
    * PMPFilterSort.updateSession() previously fired synchronous $.ajax on every user-triggered filter or sort change. The xhr blocked for ~1.98s on LiteSpeed/Cloudflare. The user saw the new card grid render while the request hung in flight in the network panel - perceived latency on top of actual latency.
    * Switched to navigator.sendBeacon when available, with the existing $.ajax block retained as a fallback for ancient browsers without sendBeacon. Filter/sort clicks are now fire-and-forget on every modern browser.
    * Race accepted: if the visitor clicks a filter and immediately clicks an animal card (within ~50ms) AND the server is still buffering the sendBeacon request, detail-page prev/next walks the OLD sort order. Three things must align in under a second; worst case is a misordered prev/next on one detail view that recovers on the next visit. Same trade-off accepted for impressions and consistent with analytics-style data quality expectations.
    * The 8.6.11.2 pmpDeferToIdle initial-load wrapper stays in place - it still applies to the page-load case where a sort is active at render time.

+ Item 5: Synchronous-flush fallback for broken-cron hosts (Layer 1 in the brief)
    * Item 1 trusts WP-Cron to drain the queue every 60 seconds. On hosts where WP-Cron is misconfigured, disabled without a host-level cron replacement, or silently failing under load, the cron handler never runs. Without a guard, the queue would grow unbounded until it hits MAX_QUEUE_ENTRIES and starts dropping oldest entries forever - customers would lose analytics with no indication anything was wrong.
    * AnalyticsQueue::flushIfStale() runs from inside enqueue() after the queue has been updated. If OPTION_LAST_FLUSH is older than STALE_THRESHOLD_SECONDS (300s, 5x the cron interval) AND the queue is at least DEEP_THRESHOLD_ENTRIES (100) deep, it calls flush() inline. Costs one slow request per fallback firing; zero overhead on healthy sites where cron drains the queue faster than the staleness check trips.
    * The DEEP_THRESHOLD prevents thrash. If cron is broken AND every enqueue triggered a sync flush, every analytics request would pay the full host-stack tax - worse than 8.6.11.x. Small batches just sit in the queue until either cron recovers or enough accumulates to justify the fallback.
    * On first-ever enqueue (OPTION_LAST_FLUSH = 0), the timestamp is primed to the current time so an empty system is not immediately judged stale.
    * AnalyticsQueue::flush() updates OPTION_LAST_FLUSH on every successful drain (including empty-queue drains - "we tried" still counts as a flush for staleness purposes).
    * Race with a recovering cron: if the staleness check runs at T+301s and the cron tick fires at T+302s, both could try to flush the same queue. The delete_option(OPTION_KEY) in flush() is effectively atomic in MySQL (single UPDATE in wp_options), so whichever runs second sees an empty queue and no-ops. No double inserts, no lost rows.

+ Item 6: Analytics Queue Health admin card (Layer 2 in the brief)
    * AnalyticsQueue::getHealthStatus(): array returns three operational metrics for the admin tab - depth (current queue count), last_flush_age (seconds since last successful flush), next_scheduled (seconds until the next cron tick). Plus is_stale (boolean: last flush older than STALE_THRESHOLD) and cron_scheduled (boolean: wp_next_scheduled returned a time vs false).
    * New renderAnalyticsQueueHealth() method on the admin settings class renders a "Analytics Queue Health" accordion card on the Analytics tab (between Tracking Settings and Dashboard, opens when tracking is enabled). Three rows display the metrics with translated copy. Two warning badges fire when the metrics indicate broken cron - "Cron may be broken" appears on Last Flush when stale, and on Next Scheduled when no event is registered.
    * Card includes a description paragraph linking to the new cron-requirement KB article via Constants::DOCS_URL + KB_LINKS['cron_requirement'].

+ Item 7: Tools tab "Flush Queue Now" + "Run Cron Self-Test" buttons (Layer 2b in the brief)
    * New file includes/analytics/class-pet-match-pro-cron-health.php exposes PetMatchPro\Analytics\CronHealth with class constants TEST_HOOK ('pmp_cron_self_test'), OPTION_STATUS / OPTION_SCHEDULED_AT / OPTION_FIRED_AT, status enum (idle / pending / healthy / failing), SCHEDULE_OFFSET_SECONDS (60), DEADLINE_SECONDS (120). startTest() schedules a one-shot WP-Cron event and writes a pending marker; handleTestFire() (the cron callback) writes a healthy marker; resolveStatus() reports current state and promotes pending->failing once the deadline elapses without a fire.
    * Three new admin AJAX handlers on AnalyticsAjax: handleFlushQueueManual (calls AnalyticsQueue::flush() and reports the depth delta), handleCronSelfTest (calls CronHealth::startTest() and returns the deadline), handleCronSelfTestStatus (polling endpoint). All three require manage_options + nonce verification (separate nonces 'pmp_flush_queue' and 'pmp_cron_self_test').
    * Two new Tools tab accordions: "Analytics Queue" and "Cron Self-Test", both gated to PREFERRED_LEVEL via new entries in admin/partials/pmp-option-levels-tools.php. Free/Junior tier shows the locked-accordion badge (matches existing pattern for SEO Diagnostics, Cache, etc.).
    * "Flush Queue Now" UI: button triggers a fetch() call, spinner runs, "Flushed N rows." text appears next to the button on success, queue depth display updates in place. JS is inline in the tools render method to keep the change self-contained.
    * "Run Cron Self-Test" UI: button starts the test, polls every 5 seconds for up to 120 seconds (the deadline), shows a green "Cron is healthy" badge on success or a yellow "Cron does not appear to be firing" badge with a KB link on failure.
    * The CronHealth class is sized to be reused by 8.7.1's planned activation-time auto-self-test (Layer 3 in the brief, parked in cron-self-test-brief.md). 8.7.0 ships only the manual button-driven path - no admin_notices rendering, no dismissal AJAX, no activation hook trigger. The same code path will gain those layers in 8.7.1 without changing the underlying class.

+ Item 8: KB articles + KB_LINKS wiring + Tracking Settings copy (Layer 4 in the brief)
    * Five new KB articles (English only) under the Analytics Tracking category: Analytics Overview, WP-Cron Requirement, Queue Health and Tools, Interpreting Position Impact, and Privacy and Data Retention. Each follows the existing KB voice, includes <h2 id="..."> anchors for deep-linking, and contains <!-- RICH: ... --> comments flagging spots where the owner intends to add screenshots, diagrams, or example walkthroughs in a future content pass.
    * KB_LINKS constant in admin/class-pet-match-pro-admin-settings.php gains five new entries: 'analytics_overview', 'cron_requirement', 'queue_health_tools', 'position_impact', 'analytics_privacy'. All slug-only (no anchor) so the docs-site renders the full article TOC by default.
    * Help icons wired: KB_LINKS['queue_health_tools'] on the Analytics tab Queue Health accordion AND the Tools tab Analytics Queue accordion. KB_LINKS['cron_requirement'] on the Tools tab Cron Self-Test accordion AND the Tracking Settings field's "Enable Click Tracking" label.
    * The "Enable Click Tracking" toggle row gained a description paragraph (under the toggle, not in the title attribute) explaining the WP-Cron requirement and linking to the cron-requirement article. Visible to operators every time they look at the Tracking Settings accordion - they cannot enable analytics without seeing the cron note.

+ Item 9: Translations explicitly deferred
    * 8.7.0 ships approximately 30 new user-facing strings: queue health labels (Queue depth / Last flush / Next scheduled), tools button copy (Flush Queue Now / Run Cron Self-Test / Flushed N rows / Cron is healthy / Cron does not appear to be firing / Running test - waiting for cron to fire / View KB article / Test failed to start / Flush failed), cron warning badges (Cron may be broken / Not scheduled / Now / Never), cron interval display name (Every Minute (PMP)), and the inline cron requirement description copy in the Tracking Settings card and Queue Health card. Plus translator-comment annotations on the human_time_diff %s ago format strings.
    * Per owner direction, translation regeneration is deferred to a separate release that will bundle 8.7.0's strings together with the still-pending Shortcode Builder Phase 4 strings. The pre-existing memory note about deferring .pot/.po regen until after Builder Phase 4 stays in effect; this release adds to the pending pile instead of clearing it.

+ Item 10: Daily summary table + rollup cron (the "dashboard times out" fix)
    * Reported behavior: cincinnatianimalcare.org's analytics tab failed to load (request timed out) after less than 24 hours of data collection on PMP 8.6.x. Same architecture would resurface within weeks on every Preferred-tier site as raw event count climbed. The dwell-gating change in Items 0+1 reduces row growth ~17x but does not change the underlying read pattern - the dashboard still GROUPed the events table on every render, so the eventual scale ceiling was just delayed, not solved.
    * Root cause: AnalyticsDB::getSummaryStats(), getTopAnimals(), getActionBreakdown(), getSourceBreakdown(), getDailyTrends(), getPriorPeriodStats(), getSpeciesEngagement() each ran SELECT ... FROM wp_pmp_analytics_events GROUP BY ... over the full date window. O(rows) per render. A 90-day shelter on the post-8.7.0 model still produces hundreds of thousands of rows. The dashboard read pattern was structurally unscalable.
    * Architectural fix: route every dashboard read through a denormalized daily summary table (wp_pmp_analytics_daily) populated by a 5-minute incremental cron and a daily 03:00 finalize cron. The summary table stores one row per (summary_date, event_type, method_type, animal_id, animal_name, species, source, action_type) combination with event_count and unique_sessions measures. Read queries become SUM(event_count) GROUP BY <dimension> over an indexed table that holds ~90 rows for a 90-day window instead of millions of raw events.
    * Schema bump: Constants::ANALYTICS_SCHEMA_VERSION raised from '1.0' to '2.0'. The 8.6.11.1 self-heal hook detects the bump and re-runs createTables() on the next request. The 1.0 daily-table schema (date, event_type, animal_id, action_type) was never populated in production - no rollup writer existed before 8.7.0 - so AnalyticsDB::createTables() does an explicit DROP TABLE IF EXISTS on the daily table when the stored version is below 2.0 before letting dbDelta build the 2.0 schema. Data-safe because nothing was ever written to the 1.0 table.
    * Empty-string defaults on dimension columns: MySQL UNIQUE keys treat NULL as distinct, which would defeat the dedup pattern needed for INSERT ... ON DUPLICATE KEY UPDATE. Storing '' for missing method_type / animal_id / species / source / action_type lets the unique key (summary_date, event_type, method_type, animal_id, species, source, action_type) collapse identical dimensional rows correctly.

+ Item 11: AnalyticsDailyRollup class + cron wiring
    * New file includes/analytics/class-pet-match-pro-analytics-daily-rollup.php exposes PetMatchPro\Analytics\AnalyticsDailyRollup. Public methods: rollupDay($date) (idempotent DELETE-then-INSERT for a single date), rollupToday() (cron handler for the 5-minute incremental tick), rollupYesterdayAndFinalize() (cron handler for the daily 03:00 finalize), backfill($maxDays) (loops over historical raw-event dates not yet in the summary, capped per call), ensureScheduled() (idempotent cron registration), clearSchedule() (deactivation cleanup).
    * rollupDay() runs one DELETE + one INSERT...SELECT GROUP BY scoped to the target date. The DELETE is keyed on the indexed summary_date column. The INSERT uses COALESCE on every dimension to coerce NULL -> '' so the UNIQUE key dedups correctly. Pre-existing aggregates are dropped first because COUNT(DISTINCT session_id) cannot be merged additively across overlapping inserts; cleanest correctness pattern.
    * Two cron hooks: pmp_rollup_today_incremental on a new 'five_minutes' cron interval (300 seconds), and pmp_rollup_yesterday_finalize on the WP core 'daily' interval scheduled for 03:00 site time. The 'five_minutes' interval is registered in pet-match-pro.php's existing cron_schedules filter alongside the 'minute' interval added for the queue flush.
    * pet-match-pro.php's plugins_loaded priority-5 hook now requires the rollup class file alongside AnalyticsQueue and CronHealth, registers both rollup cron actions, and calls AnalyticsDailyRollup::ensureScheduled(). Deactivation hook calls AnalyticsDailyRollup::clearSchedule().
    * AnalyticsDB::getEventsTable() and AnalyticsDB::getDailyTable() added as public getters so the rollup class can reach the table names without re-implementing the prefix logic.

+ Item 12: Dashboard read methods switched to summary table
    * AnalyticsDB now exposes a private buildDailyDateCondition() helper that mirrors buildDateCondition() but compares against the DATE column (summary_date) instead of the DATETIME column (created_at). Used by every method below.
    * Switched: getSummaryStats(), getTopAnimals(), getActionBreakdown(), getSourceBreakdown(), getDailyTrends(), getPriorPeriodStats(), getSpeciesEngagement(). Each method's FROM clause now points at $this->dailyTable, COUNT(*) becomes SUM(event_count), the date condition uses buildDailyDateCondition(), and dimension comparisons use <> '' instead of IS NOT NULL (because daily stores '' for missing values).
    * getSourceBreakdown() additionally maps '' -> Constants::DB_ANALYTICS_SOURCE_UNKNOWN at read time via IF({$source} = '', %s, {$source}) so the existing UI labels still render.
    * Stayed on raw events: getPositionImpact() (position breakdown excluded from daily because storing per-position rows would defeat the row-count win), getTimeToAction() and getMultiActionVisitors() and getRepeatVisitorConversion() (session-level joins on per-event timestamps), getSourceConversionRates() and getPeakEngagementTimes() (cross-cuts that don't fit the daily aggregation shape), getEnrichmentCorrelation() and getStaleListings() (need fields not present in daily), getAnimalsNeedingAttention() (HAVING clause with nested event-type counts). exportEvents() correctly stayed on raw events for full row export. These methods are far less hot than the dashboard four; revisit case-by-case in 8.7.x if any specific one becomes a problem.
    * Read staleness: today's data is up to ~5 minutes behind real time on a healthy site (5-minute incremental cron). Closed days are stable. The Tools-tab Flush Queue Now and Rebuild Daily Summaries buttons let operators pull the freshest possible view on demand.

+ Item 13: Rebuild Daily Summaries button on Tools tab
    * New AnalyticsAjax handler handleRebuildSummaries calls AnalyticsDailyRollup::backfill() with a default cap of 90 days plus a follow-up rollupToday() call so the dashboard reflects the latest activity. Returns days_processed, rows_written, today_rows, and the list of dates it touched.
    * New Tools tab accordion "Rebuild Daily Summaries" gated to PREFERRED_LEVEL via admin/partials/pmp-option-levels-tools.php (level_tools_rebuild_summaries). UI button + spinner + result text, same pattern as Flush Queue Now and Run Cron Self-Test. Inline JS calls the handler via fetch() and displays "Rebuilt N days, M rows written." or "No historical days needed rebuilding. Today refreshed." depending on the result.
    * Use cases: post-upgrade backfill (the daily table starts empty after the schema bump - clicking once reconciles all historical raw events), post-restore reconciliation, and diagnostic flow when the dashboard shows zero data despite raw events being present.
    * The button caps backfills at 90 days per click; sites with longer history can re-click. Each click picks up where the previous left off because the backfill query selects dates that aren't yet in the summary table.

+ Item 14: KB article updates for daily summaries
    * Analytics Overview KB article: extended the "How the Data Gets Into the Dashboard" section to include the daily summary rollup steps (5-minute incremental, nightly finalize) and added a "Why a separate summary table?" subsection explaining the architectural fix in operator-friendly language. Two new RICH content cues for diagrams.
    * Queue Health and Tools KB article: added a "Rebuild Daily Summaries Button" section before the "What These Tools Do Not Do" list, covering the use cases (post-upgrade, post-restore, diagnostics), behavior (90-day cap per click, refreshes today after backfill), and operator playbook ordering. Two new RICH content cues.

+ Item 15: Verification of daily summaries (manual, per-test-site)
    * Upgrade verification: upload all changed files. Visit any front-end page once so plugins_loaded fires. SQL `SELECT option_value FROM wp_options WHERE option_name = 'pmp_analytics_schema_version'` returns '2.0'. SQL `SHOW CREATE TABLE wp_pmp_analytics_daily` shows the new columns (method_type, animal_name, source) and the expanded UNIQUE key.
    * Cron registration: WP-CLI `wp cron event list` shows pmp_rollup_today_incremental (every 5 minutes) and pmp_rollup_yesterday_finalize (every 24 hours at 03:00 site time) alongside the existing pmp_flush_impression_queue.
    * Initial backfill: visit Tools tab, click "Rebuild Daily Summaries". Confirm the result text reports a non-zero days_processed if raw events existed pre-upgrade. SQL `SELECT COUNT(*) FROM wp_pmp_analytics_daily` is non-zero. SQL `SELECT DISTINCT summary_date FROM wp_pmp_analytics_daily ORDER BY summary_date` lists every date that has raw events.
    * Dashboard load time: visit Analytics tab. The page should render within ~1 second regardless of how many raw events exist. Compare against pre-upgrade timing if available.
    * Rollup correctness spot-check: pick a recent date with known raw event volume. SQL `SELECT SUM(event_count) FROM wp_pmp_analytics_daily WHERE summary_date = 'YYYY-MM-DD' AND event_type = 'impression'` should equal SQL `SELECT COUNT(*) FROM wp_pmp_analytics_events WHERE DATE(created_at) = 'YYYY-MM-DD' AND event_type = 'impression'`. Same for detail_view, action_click, etc.
    * Today drift: trigger several impressions on a search page. Wait 5+ minutes (or click Rebuild Daily Summaries). Refresh dashboard. Today's counts should reflect the new activity.

+ Item 16: Polish round - dwell-drop bug, label cleanup, accordion ordering, cron-test layout
    * Bug fix - impressions dropped on fast interaction: PMPImpressionTracker only promoted a card to the batch after MIN_VISIBLE_MS (500ms) of continuous dwell. If the visitor clicked a card within that window, the pending dwell timer was abandoned and the impression was never recorded - which means the very cards the visitor engaged with were the most likely to be lost. Added PMPImpressionTracker.commitPending() that force-promotes every pending dwell timer into the batch. Wired into the pagehide and visibilitychange handlers so any time the visitor leaves the page, in-flight pending impressions are committed before the sendBeacon flush. The dwell gate still applies to passive scrolling - this only fires when the visitor is actually leaving the page.
    * Admin UI - "Enable Click Tracking" label renamed to "Enable Tracking" in the Tracking Settings accordion. The original label predated 8.7.0's IntersectionObserver impression collector and was misleading - the toggle controls clicks AND impressions AND share/poster events, not just clicks. Pure copy change; no logic impact.
    * Admin UI - Analytics tab accordions reordered: Tracking Settings stays first (operator-flow priority), the rest sorted alphabetically by display title - Analytics Dashboard, Analytics Insights, Analytics Queue Health, Data Management, SEO Settings.
    * Admin UI - Tools tab accordions reordered alphabetically by display title: Analytics Queue, API Diagnostics (default-open, Free), Cache Management, Cron Self-Test, Export / Import Settings, License Summary, Rebuild Daily Summaries, SEO Diagnostics, Setup Wizard, System Information, Template Audit. API Diagnostics retains the default-active state so Free-tier operators see content on first load instead of an Analytics Queue locked accordion.
    * Admin UI - Queue Health table labels switched to mixed case for consistency with the rest of the admin: "Queue depth" -> "Queue Depth", "Last flush" -> "Last Flush", "Next scheduled" -> "Next Scheduled". Applied in both Analytics tab Queue Health card and the Tools tab Analytics Queue accordion. Two strings each, six total.
    * Admin UI - All three Tools-tab action accordions (Analytics Queue / Flush Queue Now, Cron Self-Test, Rebuild Daily Summaries) place the spinner + result indicator immediately to the right of the action button via a shared .pmp-action-progress wrapper inside the .pmp-tools-action-row. The action row gets flex layout (display:flex, align-items:center, gap:8px) so the progress span pins next to the button regardless of WP core's default .spinner float:right behavior. The button-adjacent placement matches operator expectations for "I clicked, what happened?" - the eye already lives at the button.
    * Admin UI - All three Tools-tab action results use shared green/red completion badges. Success states ("Cron is healthy", "Flushed N rows.", "Rebuilt N days, M rows written.") render with .pmp-badge.pmp-healthy (green #2e7d32). Failure states ("Cron does not appear to be firing", "Flush failed.", "Rebuild failed.") render with .pmp-badge.pmp-failing (red #c62828). The .pmp-warning class (yellow) is unchanged - still used on the Queue Health card for "stale but not necessarily broken" where amber is the right semantic.
    * Admin CSS - All 8.7.0 admin styling lives in admin/css/pet-match-pro-admin.css alongside the owner-contributed .pmp-queue-health rules. Earlier 8.7.0 iterations had inline <style> blocks in the Tools tab renderer for the cron-test progress indicator and badge colors; consolidated to the stylesheet so all admin CSS has one home and the renderer methods only emit markup + behavior. The final rules: .pmp-action-progress (inline-flex wrapper, used both at end of description copy AND inside action rows), .pmp-action-progress .pmp-action-spinner / .pmp-tools-action-row .pmp-action-spinner (zeroes out WP .spinner float/margin so it stays inline instead of being pushed to the far right edge of its container), .pmp-action-result (inline-flex with gap), .pmp-badge.pmp-healthy (green, unparented selector so the rule applies regardless of which container the badge ends up in), .pmp-badge.pmp-failing (red, same unparented pattern). The badge selectors are intentionally not scoped to .pmp-action-result so future result placements - e.g., dialog modals, admin notices - inherit the same styling without CSS edits.
    * Admin UI - Rebuild Daily Summaries and Flush Queue Now button JS converted to use the shared escapeHtml() helper and .innerHTML-with-badge-wrap pattern so success and failure states inherit the new styling consistently across all three accordions.
    * Admin CSS - The owner contributed custom CSS for the .pmp-queue-health card layout/typography (added directly to admin/css/pet-match-pro-admin.css). Logged here for changelog completeness; no plugin-author behavior change.
    * Data Management - "Clear All Data" extended to a fully fresh state. AnalyticsDB::clearAllData() previously truncated only the wp_pmp_analytics_events and wp_pmp_analytics_daily tables, leaving stale pipeline state in wp_options (the impression queue, the last-flush timestamp, and the three cron-self-test marker options). Now also drops the AnalyticsQueue::OPTION_KEY, AnalyticsQueue::OPTION_LAST_FLUSH, CronHealth::OPTION_STATUS, CronHealth::OPTION_SCHEDULED_AT, and CronHealth::OPTION_FIRED_AT options via delete_option(). The schema-version option (pmp_analytics_schema_version) is intentionally NOT cleared because the database schema itself is still present; clearing it would just trigger an unnecessary self-heal run on the next page load. Class files are lazy-loaded via class_exists/require_once so the method works even when the analytics support classes haven't been autoloaded yet (e.g., from an admin context where they aren't strictly needed).

+ Item 17: Session-aware impression dedup in dashboard reads
    * Reported behavior: a test scrolled through a search page (5 first-row impressions), clicked into a detail (1 detail_view), returned to the same search page (the first-row 5 cards re-render and the IntersectionObserver fires for them again because the seen-Set is per-page-session, not per-browser-session), then scrolled to a second row (5 more impressions). Total raw impressions written: 15. Operator's intuition: "I saw 10 unique animals, the count should be 10."
    * Root cause: the brief originally specified per-page-session dedup ("each animal_id reports at most once per page session"), and explicitly punted server-side per-browser-session dedup to a future release pending evidence it caused real data-quality issues. Evidence arrived in real-world testing: returning visitors revisit the same search page constantly, and counting each revisit as a fresh impression inflates totals exactly as predicted.
    * Fix: switched the dashboard read methods that surface impressions and detail_views to SUM(unique_sessions) instead of SUM(event_count). The daily table already carries unique_sessions per (date, event_type, method_type, animal_id, animal_name, species, source, action_type) row via the existing rollup query (COUNT(DISTINCT session_id) AS unique_sessions). For the test case above, the 15 raw events become 10 unique session-animal pairs (5 first-row animals seen by 1 session even though they were viewed on two page loads, plus 5 second-row animals seen by that same session once). The dashboard reads 10. Stable across rollups - no "first 15, then drops to 10" UX problem because the dashboard never reads raw events directly; it always sees the deduped daily summary.
    * Action clicks / shares / poster prints intentionally KEEP raw event_count semantics. These are discrete user intents - clicking an "Email Us" button three times in one session is meaningful behavior (e.g., the visitor bounced between the email client and the page). Dedup would hide the signal. Impressions and detail_views, on the other hand, are passive measurements where revisits are expected and should not inflate.
    * Methods updated: getSummaryStats (CASE WHEN impression/detail_view -> unique_sessions ELSE event_count), getTopAnimals (filtered to detail_view, SUM(unique_sessions)), getSourceBreakdown (filtered to detail_view, SUM(unique_sessions)), getDailyTrends (CASE pattern), getPriorPeriodStats (CASE pattern, matches getSummaryStats so trend comparisons stay apples-to-apples), getSpeciesEngagement (per-event-type CASE).
    * Methods unchanged: getActionBreakdown (filtered to action_click - keeps event_count). getPositionImpact, getTimeToAction, getMultiActionVisitors, getRepeatVisitorConversion, getEnrichmentCorrelation, getStaleListings, getPeakEngagementTimes, getSourceConversionRates - still on raw events for session-level / position / enrichment calculations that don't aggregate cleanly into the daily table.
    * UX consequence: dashboard numbers are stable and never decrease between rollups. Raw events table still records every page-session impression (preserves granular signal for forensics or future re-aggregation); the dedup happens only at the read layer. Operators querying raw SQL still see all 15; the dashboard shows 10.

+ Item 18: Action-click click handler missed profile-template buttons
    * Reported behavior: clicking the Email Us button on a profile-style detail page (e.g., adopt-profile-3-column) did not record an action_click event. detail_view from the same flow worked, so the analytics pipeline was healthy - the divergence was specific to action buttons on this template family.
    * Root cause: bindActionClicks() in public/js/pet-match-pro-public.js bound the click handler with selector `a[class*="pmp-details-button"]`. CSS attribute substring matching is literal - `pmp-details-button` is NOT a substring of `pmp-details-profile-button` (the chars `pmp-details-` are followed by `profile-`, not `button`). Profile templates render their conversion buttons with classes like `pmp-details-profile-button pmp-details-profile-button-email`, which the legacy selector silently ignored. The handler only matched navigation-template buttons (`pmp-details-button-*`), so action_click events from profile templates have been missing since the profile templates shipped.
    * Diagnosed via console testing: the user confirmed the email button existed with class `pmp-details-profile-button pmp-details-profile-button-email`, that manual `window.PMPAnalytics.trackEvent('action_click', {...})` calls reached the server correctly, that no `admin-ajax.php` POST appeared on real button clicks, and that monkey-patched `trackEvent` and `sendBeacon` shims also did not fire on real clicks - all of which pointed at the click handler not binding to the button.
    * Fix: change the selector to `a[class*="pmp-details"][class*="-button"]` (two substring tests AND-ed together). This matches every current template variant - `pmp-details-button*`, `pmp-details-profile-button*`, `pmp-details-profile-other-button*` - and is forward-compatible with any future button class that follows the same `pmp-details*-button*` convention. Inline comment documents the substring-matching gotcha so a future maintainer does not re-introduce the narrower selector.
    * Action click rates on profile-template sites will appear to "increase" after this fix relative to historical data; that increase is real signal that was previously lost. Operators reviewing trend lines that span the upgrade should expect a step change in action_click counts on the upgrade date for any site running profile templates.

+ Item 19: share_click swallowed by Monarch's stopPropagation
    * Reported behavior: clicking a Facebook / Twitter / Pinterest / LinkedIn / Gmail share button on a detail page did not record a share_click event. Same family as Item 18 - silent failure since the feature shipped.
    * Root cause: PetMatchPro integrates social sharing via the Monarch plugin (Elegant Themes), which renders the share row as a list of <li class="et_social_facebook"> elements wrapping <a class="et_social_share"> anchors with overlay <span> click targets. Monarch attaches its OWN bubble-phase click handler to each anchor, calls e.preventDefault() to open a popup window programmatically, AND calls e.stopPropagation() to keep the click event private. The PMP click tracker used jQuery delegated handlers on document - bubble-phase listeners that never fire when an upstream handler stops propagation. Net effect: the click bubble died at the Monarch anchor, never reached document, never reached PMP's tracker. Manual diagnostic confirmed Monarch's structure (li.et_social_facebook > a.et_social_share > i.et_social_icon + span.et_social_overlay) and the click-target pattern.
    * Fix: rewrote bindShareClicks() to use a CAPTURE-phase native event listener on document. Capture phase fires BEFORE bubble phase, so PMP's tracker sees the click before Monarch's stopPropagation runs. The new listener walks up from the click target looking for class markers (et_social_facebook, pmp-share-facebook, ...) or data-share attributes on any ancestor; first match wins and resolves to a platform key (facebook / twitter / pinterest / linkedin / email / copy_link). Robust against Monarch's nested DOM (overlay span -> icon i -> anchor a -> li.et_social_*) because it walks ancestors, not just the click target.
    * Three sources of share-button markup are supported in one path: Monarch (et_social_* classes), PMP-native (pmp-share-* classes - reserved for future use), and data-share attributes (custom integrations). The platformMap data structure makes adding new networks or marker patterns a one-line change.
    * Gmail (Monarch's class is et_social_gmail) maps to the same "email" platform key as et_social_email and pmp-share-email - the analytics dimension is "the visitor shared via email", not "which email client".
    * Pre-fix detail-template sites with social share enabled have been silently dropping every share_click since the social-share feature was introduced. Operators reviewing share-engagement data should expect a step change up on the upgrade date.

+ Item 20: Refresh button on Queue Health card
    * Operator request: the Queue Health metrics (Queue Depth / Last Flush / Next Scheduled) reflect state at admin-page-render time. Operators monitoring the pipeline during traffic want fresh numbers without reloading the entire admin page. Added a Refresh button to both Queue Health locations (Analytics tab card and Tools tab Analytics Queue accordion) so operators can pull current metrics in place.
    * Implementation: extracted the <dl> markup into a shared public static method Pet_Match_Pro_Admin_Settings::buildQueueHealthDl() that both renderers call AND that the new pmp_queue_health_refresh AJAX handler returns verbatim. The JS swap is byte-identical to a full page reload because both code paths produce the same HTML from the same getHealthStatus() data.
    * Shared refresh JS lives in renderQueueHealthRefreshScript() with a static guard so it emits exactly once per page render even when both Analytics-tab and Tools-tab queue-health cards are rendered. The handler is event-delegated on document and matches any .pmp-queue-health-refresh-btn, so additional refresh buttons added in future code work without JS changes.
    * Flush Queue Now success path was reworked to programmatically click the adjacent .pmp-queue-health-refresh-btn after a successful flush. Removed the manual "set #pmp-queue-depth textContent to depth_after" hack which broke when the dl became shared markup without inline IDs (two cards with the same id="pmp-queue-depth" was invalid HTML). After flush, the entire dl re-renders server-side with fresh values - depth, last_flush_age, next_scheduled, and any badge state changes.
    * New AJAX handler AnalyticsAjax::handleQueueHealthRefresh requires manage_options + nonce ('pmp_queue_health_refresh'). Lazy-loads the admin settings class for AJAX-only contexts where the admin page renderer hasn't been included yet.
    * No new CSS - reuses .pmp-action-progress / .pmp-action-spinner / .pmp-action-result / .pmp-badge classes already shipped in 8.7.0.

+ Item 21: Tools tab default-open + Analytics Queue button layout polish
    * Tools tab default-open accordion: when analytics tracking is enabled AND the operator's license tier grants access to the Analytics Queue accordion, that accordion opens by default. Otherwise API Diagnostics opens by default (the always-available Free-tier fallback). Rationale: Analytics Queue is the most operationally relevant tool for sites actively collecting analytics data, and putting it in front of operators by default reduces clicks during a workflow centered around the new 8.7.0 pipeline. Implementation reads $trackingEnabled from generalOptions and conditionally applies the active/show classes to either the Analytics Queue accordion OR API Diagnostics, but never both.
    * Analytics Queue accordion (Tools tab): Refresh and Flush Queue Now buttons now sit side-by-side in a single action row instead of stacked. Each button + its spinner + its result text are wrapped in a new .pmp-action-group span (inline-flex, 6px gap) so the two button-progress pairs stay visually distinct even when both are active simultaneously. CSS adds 16px left margin between groups so adjacent button pairs don't crowd each other.
    * Refresh-button JS scoped to .pmp-action-group when present so the Refresh button's spinner and result text only ever update the elements paired with that specific button - even when a second button is in the same row. Falls back to btn.parentNode for the Analytics-tab single-button layout where no group wrapper is needed.
    * Refresh-button JS dl-locator hardened: walks up through .pmp-tools-section, .pmp-admin-panel, and .pmp-admin-card looking for the [data-pmp-queue-health-dl] element. Works whether the dl lives in the same card as the button (Analytics tab) or in a separate card from the action row (Tools tab where the action row sits outside the queue-health card).
    * Analytics tab Queue Health card: Refresh button's action row now has 16px left margin matching the indent of the dl above it and the description copy below, so the Refresh button visually aligns with the rest of the card content instead of butting up against the card edge.

+ Item 22: Last Flush stayed populated after Clear All Data
    * Reported behavior: clicking Clear All Data correctly truncated the events and daily tables and emptied the queue, but the Queue Health card's "Last Flush" field showed a recent age ("X seconds ago") instead of "Never". Operators expected a fully fresh visual state.
    * Root cause: AnalyticsQueue used a single OPTION_LAST_FLUSH wp_options row for two distinct purposes - the operator-facing "Last Flush" UI display AND the staleness-fallback detection in flushIfStale(). flush() updated this single option on every cron tick, including ticks that drained an empty queue, because the staleness fallback needed a "last cron alive" signal. After Clear All Data, the next cron tick (within 60 seconds) ran flush() against an empty queue and bumped LAST_FLUSH to NOW, which the UI read as "X seconds ago".
    * Fix: split the conflated semantics into two separate options. OPTION_LAST_FLUSH now strictly means "last time real rows were committed to the events table" (operator-facing UI). New OPTION_LAST_TICK ('pmp_impression_queue_last_tick') means "last time the cron handler ran, regardless of whether rows drained" (staleness detection). flush() always updates LAST_TICK; only updates LAST_FLUSH when actual rows are inserted. flushIfStale() now reads LAST_TICK for the staleness check, so cron health detection still works on broken-cron hosts. After Clear All Data, both options are deleted, and LAST_FLUSH stays at 0 ("Never") through any number of empty cron drains until real impression data lands.
    * AnalyticsDB::clearAllData() updated to delete OPTION_LAST_TICK alongside OPTION_LAST_FLUSH so the fully-fresh state covers both timestamps.
    * Follow-up to the same split: AnalyticsQueue::getHealthStatus() previously computed is_stale from OPTION_LAST_FLUSH, which would (incorrectly) trip the "Cron may be broken" badge after STALE_THRESHOLD_SECONDS of no traffic on a healthy site. Switched to OPTION_LAST_TICK so the badge reflects cron HEALTH (is the cron tick firing?) rather than data freshness (has any real impression flushed lately?). On a healthy site with no recent traffic, the badge correctly stays hidden while "Last Flush" shows the age of the last real flush. On a broken-cron host, LAST_TICK ages out and the badge fires - the original intent.
    * No change to public-facing dashboard reads or to the impression pipeline. Pure internal refactor of two wp_options keys with clearer single-purpose semantics each.

+ Item 23: Version bump and minified-script regeneration
    * Bumped pet-match-pro.php Version header and Constants::VERSION to 8.7.0.
    * Bumped readme.txt Stable tag to 8.7.0 with a detailed Upgrade Notice covering all four core fixes plus the four defense layers.
    * pet-match-pro-public.min.js needs to be regenerated from the updated source and uploaded alongside the .js file. The minified file is what the plugin enqueues in production. No CSS changes ship in 8.7.0 - admin styles for pmp-help-icon, pmp-admin-card, pmp-warning, and pmp-healthy badges already exist from prior releases. Tools-tab section markup uses existing classes (pmp-tools-section, pmp-tools-action-row, etc.). If after browser testing the new Queue Health dl/dt/dd layout needs typographic refinement, follow up with a thin CSS pass.

+ Verification (manual, per-test-site):
    * Initial page load with no sort active: DevTools Network panel filtered to admin-ajax.php should show ZERO calls during the page-load critical path. As the visitor scrolls, small ping calls appear with 1-15 impressions each, fired via sendBeacon (Type column shows "ping"). Each ping's "Time" column shows the host-stack baseline but no JS or page interaction blocks on it.
    * Initial page load with sort active: one ping for pmp_update_sorted_ids fires after window.load (the 8.6.11.2 deferral still applies), plus impression pings as cards enter the viewport. Critical path remains empty.
    * Filter click: one ping for pmp_update_sorted_ids (sendBeacon, fire-and-forget). One or more pings for impressions as the new visible set scrolls through. No XHR call. No blocking time on either request.
    * WP-Cron flush verification: `wp cron event list` (WP-CLI) shows pmp_flush_impression_queue with a "Next Run" timestamp within 60 seconds. SQL `SELECT COUNT(*) FROM wp_pmp_analytics_events WHERE created_at >= DATE_SUB(NOW(), INTERVAL 2 MINUTE)` count grows once per minute, not on every page load. SQL `SELECT LENGTH(option_value) FROM wp_options WHERE option_name = 'pmp_impression_queue'` grows during the between-flush window, drops to 0 (or row missing) immediately after flush.
    * Free / Junior regression check: tracking_enabled is false on those tiers, so PMPImpressionTracker.init() short-circuits at the first check and no observer is created. No impression POSTs. Filter/sort calls still fire (those are user-feature, not analytics-feature). No regression.
    * Pagehide flush: open a search page in DevTools, scroll to load impressions into the local batch. Switch tabs (or close the tab). Observe a final ping fire on visibilitychange / pagehide. SQL count reflects those final impressions after the next cron tick.
    * Layer 1 staleness fallback: define DISABLE_WP_CRON in wp-config.php with no host-level cron replacement. Generate enough traffic to push the queue past 100 entries. The next enqueue() call after 300s should trigger a synchronous flush. Confirm via tail -f wp-content/debug.log for the "Cron stale by Xs, queue depth N - flushing synchronously" message. Queue option drops to empty, events table grows by the drained count.
    * Layer 2 admin display: visit Analytics tab. Queue Health card shows three rows. On a healthy host, last flush within last 60 seconds, next scheduled within 60 seconds, no warning badges. On a broken-cron host, last flush shows long age with "Cron may be broken" badge.
    * Layer 2b manual actions: Flush Queue Now -> spinner -> "Flushed N rows." Reload analytics tab; queue depth = 0. Run Cron Self-Test -> "Running test..." for ~90 seconds. On healthy host, finishes with "Cron is healthy" badge. On broken host, finishes with "Cron does not appear to be firing" + KB link. Both buttons require manage_options.
    * Layer 4 docs: Analytics tab description copy mentions WP-Cron requirement with a link to the new KB article. Five articles render cleanly. KB_LINKS resolution works for all five new keys.

Version 8.6.11.2 - April 28, 2026 - Hotfix: Skip Redundant Sorted-IDs POST on Initial Page Load

+ Item 0: PMPFilterSort.init() no longer fires pmp_update_sorted_ids on initial page load when no sort is active
    * Reported behavior: cincinnatianimalcare.org home page (4 featured animals) and any PMP search page were firing TWO admin-ajax POSTs on every page load, both ~1.5s on the LiteSpeed/Cloudflare host stack. One was the analytics impressions POST (expected, deferred to after window.load by 8.6.11). The other was pmp_update_sorted_ids carrying every visible animal ID in a form-encoded array (~9KB payload on a 362-animal page), fired synchronously by PMPFilterSort.updateSession() before window.load even on pages where no filter or sort had been applied yet. Two slow calls per page view doubled the perceived AJAX overhead and shipped redundant data the server already knew.
    * Root cause: PMPFilterSort.init() at public/js/pet-match-pro-public.js:880-885 had two branches - if (this.currentSort.field) call applyFiltersAndSort() (which calls updateSession() internally), else call updateSession(this.resultItems) directly. The else branch fired on initial load whenever the page had no client-side sort UI active (which is the common case - home page featured shortcodes, single-method search pages, etc.) and posted the unfiltered ID list to the server in default order. But the server already had that data: the search-render path stores the ID list in $_SESSION synchronously when the page renders. The detail-template prev/next helper at public/templates/includes/class-pet-match-pro-base-detail-template.php:2622-2649 was already coded to use the transient when present and fall back to $_SESSION when absent - it has been graceful-degrading since the feature shipped. The initial-load post was wallpapering over a fallback that was already working correctly.
    * Fix part 1 (skip the redundant post): removed the else branch entirely from PMPFilterSort.init(). Pages without an active sort do nothing on initial render; prev/next navigation continues to work because the detail template's $_SESSION fallback fires.
    * Fix part 2 (defer the active-sort post): added an initialApplyDone flag on PMPFilterSort that defaults to false during init() and flips to true after the initial applyFiltersAndSort() call returns. updateSession() checks the flag - if !initialApplyDone, the $.ajax post is wrapped in pmpDeferToIdle() (the helper added in 8.6.11) so the request waits for window.load + requestIdleCallback before firing. After init completes, subsequent calls (user clicked a filter/sort UI control) post synchronously as before because the user may immediately click into a detail page and the transient must reflect the new order before that detail page renders.
    * Trade-off: on pages with sort actively applied at page render time (URL parameter, persisted state from a previous session, default sort dropdown values), the 1.5s POST still happens but is now deferred out of the critical path - same pattern 8.6.11 used for impression tracking. Pages with no sort at render time pay zero AJAX cost from this code path. User-triggered filter/sort changes still cost 1.5s synchronously - that is unchanged and is the right behavior because of the immediate-detail-click race condition described above. The proper architectural fix for both that and the impression-tracking call is the queue-and-cron design planned for 8.6.12.
    * Verification: load any PMP page on a LiteSpeed/Cloudflare host. DevTools Network panel filtered to admin-ajax.php should show ONE call (pmp_track_impressions, ping type, fired after window.load) on pages without sort. Pages with sort show two calls but both fire after window.load. Click a filter button - one synchronous pmp_update_sorted_ids call fires. Click a card immediately after - prev/next on the detail page walks the new sorted order correctly.

+ Item 1: Version bump and minified script regeneration
    * Bumped pet-match-pro.php Version header and Constants::VERSION to 8.6.11.2.
    * Bumped readme.txt Stable tag to 8.6.11.2 with an Upgrade Notice describing the perf fix.
    * pet-match-pro-public.min.js needs to be regenerated from the updated source and uploaded alongside the .js file. The minified file is what the plugin enqueues in production (public/class-pet-match-pro-public.php:577).
    * No PHP changes ship in this hotfix - the entire fix is JS-only. PHP files are unchanged from 8.6.11.1 and do not need to be re-uploaded.

Version 8.6.11.1 - April 28, 2026 - Hotfix: LiteSpeed Analytics Writes + Schema Self-Heal

+ Item 0: LiteSpeed/LSAPI hosts were silently dropping every analytics write
    * Reported behavior: cincinnatianimalcare.org running PMP 8.6.11 on LiteSpeed + LSCache + Cloudflare collected ZERO analytics rows over 36 hours despite the AJAX endpoint returning {"success":true,"data":{"queued":N}} on every page load. Both wp_pmp_analytics_events and wp_pmp_analytics_daily were verified to exist with 0 rows. Same symptom reproduced on a second LiteSpeed test site that was on 8.6.10 (analytics never collected there either, but for the schema reason - see Item 1).
    * Root cause: 8.6.11 added an early-connection-close optimization to AnalyticsAjax::handleTrackImpressions() and handleTrackEvent() that calls fastcgi_finish_request() to release the PHP worker before running the bulk INSERT. The function exists under LiteSpeed's LSAPI module, so the existing function_exists() guard returned true. But on LSAPI, calling fastcgi_finish_request() triggers worker teardown earlier than PHP-FPM does - by the time $wpdb->query() runs against the events table, the DB connection is in an indeterminate state and query() returns false silently. No exception, no error_log entry, no row inserted. The client sees a 200 response from the pre-write JSON and assumes the impression was recorded. PHP-FPM hosts (the development environment and most non-LiteSpeed shared hosts) handled the pattern correctly, which is why this slipped through 8.6.11 testing.
    * Fix: added a private canUseEarlyClose() helper on AnalyticsAjax that gates the early-close path on three checks - function_exists('fastcgi_finish_request'), PHP_SAPI !== 'litespeed', and SERVER_SOFTWARE not containing 'litespeed' (case-insensitive). When any check rules out early-close, the handler falls through to a synchronous write-then-respond path: instantiate AnalyticsTracker, run the bulk INSERT, then call wp_send_json_success() with the actual write result. The connection stays open for the full request lifecycle but $wpdb->query() commits cleanly because the worker isn't being torn down underneath it.
    * Both handlers (handleTrackImpressions and handleTrackEvent) now branch at the canUseEarlyClose() check: FPM keeps the 8.6.11 sendJsonAndClose() optimization, LiteSpeed/mod_php takes the synchronous path. The existing sendJsonAndClose() helper is unchanged but no longer called from LiteSpeed sites.
    * Trade-off accepted on LiteSpeed hosts: the AJAX-time win from the early-close is lost (the request is back to holding the worker for the bulk INSERT duration plus whatever LSCache adds on top). Bulk-insert and JS-defer wins from 8.6.11 are unaffected. The proper fix for LiteSpeed perf is the queue+cron architecture planned for 8.6.12 - moving the writes off the user request entirely so host stack overhead stops mattering.
    * Detection notes: PHP_SAPI returns 'litespeed' on LSAPI builds, but some hosts run LiteSpeed Web Server with a CGI/FPM bridge where SAPI reports 'cgi-fcgi' or 'fpm-fcgi' instead - hence the SERVER_SOFTWARE fallback check. SERVER_SOFTWARE on LiteSpeed contains the literal string 'LiteSpeed' (case varies); strtolower + str_contains matches reliably across configurations seen in the wild.

+ Item 1: Schema self-heal so upgrade-in-place no longer leaves customer sites missing analytics tables
    * Reported behavior: even after the LSAPI fix above, two test sites still showed "NO TABLES MATCHING wp_pmp_*" via a database table check. Activation hook had never fired on those sites. Both had upgraded through normal channels (FTP overwrite or wp-admin auto-update) which do not trigger register_activation_hook - that hook only fires on a fresh install or an explicit deactivate/reactivate cycle. Customer-facing impact: the analytics feature was effectively non-functional on every site that received PMP via in-place upgrade since the analytics tables were introduced.
    * Fix: added a plugins_loaded hook in pet-match-pro.php (priority 5, before most plugin code runs) that reads a new pmp_analytics_schema_version option and compares it to Constants::ANALYTICS_SCHEMA_VERSION ('1.0' for this release). When the stored value is missing or behind, the hook requires the AnalyticsDB class file directly (it isn't autoloaded that early in the request) and calls AnalyticsDB::getInstance()->createTables(). createTables() uses dbDelta() which is idempotent - safe to run on healthy sites with the tables already present. After successful run, update_option() persists the current version so subsequent requests skip the check via the cached get_option() lookup.
    * Cost on healthy sites: one cached get_option() per request after WP boots its options cache, then a single string-equality comparison. No DB round-trip after the first hit.
    * Future schema changes only require bumping Constants::ANALYTICS_SCHEMA_VERSION; the next request on each customer site auto-migrates. The version constant lives next to Constants::VERSION at the top of the Constants class so it is obvious during release prep that schema changes need a bump.
    * The new constant Constants::ANALYTICS_SCHEMA_OPTION holds the option key name ('pmp_analytics_schema_version') so all references go through one source of truth.

+ Item 2: AF detail-overlay PHP warning eliminated ("Array to string conversion")
    * Reported behavior: cincinnatianimalcare.org wp-content/debug.log filled with repeating "PHP Warning: Array to string conversion in includes/af/class-pet-match-pro-af-detail-functions.php on line 561" entries (multiple per minute during normal browsing).
    * Root cause: AnimalsFirst\AfDetailFunctions::getOverlayIcons() at line 561 called (string) ($resultArray[AnimalsFirstFields::SPECIES] ?? '') as the second argument to getTooltipName(). The AF API occasionally returns species as a nested object ({name: "Dog", id: 1}) rather than a flat string - PHP 8.1+ emits the warning when casting an array to a string. The cast still produces a non-empty string ("Array") so the page renders, but the log noise was real.
    * Fix: capture the raw value into $speciesRaw, detect array shape, prefer the 'name' subkey when present (which is the documented AF nested form), then cast. Behavior unchanged when the API returns a scalar; no warning when it returns an object. Three lines added.
    * Note: the same shape issue exists at includes/af/class-pet-match-pro-af-api.php:1966 (strtolower($details[AnimalsFirstFields::SPECIES])). Not patched in this hotfix because no warning has been observed from that path - left for a future audit pass that can address all AF nested-object handling consistently.

+ Item 3: Version bump
    * Bumped pet-match-pro.php Version header and Constants::VERSION to 8.6.11.1.
    * Bumped readme.txt Stable tag to 8.6.11.1 with a detailed Upgrade Notice covering all three fixes.
    * No JS changes; pet-match-pro-public.min.js is unchanged from 8.6.11.

+ Verification (manual, per-test-site):
    * Upload the four changed files. Load the home page once. Tools > PMP Table Check (or any DB inspector) should show pmp_analytics_events and pmp_analytics_daily tables exist (Item 1 ran on plugins_loaded). Impression count should tick up to 1+ rows within seconds (Item 0 fix - the synchronous write path commits cleanly).
    * On a non-LiteSpeed site (any PHP-FPM host - typical Nginx + PHP-FPM or Apache + PHP-FPM), confirm the early-close path still runs. Look for the debug log message "Impressions tracked (deferred)" if WP_DEBUG_LOG is enabled. LiteSpeed sites will log "Impressions tracked (sync)" instead.
    * SQL: SELECT COUNT(*) FROM wp_pmp_analytics_events WHERE created_at >= DATE_SUB(NOW(), INTERVAL 5 MINUTE) - count grows on each page load.
    * Schema option: SELECT option_value FROM wp_options WHERE option_name = 'pmp_analytics_schema_version' returns '1.0' on any healthy site after first page load post-upgrade.

Version 8.6.11 - April 28, 2026 - Public Performance: Defer + Bulk-Insert + Early-Close Analytics + Currency Symbol Fix

+ Item 0: Analytics impression tracking deferred out of the page-load critical path
    * Reported behavior: GTmetrix on the live AnimalsFirst site flagged POST /wp-admin/admin-ajax.php?action=pmp_track_impressions taking ~1.7s and delaying page load. The fetch fires from PMPAnalytics.trackImpressions() on $(document).ready() in public/js/pet-match-pro-public.js, before window.load and before LCP, so any server time on this endpoint pushes the load event back.
    * Added a small pmpDeferToIdle(fn) helper at the top of the analytics IIFE that queues fn for window.load + requestIdleCallback (with a 2s timeout) and falls back to setTimeout(1500) on browsers without rIC. The two trackImpressions() call sites that fire on initial load - the pmp:pagination:ready handler and the no-pagination fallback - now go through this helper. The pmp:pagination:change handler stays immediate because user-initiated pager clicks are not on the critical path and deferring would risk losing impressions if the user navigated away quickly.
    * Bind-time hooks for clicks/shares/posters/detail-view stay on $(document).ready - those listeners must be attached before the user can interact, only the initial impression batch is deferred.

+ Item 1: trackImpressions() now bulk-inserts in one query instead of N round-trips
    * AnalyticsTracker::trackImpressions() at includes/analytics/class-pet-match-pro-analytics-tracker.php:254 looped over impressions and called $this->db->recordEvent() per row, each one a single $wpdb->insert() round-trip with the full WordPress filter chain. On a search page with ~20 visible cards this dominated the 1.7s response time at roughly 60ms per insert.
    * Added AnalyticsDb::recordEventsBulk(array $events): int at includes/analytics/class-pet-match-pro-analytics-db.php that builds one prepared multi-row INSERT for the whole batch, applies the same defaults and sanitization as recordEvent(), and runs it via $wpdb->prepare() + $wpdb->query() once. trackImpressions() now collects all rows into a $records array and calls recordEventsBulk($records) at the end.
    * Per-row placeholder for the position column is built dynamically: literal NULL when the input is null, %d otherwise. This preserves the NULL/0 distinction that getPositionImpact() relies on (its filter is `position IS NOT NULL AND position > 0`); a blanket cast to int would have rewritten every detail-view impression to position 0 and corrupted the position-impact analytics.
    * sanitize_text_field() casts to (string) protect against null reaching string-typed placeholders, which $wpdb->prepare rejects.

+ Item 2: handleTrackImpressions() and handleTrackEvent() send the JSON response before running the writes
    * Even with Items 0+1 the AJAX request still held a PHP-FPM/LSAPI worker for the duration of the inserts, and the request stayed visible in the browser network panel as in-flight until the writes finished. Closing the connection early frees the worker and removes the trailing network activity.
    * Added a private sendJsonAndClose(array $payload): void helper on AnalyticsAjax that emits the standard {"success":true,"data":...} envelope, calls fastcgi_finish_request() when available (PHP-FPM and LiteSpeed LSAPI both support it), and falls back to wp_send_json_success() on mod_php hosts so the behavior remains correct everywhere. Headers are guarded with !headers_sent() and include Content-Length + Connection: close so the client knows the body is complete.
    * handleTrackImpressions() now: validates input as before -> calls sendJsonAndClose(['queued' => count]) -> runs (new AnalyticsTracker())->trackImpressions(...) inside try/catch so any post-response failure logs via $this->logError() instead of leaking as an HTTP error (the response has already been sent). exit; closes the lifecycle.
    * Same pattern applied to handleTrackEvent() for clicks/shares/poster prints. The brief originally scoped this to handleTrackImpressions() only, but the helper is reusable and click events benefit equally from not blocking the worker.
    * Validation errors (tracking disabled, no data) keep the existing wp_send_json_error() slow-path - they are rare, small, and the early-close indirection is not worth it for them.

+ Item 3: Public script enqueued with strategy=defer so it does not block parse
    * public/class-pet-match-pro-public.php:573 enqueues pet-match-pro-public.min.js in the footer, which already keeps it out of the head, but inline <script> tags from themes or other plugins can still cause a parser stall while the browser fetches it. WP 6.3+ supports wp_script_add_data($handle, 'strategy', 'defer') which appends defer to the <script> tag and lets the browser continue parsing.
    * Added wp_script_add_data($this->pluginName, 'strategy', 'defer') immediately after the wp_enqueue_script() call, guarded by function_exists() so older WP cores ignore it cleanly. Plugin's Requires at least is 6.5 so this is effectively always active in supported environments.

+ Item 4: loading="lazy" + decoding="async" added to remaining card images that were missing them
    * Audited every <img> tag in public/templates/{pp,af,rg}. The AF/RG search templates and most PP search templates already emit loading="lazy" on card images, but four PP default-search templates and one icon-photo path in pp/universal-search-default.php were missing it: pp/lost-search-default.php (both photo and no-photo branches), pp/found-search-default.php (both branches), pp/featured-search-default.php (both branches), pp/universal-search-default.php (main card image both branches + icon-photo branch). Patched all eight emit sites to include loading="lazy" decoding="async".
    * The .pmp-search-result-image CSS rule already declares aspect-ratio: 1, so the browser reserves card space before the image loads - no CLS impact from the lazy attribute. No visual or layout change.

+ Item 5: Currency-symbol display bugs on detail, search, and poster templates
    * Reported behavior: discovered while verifying KB doc article 03 (Custom Currency & Date Formats). Selecting any non-USD currency in admin produced incorrect output for visitors. Three different breakages, same family of root causes.
    * Bug A (detail): public/templates/includes/class-pet-match-pro-base-detail-template.php had inline $symbolMap with 'GBP' => '&#65533;' and 'EUR' => '&#65533;'. &#65533; is U+FFFD (the Unicode replacement character) which the browser shows as a black diamond with a question mark. Original source had &pound; / &euro; HTML entities; an editor or FTP round-trip mojibake'd them.
    * Bug B (search): public/templates/includes/class-pet-match-pro-search-template-trait.php had the same inline map with 'GBP' => '-' and 'EUR' => '-'. Search-result cards showed prices like "-150.00" for GBP/EUR users. Different breakage from Bug A, same root cause (different past edit replaced the corrupted glyphs with a placeholder hyphen instead of restoring the entity).
    * Bug C (poster): public/templates/includes/class-pet-match-pro-poster-template-trait.php had a third inline map using LOWERCASE keys ('usd', 'gbp', 'eur', ...) but admin stores UPPERCASE ISO codes (USD, GBP, EUR, ...). Every lookup missed and silently fell through to the '$' default - all non-USD selections rendered as '$' on poster templates. The map also contained currencies (JPY, NZD, BRL, INR) that are not in the admin dropdown - dead code - and the CAD/AUD symbols ('CA$' / 'AU$') diverged from the detail/search templates' 'C$' / 'A$' even after a case fix would land.
    * Architectural fix: consolidated all three duplicate maps into a single source of truth. Added Constants::CURRENCY_SYMBOL_MAP (UPPERCASE ISO keys, HTML-entity values - '&pound;', '&euro;', etc. - so the source file stays ASCII-only and the rendered output is HTML-safe) and Constants::currencySymbolFor(?string $code): string in pet-match-pro.php, immediately after Constants::getOptionAsArray(). The helper normalizes input via strtoupper() so any legacy lowercase DB values from before this fix still resolve, and falls back to '$' on null, empty, or unknown input.
    * Replaced all three inline-map blocks with a single Constants::currencySymbolFor() call. Net delete of 32 lines of duplicate config across three files. All three files already had `use PetMatchPro\Constants;` at the top so no new imports needed.
    * Symbol set now matches admin dropdown exactly: USD => $, GBP => &pound;, EUR => &euro;, CAD => C$, AUD => A$. JPY/NZD/BRL/INR dropped from the poster map (they were not selectable through admin anyway). If a future feature adds them, expand Constants::CURRENCY_SYMBOL_MAP and the admin dropdown together.
    * Mojibake audit: ran LC_ALL=C grep for bytes 0x80-0xff across the four touched files - all remaining hits are arrow glyphs in pre-existing comments (e.g. "title="" -> suppress entirely"), not currency-related, not in any rendered code path. Plugin-wide grep for '&#65533;' shows ~25 hits, also exclusively in comments where arrows were corrupted in some unrelated past pipeline event. Out of scope for this fix; flagged for a future comment-cleanup pass.

+ Item 6: Version bump and minified script regenerated
    * Bumped pet-match-pro.php Version header and Constants::VERSION to 8.6.11.
    * Bumped readme.txt Stable tag to 8.6.11 with an Upgrade Notice summarizing the perf fixes and currency fix.
    * Regenerated public/js/pet-match-pro-public.min.js to match the updated source. Production loads the .min.js variant per the wp_enqueue_script() path in public/class-pet-match-pro-public.php so source-only edits would not have shipped.

+ Verification checklist (manual, per-test-site):
    * DevTools Network tab: pmp_track_impressions POST fires AFTER the load event. Response body is {"success":true,"data":{"queued":N}}, status 200.
    * SQL: SELECT COUNT(*), event_type, MAX(created_at) FROM wp_pmp_analytics_events WHERE event_type='impression' GROUP BY event_type - count grows on each page load.
    * SQL: ratio of position IS NULL to position IS NOT NULL impression rows is unchanged from pre-release (sanity check that the per-row NULL placeholder is wired correctly).
    * Pagination next/prev still tracks immediately (no defer on pmp:pagination:change).
    * Free / Junior tier: AJAX still 403s before any insert - the early-close path only fires after the license check passes.
    * Currency: cycle Settings > General > Currency Symbol through USD, GBP, EUR, CAD, AUD. Confirm correct symbol (&pound; for GBP, &euro; for EUR, $ / C$ / A$ for the others) on a search-result card, a detail page, and a poster page. View-source to confirm no &#65533; and no raw 0x80+ bytes in the price string.
    * Currency lowercase fallback: manually set wp_options row pet-match-pro-general to a value with currency_symbol='gbp' (lowercase) - page should still render &pound; thanks to the strtoupper() normalization in currencySymbolFor().

Version 8.6.10 - April 27, 2026 - Tools Polish + [pmp-option] Fix + PP Species ID + Pagination Refresh + KB Corrections

+ Item 0: renderToolsTab() $levelLabels map now exhaustive across all three license tiers
    * Map at admin/class-pet-match-pro-admin-settings.php:12850 only defined entries for PREFERRED_LEVEL and PREMIUM_LEVEL. The $getRequiredLabel closure that builds the locked-accordion badge falls back to __(PLAN_JUNIOR, ...) when a level isn't in the map.
    * Today this is harmless because every Tools sub-accordion is gated at PREMIUM, PREFERRED, or FREE (and free accordions skip the gate entirely). But if a future edit moves a currently-free accordion to gated, or adds a new free-with-locked-state accordion, the badge would silently say "Junior" even when no upgrade is actually required.
    * Added Constants::FREE_LEVEL => __(Constants::PLAN_BASIC, ...) so the map is symmetric with the three-tier license model. Pure defensive change - no current behavior is affected.

+ Item 1: Stale "(Preferred)" comment on Cache Management accordion corrected to "(Junior)"
    * HTML comment at line 12874 read `<!-- Cache Management Accordion (Preferred) -->` but the level file has `cache => PREMIUM_LEVEL` (Junior tier). Comment vs. reality drift, no functional impact.
    * Updated comment to "(Junior)" so future maintainers don't get misled while reading the markup.

+ Item 2: [pmp-option] shortcode level-key lookup fixed - all valid types now render values
    * Reported behavior: every [pmp-option] call returned the upgrade notice on every site, even on Preferred. Confirmed via KB review of article 09 (pmp-option-reference) - the documented examples did not work in practice.
    * Root cause: PublicFacing::getOptionValue() at public/class-pet-match-pro-public.php:1042 built the level-lookup key as 'level_' . $type . '_options' (e.g., 'level_general_options'), but admin/partials/pmp-option-levels.php stores keys without the '_options' suffix (e.g., 'level_general'). The keys serve double-duty as tab-visibility checks elsewhere in admin code (admin-settings.php:1087 uses LevelPrefix::LEVEL . TAB_TOOLS, no suffix), so the level file's key shape is correct - the consumer was wrong. The !isset($pmpOptionLevels[$levelKey]) branch was always TRUE, so every type fell through to the upgrade notice regardless of license tier. The 8.6.3 fix patched the wp_option key but missed this companion lookup.
    * Same bug existed in admin/class-pet-match-pro-shortcode-builder-resolver.php:787 (resolveOptionTypes for the Shortcode Builder's [pmp-option] sub-tab). Both sites now use LevelPrefix::LEVEL . $type with no suffix.

+ Item 3: 'fonts' option type added to pmp-option-levels.php
    * Settings::OPTION_FONTS = 'fonts' is referenced by the Shortcode Builder catalog (admin/class-pet-match-pro-shortcode-builder-resolver.php:779) and is a valid wp_option group (pet-match-pro-fonts), but pmp-option-levels.php had no entry for it. Even after the Item 2 lookup fix, [pmp-option type="fonts" value="..."] would have returned the upgrade notice because the level lookup would still miss.
    * Added LevelPrefix::LEVEL . Settings::OPTION_FONTS => Constants::PREMIUM_LEVEL to the level file. Junior gating mirrors the Fonts tab being a Junior+ admin feature.
    * Effective valid [pmp-option] types after this release: general (FREE), filter (FREE), contact (FREE), label (FREE), color (FREE), fonts (PREMIUM). Note: the Shortcode Builder displays 'filter' under the friendlier label 'Partner' since pet-match-pro-filter holds partner-specific configuration; both refer to the same wp_option. The 'filters-customize' option group is intentionally excluded (its values are nested arrays, not scalar strings - [pmp-option] cannot render anything useful from them).

+ Item 4: 'partner' alias added for the 'filter' option type so [pmp-option] matches the Builder UI label
    * The Shortcode Builder displays the partner-specific filter settings group under the friendlier label "Partner" (because pet-match-pro-filter holds API source, status overrides, and other partner-specific configuration). But the Builder previously emitted type="filter" because that was the underlying type key, creating a mismatch between what users saw in the dropdown and what they had to type.
    * PublicFacing::getOptionValue() now normalizes 'partner' to Settings::OPTION_FILTER at the top of the function, so [pmp-option type="partner" value="..."] resolves the same as [pmp-option type="filter" value="..."]. Both forms work; backward-compatible with any pages that already use type="filter".
    * Shortcode Builder catalog (admin/class-pet-match-pro-shortcode-builder-resolver.php resolveOptionTypes() + resolveOptionKeys()) now emits 'partner' as the type key. The level-file lookup in resolveOptionTypes() and the wp_option lookup in resolveOptionKeys() both normalize 'partner' back to OPTION_FILTER internally, so no new entry in pmp-option-levels.php is needed.
    * KB article 09 (pmp-option-reference) updated to document type="partner" as the canonical form, removed the incorrect 'filters-customize' row (the Builder excludes it intentionally - nested array values, not scalar), and added the 'fonts' row.

+ Item 5: PetPoint Shortcode Builder now emits speciesid="N" (PP's actual API parameter name) instead of species="N"
    * Reported behavior: the Builder dropdown for Species on a PP site emitted species="2" when the user selected "Cat", but selecting that shortcode on a page produced no species filter - all species rendered. AllApi::requestedSpecies() looked up "2" as a species name in the lowercased name-to-ID map ([dog=>1, cat=>2]), missed, and returned an empty filter.
    * Root cause: PetPoint's webservices API uses speciesID (integer) as the canonical parameter name. The plugin was being friendly by accepting species="Cat" and translating to speciesid=2 via the name lookup, but the Builder dropdown's option values are the IDs from the field-values file (1=Dog, 2=Cat) - so the Builder emitted IDs that processSpecies tried to interpret as names.
    * Fix part 1 (admin-settings.php renderShortcodeBuilderTab): when partnerDir is PetPoint, the catalog's species entry key is renamed from Shortcodes::SPECIES ('species') to PetPointFields::ID_SPECIES ('speciesid') before the catalog is passed to the Builder JS. The Builder now emits [pmp-search type="adopt" speciesid="2"] for PP. AF/RG continue to emit species="Cat".
    * Fix part 2 (pp-api.php processSpecies): added an early branch that recognizes items[ID_SPECIES] as a direct ID input. When present, the IDs are mapped to names ONLY for license validation (validateSpeciesLicense expects names against Constants::FREE_SPECIES), then the original IDs are passed through to the PP API. The legacy species="Cat" name path is preserved as the second branch for backward compatibility - any hand-typed shortcode using species= keeps working.
    * Hand-typed [pmp-search speciesid="2"] now also works on PP; previously it was silently ignored. species="2" (numeric value passed via the name parameter) intentionally still does not work - speciesid is the documented direct-ID form.

+ Item 6: KB article 07 ([pmp-search] exclude parameter) corrected to match actual code behavior
    * The published KB documented exclude= as accepting a comma-separated list of values to exclude by species, status, or arbitrary field (e.g., exclude="Hamster,Guinea Pig" or exclude="On Hold"). None of those work today - the actual implementation in every search template is a single animal-ID match: if (animalId === excludeId) skip. exclude= is used internally by similar-animals grids to omit the current animal from the related-animals list below the detail view.
    * Article 07 (excluding-values) rewritten to accurately describe single-ID exclusion, with a "What It Is Not" section disclaiming the previously documented species/status/multi-value behavior. Added an alternatives table pointing users to species= for species filtering, the Filter Values customization tab for site-wide value/status hiding, and the Shortcode Builder's speciesid="N" form for PP.
    * Article 05 (common-shortcode-recipes) section "Search with Exclusions" retitled "Hide a Specific Animal" with example exclude="60100465" instead of the misleading exclude="Hamster,Rabbit,Guinea Pig".
    * Shortcode Builder catalog (admin/partials/pmp-shortcode-builder-catalog.php): the EXCLUDE entry's label changed from "Exclude Values" to "Exclude Animal ID" and the help text rewritten to spell out single-ID semantics and redirect to species= / Filter Values for the use cases users were attempting.
    * Article 07 also adds a PetPoint multi-species note documenting that species="Dog,Cat" causes PP to return all species (PP API only accepts a single integer for speciesID; comma-separated input is treated as 0=All). AF and RG honor multi-species filters.

+ Item 7: PetPoint sort/filter field aliases - both card-side mirror data-attrs AND active-state reverse-normalization
    * Reported behavior: admin set the default Sort Field to Breed on a PP site. Two distinct symptoms surfaced:
      (1) Visual: the Breed sort button did not render with the active class on initial page load, so users had no indicator that Breed was the active sort.
      (2) Behavior: even when the Breed button was active (after explicit shortcode override or click), clicking it did not reorder the cards client-side. The animals stayed in API order regardless of asc/desc toggle.
    * Root cause - two parallel data sources for "Breed" that did not align:
      - Sort BUTTON keys come from the orderby filter values file (includes/pp/partials/pmp-field-values.php) where the entry is literally 'Breed' => 'Breed'. After getConfiguredSortFields() lowercases keys, the breed button's key is 'breed' and its data-sort attr is 'breed'.
      - Admin Sort Field DROPDOWN options come from pp-field-values-adopt.php where the entry is LabelPrefix::SEARCH_SORT . PetPointFields::BREED . '_adopt' => 'Breed'. PetPointFields::BREED = 'primarybreed', so the dropdown's option value is 'primarybreed'. When admin saves "Breed", $generalOptions['order_by_adopt'] = 'primarybreed'.
      - Card data-attributes come from iterating $resultArray keys. PP's search XML returns the breed value under the 'primarybreed' element, so cards have data-primarybreed but no data-breed.
    * Net effect: the user-facing name ('breed') and the API field name ('primarybreed') exist in three independent places without translation, breaking both the active-state comparison ('primarybreed' !== 'breed') and the client-side sort (card.dataset.breed = undefined).
    * Same pattern affects PP Name (sort/button = 'name', PetPointFields::NAME = 'name', PetPointFields::NAME_ANIMAL = 'animalname' - some PP responses use 'name', others 'animalname') and PP ID (PetPointFields::ID = 'id' vs ID_ANIMAL = 'animalid'). The PP search code base already has 7+ defensive '$resultArray[\'name\'] ?? $resultArray[NAME_ANIMAL] ?? $resultArray[NAME]' lookups confirming the API field key can vary by method.
    * Fix - one alias map drives two normalizations:
      - Added private const SORT_FILTER_FIELD_ALIASES = [Constants::PETPOINT => ['breed' => 'primarybreed', 'name' => 'animalname', 'id' => 'animalid']] to SearchTemplateTrait. Maps user-facing sort/filter name to the API field name.
      - Card-side mirror: new buildSortFilterAliasMirrorAttrs() helper, called from buildMirrorDataAttributes() (every search template already invokes that). For each alias entry, when the API source attr is present on the card, also emit data-{aliasName}="{value}". So PP cards now get data-breed alongside data-primarybreed, data-name alongside data-animalname, data-id alongside data-animalid. PMPFilterSort.sortItems() reads card.dataset.breed and finds it -> sort works.
      - Active-state reverse-normalization: in renderSortButtons(), after the date-collapse step, $normalizedCurrentSort is reverse-mapped through the alias table when the resolved current sort matches an alias TARGET (the API field name). Example: $currentSort='primarybreed' (admin saved value) -> alias lookup finds breed=>primarybreed -> $normalizedCurrentSort overwritten to 'breed' -> matches the breed button key -> isActive=true -> active class and asc/desc icon render.
      - AF and RG have no aliases today (their sort/filter names match their API field names) so no behavior change for those partners.
    * Adding new partner-specific mismatches as they surface is one entry in the SORT_FILTER_FIELD_ALIASES map - both the card mirror and the active-state comparison pick it up automatically. No JS or template work.

+ Item 8: Pagination now refreshes after client-side filter/sort
    * Reported behavior: with [pmp-search ... pagination="enable"], applying a filter dropdown (species, sex, age group, etc.) reduced the visible cards but pagination still showed page links sized to the original unfiltered count. Users had to click through several pages with zero visible cards before reaching the matching results.
    * Root cause: PMPPagination at public/js/pet-match-pro-public.js captured this.items from the DOM at init() and computed this.totalPages once. PMPFilterSort.applyFiltersAndSort() / updateDisplay() add the .pmp-filter-hidden class to non-matching items and trigger pmp:filtersort:updated on the document, but the pagination class never listened for that event - it kept its original item count and index-based showPage() math. A page that landed on indices 30-44 of the original 100-item set could end up with all 15 items hidden by the filter, rendering blank.
    * Fix: added PMPPagination.refresh() that re-queries the items container, drops .pmp-filter-hidden items from this.items, recalculates totalPages, resets currentPage to 1, and re-renders. PMPPagination.init() now binds a $(document).on('pmp:filtersort:updated', () => this.refresh()) listener so any filter/sort change immediately reshapes pagination. When a filter eliminates all items, the pager is hidden via .pmp-pager-hidden so the no-results message has a clean slate.
    * Both pet-match-pro-public.js and pet-match-pro-public.min.js updated. No template changes needed.

+ Item 9: Version bump to 8.6.10

Version 8.6.9 - April 27, 2026 - overlay_filter Shortcode Param Resolution Fix

+ Item 0: overlay_filter shortcode param now resolves on AF universal-search-filter-widget (and any template whose searchDetails uses the 'shortcodeItems' wrapper)
    * Reported behavior: [pmp-search type="adopt" overlay="enable" overlay_filter="altered"] on the AF universal-search-filter-widget template ignored the filter - all configured overlay icons rendered (up to max), not just altered.
    * Root cause: SearchTemplateTrait::buildOverlays() at line 440 read $this->searchDetails[Shortcodes::OVERLAY_FILTER] from the root level only. But the same trait's getMaxOverlays() at line 319 already uses the dual lookup pattern: `$shortcodeItems = $this->searchDetails['shortcodeItems'] ?? $this->searchDetails;`. AF templates pass searchDetails with shortcode params nested under 'shortcodeItems' (so $details['shortcodeItems']['overlay_filter'] = 'altered'). getMaxOverlays/Position found their params, buildOverlays missed overlay_filter - silently dropped it, treated $fieldFilter as empty, and getOverlayIcons() rendered everything.
    * Fix: buildOverlays() now uses the same dual lookup - check 'shortcodeItems' sub-array first, fall back to root. Single 5-line change in public/templates/includes/class-pet-match-pro-search-template-trait.php. The downstream {pp,af,rg}-detail-functions getOverlayIcons() implementations were already honoring $fieldFilter correctly (in_array check on $iconConfig['field']) - the bug was purely in how the trait sourced the param.
    * Affects all 18 search templates that use the trait (no template-level changes needed).

+ Item 1: Instructions tab description for overlay_filter rewritten across all six instruction files
    * Old text was ambiguous about whether the listed fields were a whitelist (display these) or a blacklist (hide these): "Enclose the overlay icon field names separated by commas in double quotes for type adopt searches ONLY." User feedback confirmed this read as "remove these icons" rather than "show only these icons."
    * New text spells out the whitelist semantics: "List the field names whose overlay icons you wish to display, separated by commas and enclosed in double quotes. Only icons for the listed fields will appear; all others are hidden. Applies to type adopt {searches|details} ONLY."
    * Applied to admin/partials/{af,pp,rg}/pmp-instructions-search-params.php and admin/partials/{af,pp,rg}/pmp-instructions-details-params.php (six files).
    * Bonus: AF and RG details-params files said "type adopt searches ONLY" (copy-paste typo from the search files); corrected to "details ONLY" in the same edit. PP details-params already said details correctly.

+ Item 2: Version bump to 8.6.9

Version 8.6.8 - April 27, 2026 - Filter Group Mirror Attrs, Robots Meta Method Coverage, Return-to-Search Detector

+ Item 0: AnimalsFirst site/location virtual-value filtering now matches results client-side
    * Reported behavior: AF site filter dropdown ("primary=site, fallback=location") rendered "Foster Home" as an option (the resolved label for an actual location='Foster' value), but selecting it returned zero cards. The card had data-location="foster home" because the result row's location field carried the value, but the dropdown carried data-filter="site" - so the JS filter at applyFiltersAndSort() looked up card.dataset.site (undefined) and nothing matched.
    * Root cause: the per-card data-attribute emission loop in each search template only wrote data-{field} for keys actually present on the result row. When a filter group declares site as primary with location as fallback, the resolver correctly produced "Foster Home" for both the dropdown option value AND the card's data-location attr - but the primary-field name (site) never made it onto the card because the row didn't have a site value to iterate over.
    * Fix: added SearchTemplateTrait::buildMirrorDataAttributes($resultArray, $emittedAttrKeys). Iterates getFilterGroupConfigs(); for any FieldFilterConfig with both fieldKey (primary) and fallbackField set, when the primary attr was NOT emitted by the main loop but the fallback attr WAS, it calls resolveFilteredFieldValue($resultArray, $config->fieldKey) and emits data-{primary}="{resolvedLowercased}". Same resolver the dropdown uses, so option values and card attrs always match.
    * Wiring: each card-builder loop now tracks emitted normalized keys in $emittedAttrKeys and calls $this->buildMirrorDataAttributes($resultArray, $emittedAttrKeys ?? []) immediately after the foreach. Applied to all 18 partner search templates: af/{universal-search-default, universal-search-filter-widget, universal-search-structured, featured-search-default, featured-search-compact, featured-search-carousel}, pp/{universal-search-default, universal-search-structured, adopt-search-default, lost-search-default, found-search-default, featured-search-default, featured-search-compact, featured-search-carousel}, rg/{adopt-search-default, adopt-search-structured, adopt-search-compact, adopt-search-carousel}.
    * Helper is no-op for partners/configs without primary/fallback filter groups - PP and RG cards are unchanged in practice today, but the wiring is in place if filter-group fallback is ever configured for them.

+ Item 1: Robots Meta admin section gains Preferred and List support
    * Analytics & SEO tab > Robots Meta only exposed Adopt / Lost / Found noindex toggles. Preferred (AF-only) and List (PP-only) had no checkboxes - admins running those method types had no way to noindex those detail pages.
    * Added Settings::SEO_ROBOTS_NOINDEX_PREFERRED and SEO_ROBOTS_NOINDEX_LIST constants. Wired through admin/class-pet-match-pro-admin-settings.php (vars + capability flags + checkbox markup + AJAX POST + save handler) and includes/class-pet-match-pro-seo.php (isNoindexMethod() match arm extended to dispatch list/preferred to their setting keys).
    * Checkbox labels now use a "Noindex %s Pages" sprintf template fed by per-method labels - Preferred reads the admin-configured label from $generalOptions[Settings::METHOD_TYPE_PREFERRED . '_methodtype_label'] (same source the AF options class uses, with ucfirst fallback), so the label matches whatever the admin renamed the method to. Adopt/Lost/Found/List use ucfirst(slug).

+ Item 2: Latent bug fixed - listMethodType set unconditionally for all partners
    * setupPartnerConfiguration() in admin-settings.php assigned $this->listMethodType = Settings::METHOD_TYPE_LIST BEFORE the partner branch, so $hasList was truthy on AF and RG too even though List is a PetPoint-only method type. The new robots-meta List checkbox surfaced this because $hasList gated its visibility - it appeared on AF.
    * Moved the assignment inside the PetPoint else branch only.
    * Symmetrically removed the `?: Settings::METHOD_TYPE_LIST` Elvis fallbacks from the registerNoResultsMessageFields() and registerDetailInstructionsFields() arrays. The downstream registerNoResultsMessageField() / registerDetailInstructionField() already guard with `if (empty($methodType)) return;`, so an empty listMethodType now flows through and gets skipped instead of being papered over by the Elvis. Same applies to lost/found/adopt entries - RG no longer renders Lost/Found options it doesn't support.

+ Item 3: AF search-template missing-template error message named the offending template
    * af-api.php outputSearch() previously emitted a generic "Search template not found." when resolveSearchTemplate() returned an empty templatePath (file missing on disk). PP and RG already named the offending template via sprintf("Search template file not found: %s. Please check the template parameter in your shortcode.", basename($tpl['template'])) - now AF matches that pattern.
    * No behavioral change for the resolveSearchTemplate() guard itself - it has always file_exists()-checked and returned empty templatePath on miss. This is purely a UX consistency fix surfaced when a client referenced adopt-celebration-similar in a [pmp-search] shortcode before the file was uploaded.

+ Item 4: Return-to-Search button now restores correct URL on all detail-template wrappers
    * Reported behavior: on the AF adopt-profile-3-column-similar template, the Return-to-Search button had href="#" (so clicking went nowhere) and sessionStorage was being polluted with the detail page URL.
    * Root cause: pet-match-pro-public.js line 1178 detected detail-page context with a single-class selector - .pmp-details-container - but only some detail templates use that wrapper class. adopt-profile-3-column-similar uses .pmp-details-profile-wrapper. The detector returned false there, the JS fell into the search-page branch, and overwrote sessionStorage.pmp_return_url with window.location.href (the detail URL). Subsequent detail templates that DID match the detector then read the polluted value and pinned it to the return button.
    * Fix: broadened the selector to a comma-list covering all four wrapper class variants used across the 29 detail templates: .pmp-details-container, .pmp-details-profile-wrapper, .pmp-details-wrapper, .pmp-details-cpa. Applied to both pet-match-pro-public.js and pet-match-pro-public.min.js.
    * Earlier same-detector instance at line 768 (PMPFilterSort.init()) is unaffected by this fix - it sets a property used elsewhere in the filter-sort module, not the return-button flow. Will revisit if filter/sort behavior on profile templates is reported.

+ Item 5: Constants::DETAIL_THUMBS_MAX raised from 18 to 25
    * Cap applied to: admin Detail Thumbnails Max dropdown (admin-settings.php:6856 loop), plugin activation default (activator.php:85), per-partner default cap in PP/RG search APIs and BaseDetailTemplate. Existing installs with thumbs_max=18 already saved keep their value; the constant change just raises the ceiling.

+ Item 6: Version bump to 8.6.8

Version 8.6.7 - April 24, 2026 - Shortcode Builder Species (AnimalsFirst) Fix

+ Item 0: Shortcode Builder Species selector now populates on AnimalsFirst sites
    * Reported behavior: on a Premium AF test site, the Species row in the [pmp-search] Shortcode Builder sub-tab did not render - users had no way to pick a species, even though the live search widget on the front end had working species filtering.
    * Root cause: ShortcodeBuilderResolver::resolveSpeciesPerMethod() only consulted the per-method pmp-field-values-{method}.php files. For PetPoint those files ship hardcoded species IDs in includes/pp/partials/pmp-field-values.php (1=Dog, 2=Cat), and for RescueGroups they ship a hydrated list via $pmpFieldValues lookup. But AnimalsFirst species are fetched live from the AF filter-values endpoint at runtime - includes/af/partials/pmp-field-values-adopt.php and the three sibling method files declare `ValuePrefix::SEARCH_FILTER . AnimalsFirstFields::SPECIES . '_' . $method => []` and never hydrate. The resolver saw empty, returned `[]`, and the builder JS hid the row via isPerMethodSourceEmpty().
    * The "Refresh Filter Values" button on the Filter Values admin tab already caches the full AF filter API response to option `pet-match-pro-af-filter-values`, shape `['data' => <filters>, 'updated' => ..., 'updated_timestamp' => ...]`, and includes\class-pet-match-pro-filter-override-manager.php:171 already reads it for the filter-customize UI. The resolver was simply not using that cache.
    * Fix: resolveSpeciesPerMethod() now dispatches to a new resolveAnimalsFirstSpeciesPerMethod() when partnerDir === ANIMALSFIRST_DIR. That helper calls a new getAnimalsFirstSpeciesFromCache() which reads the cached option (same pattern as FilterOverrideManager::getAnimalsFirstConfigurableFields), applies Settings::OPTION_FILTERS_CUSTOMIZE species exclusions so the builder mirrors what the live search widget actually renders, maps each surviving species to [species => ucfirst(species)], and dispatches that single list across every CANDIDATE_METHOD (AF species are global, not per-method).
    * PetPoint and RescueGroups code paths are untouched.

+ Item 1: Admin hint when AF cache is empty
    * New public ShortcodeBuilderResolver::hasAnimalsFirstSpeciesCache(): bool lets admin code detect an unpopulated cache without peeking at internal state.
    * admin/class-pet-match-pro-admin-settings.php renderShortcodeBuilderTab() now emits a warning notice at the top of the builder when AF is selected and the cache is empty. The notice deep-links to the Filter Values tab (?page=pet-match-pro-options&subpage=filters-customize) and tells the user to click "Refresh Filter Values". Without the hint the Species row just disappears silently - users had no path to diagnose the state.

+ Item 2: Notice styling
    * Added .pmp-sb-notice-warning modifier to admin/css/pet-match-pro-shortcode-builder.css using the existing --pmp-warning / --pmp-bg-warning custom-property conventions (with sensible fallbacks for themes that don't define them). No !important, no inline CSS.

+ Item 3: Removed dead Free-tier species override blocks from AF/RG adopt partials
    * includes/af/partials/pmp-field-values-adopt.php and includes/rg/partials/pmp-field-values-adopt.php both ended with a Free-license block that attempted to overwrite the per-method species value list with Constants::FREE_SPECIES. Both had a missing-underscore typo (`SPECIES . Settings::METHOD_TYPE_ADOPT` instead of `SPECIES . '_' . Settings::METHOD_TYPE_ADOPT`) so they wrote to a malformed key and had zero effect at runtime.
    * Even with the typo fixed the blocks would still be dead code on AF and RG: AF species are fetched live from the AF filter-values API (never from this file), and no AF/RG template or API code reads the `ValuePrefix::SEARCH_FILTER . SPECIES . '_' . $method` value - only the matching label key is consumed (af-api.php:1916, rg-api.php:1580). PP is the only partner where templates read that value key (public/templates/pp/*-search-*.php), and PP's equivalent override already has the correct underscore and real runtime readers - so PP is untouched.
    * Free-tier species restriction is enforced centrally by AllApi::validateSpeciesLicense() at request time against Constants::FREE_SPECIES. That enforcement has always been the real gate for AF and RG; the partial-file overrides were belt-and-suspenders that never latched.
    * Replaced both blocks with explanatory comments documenting why they were removed so future contributors don't re-add them.

+ Item 4: Version bump to 8.6.7

Version 8.6.6 - April 13, 2026 - Detail Icon Button Color Fix

+ Item 0: Detail icon buttons now honor admin "Detail Buttons Text" color
    * Static stylesheet rule at public/css/pet-match-pro-styles.css:5262 was forcing `color: #fff` on `.pmp-details-container .pmp-detail-icon-button` (and :hover/:focus/:visited). That selector has specificity (0,2,0), higher than the admin-generated color rule, which uses (0,1,1) selectors like `a.pmp-details-button-*` and does not emit !important for the base color state. Result: the `pet-match-pro-color[detail_result_button_text]` setting had no visible effect on icon buttons.
    * Compounding the problem, the color-CSS generator's $buttonSelectors list in public/partials/pet-match-pro-public-color-css.php never included `.pmp-detail-icon-button`, so even without the static override the admin color had nothing targeting the icon-button class (which is appended dynamically by AllApi when icons are enabled).
    * Fix: added `.pmp-detail-icon-button` and `.pmp-details-container .pmp-detail-icon-button` to $buttonSelectors, and dropped the hardcoded `color: #fff` from the static stylesheet (kept `text-decoration: none` - that is layout, not color). Admin color setting now drives icon-button text color on all three partners.
    * NOTE: public/css/pet-match-pro-styles.min.css still carries the old rule and needs rebuild/minification.

+ Item 1: Version bump to 8.6.6

Version 8.6.5 - April 13, 2026 - Default Description Shortcode Evaluation

+ Item 0: Default Animal Description now evaluates embedded shortcodes on detail pages (all partners)
    * BaseDetailTemplate::renderDescription() previously assigned $generalOptions[Settings::DESCRIPTION] raw when the feed description was empty, so markup like [pmp-detail detail="animalname" case="mixed"] rendered as literal text. The fallback now runs through AllApi::replaceDetailShortcodes() (so internal {field} placeholders resolve) and then do_shortcode() (so any registered WordPress shortcode evaluates).
    * Fix applies uniformly to PetPoint, AnimalsFirst, and RescueGroups detail pages - all three share this base class.

+ Item 1: PetPoint search results - do_shortcode on default description
    * Search-context fallback in includes/pp/class-pet-match-pro-pp-api.php wrapped the admin default in nl2br(replaceDetailShortcodes(...)) but never called do_shortcode(). Added the wrapper so PP search cards match the RescueGroups behavior (which already called do_shortcode() at rg-api.php:926).

+ Item 2: AnimalsFirst search results - default description fallback added
    * AF outputSearch() had no default-description fallback at all. If the AF feed's description field was empty, search cards rendered a blank description regardless of the admin setting. Added a per-result fallback inside the existing result-preparation loop: empty description values are replaced with the admin's default description, processed through replaceDetailShortcodes() + do_shortcode().
    * Closes a behavioral gap between the three partners in search contexts.

+ Item 3: Adopt detail title - species-fallback detection
    * When the animal name field is empty, AllApi::getAnimalName() intentionally falls back to species (e.g. "Dog") so downstream callers always get a printable string. This caused BaseDetailTemplate::renderAdoptTitle() to render "Get to Know Dog the Dog" because both $name and $species were populated and the "primary" branch fired.
    * renderAdoptTitle() now compares the resolved name to species (case-insensitive, trimmed) and, when they match, treats the name as absent so the title falls through to "Meet This {Species}" - which was the intended output for nameless animals per the docblock on the method.

+ Item 4: Version bump to 8.6.5

Version 8.6.4 - April 13, 2026 - Translation Regeneration

+ Item 0: pet-match-pro.pot regenerated from current source
    * Fresh extraction of every __(), _e(), esc_html__(), esc_html_e(), esc_attr__(), esc_attr_e(), _x(), and _n() call whose text domain resolves to Constants::PLUGIN_SLUG (petmatchpro). Pulled 1,672 unique msgids from the current codebase (1,635 previously) - net +38 strings, with 108 genuinely new entries and 52 obsolete entries dropped (old license-page copy, the malformed "Exclusion ' . $i" extraction from a prior run, etc.).
    * Project-Id-Version bumped to "Pet Match Pro 8.6.4"; POT-Creation-Date refreshed to today. X-Domain header pinned to petmatchpro.
    * Bulk of the new strings come from the Shortcode Builder Phases 2-4 (admin/partials/pmp-shortcode-builder-catalog.php and admin/class-pet-match-pro-shortcode-builder-resolver.php) - sub-tab copy for [pmp-details], [pmp-detail], and [pmp-option], including parameter descriptions, option-type labels, validation banners, and the "Missing required" notice. Also captured new hide_empty admin copy in class-pet-match-pro-admin-settings.php and the detail-page heading/tooltip strings added in 8.5.x / 8.6.x (Meet This %s, Here's %s's Story, Print %s's Poster, Hover Text: *).

+ Item 1: pet-match-pro-es_ES.po merged and translated
    * All 108 new msgids translated to Spanish (es_ES), preserving existing 1,564 translations verbatim. No fuzzy markers. %s / %d placeholders, HTML tags, and [shortcode] markers preserved exactly. Punctuation matches the terse admin-copy tone of the existing file - no em-dashes (hyphens only).
    * Header refreshed: Project-Id-Version 8.6.4, PO-Revision-Date today, Language es_ES retained.
    * Six plural forms translated (%d minute, %d Month, %d Year, animal/animals, and the two analytics-insight templates).

+ Item 2: pet-match-pro-es_ES.mo recompiled
    * Fresh binary compile from the merged PO. 1,673 entries total (1,672 messages + header). Validated by loading through Python's gettext runtime: singular, plural (ngettext), and header lookups all resolve to the expected Spanish strings.

+ Item 3: Version bump to 8.6.4

Version 8.6.3 - April 13, 2026 - Shortcode Builder (Phase 4: [pmp-option])

+ Item 0: Shortcode Builder - [pmp-option] support
Version 8.6.3 - April 13, 2026 - Shortcode Builder (Phase 4: [pmp-option])

+ Item 0: Shortcode Builder - [pmp-option] support
    * Fourth and final sub-tab wired into the builder. Catalog-driven like the prior three phases; no value-list duplication - Option Type list sourced from admin/partials/pmp-option-levels.php (level_<type>_options keys), Value list sourced from the live wp_options entry for the selected type (pet-match-pro-<type>-options).
    * Parameter coverage: type (required select: general / filter / contact / label / color), value (required select, type-dependent). Both are emitted in the output - [pmp-option] is fully parameter-driven at runtime with no URL context.
    * License gating on the type selector: option groups outside the current license tier are disabled with the plan name appended (reuses the existing methodTypes-style payload). All five groups are currently Free per pmp-option-levels.php, but the plumbing honors whatever that file declares so tier changes don't need a code edit.
    * Value list reflects what is actually saved on this site. Empty option groups produce an empty Value dropdown (expected - nothing to display yet). Settings::TEMPLATE_DETAIL is filtered out because PublicFacing::handleOptionShortcode() short-circuits that key to an empty string; omitting it from the picker prevents generating a shortcode that silently produces nothing.
    * New resolver methods: ShortcodeBuilderResolver::resolveOptionTypes() and ::resolveOptionKeys(). Dispatched via the existing resolveByValueSource() under optionTypes and optionKeys keys.
    * Builder JS extended: 'optionKeys' added to isPerMethodSource / isPerMethodSourceEmpty so the Value dropdown rebuilds when Option Type changes (the same Type-change hook used by the method-scoped controls on the Search and Details sub-tabs - the control name 'type' is shared). 'optionTypes' handled alongside 'methodTypes' and 'detailMethodTypes' for locked-option rendering.

+ Item 1: [pmp-option] handler - stale option-key fix
    * PublicFacing::handleOptionShortcode() was building the wp_option key as `pet-match-pro-<type>-options`, but an earlier migration renamed those keys to `pet-match-pro-<type>` (no -options suffix). Every [pmp-option] call was hitting a non-existent option and returning an empty string.
    * Switched to the correct key pattern. [pmp-option type="contact" value="email_support"] and similar calls now return the saved value as documented.
    * Builder's resolveOptionKeys() uses the same corrected pattern.

+ Item 2: Shortcode Builder polish
    * KB help icons added to each sub-tab header (Search, Details, Detail, Option), each linking to that shortcode's reference article on petmatchpro.com/docs. Consistent with the KB icon pattern used across the rest of the admin.
    * Required-field validation banner: if a required parameter is empty (most commonly [pmp-option] with a Type picked but no Value), a yellow "Missing required: <field>" notice appears above the Generated Shortcode textarea, listing every still-empty required field by label. The textarea continues to update so the user sees progress, but the banner makes it explicit the output is not yet complete.
    * [pmp-option] empty-state note: when the selected Option Type has nothing saved in wp_options yet, a bold hint appears under the Value dropdown prompting the admin to save that tab first. Fonts is exempt - it falls back to the Settings::FONT_SIZE_* constants so Fonts shortcodes can be generated on a fresh install.
    * KB reference articles (pmp-search, pmp-details, pmp-detail, pmp-option) gained a top-of-page tip callout pointing users at the Shortcode Builder tab. Copy is tailored per shortcode so it explains exactly which dropdowns the builder scopes (method-scoped fields, per-template-family gating, license-disabled options, saved-keys-only Value lists).
    * Shortcode Builder KB article rewritten to reflect all four sub-tabs instead of "pmp-search launch, other three coming soon." New sections cover sub-tab navigation and state preservation, required-field validation, UI-only method scoping on pmp-detail / pmp-details, and the pmp-option Value source. Next Steps now links to all four reference articles.

+ Item 3: Version bump to 8.6.3

Version 8.6.2 - April 13, 2026 - Shortcode Builder (Phase 3: [pmp-detail])

+ Item 0: Shortcode Builder - [pmp-detail] support
    * Third sub-tab wired into the existing builder. Catalog-driven like Phases 1 and 2; no value-list duplication - the Field list is resolved at runtime from pmp-field-levels-{method}.php (ANIMAL_DETAIL keys) and pmp-field-values-{method}.php (ANIMAL_DETAIL labels) with pet-match-pro-label admin overrides applied last. Same shape already used by [pmp-details] Fields to Display - reused verbatim.
    * Parameter coverage: detail (required single select, method-scoped), case (lower / mixed / upper), replace (text, substituted for any whitespace inside the returned value - e.g., "-" turns "Black Lab" into "black-lab"). UI-only Method Type selector scopes the Field list to the active method and is flagged emit => false - [pmp-detail] infers method from URL context at runtime.
    * Upgrade-gated fields are shown disabled (not hidden) in the Field dropdown with an "(Upgrade required)" suffix, so Junior-tier users can see Preferred-only fields exist but cannot select them. Per-field required level sourced from the same ANIMAL_DETAIL prefix in pmp-field-levels-{method}.php the runtime gate in PublicFacing::handleDetailShortcode() already uses - no parallel level list.
    * New resolver method: ShortcodeBuilderResolver::resolveDetailFieldLevels() returning [method => [field => requiredLevel]]. Dispatched via the existing resolveByValueSource() under the detailFieldLevels key.
    * Catalog loader (class-pet-match-pro-admin-settings.php) now walks both valueSource and levelSource on every entry, so any future level-driven dropdown just declares levelSource alongside valueSource - no new plumbing per control.
    * Builder JS gains two small capabilities: inline options dict support (for static select lists like Letter Case that don't need a resolver round-trip) and levelSource honoring on per-method sources - options whose required level is stricter than the current license get disabled=true and the suffix appended.

+ Item 1: Version bump to 8.6.2

Version 8.6.1 - April 13, 2026 - Shortcode Builder (Phase 2: [pmp-details]) + Sub-Tab Navigation + HTTP Retry + Parameter Coverage

+ Item 0: Partner-API transient-failure resilience
    * New AllApi::remoteGet() helper: 15s timeout (industry-standard for shelter APIs which can be slow under load), redirection budget of 5, sslverify on. Returns the raw wp_remote_get response - no behavior change for callers on success.
    * Automatic one-shot retry on transient cURL failures: error 6 (couldn't resolve host), error 7 (couldn't connect), error 28 (operation timed out). Non-transient errors (e.g., SSL failures, 4xx/5xx) return immediately without retry.
    * WordPress default wp_remote_get timeout is 5s and the connect phase inherits that when no explicit value is set - matching the "cURL error 28: Resolving timed out after 5001 milliseconds" reports in production error logs. The 15s bump + retry eliminates that class of transient page failures.
    * All 7 bare wp_remote_get() calls in the PetPoint and AnimalsFirst API clients routed through the helper (PP: main search, animal detail, lost/found search; AF: search fetch, filter values fetch, pagination loop, filter values 2nd site).
    * RescueGroups cURL path (postJson()) gets the same retry discipline and a CURLOPT_CONNECTTIMEOUT=10 alongside the existing CURLOPT_TIMEOUT=30.
    * Retry attempts visible in error_log under WP_DEBUG as "PMP RG API cURL error (attempt N): ..." so operators can see a successful-after-retry request vs. a hard failure.

+ Item 1: Shortcode Builder - [pmp-details] support
    * Second shortcode section wired into the existing builder. Same catalog + resolver split; no value-list duplication - detail templates, detail-displayable fields, and labels are all resolved at runtime from the existing authoritative stores (pmp-field-levels-{method}.php ANIMAL_DETAIL keys, pmp-field-values-{method}.php ANIMAL_DETAIL labels, pet-match-pro-label admin overrides, and public/templates/{partner}/{method}-details-*.php + universal-details-*.php for template discovery).
    * Per-parameter license gating continues to read from pmp-option-levels-general.php.
    * New resolver methods: ShortcodeBuilderResolver::resolveDetailTemplates() and ::resolveDetailFieldCatalog(). Poster templates ({method}-details-poster-*.php) are intentionally excluded from the detail-template dropdown - those are rendered via the poster parameter, not as a primary template.
    * Parameter coverage for [pmp-details]: template, details, quick_fields, title_fields, stats_row, stats_full, title, title_size, labels, separator, hide_empty, social, thumb (enable), thumbs (max count), icon, icons, icon_size, overlay, overlays, overlay_filter, overlay_position, poster, exclude_buttons, and all 7 hover_text_{button} overrides (meet_greet, email, call, adoption_app, foster_app, donate, return).
    * Thumbnails split cleanly into two controls: `thumb="enable|disable"` toggles the thumbnail strip on or off; `thumbs="N"` caps how many thumbnails render. Mirrors the existing icon / icons and overlay / overlays pairing.
    * UI-only method scoping: a "Method Type (for builder only)" selector filters the Template and Field lists to the active method. The selector is flagged `emit => false` in the catalog and is NEVER emitted in the output - [pmp-details] infers method from URL context at runtime.
    * PetPoint `list` method is excluded from the Details sub-tab's Method Type selector - PP list is a search-only listing with no per-animal detail page. Other partners and methods are unaffected. Driven by a new DETAIL_UNSUPPORTED_METHODS map in ShortcodeBuilderResolver; adding another unsupported combination is one line there.
    * Detail-template discovery rewritten: detail templates have no fixed `*-details-*` naming convention (examples: adopt-default, adopt-wide, adopt-conversion, adopt-conversion-similar, adopt-conversion-no-app, adopt-celebration-similar, adopt-details-navigation-similar, lost-default, found-default). Resolver now globs `{method}-*.php` and excludes search/poster variants, so every valid detail template shows up in the Template dropdown. Prior behavior only surfaced `{method}-details-*.php`, which missed the majority of installed templates.
    * Template-family gating for navigation-only params: quick_fields, title_fields, stats_row, stats_full now carry `templateFamily => 'navigation'` in the catalog. The builder classifies the selected template as `navigation` (name contains "navigation") or `single` (everything else) and hides/omits these params when the selected template is not navigation-family. When no template is selected, the controls remain visible so the user can see what's available. One declarative key per affected param; no hand-maintained template-capability list.
    * Method Type consistency: the Details sub-tab's Method Type selector is now labeled "Method Type *" (required) to match the Search sub-tab. The "builder only / not emitted" nuance moved from the label into the help text - same required-look treatment everywhere, same emit => false behavior behind the scenes.
    * Title Size re-ordered to sit directly under Title in the Display group on the Details sub-tab.

+ Item 2: Shortcode Builder - Sub-tab navigation
    * The builder tab now renders a two-button sub-tab bar: Search [pmp-search] and Details [pmp-details]. Clicking a sub-tab swaps the rendered form without a page reload.
    * Per-shortcode form state is preserved across sub-tab switches - partially filled Search values are not lost when inspecting Details, and vice versa.
    * Replaced the legacy "Building: [pmp-search] ([pmp-detail], [pmp-option] coming in later phases.)" status strip with a concise four-step How-to-use-the-builder instruction block that applies to both sub-tabs.
    * Backwards-compat: the old `pmpShortcodeBuilder.catalog` field is still populated (mirrors the search section) so any external script that read it continues to work.

+ Item 3: Version 8.6.1 housekeeping
    * Bumped Constants::VERSION and the plugin header Version to 8.6.1.
    * Bumped Stable tag in readme.txt to 8.6.1.

---

Version 8.6.0 - April 12, 2026 - Shortcode Builder (Phase 1: [pmp-search]) + Admin Width Sync + PP Filter Fix

+ Item 1: New admin tab - "Shortcode Builder" for [pmp-search]
    * Visual, click-to-configure shortcode generator for [pmp-search]. Junior and Preferred only; Basic License sites see the tab with an upgrade notice instead of the form.
    * Catalog-driven: every parameter in the builder is one entry in admin/partials/pmp-shortcode-builder-catalog.php. Adding a param produces a new control automatically.
    * Thin catalog + resolver split: value lists (methods, templates, displayable fields, filter fields, sort fields, species values, sort orders) are NEVER duplicated in the catalog. They are resolved at runtime by PetMatchPro\Admin\ShortcodeBuilderResolver from the existing authoritative stores:
        - includes/{partner}/partials/pmp-field-levels-{method}.php (SEARCH_RESULT keys -> fields catalog; SEARCH_FILTER keys -> filter fields; SEARCH_SORT keys -> sort fields)
        - includes/{partner}/partials/pmp-field-values-{method}.php (labels + species values + sort-order values)
        - pet-match-pro-label option (admin label overrides)
        - pmp-option-levels-general.php (per-param license gating)
        - public/templates/{partner}/{method}-search-*.php + universal-search-*.php (template discovery)
    * Per-parameter license gating: params above the site's tier render disabled with a lock icon, Junior/Preferred tooltip, and inline Upgrade link. Locked params stay visible so users can see what upgrading unlocks.
    * Partner + method awareness: changing "Method Type" repopulates Template, Fields, Search Filters, Species, Order By, and Sort Order controls from the new method's level/value files. Parameters unsupported by a partner (for example, PetPoint has no sort-order filter) are omitted from the DOM entirely, not rendered as empty dropdowns.
    * applies_to visibility: params with method-specific applicability hide on method change.
    * Conditional fields: Field Separator only appears when Show Field Labels is set to Disable.
    * Search Filters control is a multiselect of filter-enabled field keys with a special "Disable all filters" option that suppresses the widget entirely (emits filter="disable").
    * Min=1 constraint on Results Per Row, Number of Rows, Max Results, Max Icons Per Card, Max Overlays Per Photo.
    * Size controls (Icon Size, Title Size, Subtitle Size, Banner Size) with Small / Large / Extra Large choices, Junior-gated. Icon Size lives in the Icons & Overlays group next to Max Icons Per Card; the rest live in Display.
    * Live output: readonly textarea updates on every keystroke. Default / empty values suppressed. Double quotes escaped. Multiselect joins with comma (no spaces).
    * Copy-to-clipboard uses the modern Clipboard API with a textarea-select fallback for older browsers and non-HTTPS dev sites. Reset button returns all fields to defaults.
    * Tab-scoped enqueue: builder CSS and JS load only when the Shortcode Builder subpage is active.
    * JS emits a "Building: [pmp-search]" picker indicator at the top of the form as a placeholder for future phases (pmp-details, pmp-detail, pmp-option).
    * - admin/class-pet-match-pro-admin-settings.php (tab link, dispatch, renderShortcodeBuilderTab, upgrade notice, enqueue, payload assembly, KB_LINKS entries)
    * - admin/class-pet-match-pro-shortcode-builder-resolver.php (new - PetMatchPro\Admin\ShortcodeBuilderResolver)
    * - admin/partials/pmp-shortcode-builder-catalog.php (new)
    * - admin/css/pet-match-pro-shortcode-builder.css (new)
    * - admin/js/pet-match-pro-shortcode-builder.js (new - vanilla IIFE, no build step)
    * - pet-match-pro.php (Settings::TAB_SHORTCODE_BUILDER, Shortcodes::SPECIES)

+ Item 2: Instructions tab cross-link tip
    * Every partner's [pmp-search] instruction block now opens with a "Tip: Use the Shortcode Builder tab to generate this shortcode visually instead of writing it by hand" paragraph. Single renderer applies to PP / AF / RG via the shared instructions renderer.
    * - admin/class-pet-match-pro-instructions-renderer.php (renderShortcodeBuilderTip)

+ Item 3: Admin content panel width now tracks the tab row width (Phase 0 prerequisite)
    * Higher license tiers render more tabs. Previously the tab row could extend past the content panel's width, or wide partner-filter rows pushed the wrapper wider than the visible tab row. Fixed with:
        - A shared .pmp-admin-settings-wrapper (display: inline-block) that shrinks to its widest child
        - Tab nav switched from display: block to display: inline-block with white-space: nowrap so its intrinsic width equals the sum of its tab items
        - Form element uses width: 0 + min-width: 100% + overflow-x: auto so it contributes 0 to the wrapper's shrink-to-fit calculation but still visually fills the wrapper; oversized child elements scroll horizontally instead of pushing the wrapper wider
        - Removed max-width: 1310px from .pmp-admin-form and .pmp-admin-form table.form-table which had been capping content to a hardcoded width
    * Pure CSS, no JavaScript, no per-tier class hacks.
    * - admin/class-pet-match-pro-admin-settings.php (wrapper markup)
    * - admin/css/pet-match-pro-admin.css (.pmp-admin-settings-wrapper, .pmp-admin-tabs, form width rules)

+ Item 4: Fix - PP shortcode filter= parameter emitted empty form
    * Bug in AllApi::buildSearchForm(): a guard wrote $optionKeys = array_filter(array_keys($filterOptions)); if (empty($optionKeys)) { return buildEmptyForm(); }. When the shortcode-override path returned a plain indexed array like [0 => 'speciesid'] (which is what PetPoint::processFilterOptions does when the shortcode passes filter="..."), array_filter dropped key 0 as falsy, leaving $optionKeys empty, and the entire filter form was silently replaced with an empty placeholder. Admin-configured filter lists typically produced non-zero keys so the bug was invisible in the default rendering path.
    * Replaced the guard with a simple !empty($filterOptions) check. Shortcode-driven filter= values now render correctly in non-client mode for all three partners.
    * - includes/class-pet-match-pro-all-api.php (line 207)

+ Item 5: Universal special-character sweep
    * 113 files had em-dash and en-dash characters in user-facing strings. On test sites the PetMatchPro admin output pipeline renders UTF-8 em-dashes as "a,EU"" mojibake. Until the root cause in the output pipeline is diagnosed, every em-dash and en-dash in user-facing strings has been replaced with a plain ASCII hyphen. Scope covered admin PHP, public templates, includes, JS, readme.txt, CHANGE-LOG.txt, CLAUDE.md, and KB HTML. A feedback memory was recorded so future code changes will not reintroduce the characters.

+ Item 6: Translations
    * languages/pet-match-pro.pot regenerated with ~80 new Phase 1 strings (builder labels, upgrade notices, JS i18n payload, catalog help text).
    * languages/pet-match-pro-es_ES.po completed for Spanish (Spain) and compiled to pet-match-pro-es_ES.mo.
    * - languages/pet-match-pro.pot
    * - languages/pet-match-pro-es_ES.po
    * - languages/pet-match-pro-es_ES.mo

+ Item 7: Knowledge Base article
    * New "Shortcode Builder" article in the Shortcodes and Configuration KB category. Covers who-can-use, where-to-find, how-it-works, partner/method awareness, locked parameters, filter-disable behavior, conditional fields, clipboard fallback, source-data refresh, current limitations, and a 5-item FAQ. RICH-content placeholders for hero screenshot, tab-location screenshot, walkthrough video, locked-param screenshot, and method-change animation are marked inline for asset production.

+ Item 8: Version bumped to 8.6.0
    * - pet-match-pro.php (file header + Constants::VERSION)
    * - readme.txt (Stable tag)

--------------------------------------------------------------------------------

Version 8.5.1 - April 12, 2026 - Nameless Label Sweep + PP Search Label Refactor

+ Item 1: New AllApi::getTooltipName() helper for tooltip-safe display names
    * Returns the animal name when AllApi::hasRealAnimalName() is true; otherwise falls back to "This {Species}" or "This Animal". Intended for icon tooltips, video titles, image alt text, and analytics labels - anywhere interpolation like "{name} is a Girl" would read as noise with a numeric shelter ID (e.g. "60100465 is a Girl"). Mirrors the species-fallback pattern used in 8.5.0 for description headings and social share.
    * - includes/class-pet-match-pro-all-api.php

+ Item 2: Icon and overlay tooltip sanitization at entry points (~28 sites)
    * displayPetIcons() and getOverlayIcons() on all three partners now call getTooltipName() once at the entry point, before cascading the name to ~14 private methods per partner (getSexIcon, getAgeIcon, getConditionalIcons, getVideoIcon, processSexIcon, processDynamicIcon, processYesNoIcon, processVideoIcon, etc.). The private methods receive the already-safe name - no signature changes needed, sanitization happens once.
    * - includes/pp/class-pet-match-pro-pp-detail-functions.php
    * - includes/af/class-pet-match-pro-af-detail-functions.php
    * - includes/rg/class-pet-match-pro-rg-detail-functions.php

+ Item 3: Detail image alt text, video player titles, thumbnail/video analytics
    * BaseDetailTemplate::renderMainImage() and renderMainImageWide() alt attributes now use getTooltipName(). Species is only appended when hasRealAnimalName() is true - avoids the redundant "This Cat - Cat" output when the real name resolves to "This Cat".
    * Video player iframe title, multi-video JSON data titles, thumbnail click-tracking text, and video click-tracking text all use the same name-vs-safe-fallback pattern. Real names still render as "View Fluffy the Cat Photo #1"; numeric IDs render as "View This Cat Photo #1".
    * - public/templates/includes/class-pet-match-pro-base-detail-template.php

+ Item 4: Search card tooltips and featured-search CTAs (~14 sites)
    * PP adopt-search-default, adopt-celebration-similar, universal-search-default, universal-search-structured: "Click to View Details for {name}", "View details for {name}", and "Photo of {name}" tooltips/alt text now sanitize via getTooltipName().
    * PP/AF featured-search default/compact/carousel: "Learn More About {name}" CTA click text sanitized. Null-safe access via $this->getApiFunction()?-> where $apiFunc isn't in local scope.
    * RG adopt-search-default/compact/carousel/structured and AF universal-search-structured: the local $hover variable (previously "this {species}" fallback that didn't catch numeric IDs) is now set via getTooltipName(). One edit per file propagates to all downstream tooltip usages.
    * - public/templates/pp/*.php, public/templates/af/*.php, public/templates/rg/*.php (16 search templates)

+ Item 5: PP found-search-default and lost-search-default: name resolution hardened
    * The descriptive fallback "Found Dog #{ID}" / "Lost Cat #{ID}" was triggered by empty($animalName), which did not catch the numeric-ID-as-name case (e.g. "60100465" passes !empty but is not a real name). Replaced the empty() guard with !hasRealAnimalName() so the descriptive label now activates for both empty names and numeric intake IDs.
    * - public/templates/pp/found-search-default.php
    * - public/templates/pp/lost-search-default.php

+ Item 6: PP search templates refactored to load field labels dynamically
    * Six PP search templates previously hardcoded a $fieldOrder array mapping specific field keys to labels, with a ucwords(str_replace('_', ' ', $key)) fallback that produced "Founddate" for camelCase field names and silently dropped any field the map didn't know about (e.g. secondarycolor displayed as "Secondarycolor", founddate displayed as "Founddate").
    * Replaced the hardcoded maps with a getDefaultFieldLabels() + getFriendlyFieldLabel() pattern modeled on rg/adopt-search-structured.php. Labels now resolve through a 3-tier hierarchy: (1) admin-configured label, (2) default from the partner's pmp-field-values-{method}.php file, (3) a camelCase-aware fallback using preg_replace('/([a-z])([A-Z])/', '$1 $2', $key). No hardcoded field lists remain - any field the API returns gets its correct label automatically.
    * - public/templates/pp/adopt-search-default.php
    * - public/templates/pp/adopt-celebration-similar.php
    * - public/templates/pp/found-search-default.php
    * - public/templates/pp/lost-search-default.php
    * - public/templates/pp/featured-search-default.php
    * - public/templates/pp/featured-search-compact.php
    * - public/templates/pp/featured-search-carousel.php

+ Item 7: ageInYears + hide_empty re-check (10 templates)
    * AllApi::ageInYears(0) returns Constants::EMPTY_VALUE ("Not Defined") because empty(0) is true in PHP. The initial hide_empty check runs on the raw age value, which passes since "0" is a valid string - but after ageInYears() converts it to the EMPTY_VALUE sentinel, no re-check existed, so the field rendered as "Not Defined" even when the hide_empty checkbox was enabled.
    * Added a re-check immediately after every ageInYears() call: when the converted value is empty and hide_empty is on, drop the field entirely. For featured-search templates where the conversion lives inside formatFieldValue() (which returns a scalar, not a loop), the function returns '' to let the caller's existing hide_empty logic handle the drop.
    * - PP: adopt-search-default, found-search-default, lost-search-default, adopt-celebration-similar
    * - AF: universal-search-default, universal-search-filter-widget, universal-search-no-filter, featured-search-default, featured-search-compact, featured-search-carousel
    * - RG: adopt-search-structured

+ Item 8: AF address typo normalization documented
    * AnimalsFirstFields::LOCATION_FORMATTED is declared as 'google_formatted_address' (correct spelling), but the live AF API returns 'google_formated_address' (single "t"). AnimalsFirst\ApiClient::decomposeAddressComponents() normalizes the typo on ingest, so if AF ever corrects the spelling the workaround becomes a no-op without any template changes needed. (Documented in CLAUDE.md - no code change.)

+ Item 9: CSS / theme note
    * Image alt text on detail templates is now computed - no selector or class changes. No theme-level CSS action required for 8.5.1.

Version 8.5.0 - April 11, 2026 - Title / Location / Sex Consolidation

+ Item 1: Centralized title rendering in BaseDetailTemplate
    * Eight per-partner detail templates (pp/af x lost/found x default/poster) each re-implemented renderTitle(), getJurisdiction(), and sometimes getAnimalSex() inside anonymous subclasses. The implementations had diverged: PP lost read LOCATION_LOST_CITY with no fallback ("Male Dog Lost in Not Defined"); PP found used a bare base getJurisdiction with no fallback chain; AF lost/found copy-pasted a correct 20-line override into 4 files each plus an M/F/U sex mapper into 4 more files; PP and AF used different lost title formats.
    * BaseDetailTemplate now owns the complete implementation: renderAdoptTitle / renderLostTitle / renderFoundTitle builders, a renderTitleByMethod() match dispatcher, partner-aware getAnimalSex() (PP/AF/RG), and a full partner + method-aware getJurisdiction() fallback chain ported from PosterTemplateTrait. A new hasJurisdiction() helper lets titles gracefully degrade ("Buddy Lost" instead of "Buddy Lost in Not Defined") when location data is missing.
    * The three-way Shortcodes::TITLE contract is preserved verbatim: title="" suppresses entirely, title="Custom text" is a literal override, param absent dispatches to the method-specific builder. All three render paths now emit <h1 class="pmp-details-title"> consistently (previously adopt used <h1>, lost/found used <h2>).
    * - public/templates/includes/class-pet-match-pro-base-detail-template.php

+ Item 2: Lost title name-first standard for all partners
    * AF's name-first lost format ("{Name} Lost in {Location}") is now the standard for PetPoint too, with graceful fallback through {Sex} {Species} -> {Species} -> Animal when the name is missing. Lost animals typically have known names, so leading with the name is more actionable on a lost-pet flyer.
    * Found titles keep the species-first format ("{Sex} {Species} Found in {Location}") - found animals rarely have a known name, and using one would imply a known owner.

+ Item 3: Per-partner title / jurisdiction / sex overrides deleted (8 files)
    * Removed local renderTitle(), getJurisdiction(), getAnimalSex(), and getLostCity() methods from the eight affected files. Only shouldSkipField() and getAnimalId() remain in each subclass - those contain genuinely partner-specific field-key logic.
    * File size reductions: PP lost/found templates down 24-37 lines each; AF lost/found templates down 53-68 lines each. Total 363 lines removed.
    * - public/templates/pp/lost-default.php, pp/lost-poster.php, pp/found-default.php, pp/found-poster.php
    * - public/templates/af/lost-default.php, af/lost-poster.php, af/found-default.php, af/found-poster.php

+ Item 4: Universal poster consolidation
    * The three universal-details-poster.php files (pp/, af/, rg/) were byte-identical copies of the same UniversalPosterTemplateWrapper class and script body. They now require a single shared source at public/templates/includes/universal-details-poster-body.php; each per-partner file is an 18-line stub. The shared body guards its class declaration with class_exists() so the script can be required multiple times per request without redeclaration.
    * - public/templates/includes/universal-details-poster-body.php (new)
    * - public/templates/pp/universal-details-poster.php, af/universal-details-poster.php, rg/universal-details-poster.php (stubs)

+ Item 5: PosterTemplateTrait reconciliation
    * Deleted the trait's private getPosterJurisdiction() method (52 lines) and repointed its two internal call sites to $this->getJurisdiction() on the base class. Since every template that uses the trait also extends BaseDetailTemplate, the base method is always callable. Eliminates a divergent copy of the partner/method resolution logic and the latent RescueGroupsFields::LOCATION_FOUND_CITY bug (that constant never existed; the trait code would have fataled if RG had ever supported lost/found).
    * - public/templates/includes/class-pet-match-pro-poster-template-trait.php

+ Item 6: CSS / theme note
    * Plugin CSS targets .pmp-details-title as an unqualified class selector, so the h2 -> h1 tag change is visually transparent on any site using the plugin's own styles. Themes that author custom CSS keyed on h2.pmp-details-title will need to update their selectors to the unqualified class or h1.

+ Item 7: General vs specific location split for titles and poster CTA
    * BaseDetailTemplate::getJurisdiction() now resolves a GENERAL location (jurisdiction/county/city) used by both detail-page titles and the poster heading. A new BaseDetailTemplate::getSpecificLocation() helper resolves a SPECIFIC street-level address used by the poster CTA footer ("Last Seen:" / "Found in:" rows). Previously both zones called the same helper, so the heading and footer always showed identical strings.
    * Resolution chains by partner + method:
        - PP lost general: jurisdiction -> LOCATION_LOST_CITY + LOCATION_LOST_STATE -> combined
        - PP lost specific: LOCATION_LOST -> LOCATION_LOST_ADDRESS -> fall back to general
        - PP found general: jurisdiction -> LOCATION_FOUND_CITY + LOCATION_FOUND_STATE -> combined
        - PP found specific: LOCATION_FOUND -> LOCATION_FOUND_ADDRESS -> fall back to general
        - AF any general: intake_jurisdiction -> LOCATION_CITY + LOCATION_STATE -> combined
        - AF any specific: LOCATION_ADDRESS_1 -> LOCATION_FULL -> LOCATION_FORMATTED -> fall back to general
        - RG any general: LOCATION_CITY + LOCATION_STATE -> combined
        - RG any specific: LOCATION_ADDRESS -> LOCATION -> fall back to general
    * getSpecificLocation() always falls back to getJurisdiction() when no specific source resolves, so printed flyers never show an empty "Last Seen:" line.
    * PosterTemplateTrait::renderFoundLostPosterCTA() now calls getSpecificLocation(); renderFoundLostPosterHeading() continues to call getJurisdiction().
    * - public/templates/includes/class-pet-match-pro-base-detail-template.php
    * - public/templates/includes/class-pet-match-pro-poster-template-trait.php

+ Item 8a: Label fallback for nameless animals (buttons + description heading)
    * Found animals (and some lost/stray animals) often surface their intake number in the name field when no human name is known - e.g. "60100465". Buttons and labels that concatenated the name with a prefix produced awkward strings like "Print 60100465's Poster", "Call Us About 60100465", "Meet & Greet 60100465", and description headings like "About 60100465".
    * Added a shared helper AllApi::hasRealAnimalName(mixed): bool that returns true only when the raw animal name is non-empty, not the EMPTY_VALUE sentinel, and not purely numeric. All button/label builders now consult this helper and fall back to an unqualified label when it returns false.
    * Affected label sites:
        - Print Poster button -> "Print Poster" (was "Print {Name}'s Poster")
        - Call Us button -> "Call Us" (was "Call Us About {Name}")
        - Meet & Greet button -> "Meet & Greet" / "Visit Us" (was "Meet & Greet {Name}" / "Visit Us to Meet {Name}")
        - Email Us / Ask Us button -> "{prefix}" (was "{prefix} About {Name}")
        - Email subject line -> "{inquiryType} Inquiry about a {Species}" (was "{inquiryType} Inquiry for {Name} the {Species}")
        - Sponsor button (foster-mode donate path) -> "Sponsor Their Care" (was "Sponsor {Name}'s Care")
        - Description heading -> "About This Dog" / "About This Animal" (was "About {Name}")
        - Social share title -> "Share This Dog with Your Network" / "Share with Your Network" (was "Share {Name} with Your Network")
        - Poster description label -> "About This Dog" / "About This Animal" (was "About {Name}")
        - Poster adopt header -> "Provide This Dog" / "Provide This Animal" (was "Provide {Name}")
        - Poster overlay tooltip -> "Click to Print Poster" (was "Click to Print {Name}'s Poster")
        - Navigation headings -> "Have You Seen Me?" / "Here's My Story" when name is numeric (was "Have You Seen 60100465?" / "Here's 60100465's Story")
        - AF adopt-wide description heading -> same fallback as base description heading
    * buildFosterAppButton() now receives $animalName as a parameter so the sponsor path can check it; previously only $displayName was available in that scope.
    * Navigation heading $hasName checks in 6 files updated to use hasRealAnimalName() for consistent numeric-ID detection.
    * - includes/class-pet-match-pro-all-api.php
    * - public/templates/includes/class-pet-match-pro-base-detail-template.php
    * - public/templates/includes/class-pet-match-pro-poster-template-trait.php
    * - public/templates/af/adopt-wide.php
    * - public/templates/pp/universal-details-navigation.php
    * - public/templates/af/universal-details-navigation.php
    * - public/templates/pp/adopt-details-navigation-similar.php
    * - public/templates/af/adopt-details-navigation-similar.php
    * - public/templates/rg/adopt-details-navigation-similar.php
    * - public/templates/rg/adopt-details-navigation.php

+ Item 9: AnimalsFirst google_formated_address typo normalization
    * The live AF API returns its Google-formatted address under the key `google_formated_address` (single t), but the plugin's AnimalsFirstFields::LOCATION_FORMATTED constant uses the correct double-t spelling `google_formatted_address`. Result: the "Formatted Address" field has silently failed to resolve on every AF site since the constant was introduced - any admin who enabled it as a detail field saw nothing, and the new getSpecificLocation() chain could not use it as a fallback.
    * Fixed by normalizing the incoming key inside AnimalsFirst\ApiClient::decomposeAddressComponents(), which runs on every search result and every detail fetch. When AF's single-t key is present and the double-t key is not, the value is copied into the canonical key. If AF ever corrects the spelling on their end, the real double-t value wins and the workaround becomes a no-op.
    * - includes/af/class-pet-match-pro-af-api.php

Version 8.4.1 - April 11, 2026 - hide_empty Follow-up Fixes (RG/AF Search + Detail Core)

+ Item 1: RG search templates now honor the hide_empty shortcode parameter
    * All four RG search templates were reading hide_empty from the processed/whitelisted searchParms array (built by AllApi::processSearchParameters()), which does not forward the hide_empty key. Shortcode overrides were silently ignored; only the admin checkbox ever took effect.
    * Fixed by resolving hide_empty from the raw $this->searchDetails array the template already receives, matching the working PetPoint pattern. PP was unaffected because its search templates always read from the raw details array.
    * - public/templates/rg/adopt-search-default.php
    * - public/templates/rg/adopt-search-carousel.php
    * - public/templates/rg/adopt-search-compact.php
    * - public/templates/rg/adopt-search-structured.php

+ Item 2: AF universal-search-structured hide_empty source fix
    * Same searchParms -> searchDetails fix as the RG search templates above. The AF structured search template shared the same bug.
    * - public/templates/af/universal-search-structured.php

+ Item 3: AF search templates now render "Not Defined" for empty fields by default
    * getDisplayFields() in the AF universal search templates was unconditionally dropping empty field values before the hide_empty-aware display loop ran. This meant the default hide_empty=OFF behavior (render empty fields with Constants::EMPTY_VALUE) was never honored on AF search cards - empty fields silently disappeared regardless of the toggle state.
    * Removed the premature empty-skip from getDisplayFields() in all three universal search templates; emptiness is now decided downstream where the hide_empty toggle lives.
    * - public/templates/af/universal-search-default.php
    * - public/templates/af/universal-search-no-filter.php
    * - public/templates/af/universal-search-filter-widget.php

+ Item 4: adopt-celebration-similar templates (AF + RG) honor default hide_empty behavior
    * Same premature empty-skip in getDisplayFields() as the AF universal search templates. The RG and AF celebration-similar templates were dropping empty fields before the display loop applied the hide_empty toggle, so empty-field "Not Defined" placeholders never rendered in the similar-animal grids.
    * PP adopt-celebration-similar was already correct (uses the inline pattern without a separate getDisplayFields build step).
    * - public/templates/af/adopt-celebration-similar.php
    * - public/templates/rg/adopt-celebration-similar.php

+ Item 5: Core formatDetailSection() no longer silently drops empty non-primary fields
    * AllApi::formatDetailSection() was skipping every empty field whose key was not listed as a configured primary field across filter groups. This pre-filter ran before BaseDetailTemplate::buildDetailsHtml() got a chance to apply the hide_empty-aware render loop, so default "Not Defined" rendering and the shortcode/admin hide_empty toggle never took effect on empty non-primary detail fields (e.g., nocats, nodogs, nokids on an AF adopt-conversion-similar detail page).
    * Removed the empty-skip and the now-unused primary-field lookup from formatDetailSection(). Every configured detail field now flows into the render loop; buildDetailsHtml() applies resolveDetailFieldDisplayValue() (which still honors fallback resolution for primary fields via FieldExclusionFilterTrait) and then isEmptyFieldValue() + shouldHideEmptyFields() to decide drop vs. EMPTY_VALUE.
    * Behavior change: empty non-primary fields now render as "Not Defined" by default (matching the unified 8.4.0 rule) and drop cleanly when hide_empty is on. Primary fields with a configured fallback still resolve to the fallback value as before.
    * Affects every detail template that extends BaseDetailTemplate (PP, AF, RG).
    * - includes/class-pet-match-pro-all-api.php (formatDetailSection)

Version 8.4.0 - April 10, 2026 - Unified Empty Field Handling (hide_empty)

+ Item 1: Unified hide_empty toggle across every template
    * New per-method admin checkboxes (General > Display Options): hide_empty_fields_adopt, hide_empty_fields_lost, hide_empty_fields_found, hide_empty_fields_list, hide_empty_fields_preferred. Available on every license tier.
    * New Premium-gated shortcode parameter hide_empty="true|false" for both [pmp-search] and [pmp-details]. Overrides the admin checkbox per shortcode instance.
    * Unified rule: hide_empty OFF renders empty fields with the "Not Defined" placeholder (legacy behavior from every prior release); hide_empty ON drops empty fields entirely for a tighter layout.
    * Applies consistently to every search template, detail template, profile layout, details-navigation template, celebration-similar search template, and poster body zones across all three partners. Poster header/footer zones are intentionally exempt so lost/found flyers always print a complete layout.
    * Related-animal grids inside -similar detail templates inherit the parent detail's method setting.
    * - pet-match-pro.php (Settings::HIDE_EMPTY_FIELDS, Shortcodes::HIDE_EMPTY constants)
    * - includes/class-pet-match-pro-all-api.php (isEmptyFieldValue, isEmptyDetailRow, shouldHideEmptyFields, normalizeBoolParam; HIDE_EMPTY added to addDetailShortcodeParams whitelist)
    * - admin/class-pet-match-pro-admin-settings.php (per-method checkbox wiring)
    * - admin/partials/pmp-option-levels-general.php (Premium gate for shortcode param)
    * - public/templates/includes/class-pet-match-pro-base-detail-template.php (resolveFieldValueForHideToggle helper; buildDetailsHtml refactored)
    * - public/templates/includes/class-pet-match-pro-poster-template-trait.php (body-loop wiring, header/footer exempt)
    * - 30+ partner templates wired: PP/AF/RG search, detail, poster, similar, profile, details-navigation, celebration-similar

+ Item 2: Per-partner "zero is empty" rule for physical-measurement fields
    * New class constants declaring fields where numeric 0 should be treated as "not recorded": PetPointFields::ZERO_IS_EMPTY_FIELDS = [bodyweight], AnimalsFirstFields::ZERO_IS_EMPTY_FIELDS = [weight], RescueGroupsFields::ZERO_IS_EMPTY_FIELDS = [] (no weight field exposed).
    * isEmptyFieldValue() accepts an optional $fieldKey and consults the current partner's list. When set, numeric 0 / "0" on listed fields is treated as empty (renders "Not Defined" when toggle off, dropped when on).
    * Currency fields (fee, price, adoptionfee, cost, amount) are intentionally excluded - $0 is a valid adoption-fee display.
    * Private helper isZeroEmptyField() resolves the list via IntegrationPartner match.
    * - pet-match-pro.php (per-partner ZERO_IS_EMPTY_FIELDS constants)
    * - includes/class-pet-match-pro-all-api.php (isEmptyFieldValue field-key param, isZeroEmptyField helper)
    * - 30+ call sites updated to pass the field key through

+ Item 3: PP search SimpleXMLElement flatten fix
    * Fixed a long-standing PP search bug where empty XML tags (e.g., <NoCats/>) became empty arrays after the shallow (array) cast on SimpleXMLElement, then slipped past the is_string() empty check and rendered as blank cells on adoptable animal cards.
    * Each PP search template's extractXmlItems() / extractAdoptableSearchItem() / extractSearchItem() now fully stringifies every leaf (empty tags become empty strings, populated SimpleXMLElement leaves become their text content) so downstream emptiness checks and field resolvers never see mixed types.
    * Applied to: adopt-search-default, lost-search-default, found-search-default, featured-search-default, featured-search-carousel, featured-search-compact, adopt-celebration-similar, universal-search-structured (universal-search-default already extracted cleanly).
    * - public/templates/pp/*.php (8 templates)

+ Item 4: formatCurrencyValue parameter type widened
    * SearchTemplateTrait::formatCurrencyValue() now accepts SimpleXMLElement in addition to string|int|float. PetPoint XML leaves that flowed through per-template field builders without being coerced upstream were triggering a TypeError; the widened signature + internal (string) cast handles them safely.
    * - public/templates/includes/class-pet-match-pro-search-template-trait.php

+ Item 5: KB help icon wired for Hide Empty Fields admin group
    * Added new KB_LINKS entry 'field_hide_empty' => 'empty-field-display/#admin-checkbox' pointing to the new troubleshooting article.
    * The Empty Fields sub-accordion header on General > Display Options now shows a book help icon linking to the KB article.
    * Each per-method Hide Empty Fields checkbox label (Adopt, Lost, Found, List, Preferred) also shows an inline help icon next to its label, same pattern as field_exclude_buttons and field_hover_text.
    * CLAUDE.md updated with a new "KB help icon pattern (pmp-help-icon)" section under Admin Settings documenting the KB_LINKS / renderHelpIcon / wire-up convention so future admin UI work picks this up by default. Any new KB article added must include a KB_LINKS entry and at least one renderHelpIcon() call.
    * - admin/class-pet-match-pro-admin-settings.php (KB_LINKS entry, display_empty_fields sub-accordion, registerHideEmptyFieldsCheckboxes field labels)
    * - CLAUDE.md (Admin Settings > KB help icon pattern section)

+ Item 6: Card data attributes honor isEmptyFieldValue
    * The per-field data-attribute loop in all 18 search templates (PP/AF/RG) now skips fields flagged empty by isEmptyFieldValue($value, $key). This unifies the data layer with the display layer: empty / zero-weight / EMPTY_VALUE fields no longer emit data-bodyweight="0" (or similar) attributes that could mislead client-side sort/filter scripts.
    * Applies regardless of the hide_empty toggle state - empty data attributes have no semantic value for client-side filtering.
    * - public/templates/pp/*.php, public/templates/af/*.php, public/templates/rg/*.php (18 search template files)

+ Item 7: Setup Wizard step 4 extended with Hide Empty Fields checkboxes
    * Step 4 (Field Presets) now includes per-method Hide Empty Fields checkboxes matching the admin Display Options UI. Checkboxes are rendered for whichever method types are enabled by the earlier Method Types step.
    * saveFieldsStep() persists the checkbox values to the pet-match-pro-general option using the same array shape the admin checkbox callback writes so the wizard and admin UI round-trip cleanly.
    * Wizard JS serializer extended to pick up dynamically named pmp_wizard_hide_empty_fields_* inputs.
    * - includes/wizard/class-pet-match-pro-setup-wizard.php (renderStepFields, saveFieldsStep)
    * - admin/js/pet-match-pro-wizard.js (gatherStepData for the 'fields' case)

+ Item 8: pmpAdminInfo tooltip entries for Hide Empty Fields
    * Added per-method entries to all three partner admin-info files so the existing getAdminInfoWithDisablePrefix() helper provides the tooltip text instead of a hard-coded string.
    * registerHideEmptyFieldsCheckboxes() now reads from $this->pmpAdminInfo via the same pattern every other field in the plugin uses. Improves consistency and makes the description translatable per partner.
    * Also fixed a minor inconsistency where the checkbox array key used Settings::HIDE_EMPTY_FIELDS (the base const) instead of the per-method field id. Now uses $fieldId so the stored array self-documents: $options['hide_empty_fields_adopt'] = ['hide_empty_fields_adopt'] when checked.
    * - admin/partials/pp/pmp-admin-info.php
    * - admin/partials/af/pmp-admin-info.php
    * - admin/partials/rg/pmp-admin-info.php
    * - admin/class-pet-match-pro-admin-settings.php (registerHideEmptyFieldsCheckboxes)
    * - includes/wizard/class-pet-match-pro-setup-wizard.php (saveFieldsStep round-trip format)

+ Item 9: Admin-side shortcode parameter instructions updated
    * The hide_empty param is now documented in all 6 partner-specific instruction partials that populate the Instructions tab in the admin.
    * - admin/partials/pp/pmp-instructions-search-params.php
    * - admin/partials/af/pmp-instructions-search-params.php
    * - admin/partials/rg/pmp-instructions-search-params.php
    * - admin/partials/pp/pmp-instructions-details-params.php
    * - admin/partials/af/pmp-instructions-details-params.php
    * - admin/partials/rg/pmp-instructions-details-params.php

+ Item 10: Translation files updated (Spanish complete)
    * New translatable strings added to languages/pet-match-pro.pot and languages/pet-match-pro-es_ES.po for every string introduced by the 8.4.0 work (admin labels, tooltip descriptions, wizard UI, instruction partials).
    * Spanish translations filled in for all 8 new Hide Empty Fields strings (admin checkbox label, per-method variants, help/tooltip text, and both shortcode parameter descriptions).
    * languages/pet-match-pro-es_ES.mo recompiled - 1534/1534 strings translated, zero missing. Ready to ship to Spanish-language customers.
    * - languages/pet-match-pro.pot
    * - languages/pet-match-pro-es_ES.po
    * - languages/pet-match-pro-es_ES.mo

+ Item 11: SPEC-empty-field-handling.md marked as shipped
    * The working spec document is annotated with a "Shipped in 8.4.0" status note and a bullet list of everything delivered. Future readers will know to consult CLAUDE.md for the live architecture instead of treating this as an active spec.
    * - SPEC-empty-field-handling.md

Version 8.3.4 - April 10, 2026 - PetPoint Animal Name Defensive Fix, Admin Accordion Fix, [pmp-search-filters] Removal

+ Item 1: Defensive fix for PetPoint animal name resolution on detail templates
    * Fixed an edge case where pp/adopt-conversion (and other PP detail templates) could display the species in place of the animal's name in the page title (e.g., "Get to Know Dog the Dog").
    * Root cause: getAnimalName() fell through to the species fallback when any of three conditions hit - strict IntegrationPartner enum comparison failing on legacy string values, the details array missing the expected 'animalname' key, or XML casing variations from the PetPoint API response.
    * Resolved by adding a defensive fallback chain in AllApi::getAnimalName() that covers all three scenarios via FIELD_ALIASES lookup, direct NAME_ANIMAL lookup, and case-insensitive key scan before falling through to species.
    * New private helpers: isEmptyName(), isPetPointPartner(), findPetPointAnimalName(), normalizeNameValue().
    * Fix applies uniformly to every PP template without per-template edits.
    * - includes/class-pet-match-pro-all-api.php

+ Item 2: Display Options sub-accordion opens first group by default
    * Fixed General tab -> Display Options always opening the "Search Results" sub-accordion regardless of group position.
    * The open state was hardcoded on display_search_results (the last alphabetical entry). Changed to an index-based rule so the first sub-accordion in the group is always open by default.
    * Future reordering of the Display Options sub-accordion list will automatically respect the new convention - no further code change required.
    * - admin/class-pet-match-pro-admin-settings.php (renderDisplaySubAccordions)

+ Item 3: Removed unused [pmp-search-filters] shortcode
    * Dropped support for the [pmp-search-filters] standalone filter-widget shortcode. It was only ever implemented for the AnimalsFirst partner (PP and RG had no backing method) and the single active AF client is not using it.
    * Removed the shortcode registration, constant, public handler, AF implementation methods, and the Tools tab shortcode-scanner references so orphaned scan results no longer appear.
    * - pet-match-pro.php (removed add_shortcode registration and Shortcodes::SEARCH_FILTERS constant)
    * - public/class-pet-match-pro-public.php (removed handleSearchFiltersShortcode)
    * - includes/af/class-pet-match-pro-af-api.php (removed searchFiltersWidget and searchFiltersDisplay)
    * - admin/class-pet-match-pro-admin-settings.php (removed SEARCH_FILTERS from $shortcodePatterns scanner and format-detection branch)

Version 8.3.3 - April 9, 2026 - PetPoint Per-Animal Lost/Found Labels

+ Item 1: Per-animal label resolution for PetPoint combined lost/found searches
    * Added Lost/Found Per-Animal Labels toggle in PetPoint filter settings (Preferred tier).
    * When enabled, each animal in a combined lost/found search displays labels from its actual method type (Lost or Found) instead of a single label set from the combo dropdown.
    * API builds both lost and found label sets via getDetailLabels() and passes $labelsByMethod to the template.
    * Template resolves each animal's method type from the PetPoint Type field using the same str_starts_with() pattern already used for detail URL routing.
    * Toggle defaults to Disabled - fully backwards compatible.
    * - admin/partials/pp/pmp-option-levels-filter.php (level gate)
    * - includes/pp/class-pet-match-pro-pp-options.php (toggle registration)
    * - admin/partials/pp/pmp-admin-info.php (help text)
    * - includes/pp/class-pet-match-pro-pp-api.php (dual label set building)
    * - public/templates/pp/universal-search-default.php (per-animal resolution)
    * - languages/pet-match-pro.pot, pet-match-pro-es_ES.po (translations)

+ Item 2: Fix fatal error in renderSearchForm() for lazy-init templates
    * Fixed "method_exists(): Argument #1 ($object_or_class) must be of type object|string, null given" fatal error thrown by standalone lost and found searches.
    * The trait's renderSearchForm() was directly accessing $this->apiFunction, which is null in templates that initialize the property lazily via getApiFunction() (lost-search-default.php, found-search-default.php, and 8 other PP templates). Only universal-search-default.php initialized eagerly in its constructor.
    * Resolved by calling $this->getApiFunction() in renderSearchForm() - every search template already defines this accessor.
    * - public/templates/includes/class-pet-match-pro-search-template-trait.php

Version 8.3.2 - April 9, 2026 - Dead Code Cleanup

+ Item 1: Removed legacy bootstrap class
    * Deleted includes/class-pet-match-pro.php - the original Pet_Match_Pro orchestrator class (global namespace).
    * This file was dead code: the namespaced PetMatchPro\PetMatchPro class in pet-match-pro.php replaced it entirely.
    * The legacy file contained stale shortcode registrations with incorrect callback names (petmatch_detail, petmatch_search, etc.) that no longer matched the current PublicFacing method names.
    * No other file required, instantiated, or referenced the legacy class.
    * The Pet_Match_Pro_Loader class (class-pet-match-pro-loader.php) is retained - still used by the current namespaced class for action hooks.
    * - /includes/class-pet-match-pro.php (deleted)

Version 8.3.1 - April 2, 2026 - SEO & Celebration Page Fixes

+ Item 1: 410 Gone for noindex method types
    * When an animal is no longer in the API and the method type has noindex enabled in admin settings, the plugin now returns a 410 Gone HTTP status instead of rendering an empty page.
    * This tells Google the page is permanently removed, causing it to drop the URL from its index faster than noindex alone.
    * Only affects method types with the noindex checkbox checked (e.g., found, lost). Adopt still uses the celebration page redirect.
    * /includes/class-pet-match-pro-seo.php

+ Item 2: Celebration page URL slug fallback fix
    * Fixed a bug where the pretty URL slug fallback parser in `handleMissingAnimal()` never ran.
    * The `if (empty($params))` check always failed because `pmp_id` was already set in `$params`.
    * Changed to `if (empty($params['pmp_name']) && empty($params['pmp_species']))` so the fallback correctly checks for name/species data.
    * Adopted animals without a cached transient now show the animal name and species parsed from the pretty URL (e.g., "Camp Washington Chili found a loving home!" from `/pmp/adopt/camp-washington-chili-dog-69a0bb755b66b3e36200edec/`).
    * /includes/class-pet-match-pro-seo.php

+ Item 3: Celebration template count text accuracy
    * Fixed count text ("Meet N available dogs") showing a different number than the actual cards displayed.
    * The count was calculated from the raw API result count before `processResults()` ran, which excludes the adopted animal and applies limits.
    * Moved `processResults()` before the count text and changed `$displayCount` to use `count($results['items'])` - the actual number of rendered cards.
    * Fixed in all three partner templates.
    * /public/templates/af/adopt-celebration-similar.php
    * /public/templates/pp/adopt-celebration-similar.php
    * /public/templates/rg/adopt-celebration-similar.php

Version 8.3.0 - April 1, 2026 - Search Button Consistency

+ Item 1: Button Consistency Feature
    * New Premium feature: style client-side filter dropdowns to visually match sort buttons.
    * Priority resolution: shortcode param -> admin setting -> default (disabled).
    * Added Settings::BUTTON_CONSISTENCY and Shortcodes::BUTTON_CONSISTENCY constants.
    * Added Premium license level entries for admin setting and shortcode parameter.
    * Added admin checkbox in Performance section (child of Client Features - disabled when client features unavailable).
    * Admin checkbox dynamically toggles enabled/disabled based on Client Features checkbox state via JS.
    * Added getButtonConsistency() method to search template trait with shortcode -> admin -> default priority.
    * Modified renderFilterDropdowns() to apply pmp-filter-select-consistent modifier class when enabled.
    * Added CSS rules: CTA background, primary border, body-color text, SVG chevron matching sort button appearance.
    * Active state (filter value selected): primary background, CTA border, white text - mirrors selected sort button.
    * Hover state: primary background, CTA border, white text with smooth transition.
    * Select blur on change prevents focus state from persisting after returning to placeholder value.
    * Added button_consistency parameter entry to all 3 partner instruction files (PP, AF, RG).
    * /pet-match-pro.php
    * /admin/partials/pmp-option-levels-general.php
    * /admin/class-pet-match-pro-admin-settings.php
    * /admin/partials/pmp-admin-info.php
    * /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * /public/css/pet-match-pro-styles.css
    * /public/js/pet-match-pro-public.js
    * /public/js/pet-match-pro-public.min.js
    * /admin/js/pet-match-pro-admin.js
    * /admin/partials/pp/pmp-instructions-search-params.php
    * /admin/partials/af/pmp-instructions-search-params.php
    * /admin/partials/rg/pmp-instructions-search-params.php

+ Item 2: Knowledgebase Updates
    * Added button_consistency parameter row to pmp-search reference table (MD + HTML).
    * Added Button Consistency section to search filter button article (MD + HTML).
    * Added button_consistency rows to BetterDocs CSV (both parameter table locations).
    * pmp-search reference KB article (MD + HTML)
    * Search filter button KB article (MD + HTML)
    * Shortcodes and Configuration BetterDocs CSV

+ Item 3: Admin Label Updates
    * Changed field label from "Enable Search Button Consistency" to "Enable Client-Side Search Button Consistency".
    * Changed checkbox label from "Style filter dropdowns to match sort buttons" to "Style Filter Dropdowns to Match Sort Buttons".
    * /admin/class-pet-match-pro-admin-settings.php

+ Item 4: Translation Updates
    * Added 5 new translatable strings to .pot template for button consistency feature.
    * Translated all 5 strings to Spanish (formal usted form) in es_ES.po.
    * Note: .mo binary requires recompilation (msgfmt not available locally).
    * /languages/pet-match-pro.pot
    * /languages/pet-match-pro-es_ES.po
    * /languages/pet-match-pro-es_ES.mo

Version 8.2.2 - March 31, 2026 - Label License Levels, get_option() Safety Fix

+ Item 1: Label License Level Alignment
    * Aligned label customization license requirements to match method type availability across all three partners.
    * Previously all label editing required Preferred (legacy lockdown). Now:
        - Adopt labels: Free (all tiers) - PP, AF, RG
        - Found/Lost labels: Junior (Premium) - PP, AF
        - List labels: Junior (Premium) - PP
        - Preferred labels: Preferred - AF only
    * /admin/partials/pp/pmp-option-levels-labels.php
    * /admin/partials/af/pmp-option-levels-labels.php
    * /admin/partials/rg/pmp-option-levels-labels.php
    * /admin/partials/pmp-instructions-admin-settings.html

+ Item 2: get_option() Array Safety - Missed Instances from v8.1.2
    * Fixed fatal error: `getLabelValue(): Argument #1 ($labelOptions) must be of type array, string given` on PetPoint search pages when label options not yet saved.
    * Replaced 4 raw `get_option()` calls with `Constants::getOptionAsArray()` that were missed during the v8.1.2 conversion:
        - 2 in PP DetailFunctions (lines 204, 388)
        - 2 in AF DetailFunctions (lines 189, 330)
    * Added `is_array()` guard to RG Options `get_option()` call (line 472) for a dynamic option key not covered by `getOptionAsArray()`.
    * /includes/pp/class-pet-match-pro-pp-detail-functions.php
    * /includes/af/class-pet-match-pro-af-detail-functions.php
    * /includes/rg/class-pet-match-pro-rg-options.php

Version 8.2.1 - March 28, 2026 - Spanish Translation Sync

+ Item 1: Spanish Translation Update (es_ES)
    * Identified 146 translatable strings in PHP source code missing from .pot template and .po translation file.
    * Regenerated pet-match-pro.pot with all 1,521 strings (was 1,375).
    * Translated all 146 missing strings to Spanish (formal usted form) in pet-match-pro-es_ES.po.
    * Recompiled pet-match-pro-es_ES.mo binary.
    * Missing strings covered: font size settings (18), icon size controls (6), print poster feature, button exclusion options (3), system info labels (PHP/WordPress/cURL/JSON/SimpleXML/OPcache), celebration & navigation templates, instruction parameter documentation, license form labels, and admin UI labels.
    * Translation conventions maintained: formal Spanish throughout, technical terms preserved in English, brand names intact, shortcode parameter names in English.
    * /languages/pet-match-pro.pot - 1,521 entries (was 1,375)
    * /languages/pet-match-pro-es_ES.po - 1,521 translated, 0 untranslated, 0 fuzzy
    * /languages/pet-match-pro-es_ES.mo - recompiled

Version 8.2.0 - March 28, 2026 - CSS Variables, Exclude Buttons Admin, Icon/Font Size Controls, Button Hover Text, Display Options Reorganization, KB Expansion

+ Enhancement 1: CSS Variable Infrastructure (Phase 1)
    * Migrated hardcoded icon sizes in pet-match-pro-styles.css to CSS custom properties (`--pmp-size-*`) with original values as fallbacks. 6 selectors updated: search pet/overlay/label icons and detail pet/overlay icons.
    * Migrated hardcoded font sizes to CSS custom properties (`--pmp-font-size-*`) with original values as fallbacks. 14 selectors updated across search (title, subtitle, banner, name, value, button) and detail (title, label, value, button, heading, stat, quick field, description, instruction title, instruction text).
    * Expanded 2 shorthand `font:` declarations in `.pmp-details-button` and config notice buttons to separate `font-weight`, `font-size`, `line-height`, `font-family` properties to allow CSS variable on `font-size`.
    * Replaced legacy `font-family: CenturyGothic, sans-serif` with `font-family: inherit` on detail buttons so they inherit the theme font.
    * Renamed `ColorCssGenerator` class to `StyleCssGenerator` to reflect expanded scope (colors, sizes, fonts).
    * Updated CSS filename from `pet-match-pro-color-overrides.css` to `pet-match-pro-style-overrides.css` with automatic legacy file cleanup.
    * Updated cache key from `pmp_color_css_hash` to `pmp_style_css_hash`. Hash now includes general and fonts options alongside colors.
    * Added cache invalidation hooks for general and fonts option updates.
    * /pet-match-pro.php
        - Version bumped to 8.2.0
        - Added 6 icon size Settings constants (SIZE_SEARCH_PET_ICON, etc.)
        - Added 18 font size Settings constants (FONT_SIZE_SEARCH_TITLE, etc. including instruction title/text)
        - Added 3 exclude buttons Settings constants (EXCLUDE_BUTTONS_ADOPT/LOST/FOUND)
        - Added HOVER_TEXT_PREFIX Settings constant
        - Added TAB_FONTS, OPTION_FONTS Settings constants
        - Added 4 Shortcodes constants (ICON_SIZE, TITLE_SIZE, SUBTITLE_SIZE, BANNER_SIZE)
        - Added 7 hover text Shortcodes constants (HOVER_TEXT_MEET_GREET, etc.)
        - Updated StyleCssGenerator references and added general/fonts cache invalidation hooks
    * /public/css/pet-match-pro-styles.css
    * /public/partials/pet-match-pro-public-color-css.php
    * /public/class-pet-match-pro-public.php
    * /admin/class-pet-match-pro-admin-settings.php
    * /admin/partials/pmp-option-levels-general.php
    * /admin/partials/pmp-option-levels-fonts.php (NEW)

+ Enhancement 2: Exclude Buttons Admin Settings (Phase 2)
    * Added admin multi-checkbox fields in General tab Display Options for controlling which detail page buttons are hidden per method type.
    * Adopt method: all 7 buttons (Meet & Greet, Email, Call, Adoption App, Foster App, Donate, Return).
    * Lost/Found methods: 3 buttons (Email, Call, Return). Fields only shown when method type is enabled for current partner.
    * Priority chain: shortcode `exclude_buttons` param -> admin setting per method type -> empty (all shown).
    * License-gated at PREMIUM_LEVEL.
    * /admin/class-pet-match-pro-admin-settings.php
        - Added `registerExcludeButtonsFields()` method
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - Updated `getExcludedButtons()` with shortcode -> admin -> empty fallback chain

+ Enhancement 3: Icon Size & Font Size Admin Controls (Phase 3)
    * New "Fonts" admin tab (PREMIUM level) with 18 dropdown fields: 8 search + 10 detail font elements (including instruction title/text).
    * Each dropdown offers Small / Default / Large / Extra Large presets that scale from the base CSS value.
    * Added 6 icon size dropdown fields in General tab Display Options (pet/overlay/label x search/detail).
    * StyleCssGenerator `buildIconSizeCss()` reads icon size admin settings, maps presets to px values, outputs `:root` CSS variables.
    * StyleCssGenerator `buildFontSizeCss()` reads font size settings, applies scale multipliers to base values, outputs `:root` CSS variables.
    * Shortcode overrides: `icon_size`, `title_size`, `subtitle_size`, `banner_size` parameters output inline CSS variables on container element.
    * /admin/class-pet-match-pro-admin-settings.php
        - Added Fonts tab registration, level gating, navigation, switch case, accordion JS
        - Added `initialize_fonts_options()`, `renderFontsOptionsAccordions()`, `sanitizeFontsOptions()`
        - Added `registerIconSizeFields()`
        - Added `OPTION_FONTS` to `$validTabs` whitelist
    * /public/partials/pet-match-pro-public-color-css.php
        - Populated `buildIconSizeCss()` and `buildFontSizeCss()` with admin value mapping
        - Added `scaleFontSize()` helper for unit-aware scaling
    * /includes/class-pet-match-pro-all-api.php
        - Added `buildSizeOverrideStyle()` for shortcode inline CSS variable output
    * /public/templates/includes/class-pet-match-pro-search-template-trait.php
        - Added `getSizeOverrideStyle()` method
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - Added `getSizeOverrideStyle()` method

+ Enhancement 4: Button Hover Text with Field Interpolation (Phase 4)
    * Admin text input fields per button per method type for customizing button hover text (title attribute).
    * Supports `{FieldName}` placeholder syntax for partner-specific animal field value interpolation (e.g., PetPoint: {AnimalName}, AF: {Name}, RG: {animalName}).
    * Priority chain: shortcode `hover_text_{button}` param -> admin setting `hover_text_{button}_{method}` -> current default text.
    * Foster-to-donate button swap correctly resolves hover text for `donate` when animal is in foster care.
    * License-gated at PREMIUM_LEVEL. 7 hover_text shortcode params added to strip list.
    * /includes/class-pet-match-pro-all-api.php
        - Added `interpolateFieldTokens()` - regex-based `{field_name}` resolver via `getAnimalProperty()`
        - Added `resolveButtonHoverText()` - priority chain resolver with interpolation
        - Updated `renderConversionButton()` and `renderOtherButton()` to resolve hover text
        - Updated all 7 button builders + `buildReturnLink()` with `?string $hoverText` param
        - `renderOtherButton()` conditionally resolves hover text for donate when foster_app button renders as sponsor
    * /admin/class-pet-match-pro-admin-settings.php
        - Added `registerHoverTextFields()` method

+ Enhancement 5: Display Options Sub-Accordion Reorganization
    * Reorganized the Display Options accordion into 7 alphabetical sub-groups for improved navigation:
        1. Button Controls - Exclude buttons, hover text
        2. Detail Icons & Overlays - Overlay enable/position/max/title, icon enable/max, print poster
        3. Detail Page - Thumbnails, preferred label
        4. Formatting - Age display, currency symbol, date format
        5. Icon Sizes - 6 size dropdowns
        6. Search Icons & Overlays - Overlay enable/position/max/title, icon enable/max
        7. Search Results - Results per row, labels, pagination (opens by default)
    * /admin/class-pet-match-pro-admin-settings.php
        - Added `renderDisplaySubAccordions()` method using existing sub-accordion pattern
        - Registered 6 new sub-sections in `initialize_general_options()`
        - Re-routed all Display Options field registrations to their respective sub-sections

+ Enhancement 6: Admin Info, KB Help Icons & KB Link Fixes
    * Added 27 admin-info help text entries for all new settings (6 icon sizes, 3 exclude buttons, 18 font sizes).
    * Added 6 new KB_LINKS entries for Fonts tab, icon sizes, exclude buttons, hover text, and formatting.
    * Wired `renderHelpIcon()` to Fonts tab navigation, Fonts accordion sections, icon size fields, exclude buttons fields, and hover text fields.
    * Fixed KB_LINKS slug mismatches: partner integration guides, GA4 configuration, font-size-customization - all slugs now match WordPress article titles.
    * Registered missing Search Result Accent color field in Colors tab, Search Results group. CSS variable (`--pmp-color-search-result-accent`), level gate (PREMIUM), CSS generator output, and admin-info help text already existed - only the `add_settings_field()` call was absent.
    * /admin/class-pet-match-pro-admin-settings.php
    * /admin/partials/pmp-admin-info.php

+ Enhancement 7: Knowledge Base Documentation
    * Revised existing HTML KB docs:
        - 02-pmp-details-reference.html - Added icon_size, title_size, 7 hover_text params, admin fallback table
        - 04-parameters-by-license-tier.html - Added size params, hover_text, Junior admin settings section
        - 05-common-shortcode-recipes.html - Added 4 recipe sections for new features
        - 01-custom-labels-guide.html - Added label font size section with Fonts tab cross-reference
    * Created new HTML KB articles:
        - 03-templates-design/06-font-customization.html - Font Size Customization
        - 04-icons-overlays/06-icon-size-customization.html - Icon Size Customization
        - 02-shortcodes-configuration/10-button-hover-text.html - Button Hover Text
    * Created HTML versions of all 15 MD-only KB articles across groups 01-03
    * Revised 01-getting-started/04-understanding-license-tiers (MD + HTML) - Added 4 new features to tier table
    * Fixed all KB cross-reference links to use https://petmatchpro.com/docs/slug/ format (27+ pre-existing files had relative or /docs/ prefix links)
    * Created admin instructions: /admin/partials/pmp-instructions-fonts.html
    * Revised 6 partner instruction PHP files (pp/af/rg search + details params) with new shortcode params
    * New "Best Practices" KB group (10-best-practices/) with 10 articles:
        1. Featuring Animals on Your Homepage
        2. Adding Animals to Sidebars and Other Pages
        3. Using Banners and Subtitles Effectively
        4. Overlays and Icons for Quick Visual Cues
        5. Writing Effective Detail Page Instructions
        6. Button Configuration for Conversion
        7. Optimizing Performance with API Caching
        8. Customizing Typography and Icon Sizes
        9. Lost and Found Search Best Practices
        10. Template Selection Guide
    * Created category icon: /docs/kb-icons/kb-best-practices.svg (teal lightbulb)

Version 8.1.6 - March 27, 2026 - KB Help Icons, Modal & Submenu

+ Item 1: Contextual KB help icons across admin settings UI
    * Added `dashicons-book` help icons at tab, accordion, and field-group levels linking to petmatchpro.com/docs/ articles with bookmark anchors.
    * Icons open KB articles in an iframe modal overlay with loading spinner, "Open in new tab" fallback, and Escape/overlay-click to close.
    * Hover tooltip displays "View Knowledge Base Article" on all icons.
    * ~48 icon placements across all tabs: 5 tab-level, ~24 accordion-level, ~19 field-group level.
    * Partner tab icon is dynamic per active integration partner (PetPoint, AnimalsFirst, RescueGroups).
    * /pet-match-pro.php
        - Added `Constants::DOCS_URL` constant.
    * /admin/class-pet-match-pro-admin-settings.php
        - Added `KB_LINKS` class constant mapping ~90 keys to article slug+anchor pairs.
        - Added `renderHelpIcon()` static method returning `<span>` with `data-kb-url` attribute.
        - Added `'kb'` key to `$sections` arrays in General, Filter, Color, and Label accordion renderers.
        - Injected icons in tab navigation, Tools/Analytics dedicated render methods, and ~19 `add_settings_field()` title parameters.
    * /admin/css/pet-match-pro-admin.css
        - Added `.pmp-help-icon` base styles with cursor pointer and hover color transition.
        - Added tab-level (14px) and accordion-level (14px) icon size overrides.
        - Added `.pmp-kb-modal-overlay` and `.pmp-kb-modal` iframe modal styles.
    * /admin/js/pet-match-pro-admin.js
        - Added `initializeKBHelpModals()` - builds modal DOM, handles icon click to open iframe, close on button/overlay/Escape.

+ Item 2: Knowledge Base submenu item
    * Added "Knowledge Base" submenu under PetMatchPro in both active and inactive license menus.
    * Opens petmatchpro.com/docs/ in a new tab via `admin_footer` inline script.
    * /admin/class-pet-match-pro-admin-settings.php
        - Added `add_submenu_page()` with slug `pet-match-pro-kb` in both menu setup methods.
        - Added `admin_footer` hook to rewrite link href with `target="_blank"`.

+ Item 3: Bug fix - System Information API key display
    * Fixed corrupted UTF-8 bullet characters (`U+FFFD` replacement chars) showing as `????` in API key masking.
    * Replaced with `****` to match pattern used in API Diagnostics.
    * /admin/class-pet-match-pro-admin-settings.php
        - Fixed `ajax_get_system_info()` API Key mask from corrupted `    ` to `****`.

Version 8.1.5 - March 27, 2026 - Exclude Buttons Shortcode Parameter

+ Item 1: Exclude buttons shortcode parameter for detail templates
    * Added `exclude_buttons` shortcode parameter to `[pmp-details]` for hiding specific action buttons (Premium).
    * Accepts comma-separated button names: meet_greet, email, call, adoption_app, foster_app, donate, return.
    * Filtering in BaseDetailTemplate - all templates benefit with zero template file changes.
    * Profile grid CSS updated to auto-fit when buttons are removed.
    * /pet-match-pro.php
        - Added `Shortcodes::EXCLUDE_BUTTONS` constant.
    * /includes/class-pet-match-pro-all-api.php
        - Added `Shortcodes::EXCLUDE_BUTTONS` to `$paramsToPass` in `addDetailShortcodeParams()`.
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - Added `getExcludedButtons()` private helper to parse the shortcode parameter.
        - Updated `renderConversionButtons()` and `renderOtherButtons()` to filter out excluded buttons before delegation.
    * /admin/partials/pmp-option-levels-general.php
        - Added `PREMIUM_LEVEL` entry for `exclude_buttons` shortcode parameter.
    * /admin/partials/pp/pmp-instructions-details-params.php
    * /admin/partials/af/pmp-instructions-details-params.php
    * /admin/partials/rg/pmp-instructions-details-params.php
        - Added `exclude_buttons` parameter documentation to all three partner instruction files.
    * /public/css/pet-match-pro-styles.css
        - Changed `.pmp-details-profile-conversion-buttons-wrapper` from fixed `1fr 1fr` to `repeat(auto-fit, minmax(200px, 1fr))`.

+ Item 2: KB documentation updates
    * Updated `[pmp-details]` reference with `exclude_buttons` parameter and examples.
    * Updated parameters-by-license-tier with `exclude_buttons` in Junior parameters table.
    * Updated common shortcode recipes with "Detail Page with Hidden Buttons" section.
    * pmp-details reference KB article (MD + HTML)
    * Parameters-by-license-tier KB article (MD + HTML)
    * Common shortcode recipes KB article (MD + HTML)

+ Item 3: Poster template ignoring shortcode details= parameter
    * `renderPosterDetailsSection()` iterated all `animalDetails` keys instead of respecting the shortcode `details=` parameter or admin field list. This caused extra fields (species, duplicate location) and wrong field order on poster pages.
    * Added `getPosterFieldList()` to resolve field order: shortcode `details=` > admin `animal_details` setting > all keys fallback. Same priority as `BaseDetailTemplate::renderDetails()`.
    * Updated `renderPosterDetailsSection()` to iterate only the resolved field list in order with case-insensitive key matching.
    * /public/templates/includes/class-pet-match-pro-poster-template-trait.php
        - Added `use PetMatchPro\Shortcodes` import.
        - Replaced `renderPosterDetailsSection()` to filter and order fields from `getPosterFieldList()`.
        - Added `getPosterFieldList()` private method.

+ Item 4: Fix Migration 2 for legacy 6.x upgrades
    * Migration 2 used underscore variants (`pet-match-pro-filter_options`, `pet-match-pro-label_options`) but the legacy 6.x database stores them with hyphens (`pet-match-pro-filter-options`, `pet-match-pro-label-options`). This caused filter and label settings to be lost on upgrade from 6.x.
    * Legacy 6.x stored `order_by` and `sort_order` without method type suffix. Modern code expects `order_by_adopt` and `sort_order_adopt`. Added `migrateGeneralSubKeys()` to rename these within the general options array after migration.
    * /includes/class-pet-match-pro-migrator.php
        - Corrected legacy key names in Migration 2 key map from underscores to hyphens.
        - Added `migrateGeneralSubKeys()` to rename unsuffixed sort sub-keys to method-suffixed format.

+ Item 5: New KB article for [pmp-option] shortcode
    * Added reference documentation for the `[pmp-option]` admin settings shortcode.
    * Covers both parameters (`type`, `value`), tier access table, examples, and use cases.
    * pmp-option reference KB article (MD + HTML)

Version 8.1.4 - March 27, 2026 - Poster Overlay, Name Fix & Settings Migration

+ Item 1: Poster overlay icon - extend to all detail templates
    * The `renderPosterOverlay()` method only existed locally in the two AF profile templates and was not controlled by the `poster` shortcode parameter. Moved it to `BaseDetailTemplate` as a partner-agnostic method and wired it to the `poster` parameter.
    * New `poster` parameter values: `overlay` (show PDF icon on main image, hide button), `both` (show both icon and button). Existing `enable`/`disable`/`on`/`off` values unchanged.
    * The overlay uses the existing overlay positioning system (`pmp-overlay-container` + `pmp-overlay-pos-{1-8}`) and the `pmp-svg-icon pmp-svg-icon-pdf pmp-overlay` classes so it respects the admin overlay position setting and detail overlay icon color.
    * /pet-match-pro.php
        - Added `Constants::BOTH = 'both'` for poster parameter value checks.
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - Added `renderPosterOverlay()`: Uses `getOverlayPosition()` for placement, `pmp-svg-icon-pdf pmp-overlay` for icon color. Only renders when `poster="overlay"` or `poster="both"`.
        - `renderPrintPosterButton()`: Returns empty when `poster="overlay"`. Added `Constants::BOTH` to explicitly-enabled values.
    * /public/templates/af/adopt-profile-3-column.php
        - Removed local `renderPosterOverlay()` (now in base class).
    * /public/templates/af/adopt-profile-3-column-similar.php
        - Removed local `renderPosterOverlay()` (now in base class).
    * 29 detail templates (13 AF, 11 PP, 5 RG)
        - Added `<?php echo $templateContext->renderPosterOverlay(); ?>` after main image render in each template.
    * /includes/class-pet-match-pro-all-api.php
        - Added `Shortcodes::POSTER` to `$paramsToPass` in `addDetailShortcodeParams()` so the poster shortcode value reaches the template context.
    * /admin/partials/af/pmp-instructions-details-params.php
    * /admin/partials/pp/pmp-instructions-details-params.php
    * /admin/partials/rg/pmp-instructions-details-params.php
        - Updated poster parameter values to `enable/disable/overlay/both` with expanded description.

+ Item 2: Name duplication bug in AF profile-3-column-similar template
    * When the shortcode `details` parameter included the name field, the animal name displayed in both the profile title and the details list. The shortcode branch in `renderProfileFields()` did not include `AnimalsFirstFields::NAME` in the `$skipFields` array.
    * /public/templates/af/adopt-profile-3-column-similar.php
        - Added `AnimalsFirstFields::NAME` to `$skipFields` array so name is always excluded from the details grid regardless of code path.

+ Item 3: Legacy settings migration for plugin upgrades
    * Upgrading from the legacy plugin to the modern version lost all user settings because option key names changed (e.g., `pet-match-pro-general-options` -> `pet-match-pro-general`).
    * /includes/class-pet-match-pro-migrator.php
        - Added Migration 2 (`migration2_migrateLegacyOptionKeys()`): Checks for legacy option keys, creates backup (`pet-match-pro-backup-pre-migration`), copies data to modern keys only if modern key doesn't exist, deletes legacy keys, logs via `error_log()`.
        - Bumped `CURRENT_VERSION` from 1 to 2.

+ Item 4: Gender label missing in AF profile 3-column templates
    * The gender field displayed value only (no label) in both AF profile 3-column templates. Breed and age intentionally show value-only, but gender should include its label.
    * /public/templates/af/adopt-profile-3-column.php
        - Removed `AnimalsFirstFields::GENDER` from `$noLabelFields`. Updated gender output to include `$genderLabel` alongside `$genderValue`.
    * /public/templates/af/adopt-profile-3-column-similar.php
        - Removed `AnimalsFirstFields::GENDER` from `$noLabelFields`. The template's existing label/no-label branching handles it automatically.

+ Item 5: Poster button underline keeps returning
    * The print poster button link kept getting underlined by Divi theme CSS. The existing fix targeted `.pmp-details-container .pmp-details-media` which doesn't match the profile template's wrapper structure, and the lower-specificity rule was insufficient.
    * /public/css/pet-match-pro-styles.css, pet-match-pro-styles.min.css
        - Replaced template-specific selector with `div.pmp-details-poster-print-button a` + all pseudo-states (`:link`, `:hover`, `:focus`, `:visited`, `:active`). The `div` element qualifier adds specificity that beats Divi's `a` rules.

+ Item 6: Profile template return button not styled as reverse
    * The return-to-search button on profile 3-column templates used the same primary color as the conversion buttons instead of the reverse (widget/CTA) color scheme.
    * /public/css/pet-match-pro-styles.css, pet-match-pro-styles.min.css
        - `.pmp-details-profile-button-return`: Changed from background-image only to full reverse styling - widget color background, widget border, primary color on hover.

Version 8.1.3 - March 24, 2026 - Value-Based Icon Resolution for Label and Detail Icons

+ Item 1: Label icons in search results show field-name icon instead of value-specific icon
    * When labels are enabled in AF universal search templates, the icon always reflected the field name (e.g., `icon-gender.svg`) rather than the actual value (e.g., `icon-female.svg`). Value-specific icons were already working for pet icons and overlays but not for label icons.
    * /public/templates/includes/class-pet-match-pro-search-template-trait.php
        - `getLabelIconClass()`: Added optional `$value` parameter. When provided, normalizes the value and checks for a matching `icon-{value}.svg` file before falling back to the field-name icon.
        - Added `normalizeIconKey()`: Converts field values to icon filename keys (e.g., "Young Adult" -> "young-adult", "OK With Cats" -> "ok-with-cats").
        - Added `iconFileExists()`: Checks theme directory (Premium+ only) then plugin directory for icon file existence. Follows same resolution chain as `AllApi::getImageUrl()`.
    * /public/templates/af/universal-search-filter-widget.php
        - Pass `$value` to `getLabelIconClass()` so label icons resolve to value-specific icons when available.
    * /public/templates/af/universal-search-no-filter.php
        - Same change - pass `$value` to `getLabelIconClass()`.

+ Item 2: AF 3-column detail templates not using value-based icons for remaining fields
    * The AF 3-column detail templates already used value-based icons for gender (hardcoded) and species-based icons for breed, but all other fields (age, size, color, etc.) used field-name icons only. Extended value-based resolution to all fields.
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - Added `normalizeIconKey()`: Same normalization as SearchTemplateTrait version, available to all detail templates.
        - Added `iconFileExists()`: Uses `$this->licenseLevel` for theme directory gating.
        - Added `getValueBasedIconClass()`: Returns value-based CSS class suffix when a matching icon file exists, otherwise falls back to field-name class.
    * /public/templates/af/adopt-profile-3-column.php
        - Age field: Now uses `getValueBasedIconClass()` instead of hardcoded `AnimalsFirstFields::AGE`.
        - Remaining fields loop: Now uses `getValueBasedIconClass()` instead of `str_replace('_', '-', $fieldKey)`.
    * /public/templates/af/adopt-profile-3-column-similar.php
        - `getFieldIconClass()`: Replaced hardcoded gender value check and field-name default with unified `getValueBasedIconClass()` call. Breed still uses species-based resolution.

+ Item 3: Theme-only value icons missing CSS variable generation
    * `getImageCssVariables()` only scanned the plugin `includes/images/` directory for `icon-*.svg` files. Theme-only value icons (icons that exist in the theme directory but not in the plugin) were never picked up, so their CSS classes were never generated.
    * /includes/class-pet-match-pro-all-api.php
        - `getImageCssVariables()`: After scanning plugin directory, now also scans theme icon directory (Premium+ only) for additional `icon-*.svg` files. Only adds icons not already defined by the plugin scan. Uses existing `getImageUrl()` for URL resolution.

Version 8.1.2 - March 23, 2026 - get_option() Array Safety: Replace Direct Calls with getOptionAsArray()

+ Item 1: Fatal TypeError when label/general/filter/color/contact options not yet saved
    * `get_option()` returns `''` (empty string) instead of `[]` when a serialized option hasn't been saved, even with `[]` as the default. Any downstream code with an `array` type hint or array access crashes with a fatal TypeError. Confirmed in production on PetPoint detail pages.
    * Replaced all direct `get_option(Constants::PLUGIN_NAME . '-' . Settings::OPTION_*, [])` calls with `Constants::getOptionAsArray()` which guarantees an array return via `is_array()` coercion.
    * 105 replacements across 41 files:
        - /includes/pp/class-pet-match-pro-pp-detail-functions.php (1)
        - /includes/af/class-pet-match-pro-af-detail-functions.php (1)
        - /includes/rg/class-pet-match-pro-rg-detail-functions.php (3 - also removed stale `get_option returns false` comments)
        - /public/templates/af/ - 13 files (22)
        - /public/templates/pp/ - 12 files (25)
        - /public/templates/rg/ - 8 files (18)
        - /public/templates/includes/class-pet-match-pro-base-detail-template.php (4 - also removed redundant is_array() guard in getAdminDetailList())
        - /public/templates/includes/class-pet-match-pro-search-template-trait.php (1)
        - /admin/class-pet-match-pro-admin-settings.php (12 - including analytics save verification comparison)
        - /admin/class-pet-match-pro-functions.php (2)
        - /admin/partials/af/pmp-admin-info.php (1)
        - /includes/wizard/class-pet-match-pro-setup-wizard.php (12)
        - /includes/af/class-pet-match-pro-af-options.php (1)
        - /includes/class-pet-match-pro-deactivator.php (5)
    * Not changed: license scalar options (LICENSE_LEVEL, LICENSE_KEY, etc.), WordPress native options (date_format, time_format), boolean existence checks, and API classes (already using getOptionAsArray()).

Version 8.1.1 - March 23, 2026 - Pre-Release Hardening: Security, Debug Cleanup, Bug Fixes

+ Item 1: Fatal error on sites missing WP_POST_REVISIONS constant
    * `migrateCustomCssToCustomizer()` called `wp_update_custom_css_post()` which triggered WP's revision system. Sites without `WP_POST_REVISIONS` defined in `wp-config.php` threw a fatal error in `wp-includes/revision.php`.
    * /admin/class-pet-match-pro-admin-settings.php
        - `migrateCustomCssToCustomizer()`: Added `define('WP_POST_REVISIONS', true)` guard when constant is missing. Wrapped `wp_update_custom_css_post()` call in try/catch - on failure, logs error and retries on next admin load instead of crashing.

+ Item 2: Carousel template CTA button ignoring admin border color
    * Carousel-specific CSS rule `a.pmp-button.pmp-search-cta-button.pmp-template-carousel` set `border: none`, overriding the dynamic border color from admin settings due to higher specificity.
    * /public/css/pet-match-pro-styles.css
        - Changed carousel CTA from `border: none` to `border: 1px solid var(--pmp-color-cta, #F38D81)`. Added `border-color` to transition property.

+ Item 3: LevelPrefix typo causing fatal error in PP search field level check
    * `LevellPrefix::SEARCH_RESULT` (double 'l') on line 3383 would throw undefined class error at runtime.
    * /includes/pp/class-pet-match-pro-pp-api.php
        - Fixed to `LevelPrefix::SEARCH_RESULT`.

+ Item 4: Deprecated `${$var}` syntax (PHP 8.2 deprecation, removed in 9.0)
    * 24 instances of `${$variableName}` across 16 files. Throws deprecation warnings on PHP 8.2+.
    * Changed all to `$$variableName` across: pp-api, pp-options, pp-detail-functions, af-api, af-options, af-detail-functions, admin-functions, filter-override-manager, admin-info partials (af, rg), and search templates (pp featured-default/compact/carousel, af adopt-conversion-similar/adopt-profile-3-column-similar, pp adopt-conversion-similar).

+ Item 5: OPcache reset moved from insecure standalone file to admin Tools tab
    * `clear-cache.php` in plugin root had no authentication - anyone could reset the server's OPcache.
    * Deleted /clear-cache.php
    * /admin/class-pet-match-pro-admin-settings.php
        - `renderToolsCacheManagement()`: Added PHP OPcache card to cache management grid (dashicons-performance icon).
        - `ajax_clear_all_caches()`: Added `opcache` cache type handler with `function_exists('opcache_reset')` guard. Returns "not available" message when OPcache is not enabled on the server. Included in "Clear All" operation.
    * /admin/js/pet-match-pro-admin.js
        - Updated individual and "Clear All" button handlers to respect `cleared: false` response for proper error/success styling.
    * OPcache card inherits the Cache Management accordion gate (Preferred level).

+ Item 6: Removed test utility files
    * Deleted /includes/for-master/index.php - 124-line PhpFormBuilder test page with `display_errors` enabled. Should not ship in production.

+ Item 7: Removed console debug logging from admin JavaScript
    * 10 `console.log` calls removed from admin JS including: RG Filter Debug block (6 lines logging species, colors, exclusions), RG Filter Refresh response log, and Analytics Save debug object. Retained 7 `console.error` calls for legitimate error handling.
    * /admin/js/pet-match-pro-admin.js

+ Item 8: Gated error_log() calls behind WP_DEBUG
    * 29 direct `error_log()` calls across templates and API classes were logging unconditionally in production. Wrapped with `if (defined('WP_DEBUG') && WP_DEBUG)` guard.
    * Affected files: rg-api (8 calls), color-css generator, pet-match-pro.php, admin-settings, and all AF/PP/RG search templates with catch block logging.
    * Excluded: ErrorLogger class (has own level filtering), Deactivator (operational logging), Field Exclusion Filter Trait (already gated behind `$debugEnabled`).

+ Item 9: Missing wp_unslash() before sanitization (WordPress coding standard)
    * ~50 instances of `$_POST`/`$_GET` values passed to `sanitize_text_field()`, `sanitize_textarea_field()`, `esc_url_raw()`, or `wp_verify_nonce()` without `wp_unslash()`.
    * Fixed across: admin-settings (SEO, import/export, analytics, filters), analytics-ajax (event tracking, settings, impressions), SEO class, poster templates, public-ajax, and all partner search/detail templates.

+ Item 10: Removed debugVideos() dead code with inline styles
    * Debug method in base detail template outputting red-bordered diagnostic HTML. The only call site was already commented out.
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php

+ Item 11: Custom colors still lost on plugin update (8.1.0 Item 7 fix incomplete)
    * The 8.1.0 fix used a `plugins_loaded` hook to invalidate the color CSS cache transient on version change. Two problems: (a) `class_exists('ColorCssGenerator')` guard always returned false because the class isn't loaded until `wp_enqueue_scripts`, so `invalidateCache()` never ran; (b) the approach required version-tracking in the database, making it difficult to test across multiple sites.
    * Replaced with a self-healing approach: `needsRegeneration()` now checks filesize. Plugin update overwrites `color-overrides.css` with the packaged BOM-only file (3 bytes). The generator detects `filesize <= 3` and rebuilds from saved color options on next page load. No database flags or transient management needed.
    * /public/partials/pet-match-pro-public-color-css.php
        - `needsRegeneration()`: Added `filesize($this->cssFullPath) <= 3` check alongside the existing `!file_exists()` check.
    * /pet-match-pro.php
        - Removed `plugins_loaded` version-change handler. Replaced with one-time cleanup to delete the `pet-match-pro_version` option from sites that have it.

+ Item 12: AF essential fields displaying in 3-column detail templates when not configured
    * AF `outputDetails()` force-added 14 "essential" fields to `$animalDetails` for internal use (SEO, posters, overlays). The 3-column templates iterated over all `$animalDetails` without filtering by admin config or shortcode `details=` parameter, causing unconfigured fields to display with auto-generated labels (wrong casing, missing colons).
    * /includes/af/class-pet-match-pro-af-api.php
        - Essential fields loop: Marked entries with `'_internal' => true` and empty label. Still available for SEO/posters/overlays but identifiable by templates.
    * /public/templates/af/adopt-profile-3-column.php
        - `renderProfileFields()`: Added admin detail list filtering via `getAdminDetailList()`. Skips `_internal` fields and fields not in admin config. Breed/gender/age also gated by admin config.
    * /public/templates/af/adopt-profile-3-column-similar.php
        - Same fixes applied to the no-shortcode branch. Shortcode branch already filtered correctly.

+ Item 13: Birthdate-to-age checkbox not working in RG search templates, inconsistent across partners
    * Multiple issues across all three partners:
    * (a) RG search templates (carousel, default, compact) used `array_key_exists()` instead of `!empty() && is_array()` to check the checkbox state. Also had an inconsistent FREE license gate not present in the RG API's `processBirthdate()`.
    * (b) RG search templates: `resolveFilteredFieldValue()` ran after age conversion and could overwrite the converted value with the raw date.
    * (c) AF featured search templates (carousel, default, compact) always converted birthdate to age without checking the admin checkbox.
    * (d) AF universal search templates (default, no-filter, filter-widget) had no birthdate-to-age conversion at all.
    * (e) AF structured search template used `array_key_exists()` instead of `!empty() && is_array()`.
    * /public/templates/rg/adopt-search-carousel.php, adopt-search-default.php, adopt-search-compact.php
        - Fixed checkbox check to `!empty() && is_array()`. Removed FREE license gate. Added `$ageConverted` flag to prevent filter resolution from overriding age value.
    * /public/templates/af/featured-search-carousel.php, featured-search-default.php, featured-search-compact.php
        - Added `!empty() && is_array()` checkbox gate around existing age conversion.
    * /public/templates/af/universal-search-default.php, universal-search-no-filter.php, universal-search-filter-widget.php
        - Added birthdate-to-age conversion in `getDisplayFields()` when admin checkbox enabled.
    * /public/templates/af/universal-search-structured.php
        - Fixed checkbox check to `!empty() && is_array()`. Added `$ageConverted` flag to prevent filter override.

+ Item 14: Date fields using hardcoded format instead of admin DATE_FORMAT setting
    * Four locations in partner API classes hardcoded `m-d-Y` for date display instead of using the admin `Settings::DATE_FORMAT` setting. The setting was only honored in template-level formatting (BaseDetailTemplate, SearchTemplateTrait, PosterTemplateTrait).
    * /includes/af/class-pet-match-pro-af-api.php
        - `processBirthdate()`: Changed fallback date format from hardcoded `m-d-Y` to `$this->generalOptions[Settings::DATE_FORMAT] ?? 'm-d-Y'`.
    * /includes/rg/class-pet-match-pro-rg-api.php
        - `processBirthdate()`: Same fix.
        - `buildAnimalDetails()` date callback: Same fix.
    * /includes/pp/class-pet-match-pro-pp-api.php
        - `createDetails()` date callback: Same fix.
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - Detail field rendering: `formatDateValue()` now skipped when the value has already been converted to age text (e.g., "2 Years") to prevent reformatting age strings as dates.

Version 8.1.0 - March 23, 2026 - Universal Print Poster, RG Search Name Order, RG Detail Fixes

+ Item 1: Print poster button extracted from adopt-conversion-poster template and made universal
    * Print poster button was only available on adopt-conversion-poster templates (PP + AF). Extracted to a universal feature controlled by admin setting and shortcode parameter, following the same pattern as social sharing. Default: disabled. Level: FREE.
    * /pet-match-pro.php
        - Added `Settings::PRINT_POSTER = 'print_poster'` constant.
        - Added `Shortcodes::POSTER = 'poster'` constant.
    * /admin/partials/pmp-admin-info.php
        - Added help text for `Settings::PRINT_POSTER`.
    * /admin/partials/pmp-option-levels-general.php
        - Added `LevelPrefix::LEVEL . Settings::PRINT_POSTER => Constants::FREE_LEVEL` in detail settings section.
        - Added `LevelPrefix::LEVEL . 'shortcode_' . Shortcodes::POSTER => Constants::FREE_LEVEL` in shortcode parameters section.
    * /admin/class-pet-match-pro-admin-settings.php
        - Added `registerDetailPrintPosterField()` method (checkbox, after Detail Icons Max).
        - Registered field call in display section after `registerDetailIconsMaxField()`.
    * /includes/class-pet-match-pro-activator.php
        - Added `Settings::PRINT_POSTER => ''` default (disabled) in contact options.
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - `renderPrintPosterButton()`: Added enable/disable gate at top of method. Shortcode `poster="enable/disable"` overrides admin setting. Admin checkbox checked = show, unchecked = hide.
    * /public/templates/pp/ - Added `renderPrintPosterButton()` after `renderSocialShare()` in: adopt-default.php, adopt-conversion.php, adopt-conversion-no-app.php, adopt-conversion-similar.php, adopt-wide.php, adopt-details-navigation-similar.php, found-default.php, lost-default.php, universal-details-navigation.php.
    * /public/templates/af/ - Added `renderPrintPosterButton()` after `renderSocialShare()` in: adopt-default.php, adopt-conversion.php, adopt-conversion-no-app.php, adopt-conversion-similar.php, adopt-wide.php, adopt-profile-3-column.php, adopt-profile-3-column-similar.php, adopt-details-navigation-similar.php, found-default.php, lost-default.php, universal-details-navigation.php.
    * /public/templates/rg/ - Added `renderPrintPosterButton()` after `renderSocialShare()` in: adopt-default.php, adopt-similar.php, adopt-details-navigation.php, adopt-details-navigation-similar.php. Moved `renderPrintPosterButton()` to after `renderSocialShare()` in adopt-cpa.php.
    * Deleted /public/templates/pp/adopt-conversion-poster.php (redundant - poster button now universal).
    * Deleted /public/templates/af/adopt-conversion-poster.php (redundant - poster button now universal).
    * /admin/partials/pp/pmp-instructions-details-params.php - Added `poster` parameter documentation.
    * /admin/partials/af/pmp-instructions-details-params.php - Added `poster` parameter documentation.
    * /admin/partials/rg/pmp-instructions-details-params.php - Added `poster` parameter documentation.

+ Item 2: Search results - animal name not forced first when using admin settings (all partners)
    * When no `details=` shortcode parameter is provided (admin settings source), animal name should always render first, separate from the details field loop. When `details=` is provided, name renders at the shortcode-specified position. Applied consistently to all three partners.
    * /includes/class-pet-match-pro-all-api.php
        - Added `hasShortcodeDetails` public bool property. Set by `finalizeItems()` - `true` when shortcode provides `details=`, `false` when admin settings drive the field list. This property is not overwritten by later `getDetailList()` calls, unlike `searchDetailSource`.
    * /public/templates/rg/adopt-search-default.php
        - `buildAnimalCard()`: Added separate name rendering before `buildDetailsSection()`, conditional on `!hasShortcodeDetails`.
        - `buildDetailsSection()`: Name field skipped in loop when admin source (rendered separately above). When shortcode source, name renders in-loop at shortcode-specified position with link treatment and analytics. Fixed field count to always exclude name (renders as block div, not inline).
    * /public/templates/af/universal-search-default.php
        - `buildAnimalCard()`: Made separate name rendering conditional on `!hasShortcodeDetails`.
        - `getDisplayFields()`: Made name field skip conditional on `!hasShortcodeDetails`.
        - `buildDetailsSection()`: Added `$detailUrl` and `$animalId` parameters. Added in-loop name-as-link rendering for shortcode source.
    * /public/templates/pp/adopt-search-default.php
        - `buildAnimalCard()`: Added separate name rendering before `buildDetailsSection()`, conditional on `!hasShortcodeDetails`.
        - `getDetailFields()`: Added name field skip when `!hasShortcodeDetails`.

+ Item 3: [pmp-detail] shortcode not working for RescueGroups - celebration redirect for all animals
    * Two issues with RG's `animalDetail()`: (a) returned the entire cached API response array instead of extracting the requested field value, causing `[pmp-detail]` shortcode to output nothing; (b) the RG API response nests animal data under `data[id]` with camelCase keys (e.g., `animalStatus`), but the field lookup used lowercase keys at the top level. Status field lookup failed -> empty status -> SEO handler treated all animals as unavailable -> celebration redirect.
    * /includes/rg/class-pet-match-pro-rg-api.php
        - `animalDetail()`: Now drills into `$cached['data'][0]` to reach the animal record, then performs case-insensitive key lookup via `array_change_key_case()`. Handles both flat and nested API response structures.

+ Item 4: Navigation detail templates - hardcoded 'Name' label ignoring custom labels
    * All navigation detail templates hardcoded `__('Name', ...)` for the name field label instead of checking custom labels via `getFieldLabel()`. Fixed across all 3 partners to use custom label with fallback to default.
    * /public/templates/rg/adopt-details-navigation.php - `renderQuickFields()`: Name label now uses `$this->getFieldLabel($fieldKey) ?? __('Name', ...)`.
    * /public/templates/rg/adopt-details-navigation-similar.php - Same fix.
    * /public/templates/pp/adopt-details-navigation-similar.php - Same fix.
    * /public/templates/pp/universal-details-navigation.php - Same fix.
    * /public/templates/af/adopt-details-navigation-similar.php - Same fix.
    * /public/templates/af/universal-details-navigation.php - Same fix.

+ Item 5: RG detail templates not using custom labels from admin
    * RG admin registers a combined "Search/Detail Result Labels" section using `LabelPrefix::SEARCH_RESULT`, but `getDetailLabels()` looked up `LabelPrefix::ANIMAL_DETAIL` keys for detail pages - keys that are never saved because no separate detail label section exists in admin. Custom labels set in admin were ignored on detail pages.
    * /includes/rg/class-pet-match-pro-rg-detail-functions.php
        - `getDetailLabels()`: Added fallback from `ANIMAL_DETAIL` to `SEARCH_RESULT` custom label lookup. Priority: ANIMAL_DETAIL custom label -> SEARCH_RESULT custom label -> field values default.

+ Item 6: RG "Convert Birth Date to Age" admin checkbox not working
    * `processBirthdate()` used `isset()` to check the checkbox state, which returns true even when unchecked (empty value). Also had an incorrect Premium-only license gate despite the feature being registered as FREE level, and showed "Upgrade Required" for free users.
    * /includes/rg/class-pet-match-pro-rg-api.php
        - `processBirthdate()`: Changed `isset()` to `!empty() && is_array()` to properly detect checked state (matching AF pattern). Removed license gate. When unchecked, returns formatted date instead of upgrade link.

+ Item 7: Custom colors lost on plugin update
    * Plugin update overwrites `color-overrides.css` with the empty packaged version. The `ColorCssGenerator` cache transient still matched the saved options hash, so regeneration was skipped. Colors remained blank until manually re-saved.
    * /pet-match-pro.php
        - Added `plugins_loaded` hook that compares stored plugin version against `Constants::VERSION`. On version change, invalidates the color CSS cache transient and updates the stored version. Next page load regenerates `color-overrides.css` from saved color options automatically.

Version 8.0.9 - March 22, 2026 - Wide Template Overlay Colors & Poster Location Fix

+ Item 1: Wide detail template overlay icons not using configured colors
    * Color CSS targeted `.pmp-details-image-main .pmp-overlay` for detail overlay icon color, but `renderMainImageWide()` outputs the image inside `.pmp-wide-image` without the `pmp-details-image-main` wrapper. Overlay icons rendered black regardless of admin color setting. Added `.pmp-wide-image .pmp-overlay` to the detail overlay icon color selectors. Affects AF and PP wide templates.
    * /public/partials/pet-match-pro-public-color-css.php
        - `buildIconColorsCss()`: Added `.pmp-wide-image .pmp-overlay` to the Detail Overlay Icon Color selector list.

+ Item 2: Found/lost poster footer location shows "NOT DEFINED" despite header showing correct location
    * Poster CTA used `getPosterFieldValue('jurisdiction')` (found) and `getPosterFieldValue('city')` (lost) with no fallback when the raw field is empty. Header used `getPosterJurisdiction()` which falls back to city+state from decomposed address. Replaced both CTA lookups with `getPosterJurisdiction()` for consistent resolution across all partners.
    * /public/templates/includes/class-pet-match-pro-poster-template-trait.php
        - `renderFoundLostPosterCTA()`: Replaced conditional `getPosterFieldValue('jurisdiction'/'city')` with `getPosterJurisdiction()`.

Version 8.0.8 - March 21, 2026 - AF Address Decomposition & Poster Jurisdiction/Date Fix

+ Item 1: AF address sub-components null despite long_address being populated
    * AF API returns address.long_address and address.adr_address with full values but leaves city, state, zip, country, address_1 as null. Added parsing after the existing address flatten to fill null components from adr_address semantic HTML spans (preferred) or long_address comma-split (fallback). Only null/empty fields are filled - AF-populated values are preserved. Applied to both detail and search paths.
    * /pet-match-pro.php
        - Added `LOCATION_ADR_ADDRESS = 'adr_address'` constant to `AnimalsFirstFields`.
    * /includes/af/class-pet-match-pro-af-api.php
        - Added `decomposeAddressComponents()` private method: parses adr_address spans (street-address, locality, region, postal-code, country-name) or falls back to long_address comma-split. Fills only null/empty fields.
        - `outputDetails()`: Calls `decomposeAddressComponents()` after existing address array flatten.
        - `outputSearch()`: Added pre-processing loop to flatten and decompose address for each search result before template render.
        - Added `INTAKE_JURISDICTION`, `LOCATION_CITY`, `LOCATION_STATE`, `DATE_INTAKE` to essential fields array.

+ Item 2: AF lost/found detail templates - title shows "N/A" when intake_jurisdiction is empty
    * `getJurisdiction()` only checked `intake_jurisdiction` with no fallback. Added fallback chain: intake_jurisdiction -> city + state (from decomposed address) -> city -> state -> EMPTY_VALUE.
    * /public/templates/af/lost-default.php
    * /public/templates/af/lost-poster.php
    * /public/templates/af/found-default.php
    * /public/templates/af/found-poster.php

+ Item 3: Poster heading missing jurisdiction and date for AF lost/found
    * Poster trait `renderFoundLostPosterHeading()` showed "Lost [date]" / "Found [date]" with no location. AF date_lost mapped to non-existent 'lostdate' field causing "Not Defined". Updated heading to: "Lost in [jurisdiction] on [date]" / "Found in [jurisdiction] on [date]". Jurisdiction and date are conditionally included only when non-empty.
    * /public/templates/includes/class-pet-match-pro-poster-template-trait.php
        - `getPosterFieldKey()`: AF `date_lost` mapping changed from `'lostdate'` to `AnimalsFirstFields::DATE_INTAKE`.
        - Added `getPosterJurisdiction()`: partner-aware with fallback chain (jurisdiction field -> city + state -> EMPTY_VALUE). Works for all three partners.
        - `renderFoundLostPosterHeading()`: 3rd line now builds conditionally: "[Lost/Found] in [jurisdiction] on [date]".

Version 8.0.7 - March 20, 2026 - AF Search/Detail Fixes, CSS Migration & Icon Fixes

+ Item 1: Search detail fields not configured - admin fallback missing in search trait
    * AF and RG search templates used `getDetailsParam()` which only checked shortcode params and `searchParms`. When no shortcode `details=` was provided, `processSearchParameters()` never included the details key, so templates got an empty string and showed "Search detail fields not configured."
    * /includes/class-pet-match-pro-all-api.php
        - `getDetailListString()`: Changed from `private` to `public` so the search trait can access it.
    * /public/templates/includes/class-pet-match-pro-search-template-trait.php
        - `getDetailsParam()`: Added step 3 fallback that reads admin-configured search detail fields via `allAPIFunction->getDetailListString()`.

+ Item 2: "Not configured" error icon class missing - `pmp-svg-icon-configure` does not exist
    * Search templates returned `'key' => 'configure'` for the error state, producing CSS class `pmp-svg-icon-configure` which has no matching icon definition. Changed to `'key' => 'error'` to use the existing `pmp-svg-icon-error` icon.
    * /public/templates/af/adopt-celebration-similar.php
    * /public/templates/af/universal-search-default.php
    * /public/templates/af/universal-search-filter-widget.php
    * /public/templates/af/universal-search-no-filter.php
    * /public/templates/rg/adopt-celebration-similar.php
    * /public/templates/pp/adopt-celebration-similar.php
    * /public/templates/pp/adopt-search-default.php
    * /public/templates/pp/featured-search-default.php
    * /public/templates/pp/lost-search-default.php
    * /public/templates/pp/found-search-default.php
    * /public/templates/pp/universal-search-structured.php
    * /public/templates/pp/universal-search-default.php

+ Item 3: Custom CSS migrated to WordPress Customizer - Advanced section removed
    * Custom CSS setting moved from PMP admin General tab to WordPress Appearance > Customize > Additional CSS. One-time migration runs on admin page load: appends existing PMP CSS (wrapped in comment markers) to Customizer, removes the value from general options, and shows a success notice.
    * /admin/class-pet-match-pro-admin-settings.php
        - Added `migrateCustomCssToCustomizer()` method.
        - Removed `registerCustomCSSField()` method.
        - Removed Advanced accordion section and label map entry.
    * /public/partials/pet-match-pro-public-color-css.php
        - Removed `buildCustomCss()` method and its call.
        - Removed custom CSS from cache hash calculation.
    * /admin/partials/pmp-option-levels-general.php
        - Removed custom CSS level entry.
    * /admin/partials/pmp-admin-info.php
        - Removed custom CSS admin info entry.
    * /pet-match-pro.php
        - Removed `update_option_` hook for CSS cache invalidation.
    * /includes/class-pet-match-pro-activator.php
        - Removed default CSS value from activation defaults.
    * /includes/wizard/class-pet-match-pro-setup-wizard.php
        - Removed "Custom CSS editor" from Premium feature list.

+ Item 4: AF search labels display option - preferred method uses hardcoded label
    * The General tab search labels field for AnimalsFirst showed "Search Labels (Preferred)" even when a custom preferred method label was configured.
    * /admin/class-pet-match-pro-admin-settings.php
        - `registerSearchLabelsFields()`: AF preferred entry now uses `getPreferredMethodLabel()` instead of hardcoded "Preferred".

+ Item 5: Profile 3-column detail template icons not displaying
    * All `pmp-detail-profile-result-*::before` CSS rules used undefined CSS custom properties (`var(--pmp-icon-*)`) for `mask-image`. Replaced with direct `url()` paths to SVG files matching the working `pmp-svg-icon-*` pattern.
    * /public/css/pet-match-pro-styles.css
        - Replaced all `var(--pmp-icon-*)` references with `url(../../includes/images/icon-*.svg)` paths (~170 rules).

+ Item 6: Profile 3-column detail template labels missing - AF label key stripping bug
    * AF's `getResultLabels()` stripped only `label_` prefix from option keys (e.g., `label_detail_age_adopt` -> `detail_age`), but `buildAnimalDetailsArray()` looked up keys without the `detail_` prefix (e.g., `age`). PP correctly stripped `label_detail_` to produce matching keys.
    * /includes/af/class-pet-match-pro-af-api.php
        - `getResultLabels()`: Now uses `LabelPrefix::ANIMAL_DETAIL` (`label_detail_`) for stripping and filters to detail label keys only, matching PP's `buildResultLabels()` approach.

+ Item 7: Profile 3-column detail template labels missing colon suffix
    * AF's `buildAnimalDetailsArray()` passed empty string `''` as `labelSuffix`, while PP and RG pass `': '`. Labels displayed without the trailing colon separator.
    * /includes/af/class-pet-match-pro-af-api.php
        - `buildAnimalDetailsArray()`: Changed `labelSuffix` from `''` to `': '`.

+ Item 8: Profile 3-column detail template description - no line breaks
    * Description text rendered as a single paragraph with no line breaks. Other detail templates apply `nl2br()` when description lacks existing HTML block tags.
    * /public/templates/af/adopt-profile-3-column.php
    * /public/templates/af/adopt-profile-3-column-similar.php
        - `renderDescription()`: Added `nl2br()` with `<br>`/`<p>` tag check before output.

+ Item 9: AF preferred method sub-types (outcome, foster, stray) - search returns no results
    * `callMethod_Parameters()` set both METHOD and METHOD_TYPE to the sub-type value (e.g., "outcome"), causing template lookups for `search_template_outcome` (doesn't exist) and filter lookups for `outcome_search_details` (doesn't exist). These sub-types should map to the "preferred" method type for internal routing while preserving the actual type value for the API URL.
    * /includes/class-pet-match-pro-all-api.php
        - `callMethod_Parameters()`: Non-standard valid types now set METHOD_TYPE to `preferred` while METHOD keeps the actual value for API URL building.
    * /includes/af/class-pet-match-pro-af-api.php
        - `createSearch()`: Swapped assignment so `methodValue` reads from METHOD_TYPE (internal routing) and `typeValue` reads from METHOD (API URL).

+ Item 10: AF status shortcode parameter - array syntax in API URL returns no results
    * `status` was defined in `filter_fields_arrays` causing the URL builder to produce `&status[]=Adopted` instead of `&status=Adopted`. The AF v2 API expects scalar status values. Additionally, the shortcode-to-urlParms mapping at line 902 placed status into urlParms, causing the dedicated status handler to skip it (thinking it was already in the URL), while the generic filter loop added it with array syntax.
    * /includes/af/partials/pmp-field-values.php
        - Moved `AnimalsFirstFields::STATUS` from `filter_fields_arrays` to `filter_fields_text`.
    * /includes/af/class-pet-match-pro-af-api.php
        - `buildSearchQueryString()`: Status handler now always adds status as scalar. Added STATUS to the skip list in the generic filter loop to prevent duplicates.

Version 8.0.6 - March 19, 2026 - Performance & CSS Loading Fix
+ Item 1: CSS file not loading - wrong filename in enqueue
    * The base public stylesheet enqueue used `Constants::FILE_PUBLIC` ('public') to build the filename, producing `pet-match-pro-public.min.css`. The actual file is `pet-match-pro-styles.min.css`.
    * /public/class-pet-match-pro-public.php
        - `enqueueStylesScripts()`: Changed `Constants::FILE_PUBLIC` to `Constants::FILE_STYLES` so the enqueued URL matches the existing CSS file.
+ Item 2: Impression tracking blocking page load (1.4s admin-ajax.php POST)
    * `trackImpressions()` used a blocking `$.ajax` call on page load. The companion `trackEvent()` already used `navigator.sendBeacon` but impressions did not.
    * /public/js/pet-match-pro-public.js
    * /public/js/pet-match-pro-public.min.js
        - `trackImpressions()`: Switched to `navigator.sendBeacon` with `FormData` payload (fire-and-forget), falling back to async `$.ajax` for older browsers.
+ Item 3: Plugin JS loaded in <head> blocking page render
    * The public JS was enqueued with `in_footer: false`, forcing it into `<head>` and blocking first paint. The script only runs on DOM ready, so footer loading is functionally identical.
    * /public/class-pet-match-pro-public.php
        - `enqueueStylesScripts()`: Changed `in_footer` parameter from `false` to `true` to defer script to footer.

Version 8.0.5 - March 19, 2026 - Celebration Page Fixes & Setup Wizard Improvements
+ Item 1: Celebration page - adopted animal's photo not displaying (PetPoint)
    * PetPoint adoptable detail XML uses numbered photo fields (`photo1`, `photo2`, ...) with no bare `photo` key. `fetchAnimalDataForSeo()` requested `PetPointFields::PHOTO` which returned empty, so the transient cached no photo URL and the celebration page always fell back to the default placeholder image.
    * /public/class-pet-match-pro-public.php
        - `fetchAnimalDataForSeo()`: After trying the base photo field, falls back to `photo1` for PetPoint to capture the primary photo from numbered fields.
+ Item 2: Celebration page - adopted animal appearing in similar results
    * The celebration redirect URL did not include the animal ID, so the celebration template had no way to exclude the adopted animal from the "similar animals" search results.
    * /includes/class-pet-match-pro-seo.php
        - `handleMissingAnimal()`: Now adds `pmp_id` to the redirect URL query params.
    * /public/templates/pp/adopt-celebration-similar.php
    * /public/templates/af/adopt-celebration-similar.php
    * /public/templates/rg/adopt-celebration-similar.php
        - `processResults()`: Reads `$_GET['pmp_id']` as fallback exclude ID when the shortcode `exclude` param is empty. Bypasses Premium license gate since this is internal behavior.
+ Item 3: Setup wizard - page select not sending type to backend
    * The JS `collectStepData()` for the pages step sent `method` and `page_id` but not `type` (search/detail). The backend defaulted `type` to `'detail'`, so search page selections were silently saved as detail pages.
    * /admin/js/pet-match-pro-wizard.js
        - Added `type: el.data('type')` to the page select data payload.
+ Item 4: Setup wizard - removed search page mapping from existing-page mode
    * The admin General tab only has detail page dropdowns. The wizard's `search_page_` key was wizard-only and unused elsewhere. Removed search page dropdowns from the existing-page mapping UI and simplified the save handler to only persist detail page IDs.
    * /includes/wizard/class-pet-match-pro-setup-wizard.php
        - `renderStepPages()`: Existing-page section now shows only detail page dropdowns per method.
        - `savePagesStep()`: Removed `search_page_` save logic; saves only `Settings::PAGE_DETAILS` entries.
+ Item 5: Setup wizard - hide detail page/template for PetPoint list method
    * The `list` method type is search-only (no detail page exists in admin settings), but the wizard showed detail page and detail template options for it.
    * /includes/wizard/class-pet-match-pro-setup-wizard.php
        - `renderStepPages()`: Auto-preview and existing-page sections skip detail page item for `list`.
        - `renderStepTemplates()`: Detail template dropdown hidden for `list`.
        - `ajaxCreatePages()`: Skips detail page creation for `list`.
+ Item 6: Setup wizard - added data retention setting to analytics step
    * The analytics wizard step had tracking toggle and GA method but no data retention setting, which was only configurable in the admin Analytics tab.
    * /includes/wizard/class-pet-match-pro-setup-wizard.php
        - `renderStepAnalytics()`: Added data retention dropdown (30/60/90/180/365 days, Forever) reading from `Settings::ANALYTICS_DATA_RETENTION`.
        - `saveAnalyticsStep()`: Persists retention value with allowlist validation.
    * /admin/js/pet-match-pro-wizard.js
        - Added `data_retention` to analytics step data collection.

Version 8.0.4 - March 18, 2026 - Date Formatting & Detail Template Fixes
+ Item 1: Centralized date formatting with admin setting
    * Date fields across search, detail, and poster templates used hardcoded `date('m-d-Y')` or had no formatting at all (raw ISO dates like `2026-03-17T17:24:16.643`). No way for clients to choose their preferred date format.
    * /pet-match-pro.php
        - Added `Settings::DATE_FORMAT` constant.
    * /admin/class-pet-match-pro-admin-settings.php
        - Added `registerDateFormatField()` method with 5 format options: `m-d-Y`, `d-m-Y`, `Y-m-d`, `M d, Y`, `d M Y`. Default: `m-d-Y`. Registered after currency symbol field.
    * /admin/partials/pmp-admin-info.php
        - Added tooltip for date format setting.
    * /admin/partials/pmp-option-levels-general.php
        - Added FREE level and enable keys for all three partners.
    * /public/templates/includes/class-pet-match-pro-search-template-trait.php
        - Added `formatDateValue()` method reading `Settings::DATE_FORMAT` from `generalOptions`.
        - Added auto-detection in `resolveFilteredFieldValue()` for fields containing "date".
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - Added `formatDateValue()` method and date detection in `formatDetailSection()` after currency formatting.
    * /public/templates/includes/class-pet-match-pro-poster-template-trait.php
        - Added `formatPosterDateValue()` method. Replaced hardcoded `date('m/d/Y')` in `getLostDate()` and `getFoundDate()`.
    * Removed inline date formatting from 14 search templates (all birthdate-to-age conversions preserved):
        - /public/templates/pp/featured-search-default.php
        - /public/templates/pp/featured-search-compact.php
        - /public/templates/pp/featured-search-carousel.php
        - /public/templates/pp/found-search-default.php
        - /public/templates/pp/lost-search-default.php
        - /public/templates/pp/universal-search-default.php (auto-fixed via trait)
        - /public/templates/pp/universal-search-structured.php (auto-fixed via trait)
        - /public/templates/pp/adopt-search-default.php (auto-fixed via trait)
        - /public/templates/af/universal-search-structured.php
        - /public/templates/af/featured-search-default.php
        - /public/templates/af/featured-search-compact.php
        - /public/templates/af/featured-search-carousel.php
        - /public/templates/rg/adopt-search-default.php
        - /public/templates/rg/adopt-search-compact.php
        - /public/templates/rg/adopt-search-carousel.php
        - /public/templates/rg/adopt-celebration-similar.php
+ Item 2: PetPoint list method - remove detail page links from search cards
    * List method has no detail page, but search cards were wrapped in `<a>` tags pointing to invalid URLs.
    * /public/templates/pp/universal-search-default.php
        - `buildDetailUrl()` returns empty string for list method.
        - Card wrapper uses `<div>` instead of `<a>` when no detail URL.
        - `buildIconString()` uses `<span>` instead of `<a>` when no detail URL.
    * /public/templates/pp/universal-search-structured.php
        - `$detailsPage` set to empty string for list method.
        - `$photoClickable` driven by `!empty($detailsPage)` instead of hardcoded `true`.
        - Name field renders as `<span>` instead of `<a>` when no detail URL.
        - Fixed `resolveMethodTypeFromCall()` to recognize `'adoptionlist'` as list method type.
+ Item 3: Lost/found detail title - sex field and location fixes
    * Lost and found detail titles showed "Sex Dog Lost in Not Defined" instead of "Male Dog Lost in Eugene". Two issues: `getAnimalSex()` only handled array structure but `ensureIconFields` injects raw strings; lost templates used `jurisdiction` which doesn't exist for lost animals.
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - `getAnimalSex()`: now handles both `['value' => 'Male']` array and raw `'Male'` string from `ensureIconFields`.
        - `getJurisdiction()`: same dual-structure handling for found templates.
    * /public/templates/pp/lost-default.php
        - Overrides `renderTitle()` with `getLostCity()` using `PetPointFields::LOCATION_LOST_CITY` instead of jurisdiction.
    * /public/templates/pp/lost-poster.php
        - Same `renderTitle()` override and `getLostCity()` method as lost-default.
    * /public/templates/includes/class-pet-match-pro-poster-template-trait.php
        - `renderFoundLostPosterCTA()`: lost posters use city for "Last Seen:" instead of jurisdiction. Found posters still use jurisdiction.
        - Added `'city'` -> `PetPointFields::LOCATION_LOST_CITY` mapping to `getPosterFieldKey()`.
+ Item 4: Detail templates showing fields not selected in admin
    * `BaseDetailTemplate::renderDetails()` called `getAnimalDetails()` which returns ALL available fields for the method type, not just admin-selected ones. Combined with `ensureIconFields` injecting extra fields (species, sex, breed, etc.) into `$animalDetails`, unselected fields were rendered on detail pages.
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - `renderDetails()`: now reads admin-selected field list from filter options (`{method}_animal_details`) and filters the full field config to only include those fields before building labels.
        - Added `getAdminDetailList()` method to read admin detail field selections from `pet-match-pro-filter` option, matching the same source used by `AllApi::showDetails()`.

Version 8.0.3 - March 17, 2026 - PetPoint method types, filter key migration, search form/sort button fixes, eager apiFunction init
+ Item 1: PetPoint default method type dropdown limited to adopt only
    * `getDefaultMethodTypes()` in admin settings hardcoded PetPoint to only return `adopt`, ignoring found/lost/list that PetPoint supports at Premium+.
    * /admin/class-pet-match-pro-admin-settings.php
        - PetPoint branch now returns found, lost, and list method types gated at `Constants::PREMIUM_LEVEL`, matching the AnimalsFirst pattern.
+ Item 2: PetPoint filter option key mismatch - admin saved `adopt_get_criteria`, code read `adopt_search_criteria`
    * PP admin registered search criteria checkboxes with `Settings::SEARCH_POST_METHOD` (`get`) producing keys like `adopt_get_criteria`. The API code and AllApi read `Settings::SORTFIELDS` (`search_criteria`). AnimalsFirst and RescueGroups already used `search_criteria`. This mismatch caused the traditional search form to never render for PetPoint when relying on admin settings (no shortcode `filter=` param).
    * /includes/pp/class-pet-match-pro-pp-options.php
        - `registerSearchCriteria()`: Changed field ID from `$methodType . '_' . Settings::SEARCH_POST_METHOD . '_criteria'` to `$methodType . '_' . Settings::SORTFIELDS` to align with AF/RG and the API read path.
+ Item 3: Data migration system for option key renames
    * No migration system existed. Added versioned migration runner hooked to `admin_init` with version tracking via `pmp_data_version` option.
    * /includes/class-pet-match-pro-migrator.php (new)
        - `Migrator::run()` checks stored version against current, runs pending migrations once.
        - Migration 1: Renames PetPoint filter keys `{method}_get_criteria` -> `{method}_search_criteria` (adopt, found, lost, list) in `pet-match-pro-filter` option for existing sites.
    * /pet-match-pro.php
        - Loads migrator in `loadDependencies()` and hooks `Migrator::run()` on `admin_init`.
+ Item 4: `Shortcodes::FILTER` missing from `processSearchParameters()` return array
    * The filter shortcode parameter was never included in `searchParms` for any partner. The trait's `getFilterFields()` and `hasSortEnabled()` fell back to `$this->apiInstance->searchParms[Shortcodes::FILTER]` which was always unset. This prevented client-side filter dropdowns and sort buttons from rendering.
    * /includes/class-pet-match-pro-all-api.php
        - Added `processFilter()` private method: uses explicit shortcode `filter` param if set (normalizes disable/off, strips whitespace), otherwise falls back to admin filter list via `getFilterListString()`.
        - Added `Shortcodes::FILTER => $this->processFilter($details, $method)` to `processSearchParameters()` return array. Fixes all three partners.
+ Item 5: Eager `apiFunction` initialization in search templates
    * Six AF templates and PP universal-search-default used lazy initialization (`private ?object $apiFunction = null` with a `getApiFunction()` getter). The `SearchTemplateTrait` accesses `$this->apiFunction` directly, causing `TypeError: method_exists(): Argument #1 must be of type object|string, null given` when `renderSearchForm()` was called before the getter.
    * /public/templates/pp/universal-search-default.php - Added eager init in constructor matching adopt-search-default pattern.
    * /public/templates/af/universal-search-filter-widget.php - Added `$this->getApiFunction()` call in constructor.
    * /public/templates/af/universal-search-no-filter.php - Added `$this->getApiFunction()` call in constructor.
    * /public/templates/af/featured-search-default.php - Added `$this->getApiFunction()` call in constructor.
    * /public/templates/af/featured-search-carousel.php - Added `$this->getApiFunction()` call in constructor.
    * /public/templates/af/featured-search-compact.php - Added `$this->getApiFunction()` call in constructor.
+ Item 6: Filter disable/off check missing from structured and filter-widget templates
    * Four templates rendered filter/sort UI without checking if `filter=disable` or `filter=off` was set, inconsistent with default templates.
    * /public/templates/pp/universal-search-structured.php - Added `filterParam` disable/off guard.
    * /public/templates/af/universal-search-structured.php - Added `filterParam` disable/off guard.
    * /public/templates/af/universal-search-filter-widget.php - Added `$filterDisabled` check to `$showClientBar` condition.
    * /public/templates/rg/adopt-search-structured.php - Added `filterParam` disable/off guard.

Version 8.0.2 - March 16, 2026 - Security hardening: XSS escaping, path traversal, SQL preparation, input sanitization, type safety
+ Item 1: `get_option()` array type safety via `Constants::getOptionAsArray()`
    * `get_option()` can return a string instead of an array when a WordPress option was saved incorrectly, causing PHP Fatal TypeError on `declare(strict_types=1)` classes with typed `array` properties. Production error on PetPoint site: `Cannot assign string to property ... of type array`.
    * /pet-match-pro.php
        - Added `Constants::getOptionAsArray(string $optionSuffix): array` static helper. Wraps `get_option()` with `is_array()` guard, returns empty array on non-array values.
    * 12 files converted from raw `get_option()` to `Constants::getOptionAsArray()`:
        - /includes/class-pet-match-pro-all-api.php - generalOptions, filterOptions, labelOptions, contactOptions
        - /includes/pp/class-pet-match-pro-pp-api.php - generalOptions, contactOptions, labelOptions, filterAdminOptions
        - /includes/af/class-pet-match-pro-af-api.php - generalOptions, contactOptions, labelOptions, filterAdminOptions
        - /includes/rg/class-pet-match-pro-rg-api.php - generalOptions, contactOptions, labelOptions, filterAdminOptions
        - /includes/class-pet-match-pro.php - generalOptions
        - /includes/class-pet-match-pro-filter-override-manager.php - generalOptions, filterOptions
        - /includes/cache/class-pet-match-pro-api-cache.php - generalOptions
        - /admin/class-pet-match-pro-admin-settings.php - generalOptions, filterOptions, contactOptions, labelOptions
        - /admin/class-pet-match-pro-functions.php - generalOptions
        - /public/class-pet-match-pro-public.php - generalOptions, colorOptions
        - /public/partials/pet-match-pro-public-color-css.php - colorOptions
        - /includes/analytics/class-pet-match-pro-analytics-db.php - generalOptions
+ Item 2: `basename()` path traversal prevention on template and icon resolution
    * Template names from shortcode attributes and admin settings were passed directly to `file_exists()` / `require` without path sanitization. A crafted `template` shortcode parameter could traverse directories.
    * /includes/class-pet-match-pro-all-api.php
        - `processTemplate()`: `basename()` on shortcode `template` param and admin `search_template_*` option before file resolution.
        - `resolveSearchTemplate()`: `basename()` on cleaned template name before `file_exists()`.
        - `resolveDetailTemplate()`: `basename()` on shortcode override and admin `detail_template_*` option return values.
        - `getImageUrl()`: `basename()` on `$filename` parameter.
    * /includes/pp/class-pet-match-pro-pp-api.php - `getIconUrl()`: `basename()` on icon filename.
    * /includes/af/class-pet-match-pro-af-api.php - `getIconUrl()`: `basename()` on icon filename.
    * /includes/rg/class-pet-match-pro-rg-api.php - `getIconUrl()`: `basename()` on icon filename.
    * /includes/pp/class-pet-match-pro-pp-detail-functions.php - `basename()` on detail template resolution.
    * /includes/af/class-pet-match-pro-af-detail-functions.php - `basename()` on detail template resolution.
    * /includes/rg/class-pet-match-pro-rg-detail-functions.php - `basename()` on detail template resolution.
+ Item 3: `esc_attr()` on onclick handlers in 6 template files (XSS hardening)
    * Older RG search templates, AF structured search, and the base detail template output `onClickValue()` results into `onclick="%s"` via `sprintf` without `esc_attr()`. Newer PP/AF templates already escaped correctly.
    * /public/templates/rg/adopt-search-default.php - `$imageOnClick`, `$nameOnClick`
    * /public/templates/rg/adopt-search-carousel.php - `$imageOnClick`, `$nameOnClick`, `$buttonOnClick`
    * /public/templates/rg/adopt-search-compact.php - `$imageOnClick`, `$nameOnClick`, `$buttonOnClick`
    * /public/templates/rg/adopt-search-structured.php - `$imageOnClick`, `$nameOnClick`
    * /public/templates/af/universal-search-structured.php - `$imageOnClick`, `$nameOnClick`
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php - sponsor link `$onClick`, poster button `$onClick`
+ Item 4: `wp_json_encode()` in GA4/GTM onclick builders (XSS hardening)
    * `buildGa4OnClick()` and `buildGtmOnClick()` used manual `str_replace` for JS escaping and string concatenation. `$gaName` was unescaped entirely.
    * /includes/class-pet-match-pro-all-api.php
        - `buildGa4OnClick()`: replaced manual param building with `wp_json_encode($gaName)` and `wp_json_encode($gaParms)`.
        - `buildGtmOnClick()`: replaced manual param building with `array_merge()` + `wp_json_encode($pushData)`.
+ Item 5: `$wpdb->prepare()` on hardcoded LIKE queries (SQL best practice)
    * 5 queries used hardcoded LIKE patterns without `$wpdb->prepare()`. Not exploitable (no user input), but WordPress coding standards require prepared statements.
    * /admin/class-pet-match-pro-admin-settings.php
        - Transient count query (SEO diagnostics render): wrapped with `$wpdb->prepare()` + `$wpdb->esc_like()`.
        - AJAX transient list query: wrapped with `$wpdb->prepare()` + `$wpdb->esc_like()`.
        - AJAX transient count query: wrapped with `$wpdb->prepare()` + `$wpdb->esc_like()`.
        - AJAX transient delete query: wrapped with `$wpdb->prepare()` + `$wpdb->esc_like()`.
        - AJAX post-delete count query: wrapped with `$wpdb->prepare()` + `$wpdb->esc_like()`.
+ Item 6: Sanitize `$_GET`/`$_REQUEST` at point of assignment in similar animal templates
    * Three templates assigned `$_GET[Fields::ID]` to variables using null coalescing without sanitization.
    * /public/templates/af/adopt-conversion-similar.php - `$currentAnimalId` now uses `sanitize_text_field(wp_unslash(...))`.
    * /public/templates/af/adopt-profile-3-column-similar.php - `$currentAnimalId` now uses `sanitize_text_field(wp_unslash(...))`.
    * /public/templates/rg/adopt-similar.php - `$currentAnimalId` now uses `sanitize_text_field(wp_unslash(...))`.
+ Item 7: JSON decode validation in AJAX handlers
    * AJAX handlers decoded JSON from `$_POST` without validating the decode result was the expected type.
    * /includes/analytics/class-pet-match-pro-analytics-ajax.php - search params `json_decode` result validated with `is_array()`, falls back to `null`.
    * /admin/class-pet-match-pro-admin-settings.php - API response `json_decode` result guarded with `is_array()` check, falls back to empty array.
+ Item 8: Analytics DB null guards
    * `$wpdb->get_results()` and `$wpdb->get_row()` can return `null` on query failure. Downstream code assumed array/object returns.
    * /includes/analytics/class-pet-match-pro-analytics-db.php - added null coalescing guards on query results.

Version 8.0.1 - March 15, 2026 - SEO Diagnostics tools, JSON-LD refactor, lost/found SEO, price/currency, location resolution, RG direct API fix
+ Item 1: SEO Diagnostics accordion in Tools tab (Preferred)
    * No admin tools existed to inspect or manage SEO artifacts. Admins had to view page source, check robots.txt manually, and query the database to manage transients.
    * /admin/class-pet-match-pro-admin-settings.php
        - `renderToolsSEODiagnostics()`: new method renders 5 tool groups in the SEO Diagnostics accordion:
          1. JSON-LD Preview - enter animal ID + method type, preview rendered structured data
          2. Pretty URL Status - verify rewrite rules and query vars are registered, flush button
          3. Robots.txt Check - verify sitemap directive present in robots.txt output
          4. Sitemap Status - transient status, generation timestamp, entry counts per method type, rebuild/view buttons
          5. Transient Cache - list/count/remove celebration page transients (pmp_animal_*)
        - 6 new AJAX handlers: `ajax_seo_preview_jsonld`, `ajax_seo_flush_rewrite_rules`, `ajax_seo_rebuild_sitemap`, `ajax_seo_rebuild_and_view_sitemap`, `ajax_seo_list_transients`, `ajax_seo_clear_transients`
        - Accordion inserted between License Summary and Setup Wizard in alphabetical order.
        - Each tool group shows disabled message when its parent SEO feature is toggled off.
        - JSON-LD Preview AJAX handler bootstraps partner API client, fetches animal data including fee and location fields, calls `SeoManager::buildJsonLdArray()` static method.
        - Transient Cache list sorted by newest first, then alphabetically by animal name.
    * /admin/partials/pmp-option-levels-tools.php
        - Added `_seo_diagnostics` at `PREFERRED_LEVEL`.
    * /admin/partials/pmp-admin-info.php
        - Added description for SEO Diagnostics accordion.
    * /admin/js/pet-match-pro-admin.js
        - Added JS handlers for all 5 tool groups: JSON-LD preview AJAX, flush rewrite rules, rebuild sitemap, rebuild & view (opens new tab), load/clear/remove transients with table rendering.
    * /admin/css/pet-match-pro-admin.css
        - Added styles for status lines (`.pmp-seo-diag-status-line`), JSON-LD preview `<pre>` block (`.pmp-seo-diag-pre`), transient table (`.pmp-seo-diag-table`), disabled messages, and status icons (green/red/blue).
+ Item 2: JSON-LD builder refactored to static method with method-aware output
    * `outputJsonLd()` built the Schema.org array and echoed it - not callable from the admin AJAX preview without a full SeoManager instance. Also treated all method types as adopt (always showed "for Adoption", offers block, and "Animal Adoption" category).
    * /includes/class-pet-match-pro-seo.php
        - Extracted `buildJsonLdArray(array $data, array $generalOptions): array` as a public static method. Builds meta description, OG image, and canonical URL internally from general options - no instance dependencies.
        - `outputJsonLd()` now calls `self::buildJsonLdArray()` and echoes the result.
        - Method-aware JSON-LD output:
          * Adopt: "Name - Species for Adoption", offers block with price/currency, category "Animal Adoption"
          * Lost: "Name - Species", no offers block, category "Lost Pet", Status/Location in additionalProperty
          * Found: "Name - Species", no offers block, category "Found Pet", Status/Location in additionalProperty
        - Lost/found meta descriptions use location-aware templates: "was last seen near {location}" / "was found near {location}"
        - Nameless stray fallback: "Found Dog" / "Lost Cat" when name is empty
        - Added `set_transient('pmp_sitemap_generated_at', ...)` in `renderSitemap()` for Sitemap Status diagnostic display.
    * /includes/class-pet-match-pro-seo.php - `onTemplateRedirect()`
        - Celebration page redirect now only fires for adopt method. Lost/found may return null from `fetchAnimalDataForSeo()` due to empty name fields on strays, which is not an adoption event.
+ Item 3: JSON-LD price and currency from actual data
    * The JSON-LD offers block hardcoded `"price": "0"` and `"priceCurrency": "USD"` regardless of the animal's actual adoption fee or the admin's currency setting.
    * /public/class-pet-match-pro-public.php
        - `fetchAnimalDataForSeo()`: added fee field lookup per partner (PP: `price`, AF: `adoption_fee`, RG: `animaladoptionfee`). Returns `fee` in data array.
    * /includes/class-pet-match-pro-seo.php
        - `buildJsonLdArray()`: reads `Settings::CURRENCY_SYMBOL` from admin settings, maps to ISO 4217 code ($->USD, CA$/C$->CAD, £->GBP, €->EUR). Extracts numeric value from fee field, stripping currency symbols. Falls back to "0" when empty.
    * /admin/class-pet-match-pro-admin-settings.php
        - `ajax_seo_preview_jsonld()`: fetches fee field per partner for JSON-LD preview.
+ Item 4: Lost/found location resolution for SEO
    * Lost/found JSON-LD and meta descriptions showed no location data. AnimalsFirst `location` field returned "Shelter" (not useful), and the real address was nested inside an `address` object.
    * /public/class-pet-match-pro-public.php
        - `fetchAnimalDataForSeo()`: added location fetch for lost/found animals with multi-level fallback:
          1. Primary location field (PP: `lostlocation`/`foundlocation`, AF: `location`)
          2. Skip if value is "Shelter" (generic, not useful for SEO)
          3. AF fallback: read `address.long_address` from cache directly (bypasses `animalDetail()` string return type since `address` is a nested object)
          4. Final fallback: build from `city` + `state` fields
        - Returns `location` in data array.
    * /admin/class-pet-match-pro-admin-settings.php
        - `ajax_seo_preview_jsonld()`: same location resolution logic with AF nested address fallback reading from cache.
+ Item 5: PetPoint fee field constant fix
    * AJAX JSON-LD preview and `fetchAnimalDataForSeo()` referenced `PetPointFields::ADOPTION_FEE` which does not exist - caused fatal TypeError. PetPoint uses `PetPointFields::PRICE` for the fee field.
    * /public/class-pet-match-pro-public.php
        - Fixed fee field match: PP uses `PetPointFields::PRICE`, AF uses `AnimalsFirstFields::ADOPTION_FEE`.
    * /admin/class-pet-match-pro-admin-settings.php
        - Same fix in AJAX handler.
+ Item 6: RescueGroups JSON-LD Preview - direct API call bypass
    * RG `animalDetail()` has frontend-context dependencies (`$_GET` page builder guard, field-level permission checks, method parameter parsing) that return empty in AJAX context. JSON-LD Preview showed "Animal not found" for all RG animals.
    * /admin/class-pet-match-pro-admin-settings.php
        - `ajax_seo_preview_jsonld()`: RG path now makes a direct `wp_remote_post` to the RescueGroups JSON API (`publicView` action) requesting only the fields needed for JSON-LD (name, species, breed, description, photos, fee, status). Bypasses `animalDetail()` entirely.
        - Resolves photo from `animalPictures` array (fullsize -> thumbnail fallback).
        - PP and AF paths unchanged - still use `animalDetail()`.
+ Item 7: Removed custom plugin update checker
    * Plugin had custom `pre_set_site_transient_update_plugins`, `plugins_api`, `plugin_row_meta` (Update Check link), and `in_plugin_update_message` hooks. WordPress.org now handles plugin updates natively.
    * /admin/license/class-pet-match-pro-license.php
        - Removed 4 plugin update hooks from the constructor's `else` block. Theme update hooks retained.
+ Item 7: Translation strings for SEO Phase 2 and SEO Diagnostics
    * 48 new translatable strings were not in the .pot/.po files.
    * /languages/pet-match-pro.pot
        - Added 48 new msgid entries for SEO settings toggles, diagnostics UI labels, AJAX messages, and admin info description.
    * /languages/pet-match-pro-es_ES.po
        - Added Spanish translations for all 48 new strings.
    * /languages/pet-match-pro-es_ES.mo
        - Recompiled with updated Spanish translations.

Version 8.0.0 - March 15, 2026 - SEO Phase 2: JSON-LD structured data, XML sitemap, robots meta control
+ Item 1: JSON-LD structured data for Google rich results
    * Animal detail pages had no structured data - Google could not display rich results (name, photo, breed) in search listings.
    * /includes/class-pet-match-pro-seo.php
        - `outputJsonLd()`: new method outputs Schema.org `Product` type with animal name, species, breed, photo, shelter info, and canonical URL inside `<script type="application/ld+json">` tag.
        - Called from `onWpHead()` when `SEO_JSONLD_ENABLED` is truthy and animal data is populated.
        - Reuses existing `buildMetaDescription()`, `getOgImage()`, `buildCanonicalUrl()` - no duplicated logic.
        - Uses `wp_json_encode()` with `JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT`.
    * /pet-match-pro.php
        - Added `Settings::SEO_JSONLD_ENABLED` constant.
    * /admin/partials/pmp-option-levels-analytics.php
        - Added `SEO_JSONLD_ENABLED` at `PREFERRED_LEVEL`.
    * /admin/class-pet-match-pro-admin-settings.php
        - `renderSeoSettings()`: added "Enable Structured Data (JSON-LD)" toggle after Pretty URLs.
        - AJAX JS payload: added `seo_jsonld_enabled` field.
        - `ajax_save_seo_settings()`: saves `SEO_JSONLD_ENABLED`.
        - `sanitizeGeneralOptions()`: added to `$analyticsFields` preservation array.
+ Item 2: XML sitemap for animal detail page discovery
    * Googlebot could not discover pretty URLs without crawling search result pages first. No sitemap existed for animal detail pages.
    * /includes/class-pet-match-pro-seo.php
        - `registerSitemapRewriteRules()`: adds rewrite rule `^pmp-sitemap\.xml$` -> `index.php?pmp_sitemap=1`.
        - `registerSitemapQueryVars()`: registers `pmp_sitemap` query var.
        - `onSitemapRequest()`: hooked to `template_redirect` at priority 0, intercepts sitemap requests.
        - `renderSitemap()`: serves cached XML from `pmp_sitemap_xml` transient, regenerates when expired.
        - `buildSitemapXml()`: iterates enabled method types (adopt + lost/found if enabled), fetches animal list via `fetchAnimalListForSitemap()`, builds pretty URLs for each animal.
        - `outputSitemapXml()`: sends `Content-Type: application/xml` header with `X-Robots-Tag: noindex`.
        - `filterRobotsTxt()`: appends `Sitemap: {home_url}/pmp-sitemap.xml` to robots.txt output.
        - Hooks only register when both sitemap and pretty URLs are enabled.
    * /public/class-pet-match-pro-public.php
        - `fetchAnimalListForSitemap()`: new method fetches lightweight animal list (id, name, species) for a given method type. Delegates to partner API's `getAnimalListForSitemap()` if available, otherwise calls `createSearch()` with output buffering and extracts from internal result cache. Max 1000 entries.
    * /pet-match-pro.php
        - Added `Settings::SEO_SITEMAP_ENABLED` and `Settings::SEO_SITEMAP_TTL` constants.
    * /admin/partials/pmp-option-levels-analytics.php
        - Added `SEO_SITEMAP_ENABLED` and `SEO_SITEMAP_TTL` at `PREFERRED_LEVEL`.
    * /admin/class-pet-match-pro-admin-settings.php
        - `renderSeoSettings()`: added "Enable XML Sitemap" toggle with "Requires Pretty URLs" note, and "Sitemap Refresh Interval" dropdown (12h/24h/48h/72h, default 24h).
        - AJAX JS payload: added `seo_sitemap_enabled` and `seo_sitemap_ttl` fields.
        - `ajax_save_seo_settings()`: saves both fields, clears `pmp_sitemap_xml` transient on every save, flushes rewrite rules when sitemap toggle changes.
        - `sanitizeGeneralOptions()`: added to `$analyticsFields` preservation array.
+ Item 3: Robots meta control - per-method-type noindex directives
    * All animal detail pages were indexable by default. Lost/found pages go stale quickly and create dead-end indexed pages, but admins had no way to noindex them selectively.
    * /includes/class-pet-match-pro-seo.php
        - `isNoindexMethod()`: new method checks the noindex setting for the current base method type (adopt/lost/found).
        - `onWpHead()`: when noindex is enabled for the current method, outputs `<meta name="robots" content="noindex, follow">` and skips all other SEO meta output (canonical, OG, Twitter Card, JSON-LD).
    * /pet-match-pro.php
        - Added `Settings::SEO_ROBOTS_NOINDEX_ADOPT`, `SEO_ROBOTS_NOINDEX_LOST`, `SEO_ROBOTS_NOINDEX_FOUND` constants.
    * /admin/partials/pmp-option-levels-analytics.php
        - Added all three at `PREFERRED_LEVEL`.
    * /admin/class-pet-match-pro-admin-settings.php
        - `renderSeoSettings()`: added "Robots Meta" section with three checkboxes (Noindex Adopt, Lost, Found). Lost/Found checkboxes only render when the partner supports those method types. Defaults: adopt=off, lost=off, found=off (admin must opt in).
        - AJAX JS payload: added three `seo_robots_noindex_*` fields.
        - `ajax_save_seo_settings()`: saves all three fields.
        - `sanitizeGeneralOptions()`: added to `$analyticsFields` preservation array.

Version 7.9.9 - March 14, 2026 - Shortcode/admin priority system, per-field license gating on frontend, filter form priority, detail config messages, admin checkbox save fix
+ Item 1: Shortcode > admin > config message priority for search details=
    * Search shortcode details= parameter was not license-gated - fields above the user's license level were displayed. When no shortcode param was present, admin settings were not picked up for adopt due to a key mismatch (animal_details_adopt_search vs adopt_search_details).
    * /includes/class-pet-match-pro-all-api.php
        - `getDetailList()`: shortcode details= values now filtered through `filterFieldsByLicense()` which checks per-field levels from partner field-levels files.
        - `filterFieldsByLicense()`: new method loads field levels and removes fields exceeding the user's license.
        - `getDetailKey()`: fixed adopt search key to use consistent `{method}_search_details` pattern for all methods.
        - `ensureNameFirst()`: new method ensures partner-specific name field is first when using admin settings for search.
        - `getNameField()`: new helper returns partner-specific name field constant, also used by `getAnimalName()`.
        - `showDetails()`: returns empty array and logs error when no fields configured (empty array or missing key).
        - `getFilterListString()`: returns `Constants::DISABLE` instead of `Constants::ERROR` when no admin filter setting exists.
        - `ageInYears()`: removed hardcoded Free license block - gating now handled by level file only.
+ Item 2: Filter form priority - shortcode filter= > admin setting > no form
    * Filter form displayed all license-allowed fields regardless of admin checkbox settings. When all filter checkboxes were unchecked, the form still rendered with all fields.
    * /includes/pp/class-pet-match-pro-pp-api.php
        - `processFilterOptions()`: returns empty array instead of all fields when admin setting is absent or empty.
    * /includes/af/class-pet-match-pro-af-api.php
        - Admin filter branch: initializes `$filterOptions = []` before check, removes fallback to all available fields.
    * /includes/rg/class-pet-match-pro-rg-api.php - already correct (returns empty when no admin setting).
+ Item 3: Admin checkbox save fix - multiple unchecks not persisting
    * Unchecking multiple filter/detail checkboxes at once and saving did not persist - the sanitize callback restored old values for all missing POST keys. Single unchecks worked because the merge loop only restored one key.
    * /admin/class-pet-match-pro-functions.php
        - `sanitize_filter_options()`: existing array-type keys absent from POST now saved as empty array instead of restoring previous values. Scalar keys (disabled fields) still preserved.
+ Item 4: Search config message with ERROR label and pmp-error styling
    * When no search detail fields are configured (no shortcode, no admin setting), templates displayed the message without a label or error styling.
    * 10 HTML-pattern templates (RG default/structured/compact/carousel, AF structured/featured-carousel/featured-default/featured-compact, PP featured-carousel/featured-compact):
        - Added `pmp-error` class to details container, label span, and value span.
        - Added ERROR constant as label text.
    * 12 array-pattern templates (PP adopt-default/featured-default/found-default/lost-default/universal-default/universal-structured/celebration-similar, AF universal-default/filter-widget/no-filter/celebration-similar, RG celebration-similar):
        - Changed `'label' => ''` to `'label' => Constants::ERROR`.
        - Added `$errorClass` variable and applied `pmp-error` class to container, label, and value elements in rendering loops.
    * /public/css/pet-match-pro-styles.css
        - Added scoped rule: `.pmp-search-result-details .pmp-error` resets padding, font-size, grid-column to prevent standalone error styles bleeding into search cards.
        - Added `margin-top: 1.5rem` to `.pmp-search-results-container` for spacing between filter submit button and results.
+ Item 5: [pmp-details] shortcode priority and config message
    * AnimalsFirst detail pages ignored the shortcode details= parameter entirely - only admin settings were used. PetPoint and RescueGroups used showDetails() but had no front-end message when no fields were configured.
    * /includes/af/class-pet-match-pro-af-api.php
        - `outputDetails()`: added `showDetails()` call to apply shortcode > admin priority. Filters `detailsOutput` to only allowed keys from resolved field list. Returns error message when empty.
    * /includes/pp/class-pet-match-pro-pp-api.php
        - `outputDetails()`: added early return with "Detail fields not configured." error message when `showDetails()` returns empty.
    * /includes/rg/class-pet-match-pro-rg-api.php
        - `outputDetails()`: added early return with "Detail fields not configured." error message when `showDetails()` returns empty.
+ Item 6: [pmp-detail] per-field license gating
    * The [pmp-detail] shortcode was blanket-blocked for Free users. Premium users could access Preferred-level fields without restriction.
    * /public/class-pet-match-pro-public.php
        - `handleDetailShortcode()`: removed blanket Free license block. Added per-field license check using field levels from partner field-levels files. Fields above the user's license show upgrade notice; allowed fields render normally.
+ Item 7: Thumbnails and age-in-years moved to Free level
    * Photo thumbnails on detail pages showed an upgrade notice for Free users despite being a basic display feature. Age-in-years conversion was gated at Premium in both the level file and a hardcoded check.
    * /admin/partials/pmp-option-levels-general.php
        - Changed `Settings::THUMBS` level from `PREMIUM_LEVEL` to `FREE_LEVEL`.
        - Changed `Settings::AGE_YEARS` level from `PREMIUM_LEVEL` to `FREE_LEVEL`.
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - Removed hardcoded `licenseLevel === FREE_LEVEL` block that forced upgrade notice for thumbnails.
    * /includes/class-pet-match-pro-all-api.php
        - `ageInYears()`: removed `licenseTypeId === FREE_LEVEL` check - level file now sole gating mechanism.

Version 7.9.9 - March 14, 2026 - Featured method type license gating, per-field license gating in admin, diagnostics license indicators, badge/color consistency, system info fixes
+ Item 1: Gate featured method type by license level
    * `type=featured` in the search shortcode was never license-checked. `processFeatured()` returned 1/0 based purely on type value with no license comparison.
    * /admin/partials/pmp-option-levels-general.php
        - Added featured search level (PREMIUM) and enable entries (PP: enabled, AF: enabled, RG: disabled).
    * /includes/class-pet-match-pro-all-api.php
        - `processFeatured()` now accepts levels array and checks user license against required level. FREE users get `featured=0` (runs as normal adopt), PREMIUM+ get `featured=1`.
+ Item 2: Diagnostics method type license indicators
    * Diagnostics always shows all method types available for the partner (including featured when adopt exists). Each method type now carries a `licensed` flag based on the user's license vs. the level requirement. Unlicensed types show "upgrade required" with suppressed template check marks.
    * Adopt is always FREE. Non-adopt types (found, lost, list) use the `level_method_types` level (Premium). Featured uses its own `level_featured` key (Premium).
    * /admin/class-pet-match-pro-admin-settings.php
        - `ajax_api_diagnostics()` always includes featured when adopt exists. License check uses per-type level key with fallback to `level_method_types` for non-adopt types.
    * /admin/js/pet-match-pro-admin.js
        - Diagnostics method type rendering checks `isLicensed` and appends a gated indicator span when false. Template check marks suppressed to "-" when not licensed.
    * /admin/css/pet-match-pro-admin.css
        - Added `.pmp-diag-gated` style for the upgrade required indicator (red, italic).
+ Item 3: Fix API key mask and license type display in System Information
    * `ajax_system_information()` used `-` (Unicode replacement character) as the API key mask. License Type showed blank when no license code was activated.
    * /admin/class-pet-match-pro-admin-settings.php
        - Replaced `----` with bullet mask `••••`.
        - When no license code is activated, License Type now shows "Basic (no license code)" using `Constants::PLAN_BASIC` and a translatable format string.
    * /languages/pet-match-pro.pot
        - Added translatable string `"%s (no license code)"`.
    * /languages/pet-match-pro-es_ES.po
        - Added Spanish translation: `"%s (sin código de licencia)"`.
+ Item 4: Fix license badge colors and visibility
    * PHP renders badge labels as "Junior" and "Basic" (from Constants), JS generates CSS classes via `.toLowerCase()`. Only `pmp-license-badge-premium` and `pmp-license-badge-free` had CSS - the renamed classes had no background color, resulting in white text on white background.
    * /admin/css/pet-match-pro-admin.css
        - Added `.pmp-license-badge-junior` (gold, #c9a80b with white text).
        - Added `.pmp-license-badge-basic` (gray, #646970).
        - Changed `.pmp-license-badge-preferred` from green (#00a32a) to purple (#9b59b6 - matches PRF instruction badge).
        - Unified all Junior/PRO gold color references from #dba617 to #c9a80b across tools badges, accordion borders, and license indicators.
        - Added `opacity: 0.7` to `.pmp-license-feature-locked` for license summary locked rows.
    * /admin/css/pet-match-pro-wizard.css
        - Updated `.pmp-wizard-badge-locked` from light gold (#fff3cd/#856404) to solid gold (#c9a80b with white text).
        - Added `.pmp-wizard-badge-locked.pmp-wizard-badge-preferred` variant (purple, #9b59b6).
        - Updated `.pmp-wizard-upgrade-badge` from blue (#2271b1) to gold (#c9a80b).
        - Updated `.pmp-wizard-upgrade-card` border/background from blue to gold (#c9a80b/#fdf8e8).
        - Added `.pmp-wizard-field-locked` with `opacity: 0.7` for locked contact/field groups.
        - Lock icon color in preset cards changed to white for contrast on gold/purple backgrounds.
    * /includes/wizard/class-pet-match-pro-setup-wizard.php
        - Wizard step 4: All Fields preset badge now uses `pmp-wizard-badge-preferred` class (purple) instead of generic locked gold.
        - Method type badges dynamically apply `pmp-wizard-badge-preferred` when the lock is Preferred-level.
        - Wizard summary step: License type shows "Basic (no license code)" when no license is activated, matching the Tools tab system info display.
+ Item 5: Consistent opacity (0.7) across all locked/disabled contexts
    * Locked items now consistently use `opacity: 0.7` across admin and wizard:
    * /admin/css/pet-match-pro-admin.css
        - `.pmp-admin-accordion-locked` (tools tab groups)
        - `.pmp-license-feature-locked` (license summary rows)
    * /admin/css/pet-match-pro-wizard.css
        - `.pmp-wizard-method-locked` (method type cards)
        - `.pmp-wizard-preset-locked` (field preset cards)
        - `.pmp-wizard-field-locked` (contact/field groups)
+ Item 6: Per-field license gating in admin filter/detail checkboxes
    * Individual filter/detail checkboxes in the admin Partner tab were not gated by their field-level license requirement. Every field rendered as enabled regardless of license. Disabled HTML inputs are excluded from POST data, so a sanitize callback was added to preserve disabled field values on save.
    * /admin/class-pet-match-pro-functions.php
        - Added `getDisabledFields()` method: loads field levels for the method type, compares each field's required level against the user's license, returns array of field keys that exceed the user's tier.
        - Modified `checkbox_element_callback()`: accepts optional `disabled_fields` array in args, applies per-field `disabled="disabled"` attribute (section-level disable takes priority).
        - Updated `sanitize_filter_options()`: loads existing saved options and merges keys not present in POST (disabled checkboxes) to prevent value loss on save. Added proper array handling for checkbox values.
    * /admin/class-pet-match-pro-admin-settings.php
        - Registered `sanitize_filter_options()` as sanitize callback for filter option group `register_setting()`.
    * /includes/pp/class-pet-match-pro-pp-options.php
        - `registerSearchCriteria()`: added `getDisabledFields()` call with `LevelPrefix::SEARCH_FILTER`, passes `disabled_fields` in args.
        - `registerSearchDetails()`: same with `LevelPrefix::SEARCH_RESULT`.
        - `registerAnimalDetails()`: same with `LevelPrefix::ANIMAL_DETAIL`.
    * /includes/af/class-pet-match-pro-af-options.php
        - `registerSearchCriteria()`: added `disabled_fields` with `LevelPrefix::SEARCH_FILTER`.
        - `registerSearchDetails()`: added `disabled_fields` with `LevelPrefix::SEARCH_DETAIL` (AF-specific prefix).
        - `registerAnimalDetails()`: added `disabled_fields` with `LevelPrefix::ANIMAL_DETAIL`.
    * /includes/rg/class-pet-match-pro-rg-options.php
        - Search Criteria: added `disabled_fields` with `LevelPrefix::SEARCH_FILTER`.
        - Search Details: added `disabled_fields` with `LevelPrefix::SEARCH_RESULT`.
        - Animal Details: added `disabled_fields` with `LevelPrefix::ANIMAL_DETAIL`.

Version 7.9.8 - March 13, 2026 - Icon class filter fix, strtolower TypeError, similar animals filter disable, client-side date sort + init sort, CSS specificity/color fixes, carousel inline mode fix, age ERROR fix
+ Item 1: Fix AF 3-column-similar detail template icon class using filtered value
    * When a filter override changed a field value (e.g., "Male" -> "Boy"), the icon CSS class used the filtered value instead of the original API value, breaking the icon filename match.
    * /public/templates/af/adopt-profile-3-column-similar.php
        - Both shortcode-filtered and unfiltered loops now capture $originalValue before resolveDetailFieldDisplayValue() and pass it to getFieldIconClass() instead of the post-filter $value.
+ Item 2: Fix PHP 8 strtolower TypeError on integer option keys in AllApi
    * Filter options arrays can have numeric keys/values. PHP 8 no longer auto-casts int->string for strtolower(), causing a fatal error during case-insensitive selected value matching in addFormFields().
    * /includes/class-pet-match-pro-all-api.php
        - Line 411: Cast $optKey and $optValue to (string) before strtolower() in the selected value matching loop.
+ Item 3: Add missing filter=disable to PP navigation-similar template
    * Similar animals section was rendering the full legacy search form because the shortcode params did not include filter=disable. The AF and PP conversion-similar templates already had this param.
    * /public/templates/pp/adopt-details-navigation-similar.php
        - Added Shortcodes::FILTER => Constants::DISABLE to the shortcode params array in renderSimilarAnimals().
+ Item 4: Fix client-side date sort for MM-DD-YYYY format and apply initial sort on page load
    * The JS sort comparison only recognized YYYY-MM-DD dates (ISO format). PetPoint founddate/lostdate arrive as MM-DD-YYYY, causing parseFloat() to extract just the month number and sort incorrectly.
    * Additionally, the client-side init() read the active sort button state but never called applyFiltersAndSort(), so cards displayed in server order on initial page load even though a sort button appeared active.
    * /public/js/pet-match-pro-public.js
        - sortItems(): Added parseDate() helper that handles both YYYY-MM-DD and MM-DD-YYYY formats, parses into Date objects, and compares chronologically. Dates now take priority over numeric/string fallback.
        - init(): Added applyFiltersAndSort() call when a default sort field is active on page load. Moved updateSession() into an else branch so it only runs when no sort is applied (applyFiltersAndSort() already calls updateSession() internally).
+ Item 5: Standardize search template field display - remove hardcoded fallbacks, gate name rendering
    * Problem 1: All search templates had hardcoded fallback field lists ($fieldOrder arrays, DEFAULT_DETAILS constants, match() blocks) used when $allowedFields/$searchResultDetails was empty. Fields must come ONLY from configured sources (shortcode details= -> admin config). When empty, templates now display "Search detail fields not configured." visible to all visitors.
    * Problem 2: Multiple templates rendered the animal name unconditionally outside the field loop, ignoring whether name was in the configured field list. Name now only renders when present in configured fields.
    * PetPoint templates (9 files):
        - /public/templates/pp/adopt-search-default.php - Removed $fieldOrder fallback, added configure message
        - /public/templates/pp/adopt-celebration-similar.php - Same
        - /public/templates/pp/found-search-default.php - Same
        - /public/templates/pp/lost-search-default.php - Same
        - /public/templates/pp/featured-search-default.php - Removed fallback, added configure message, added $nameInConfig check
        - /public/templates/pp/featured-search-carousel.php - Removed DEFAULT_DETAILS, simplified getDetailsToDisplay(), added configure message HTML, added $nameInConfig check
        - /public/templates/pp/featured-search-compact.php - Same as carousel
        - /public/templates/pp/universal-search-default.php - Removed fallback, added configure message, added $nameInConfig check
        - /public/templates/pp/universal-search-structured.php - Removed match() fallback block, added configure message
    * AnimalsFirst templates (8 files):
        - /public/templates/af/universal-search-default.php - Removed $defaultFields fallback, added configure message, added $nameInConfig check
        - /public/templates/af/universal-search-filter-widget.php - Same
        - /public/templates/af/universal-search-no-filter.php - Same
        - /public/templates/af/universal-search-structured.php - Added configure message early return in buildDetailsSection()
        - /public/templates/af/featured-search-carousel.php - Removed DEFAULT_DETAILS, added configure message, added name check
        - /public/templates/af/featured-search-compact.php - Same as carousel
        - /public/templates/af/featured-search-default.php - Removed default field list, added configure message, added name check
        - /public/templates/af/adopt-celebration-similar.php - Removed $defaultFields fallback, added configure message, added name check
    * RescueGroups templates (5 files):
        - /public/templates/rg/adopt-search-carousel.php - Removed DEFAULT_DETAILS, added configure message, added name check
        - /public/templates/rg/adopt-search-compact.php - Same as carousel
        - /public/templates/rg/adopt-search-default.php - Added configure message check in buildDetailsSection()
        - /public/templates/rg/adopt-search-structured.php - Same
        - /public/templates/rg/adopt-celebration-similar.php - Removed $defaultFields fallback, added configure message, added $nameInConfig check
    * Translation files:
        - /languages/pet-match-pro.pot - Added "Search detail fields not configured." translatable string
        - /languages/pet-match-pro-es_ES.po - Added Spanish translation: "Campos de detalle de búsqueda no configurados."
        - Note: .mo file requires regeneration via msgfmt or Loco Translate
+ Item 6: Fix CTA button text-decoration underline from Astra theme override without !important
    * Astra's .ast-single-post .entry-content a selector (specificity 0,2,1) overrode PMP's text-decoration: none on CTA buttons. The previous fix used deep parent-dependent selectors tied to .pmp-search-results-container which didn't cover carousel or other contexts.
    * /public/css/pet-match-pro-styles.css
        - Replaced .pmp-search-results-container chain with a.pmp-button.pmp-search-cta-button (specificity 0,2,1) - works in any parent wrapper.
        - Boosted carousel CTA selectors from .pmp-search-cta-button.pmp-template-carousel to a.pmp-button.pmp-search-cta-button.pmp-template-carousel (specificity 0,3,1) for both default and :hover states.
+ Item 7: Separate heading vs body text color variables in CSS
    * Changing the admin heading color setting (--pmp-color-heading) affected filter labels, detail field labels, detail values, and description text - not just headings. These non-heading elements now use --pmp-color-body instead.
    * /public/css/pet-match-pro-styles.css
        - .pmp-search-filter-label: changed to var(--pmp-color-body, #333)
        - .pmp-search-result-detail: changed to var(--pmp-color-body, #333)
        - .pmp-search-result-label: changed to var(--pmp-color-body, #333)
        - .pmp-search-result-value: changed to var(--pmp-color-body, #333)
        - .pmp-detail-profile-description-value: changed to var(--pmp-color-body, #333)
+ Item 8: Align carousel animal name styling with standard search templates
    * Carousel name used smaller font (1.05rem), different hover color variable (--pmp-color-link-hover), and tighter margins than other search templates.
    * /public/css/pet-match-pro-styles.css
        - .pmp-search-result-name.pmp-template-carousel: font-size 1.05rem -> 1.25rem, margin-bottom 0.25rem -> 0.5rem, added display: block and cursor: pointer
        - Hover state: changed from --pmp-color-link-hover to --pmp-color-cta to match other templates
+ Item 9: Fix carousel separator - corrupted character and hardcoded bypass of getSeparator()
    * AF carousel had a corrupted Unicode Replacement Character (U+FFFD) as separator, displaying as "?". All three partner carousels hardcoded the non-inline separator instead of routing through getSeparator(), preventing shortcode separator= control.
    * /public/templates/af/featured-search-carousel.php
        - Replaced corrupted hardcoded separator with $this->getSeparator(' • ')
    * /public/templates/pp/featured-search-carousel.php
        - Replaced hardcoded ' · ' with $this->getSeparator(' • ')
    * /public/templates/rg/adopt-search-carousel.php
        - Replaced hardcoded ' · ' with $this->getSeparator(' • ')
+ Item 10: Remove $carouselInline override - carousel follows standard inline mode rules
    * Carousels forced inline mode via $carouselInline = !$useInlineMode && !$useLabels, bypassing the standard rule that inline mode only activates when a separator is explicitly provided. This caused details to always render inline without separators when labels were off.
    * /public/templates/af/featured-search-carousel.php
        - Removed $carouselInline variable and all references. Inline class, no-labels class, labels check, and rendering block now use $useInlineMode and $useLabels only.
    * /public/templates/pp/featured-search-carousel.php
        - Same changes as AF.
    * /public/templates/rg/adopt-search-carousel.php
        - Same changes as AF.
+ Item 11: Fix ageInYears() returning ERROR for animals without age data
    * Lost/found and some shelter animals in PetPoint don't have age data. ageInYears() returned Constants::ERROR for empty/missing values, displaying "ERROR" on search cards instead of hiding the field.
    * /includes/class-pet-match-pro-all-api.php
        - Line 988: Changed return Constants::ERROR to return Constants::EMPTY_VALUE so templates skip the field.
+ Item 12: Compile Spanish translation .mo file
    * .mo binary was not regenerated after .po updates in 7.9.3. Compiled via msgfmt.
    * /languages/pet-match-pro-es_ES.mo - Regenerated from pet-match-pro-es_ES.po

Version 7.9.7 - March 12, 2026 - Similar animals sort bar removal, array-to-string fix
+ Item 1: Remove sort bar from similar animals sections across all partners
    * Sort buttons (orderby/sortfield) are not meaningful for curated similar animal results (small count, pre-filtered by species/age/sex). Removed sort bar enablement from renderSimilarAnimals() in all detail templates. Filter kept as disabled; exclude_filters retained as defense.
    * /public/templates/rg/adopt-details-navigation-similar.php
        - Removed unconditional filter=orderby and orderby=animalName shortcode params.
        - Added birthdate to excluded filters (redundant with age group mapping).
    * /public/templates/rg/adopt-similar.php
        - Removed $clientEnabled conditional that overrode filter=disable with orderby sort params.
        - Added birthdate to excluded filters.
    * /public/templates/af/adopt-details-navigation-similar.php
        - Removed unconditional filter=orderby and orderby=name shortcode params.
    * /public/templates/af/adopt-profile-3-column-similar.php
        - Removed $clientEnabled conditional sort bar block.
    * /public/templates/af/adopt-conversion-similar.php
        - Removed $clientEnabled conditional sort bar block.
    * /public/templates/pp/adopt-details-navigation-similar.php
        - Removed unconditional filter=orderby and orderby=AnimalName shortcode params.
    * /public/templates/pp/adopt-conversion-similar.php
        - Removed $clientEnabled conditional sort bar block.
+ Item 2: Fix array-to-string conversion warning in RG search template
    * /public/templates/rg/adopt-search-default.php
        - Line 559: resolveFilteredFieldValue() comparison cast $resultArray[$animalKey] directly to string, but RG stores fields as arrays (['value' => ..., 'label' => ...]). Now unwraps array values before casting.

Version 7.9.6 - March 12, 2026 - Pill badge license indicators, derived sort fields, section header spanning, shortcode level audit
+ Item 1: Replace bold-text license indicators with PRO/PRF pill badges
    * /admin/class-pet-match-pro-instructions-renderer.php
        - renderFieldList(): Groups fields by license level (Free, Premium, Preferred) on separate lines instead of a single comma-separated list.
        - Free fields render first with no badge, PRO fields on the next line with pmp-field-badge-pro badge, PRF fields on the next line with pmp-field-badge-preferred badge.
        - renderFieldListNote(): Replaced "bold values requires a paid subscription" text with PRO/PRF pill badge legend: "PRO requires a Junior or Preferred subscription. PRF requires a Preferred subscription."
+ Item 2: Add derived sort field rendering (derive_sort, derive_sort_by_type)
    * /admin/class-pet-match-pro-instructions-renderer.php
        - Added derive_sort and derive_sort_by_type to single and multi derive maps, mapped to LevelPrefix::SEARCH_SORT.
        - orderby/sortfield shortcode params now render dynamically from field-level config instead of hardcoded strings.
    * /admin/partials/pp/pmp-instructions-search-params.php
        - orderby: Converted from hardcoded static HTML to derive_sort_by_type with method_types (adopt/featured, found, lost).
    * /admin/partials/af/pmp-instructions-search-params.php
        - sortfield: Converted from hardcoded static HTML to derive_sort with method_type adopt.
    * /admin/partials/rg/pmp-instructions-search-params.php
        - sortfield: Converted from hardcoded static HTML to derive_sort with method_type adopt.
+ Item 3: Full pmp-bold to pill badge sweep across all param files
    * /admin/partials/pp/pmp-instructions-search-params.php
    * /admin/partials/pp/pmp-instructions-details-params.php
    * /admin/partials/af/pmp-instructions-search-params.php
    * /admin/partials/af/pmp-instructions-details-params.php
    * /admin/partials/rg/pmp-instructions-search-params.php
    * /admin/partials/rg/pmp-instructions-details-params.php
        - All pmp-bold spans replaced with pmp-field-badge-pro (PRO) or pmp-field-badge-preferred (PRF) pill badges.
        - Method type labels in method_types arrays updated (featured, found, lost, list -> PRO badges).
        - Type parameter values updated with individual PRO/PRF badges per value (e.g. lost_found -> PRF).
        - Species values split: free species on first line, PRO species on second line (PP, RG).
+ Item 4: Shortcode parameter license level audit against pmp-option-levels-general.php
    * /admin/partials/af/pmp-instructions-search-params.php
        - count: Changed paid from false to true with PRO badge (PREMIUM per shortcode levels).
    * /admin/partials/pp/pmp-instructions-details-params.php
    * /admin/partials/af/pmp-instructions-details-params.php
    * /admin/partials/rg/pmp-instructions-details-params.php
        - thumb: Removed incorrect PRO badge from values (not in shortcode strip list; only thumbs is PREMIUM).
    * /admin/partials/pp/pmp-instructions-search-params.php
    * /admin/partials/af/pmp-instructions-search-params.php
    * /admin/partials/rg/pmp-instructions-search-params.php
        - subtitle and count: Added missing PRO badges for params that were paid but had no badge in original static HTML.
+ Item 5: Section header row spanning
    * /admin/class-pet-match-pro-instructions-renderer.php
        - renderSectionHeaderRow(): Renders single cell with pmp-div-table-section-header class instead of three cells (one full-width + two empty).
    * /admin/css/pet-match-pro-admin.css
        - Added pmp-div-table-section-header: grid-column 1/-1 spanning all three table columns.
        - Styled with bold text, centered, secondary background matching table heading row.
        - Removed obsolete pmp-full-width width:100% rule.
+ Item 6: Pill badge CSS
    * /admin/css/pet-match-pro-admin.css
        - Added pmp-field-badge-pro and pmp-field-badge-preferred shared base styles (inline-block, 10px font, 600 weight, padding, border-radius).
        - pmp-field-badge-pro: yellow background (#fff3cd), dark gold text (#856404).
        - pmp-field-badge-preferred: purple background (#9b59b6), white text.
+ Item 7: Delete static HTML instruction files replaced by dynamic renderer
    * /admin/partials/af/pmp-instructions-animal-detail.html (DELETED)
    * /admin/partials/af/pmp-instructions-details.html (DELETED)
    * /admin/partials/af/pmp-instructions-search.html (DELETED)
    * /admin/partials/pp/pmp-instructions-animal-detail.html (DELETED)
    * /admin/partials/pp/pmp-instructions-details.html (DELETED)
    * /admin/partials/pp/pmp-instructions-search.html (DELETED)
    * /admin/partials/rg/pmp-instructions-animal-detail.html (DELETED)
    * /admin/partials/rg/pmp-instructions-details.html (DELETED)
    * /admin/partials/rg/pmp-instructions-search.html (DELETED)

Version 7.9.5 - March 11, 2026 - Dynamic instruction rendering, wizard license gating, marketing plan names, navigation template fixes, return-to-search fixes, no-photo fallback corrections
+ Item 1: Dynamic Instruction Renderer
    * /admin/class-pet-match-pro-instructions-renderer.php (NEW)
        - New InstructionsRenderer class (PetMatchPro\Admin) replaces static HTML instruction files with dynamically generated, license-aware output.
        - Renders shortcode parameter tables, field lists with free/premium/preferred badges, best practices, and examples.
        - renderFieldList() derives field names from field-level config arrays, wraps paid fields in bold spans.
        - renderFieldListByType() renders per-method-type field lists in separate <p> blocks (e.g. adopt/featured, found, lost, list).
+ Item 2: Partner-specific search parameter definitions
    * /admin/partials/af/pmp-instructions-search-params.php (NEW)
        - AnimalsFirst search parameter definitions: 26 parameters with AF-specific type/status values, count as free.
        - Uses derive_filters/derive_details with method_type adopt (flat list matching AF static HTML).
    * /admin/partials/pp/pmp-instructions-search-params.php (NEW)
        - PetPoint search parameter definitions: species required, orderby instead of sortfield/sortorder, date vs founddate/lostdate note.
        - Uses derive_filters_by_type/derive_details_by_type with per-method-type groupings (adopt/featured, found, lost, list).
    * /admin/partials/rg/pmp-instructions-search-params.php (NEW)
        - RescueGroups search parameter definitions: adopt only, animalspecies instead of species, RG-specific sort fields.
+ Item 3: Dynamic instruction loading in admin settings
    * /admin/class-pet-match-pro-admin-settings.php
        - addInstructionSections(): Try dynamic renderer first, fall back to static HTML if no params file exists.
        - addDynamicInstructionField(): Loads and merges field-level configs across all partner method types, instantiates renderer, includes params file.
        - getInstructionMethodTypes(): Returns method types per partner (PP: adopt/found/lost/list, AF: adopt/found/lost/preferred, RG: adopt).
+ Item 4: Wizard Step 4 preset license gating
    * /includes/wizard/class-pet-match-pro-setup-wizard.php
        - renderStepFields(): Recommended preset locked for Basic users (requires Junior), All Fields locked for Basic/Junior users (requires Preferred).
        - Locked cards show lock icon + "Requires Junior/Preferred" badge with input disabled.
        - Saved preset falls back to Minimal if the saved selection is now locked for the current license.
    * /admin/css/pet-match-pro-wizard.css
        - Added .pmp-wizard-preset-locked styles: reduced opacity, not-allowed cursor, muted colors, lock icon badge sizing.
    * /admin/js/pet-match-pro-wizard.js
        - Click handler on .pmp-wizard-preset-locked prevents selection; guard in change handler rejects locked cards.
+ Item 5: Wizard license activation panel refresh
    * /includes/wizard/class-pet-match-pro-setup-wizard.php
        - ajaxActivateLicense(): All three license paths (valid key, empty key, failed key) now re-render and return methods_html, fields_html, and templates_html.
        - Steps 3 (Methods), 4 (Fields), and 5 (Templates) refresh dynamically when license changes without page reload.
    * /admin/js/pet-match-pro-wizard.js
        - License activation success and error callbacks now handle fields_html refresh via existing refreshPanel() mechanism.
+ Item 6: Marketing plan name constants and label standardization
    * /pet-match-pro.php
        - Added Constants: PLAN_BASIC ('Basic'), PLAN_JUNIOR ('Junior'), PLAN_PREFERRED ('Preferred').
        - Added Constants::planName(int $level) static helper to map license level to marketing plan name.
    * /includes/wizard/class-pet-match-pro-setup-wizard.php
        - All user-facing tier labels replaced: "Free" -> "Basic", "Premium" -> "Junior", "Preferred" stays "Preferred".
        - Includes: method type requires badges, preset card badges, upgrade cards, license info step, license messages.
    * /admin/class-pet-match-pro-admin-settings.php
        - Level label maps updated to use Constants::PLAN_* values.
        - "Upgrade to Premium" messages -> sprintf with Constants::PLAN_JUNIOR.
        - "This feature requires a Preferred license" -> sprintf with Constants::PLAN_PREFERRED.
        - "Analytics Requires Preferred License" -> sprintf with Constants::PLAN_PREFERRED.
    * /languages/pet-match-pro.pot
        - Updated all affected msgid strings to use %s/%1$s/%2$s placeholders.
        - Added new entries: "Basic", "Junior", "Requires %s".
    * /languages/pet-match-pro-es_ES.po
        - Updated all affected msgid/msgstr pairs with placeholder syntax.
        - Added translations: "Basic" -> "Básico", "Junior" -> "Junior", "Requires %s" -> "Requiere %s".
        - .mo file requires recompilation with msgfmt.
+ Item 7: Fix return button styling variation on navigation detail templates
    * /public/templates/af/universal-details-navigation.php
    * /public/templates/af/adopt-details-navigation-similar.php
    * /public/templates/pp/universal-details-navigation.php
    * /public/templates/pp/adopt-details-navigation-similar.php
    * /public/templates/rg/adopt-details-navigation.php
    * /public/templates/rg/adopt-details-navigation-similar.php
        - Removed pmp-details-button from return button class (was applying width:30% and border-radius:4px, overriding pill style).
        - Added pmp-details-button-return for return-specific targeting and client customization.
    * /public/css/pet-match-pro-styles.css
        - Added scoped .pmp-details-container .pmp-detail-icon-button rule with :hover/:focus/:visited states.
        - Sets color:#fff without !important, overriding theme link color rules via specificity.
+ Item 8: Fix detail navigation prev/next for lost/found method types
    * /public/templates/af/universal-search-default.php
    * /public/templates/af/universal-search-filter-widget.php
    * /public/templates/af/universal-search-no-filter.php
    * /public/templates/pp/universal-search-default.php
        - Added data-method-type attribute to inner .pmp-search-results div.
        - JS detectMethodType() reads dataset.methodType from this element; missing attribute caused method type to default to 'adopt' for lost/found, storing transient under wrong key.
        - Fixes: species sorting, prev/next navigation order, and return-to-search URL for lost/found.
        - Also added missing data-client-features to universal-search-no-filter.php container.
+ Item 9: Fix no-photo fallback on navigation detail templates
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
    * /public/templates/af/universal-details-navigation.php
    * /public/templates/af/adopt-details-navigation-similar.php
    * /public/templates/pp/universal-details-navigation.php
    * /public/templates/pp/adopt-details-navigation-similar.php
    * /public/templates/rg/adopt-details-navigation.php
    * /public/templates/rg/adopt-details-navigation-similar.php
        - renderMainImage() was calling buildUpgradeNotice('photos') when no photos exist.
        - Replaced with no-photo.png fallback image (matching search template behavior).
+ Item 10: Fix return-to-search button showing previous detail page URL
    * /public/templates/includes/class-pet-match-pro-search-template-trait.php
        - storeAnimalIdsInSession() now stores search page URL in session for return button and prev/next nav.
        - Detects detail pages (URL method contains 'Details') and skips URL/ID writes to prevent similar animals from overwriting the search URL.
        - Moved client-features guard to only gate animal ID storage; URL storage runs for all search pages.
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - getReturnToSearchUrl(): Flipped priority - session first (synchronous), transient fallback (async). Both sources validated to reject stale detail page URLs.
        - getPrevNextNavigation(): Same session-first priority and detail URL validation applied to return URL reads.
    * /public/js/pet-match-pro-public.js
        - Replaced document.referrer approach (was overwriting PHP-set URL with previous detail page URL on prev/next clicks) with sessionStorage.
        - Search pages store URL in sessionStorage; detail pages read it for the return button. Client-side, synchronous, no timing issues.
    * /includes/class-pet-match-pro-public-ajax.php
        - updateSortedIds(): Rejects return_url values containing 'Details' before writing to transient.
+ Item 11: Fix undefined constant SEARCH_RESULT_PER_ROW
    * /public/templates/includes/class-pet-match-pro-search-template-trait.php
        - getResultsPerRow() line 746: Constants::SEARCH_RESULT_PER_ROW -> Constants::SEARCH_COUNT_ROW.
        - Fatal error on adopt-celebration-similar template.
+ Item 12: Fix no-photo fallback image path across all partners
    * All references to nonexistent images/no-photo.png corrected to includes/images/photo-animal-logo.jpg.
    * AF templates:
        - /public/templates/af/universal-search-default.php
        - /public/templates/af/universal-search-filter-widget.php
        - /public/templates/af/universal-search-no-filter.php
        - /public/templates/af/universal-search-structured.php
        - /public/templates/af/universal-details-navigation.php
        - /public/templates/af/adopt-details-navigation-similar.php
        - /public/templates/af/adopt-celebration-similar.php
        - /public/templates/af/featured-search-carousel.php
        - /public/templates/af/featured-search-compact.php
        - /public/templates/af/featured-search-default.php
    * PP templates:
        - /public/templates/pp/universal-details-navigation.php
        - /public/templates/pp/adopt-details-navigation-similar.php
    * RG templates:
        - /public/templates/rg/adopt-details-navigation.php
        - /public/templates/rg/adopt-details-navigation-similar.php
    * Shared:
        - /public/templates/includes/class-pet-match-pro-base-detail-template.php (renderMainImage, renderMainImageWide)
        - /public/templates/includes/class-pet-match-pro-poster-template-trait.php (renderPosterMediaSection, renderFoundLostPosterPhoto)
+ Item 13: Fix thumbnail upgrade notice showing for paid users with no photos
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - renderThumbnails(): Split FREE_LEVEL || empty($photos) condition. Free license shows upgrade notice. Paid users with no photos return empty (no misleading upgrade notice).
+ Item 14: Fix renderMainImageWide returning upgrade notice instead of fallback image
    * /public/templates/includes/class-pet-match-pro-base-detail-template.php
        - renderMainImageWide(): Replaced buildUpgradeNotice('photos') with photo-animal-logo.jpg fallback matching renderMainImage() behavior.
+ Item 15: Extend dynamic instruction rendering to details and animal-detail shortcodes
    * /admin/class-pet-match-pro-instructions-renderer.php
        - Added renderSectionHeaderRow() for full-width section divider rows (e.g. "Navigation Details Templates").
        - Added is_section_header/section_label support to renderParameterRow().
        - Added renderFieldListNote() call to renderAnimalDetailInstructions() (was missing NOTES paragraph).
    * /admin/partials/af/pmp-instructions-details-params.php (NEW)
        - AnimalsFirst [pmp-details] parameter definitions: 18 parameters including navigation template fields.
        - Uses derive_detail_fields with method_type adopt. Includes best practices and two examples.
    * /admin/partials/af/pmp-instructions-animal-detail-params.php (NEW)
        - AnimalsFirst [pmp-detail] parameter definitions: detail (required, derived field list), case, replace.
    * /admin/partials/pp/pmp-instructions-details-params.php (NEW)
        - PetPoint [pmp-details] parameter definitions with derive_detail_fields_by_type for adopt/found/lost.
    * /admin/partials/pp/pmp-instructions-animal-detail-params.php (NEW)
        - PetPoint [pmp-detail] parameter definitions: detail (required, derived field list), case, replace.
    * /admin/partials/rg/pmp-instructions-details-params.php (NEW)
        - RescueGroups [pmp-details] parameter definitions: adopt only, includes video and navigation fields.
    * /admin/partials/rg/pmp-instructions-animal-detail-params.php (NEW)
        - RescueGroups [pmp-detail] parameter definitions: detail (required, derived field list), case, replace.
    * /admin/css/pet-match-pro-admin.css
        - Added div.pmp-div-table-col.pmp-full-width class for section header rows.
+ Item 16: Fix admin-settings instruction HTML cleanup
    * /admin/partials/pmp-instructions-admin-settings.html
        - Fixed typo: "settng" -> "setting".
        - Fixed typo: "adninistration" -> "administration".
        - Replaced inline style="min-height:100px;" with pmp-min-height-100 CSS class (3 occurrences).
+ Item 17: Translation updates for dynamic instruction rendering
    * /languages/pet-match-pro.pot
        - Updated header to version 7.9.5, POT-Creation-Date 2026-03-11.
        - Added ~120 new translatable string entries from renderer and all parameter definition files.
    * /languages/pet-match-pro-es_ES.po
        - Updated header to version 7.9.5, PO-Revision-Date 2026-03-11.
        - Added Spanish translations for all new instruction renderer and parameter definition strings.
        - .mo file requires recompilation with msgfmt.

Version 7.9.4 - March 10, 2026 - License level consistency audit and fixes
+ Item 1: Fix duplicate social_share level definition
    * /admin/partials/pmp-option-levels-general.php
        - Remove SOCIAL_SHARE entry (was Preferred). Contact tab entry (Premium) is authoritative.
        - Social share now consistently Premium across admin, shortcode, field levels, and color settings.
+ Item 2: Fix preferred method enable keys for PetPoint
    * /admin/partials/pmp-option-levels-general.php
        - Change preferred search labels PP enable from 1 to 0 (preferred method does not exist for PetPoint).
+ Item 3: Add missing partner enable/disable keys
    * /admin/partials/pmp-option-levels-general.php
        - Add preferred search template enable keys: PP=0, RG=0, AF=1.
        - Add preferred detail template enable keys: PP=0, RG=0, AF=1.
        - Add preferred details page enable keys: PP=0, RG=0, AF=1.
        - Add poster details page enable keys: PP=1, RG=1, AF=1.
        - Add preferred no-results message enable keys: PP=0, RG=0, AF=1.
        - Add method type selector enable keys: PP=1, RG=0, AF=1.
        - Add default method type enable keys: PP=1, RG=0, AF=1.
+ Item 4: Fix tools cache accordion tier mismatch
    * /admin/partials/pmp-option-levels-tools.php
        - Change cache management from Preferred to Premium (matches API_CACHE_ENABLED setting).
+ Item 5: Fix General tab gating blocking Free-level fields
    * /admin/partials/pmp-option-levels.php
        - Change General tab from Premium to Free (individual fields handle their own level gating).
+ Item 6: Add Color tab level entry
    * /admin/partials/pmp-option-levels.php
        - Add Color tab at Free level (individual color settings gated by their own levels).
+ Item 7: Fix RescueGroups sort order descending tier
    * /includes/rg/partials/pmp-field-levels-adopt.php
        - Change descending sort order from Free to Premium (matches AnimalsFirst).
+ Item 8: Fix RescueGroups detail description tier
    * /includes/rg/partials/pmp-field-levels-adopt.php
        - Change animal detail description from Preferred to Free (matches PetPoint and AnimalsFirst adopt detail).
+ Item 9: Add enable key checks to admin register methods
    * /admin/class-pet-match-pro-admin-settings.php
        - registerDefaultMethodTypeField(): Add enable key check to hide field for RescueGroups.
        - registerDetailsPosterPageField(): Add enable key check for partner-specific poster page visibility.

Version 7.9.3 - March 10, 2026 - Spanish (es_ES) translation files
+ Item 1: Spanish translation .po file
    * /languages/pet-match-pro-es_ES.po
        - Full es_ES translation of all 1,177 strings from pet-match-pro.pot
        - Formal usted form throughout
        - Proper nouns preserved in English (PetPoint, RescueGroups, AnimalsFirst, PetMatchPro, WordPress, PHP, API, JSON, CSV, CTR, SEO, GTM, URL, QR)
        - All printf placeholders (%s, %d, %1$s, etc.) preserved exactly
        - Fixed 13 invalid \' escape sequences inherited from .pot (single quotes do not require escaping in .po format)
+ Item 2: Compiled Spanish translation binary
    * /languages/pet-match-pro-es_ES.mo
        - Compiled from pet-match-pro-es_ES.po using msgfmt (GNU gettext 0.21)
        - Verified clean compile with zero errors

Version 7.9.2 - March 10, 2026 - Generated pet-match-pro.pot translation template file
+ Item 1: Generate .pot translation template
    * /languages/pet-match-pro.pot
        - Extract 1,177 translatable strings from all PHP files using __(), esc_html__(), esc_html_e(), _e(), _n(), esc_attr__(), esc_attr_e().
        - Include file:line references for each string.
        - Header: Pet Match Pro 7.9.0, copyright 25 Fathoms LLC, text domain petmatchpro.

Version 7.9.1 - March 10, 2026 - i18n compliance fixes across all PHP files
+ Item 1: Main plugin file (pet-match-pro.php)
    * Wrap direct access, PHP version check, security check, and activation error strings in translation functions.
    * Use literal 'petmatchpro' text domain for pre-namespace code paths (PHP version checks run before Constants class loads).
+ Item 2: Public class (public/class-pet-match-pro-public.php)
    * Wrap all renderError() calls: No Integration Partner, API File Not Found, API Class Not Found, API Error,
      Missing Parameter, Missing Parameters, Configuration Error, Plugin Error messages.
    * Wrap renderUpgradeNotice() calls: Display Settings, Social Share.
    * Wrap Monarch social share notice with translatable string using %1$s/%2$s anchor placeholders.
+ Item 3: PetPoint API (includes/pp/class-pet-match-pro-pp-api.php)
    * Wrap renderError()/buildErrorMessage() calls: Method Error, No Results, Configuration Error,
      search/detail template not found messages, Integration partner API key missing.
+ Item 4: AnimalsFirst API (includes/af/class-pet-match-pro-af-api.php)
    * Wrap buildErrorMessage() calls: configuration errors, invalid type/status values, API connection errors,
      no animal ID, search template not found, animal not found, Integration partner API key missing.
    * Wrap buildUpgradeMessage() calls: Search for Type Value, Filter by Status.
+ Item 5: RescueGroups API (includes/rg/class-pet-match-pro-rg-api.php)
    * Wrap buildErrorMessage() calls: API request failed, JSON Error, API Error, Unknown method,
      API key not configured, Animal ID not provided, API configuration error, Check configuration.
    * Wrap unsupported search type warning in both createSearch() and handlePetSearch().
+ Item 6: Analytics insights (includes/analytics/class-pet-match-pro-analytics-insights.php)
    * Wrap AM/PM time format strings in formatHour() using sprintf with __().
+ Item 7: Admin info files - AF and RG (admin/partials/af|rg/pmp-admin-info.php)
    * Rewrite both files to match PP admin info pattern: all strings use sprintf() with __() and Constants::PLUGIN_SLUG.
    * Covers method type info, lost/found combination, location settings, poster template, and dynamic field labels.
+ Item 8: Admin settings (admin/class-pet-match-pro-admin-settings.php)
    * Wrap accordion section titles: General tab (11), Contact tab (7), Colors tab (13).
    * Wrap dropdown option labels: link target (New, Parent, Same), method types (Adopt, Found, Lost, List, Preferred).
    * Wrap License Activation page titles, instruction type labels.
+ Item 9: Template files - celebration, featured, found/lost, search templates
    * PP/AF/RG adopt-celebration-similar.php: Wrap adoption messaging (headline, subheadline, alt text, overlay).
    * PP/AF found-default, lost-default, found-poster, lost-poster: Wrap title format strings.
    * 11 search templates (PP/AF/RG): Replace hardcoded 'Learn More About ' with sprintf(__('Learn More About %s')).

Version 7.9.0 - March 10, 2026 - Template Standardization Phase 2
+ Item 1: Animal ID session storage across all search templates
    * /public/templates/includes/class-pet-match-pro-search-template-trait.php
        - Add storeAnimalIdsInSession() - stores rendered animal IDs in PHP session when client features enabled.
        - Add renderSearchForm() - consolidated trait method replacing per-template buildSearchForm() calls.
        - Add getFormIdSuffix() - returns Constants::SEARCH_FORM by default, overrideable per template.
    * Wire storeAnimalIdsInSession() to all 19 search templates (PP: 8, AF: 7, RG: 4).
        - Each template collects IDs using partner-specific field constants during processResults() loop.
        - PP templates use PetPointFields::ID, AF use AnimalsFirstFields::ID, RG use RescueGroupsFields::ID.
+ Item 2: Traditional search form support in structured templates
    * Remove 14 per-template renderSearchForm() implementations - replaced by trait method.
    * Add client features conditional to 3 structured templates (PP, AF, RG):
        - Client features enabled -> render sort buttons (unchanged).
        - Client features disabled -> render traditional search form via trait renderSearchForm().
    * AF universal-search-filter-widget - override getFormIdSuffix() to return Constants::SEARCH_WIDGET
      (preserves CSS targeting for #pmp-search-widget).
+ Item 3: Remove obsolete matchesCriteria() - redundant client-side post-filtering
    * All criteria fields are server-side API filters; matchesCriteria() duplicated work the API already performed.
    * Remove matchesCriteria() method and call sites from 8 templates:
        - PP: adopt-search-default, universal-search-default, universal-search-structured, adopt-celebration-similar.
        - AF: universal-search-default, universal-search-no-filter, universal-search-filter-widget, adopt-celebration-similar.
    * Fix PP adopt-details-navigation-similar extractFilterValues() - was using Settings::SORTFIELDS
      (adopt_search_criteria, wrong key for PP); corrected to Settings::SEARCH_POST_METHOD . '_criteria'
      (adopt_get_criteria, matching PP admin registration).
+ Item 4: Analytics onClick consistency - no changes needed
    * buildAnalyticsOnClick() and onClickValue() are complementary layers, not duplicates.
    * buildAnalyticsOnClick() generates full onclick attribute string for form submit buttons.
    * onClickValue() generates onclick for individual animal card links (name, photo).

Version 7.8.4 - March 6, 2026
+ Setup Wizard - fix step settings not persisting on reload
    * /includes/wizard/class-pet-match-pro-setup-wizard.php
        - Step 4 (Field Config): save selected preset (recommended/all/minimal/custom) to general options;
          restore checked state and selected card highlight on render.
        - Step 5 (Templates): root cause was jQuery $.post not preserving nested array object keys;
          switched data payload to JSON.stringify - PHP decodes via json_decode before sanitizing.
        - Step 5 (Templates): saveMethodsStep was unconditionally returning templates_html on every save,
          causing JS to overwrite user template selections; now only refreshes dependent panels when
          enabled methods actually change.
        - Step 6 (Pages): page mode radio always defaulted to "auto" on render; now reads wizard_page_mode
          from saved options and restores checked state and card highlight.
        - Step 6 (Pages): search page select had no \selected() call - only detail page did; added
          \selected() and reads saved search_page_{method} option on render.
        - Step 6 (Pages): savePagesStep was saving all page selections as details_page_{method};
          now uses type field to route search pages to search_page_{method} and detail pages to
          details_page_{method}; also saves wizard_page_mode for render restore.
    * /admin/js/pet-match-pro-wizard.js
        - Pages step JS now includes type (search/detail) in the pages array when collecting step data.
        - Page mode toggle extracted to applyPageMode() helper and called on init so the correct
          section (auto-create preview vs existing page selects) displays immediately on page load.

Version 7.8.3 - March 6, 2026
+ Standardize similar animal templates across all partners
    * All non-celebration/non-navigation similar templates: add admin search template resolution with fallback,
      conditional client sort bar (orderby=name, exclude constrained fields) when client features enabled.
        - PP: adopt-conversion-similar (fallback: universal-search-default)
        - AF: adopt-conversion-similar, adopt-profile-3-column-similar (fallback: universal-search-default)
        - RG: adopt-similar (fallback: adopt-search-default)
    * Celebration templates: override getCountParam() to read search_result_limit_similar admin setting,
      update $displayCount to use getCountParam() instead of shortcode COUNT parameter.
        - PP: adopt-celebration-similar
        - AF: adopt-celebration-similar
        - RG: adopt-celebration-similar
    * Navigation-similar templates: no changes needed (already standardized).
+ Fix structured search templates ignoring outputMax (search_result_limit_similar count not respected)
    * /public/templates/af/universal-search-structured.php - add outputMax check to processResults() loop.
    * /public/templates/rg/adopt-search-structured.php - same fix.
    * PP structured template already respected outputMax.
+ /public/js/pet-match-pro-public.js
    * Remove debug alert() calls from bindEvents, filter change handler, and applyFiltersAndSort.
+ Standardize client-side filter/sort rendering in default search templates (7 files)
    * PP: universal-search-default, adopt-search-default, lost-search-default, found-search-default.
    * AF: universal-search-default, universal-search-filter-widget.
    * RG: adopt-search-default.
    * When client mode enabled: render renderSortButtons() exclusively (no duplicate server-side form).
    * When client mode disabled: render server-side renderSearchForm() as fallback.
    * Remove pmp-search-form-client-mode class (no longer needed - form never renders in client mode).
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Fix resolveFilteredFieldValue() to apply formatGender() for sex/gender fields when no admin filter group config matches.
    * Filter dropdowns now show "Female", "Male", "Unknown" instead of raw API codes "F", "M", "U".
    * Card data attributes also use formatted values (data-sex="female") for consistent filter/sort matching.
+ /public/js/pet-match-pro-public.js
    * Fix sortItems() date collapse mapping - probe card for method-specific attribute (data-lostdate/data-founddate), fallback to generic data-date for combo search where prefixes are stripped.
+ /includes/class-pet-match-pro-all-api.php
    * Add optional $title parameter to buildErrorMessage() - replaces hardcoded "Error" prefix when provided.
    * Fix buildDetailsURL() for PetPoint to use method-specific endpoints (lostDetails, foundDetails) instead of always AdoptableDetails.
    * Fix buildDetailsURL() for PP/AF combo search - override method based on animal's actual type (PP: Type field, AF: intake_type field) so lost animals route to lostDetails/lost detail page and found animals to foundDetails/found detail page.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Fix renderError() to pass title and message separately to buildErrorMessage(), avoiding "Error: Parse Error:" duplication.
+ /includes/class-pet-match-pro-seo.php
    * Fix buildMethodParam() for PetPoint lost/found - use lowercase first letter (lostDetails, foundDetails) matching PP API endpoints and routeMethodResponse() casing.
+ Fix PetPoint lost_found combination search (type="lost_found" shortcode)
    + /includes/pp/class-pet-match-pro-pp-api.php
        * Fix executeLostFoundMethod() - normalize XML-to-array structure: handle single-result case (wrap in indexed array), standardize method-specific keys (lostSearch/foundSearch) to generic 'an' key.
        * Fix combineLostFoundResults() - create unified Date field from DateLast/DateFirst for sorting (was accessing nonexistent 'Date' key).
    + /public/templates/pp/lost-search-default.php
        * Add array format support to extractXmlItems() for combo search data (matching found template).
        * Fix processResults() loop bound - remove -1 offset (trailing empty node already handled by empty check).
    + /public/templates/pp/found-search-default.php
        * Fix processResults() loop bound - remove -1 offset (trailing empty node already handled by empty check).
    + /includes/class-pet-match-pro-all-api.php
        * Remove commented-out debug echo statements from processTemplate().
+ Template File Renames (9 files)
    * Rename PP/AF universal-search-filter-horizontal.php -> universal-search-structured.php.
    * Rename RG adopt-search-filter-horizontal.php -> adopt-search-structured.php.
    * Rename PP/AF universal-details-filter-horizontal.php -> universal-details-navigation.php.
    * Rename RG adopt-details-filter-horizontal.php -> adopt-details-navigation.php.
    * Rename PP/AF/RG adopt-details-filter-horizontal-similar.php -> adopt-details-navigation-similar.php.
+ /public/templates/pp/adopt-details-navigation-similar.php
    * Update search template fallback from 'universal-search-filter-horizontal' to 'universal-search-structured'.
    * Delete formatCurrencyValue() override - base class version is more robust (handles HTML entities, multiple decimals, uses $this->generalOptions).
+ /public/templates/af/adopt-details-navigation-similar.php
    * Update search template fallback from 'af-universal-search-filter-horizontal' to 'af-universal-search-structured'.
+ /public/templates/rg/adopt-details-navigation-similar.php
    * Update search template fallback from 'adopt-search-filter-horizontal' to 'adopt-search-structured'.
+ /public/templates/rg/adopt-similar.php
    * Remove explicit Shortcodes::TEMPLATE param - let shortcode handler resolve from admin settings (matches other similar templates).
+ /admin/partials/pp/pmp-instructions-details.html
    * Update shortcode example template reference from adopt-details-filter-horizontal.php to adopt-details-navigation.php.
+ /public/css/pet-match-pro-styles.css
    * Split dual-purpose pmp-template-filter-horizontal CSS class into pmp-template-structured (search context) and pmp-template-details-navigation (detail context).
    * Update all CSS selectors and section comments to match new class names.
+ /public/templates/pp/universal-search-structured.php
    * Update TEMPLATE_ID constant from 'filter-horizontal' to 'structured'.
    * Delete extractAndSortResults() - PHP server-side sort removed; sorting handled client-side.
    * Delete storeAnimalIdsInSession() - session-based ID storage removed.
    * Refactor processResults() to use inline extraction instead of extractAndSortResults().
    * Refactor getUniqueFilterValues() to use inline extraction instead of extractAndSortResults().
    * Standardize card data attributes - replace selective filter-field/sort-field loops with all-fields iteration matching AF/RG pattern; add SimpleXMLElement cast for PP XML data.
+ /public/templates/af/universal-search-structured.php
    * Update TEMPLATE_ID constant from 'filter-horizontal' to 'structured'.
+ /public/templates/rg/adopt-search-structured.php
    * Update TEMPLATE_ID constant from 'filter-horizontal' to 'structured'.
    * Delete extractAndSortResults() - dead code; processResults() already uses direct iteration.
    * Delete storeAnimalIdsInSession() - session-based ID storage removed.
    * Remove storeAnimalIdsInSession() call from processResults().
+ /public/templates/pp/universal-details-navigation.php
    * Update container class from pmp-template-filter-horizontal to pmp-template-details-navigation.
    * Fix thumbnail layout - add pmp-details-thumbs-row wrapper and video player support to match AF/RG structure (thumbnails were stacking vertically).
+ /public/templates/af/universal-details-navigation.php
    * Update container class from pmp-template-filter-horizontal to pmp-template-details-navigation.
+ /public/templates/rg/adopt-details-navigation.php
    * Update container class from pmp-template-filter-horizontal to pmp-template-details-navigation.
+ /public/templates/pp/adopt-details-navigation-similar.php
    * Update container classes from pmp-template-filter-horizontal/pmp-template-filter-horizontal-similar to pmp-template-details-navigation/pmp-template-details-navigation-similar.
    * Fix thumbnail layout - add pmp-details-thumbs-row wrapper and video player support to match AF/RG structure.
+ /public/templates/af/adopt-details-navigation-similar.php
    * Update container classes from pmp-template-filter-horizontal/pmp-template-filter-horizontal-similar to pmp-template-details-navigation/pmp-template-details-navigation-similar.
+ /public/templates/rg/adopt-details-navigation-similar.php
    * Update container classes from pmp-template-filter-horizontal/pmp-template-filter-horizontal-similar to pmp-template-details-navigation/pmp-template-details-navigation-similar.

Version 7.8.2 - March 4, 2026
+ /admin/class-pet-match-pro-admin-settings.php
    * Fix virtual value API field selector - re-add 'type' as a valid option in the pmp-virtual-api-field select (Filter Values tab, Site field accordion).
+ /includes/class-pet-match-pro-filter-override-manager.php
    * Fix saveVirtualValue() - store virtual config under 'api_field' key (was 'target_field') to match getFieldMapping() and admin save handler; add 'target_field' fallback for backward compatibility.
+ /includes/af/class-pet-match-pro-af-detail-functions.php
    * Fix getAgeIcon() - normalize spaces around dashes before slug generation (e.g., "Unweaned - Bottle" -> "unweaned-bottle" not "unweaned---bottle").
+ /public/css/pet-match-pro-styles.css
    * Add .pmp-svg-icon-unweaned-bottle, .pmp-svg-icon-unweaned-gruel, .pmp-svg-icon-unweaned-with-mom mask-image classes.
    * Add .pmp-detail-profile-result-unweaned-bottle, .pmp-detail-profile-result-unweaned-gruel, .pmp-detail-profile-result-unweaned-with-mom ::before blocks.
+ /includes/images/unweaned-*.svg
    * New age images.
+ /public/templates/pp/universal-search-filter-horizontal.php
    * Fix extractAndSortResults() - default sort order was DESC when no sort order configured, causing initial results to display DESC while sort button showed ASC; changed fallback to always use SORTORDER_ASCENDING to match renderSortButtons() behavior.

Version 7.8.1 - March 3, 2026
+ /admin/class-pet-match-pro-admin-settings.php
    * Add renderExpandCollapseLink() - renders "Expand All / Collapse All" toggle link for accordion containers.
    * Add expand/collapse link to 7 tabs: General, Filter, Contact, Colors, Labels, Analytics, Tools.
+ /admin/js/pet-match-pro-admin.js
    * Add initializeExpandCollapseAll() - click handler toggles all direct-child accordions open/closed, skips locked and sub-accordions, auto-hides when container has only 1 group.
+ /admin/css/pet-match-pro-admin.css
    * Add styles for .pmp-admin-accordion-controls and .pmp-admin-expand-all link.
+ /public/class-pet-match-pro-public.php
    * Fix fatal error: PetPointFields::PHOTO_URL -> PetPointFields::PHOTO in fetchAnimalDataForSeo().
    * Fix fatal error: PetPointFields::STATUS -> PetPointFields::STAGE in fetchAnimalDataForSeo().
+ /public/templates/includes/class-pet-match-pro-base-detail-template.php
    * Fix renderPrintPosterButton() - PetPoint ID param case mismatch ('animalid' vs URL's 'animalID') caused empty animal ID, resulting in poster URL without query args.
+ /public/templates/includes/class-pet-match-pro-field-exclusion-filter-trait.php
    * Fix normalizeForComparison() - normalize spaces around hyphens (" - ", "- ", " -" -> "-") so filter values with hyphens match consistently between search and detail pages.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Fix double-processing: remove resolveDetailFieldLabel() call from buildAnimalDetails callback - detail templates handle filter label resolution via resolveDetailFieldDisplayValue().
    * Fix outputDetails() - check file_exists before license check for detail templates; use buildErrorMessage() directly for consistent "Error:" prefix.
    * Fix combo search outputComboSearch() - check file_exists before license check for search templates; use buildErrorMessage() directly for consistent "Error:" prefix.
    * Fix outputSearch() - use buildErrorMessage() directly for consistent "Error:" prefix on template not found.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Fix double-processing: remove resolveDetailFieldLabel() call from buildAnimalDetails callback - same fix as PP.
    * Fix outputDetails() - check file_exists before license check; show error for missing templates instead of silent fallback to default.
+ /includes/class-pet-match-pro-all-api.php
    * Fix resolveSearchTemplate() - check file_exists before license check so missing templates show "not found" error instead of "upgrade required."
    * Fix resolveLocationThroughFilters() - apply same hyphen normalization for consistent filter matching.
    * Add resolveSortField() - unified sort field resolution with fallback chain: URL param (premium+) -> shortcode -> method-specific admin setting -> global admin setting -> default.
    * Add resolveSortOrder() - unified sort order resolution with same fallback chain.
    * Add validateSpeciesLicense() - validates species array against license restrictions with case-sensitivity option.
    * Add buildSpeciesUpgradeMessage() - shared upgrade HTML for species license errors.
    * Add buildAnimalDetailsArray() - shared detail loop: iterates fields, normalizes empties, applies partner callback, builds label/value pairs.
    * Add hasCachedDetail(), getCachedDetail(), setCachedDetail() - centralized detail cache replacing per-partner caches.
    * Add extractMethodFromCallFunc() - extracts base method type from callFunc string, strips Search/Details suffix, normalizes 'adoptable' -> 'adopt'.
    * Add loadFilterContext() - loads filter options/values from admin function for a given method.
+ /includes/af/class-pet-match-pro-af-api.php
    * Fix outputDetails() - check file_exists before license check; show descriptive error for missing templates instead of silent fallback to default.
    * Delete getSortField(), getSortOrder() - replaced by AllApi resolveSortField/resolveSortOrder.
    * Delete private $detailCache property - replaced by AllApi centralized cache.
    * Refactor processSpeciesParameters() to use AllApi validateSpeciesLicense().
    * Refactor buildAnimalDetailsArray() to use AllApi buildAnimalDetailsArray() with AF callback.
    * Refactor animalDetail() and outputDetails() to use AllApi cache methods.
    * Refactor SearchFilters() to use AllApi loadFilterContext().
    * Refactor extractMethodType() to delegate to AllApi extractMethodFromCallFunc().
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Delete getSortField() - replaced by AllApi resolveSortField. Keep formatSortField() as PP-specific.
    * Refactor buildQueryDefaults(), buildLostFoundQueryDefaults(), buildLostFoundDefaults() to use AllApi resolveSortField.
    * Refactor processSpecies() and processLostFoundSpecies() to use AllApi validateSpeciesLicense/buildSpeciesUpgradeMessage.
    * Refactor buildAnimalDetails() to use AllApi buildAnimalDetailsArray() with PP callback.
    * Convert static $animalCache to AllApi centralized cache in animalDetail().
    * Refactor SearchFilters() to use AllApi loadFilterContext().
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Delete getOrderBy(), getSortOrder() - replaced by AllApi resolveSortField/resolveSortOrder.
    * Delete private $detailCache property - replaced by AllApi centralized cache.
    * Refactor processSpecies() to use AllApi validateSpeciesLicense/buildSpeciesUpgradeMessage.
    * Refactor buildAnimalDetails() to use AllApi buildAnimalDetailsArray() with RG callback.
    * Refactor animalDetail() and outputDetails() to use AllApi cache methods.
    * Refactor searchFilters() to use AllApi loadFilterContext().

Version 7.8.0 - March 1, 2026
+ /includes/class-pet-match-pro-all-api.php
    * Add getCurrentPageUrl() - centralizes page URL detection from server variables.
    * Add stripThemeSuffix() - removes theme template suffix for filename resolution.
    * Add storePageSession() - unified session management for search/detail back-navigation.
    * Add resolveDisplayIcons() - license-gated icon display check using level file constants.
    * Add prepareSearchContext() - combines processSearchParameters, showDetails, and getDetailLabels into a single call.
    * Add resolveSearchTemplate() - handles template license check and file existence validation for search output.
    * Add resolveDetailTemplate() - resolves detail template from shortcode override, poster page detection, or admin setting.
    * Add ensureLayoutFields() - pulls missing layout-referenced fields (quick_fields, title_fields, stats_row, stats_full, details) from raw data with optional partner-specific field processor callback.
    * Add ensureIconFields() - copies icon/filter fields from raw API data into the animal details array.
+ /includes/af/class-pet-match-pro-af-api.php
    * Delete getCurrentPageUrl(), storeSearchSession(), initializeDisplayOptions(), getSupportEmail(), buildErrorMessage() - replaced by AllApi shared methods.
    * Remove $supportEmail parameter from fetchFromApi(), handlePetSearchFetch(), fetchWithTypeValidation(), fetchWithStatusValidation() and all callers.
    * Replace all $this->buildErrorMessage() calls with $this->allAPIFunction->buildErrorMessage().
    * Refactor outputSearch() to use AllApi prepareSearchContext() and resolveSearchTemplate().
    * Refactor outputDetails() to use AllApi resolveDetailTemplate(), ensureLayoutFields() (with callback for normalizeFieldValue/processBirthdate), and ensureIconFields().
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Delete stripThemeSuffix(), storePageUrlInSession(), initializeDisplayOptions(), getSearchResultLabels(), getDetailTemplate() - replaced by AllApi shared methods.
    * Delete logDebug(), logWarning(), logError() - redirect all logging calls to AllApi with 'PetPoint: ' prefix.
    * Convert renderError() to thin wrapper delegating to AllApi buildErrorMessage().
    * Refactor outputSearch() to use AllApi prepareSearchContext() and resolveSearchTemplate().
    * Refactor outputDetails() to use AllApi resolveDetailTemplate() and ensureIconFields(). Layout fields retained as partner-specific due to PetPoint field alias system.
    * Fix double-processing bug in applyFieldFiltering() - filter label transforms were applied to XML values before template rendering, then the template trait resolved labels again against already-transformed values, causing filter mismatches. Removed filter label transform block; label resolution now handled solely by the template trait's resolveFieldLabel().
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Delete getCurrentPageUrl(), stripThemeSuffix(), updateSearchSession(), updateDetailSession(), initializeDisplayOptions(), buildErrorMessage() - replaced by AllApi shared methods.
    * Delete logDebug(), logWarning(), logError() - never called, removed as dead code.
    * Remove $supportEmail setup and HTML fallback from fetch_rg_data(). Fix bug where empty support email was replaced with HTML string used as mailto address.
    * Remove unused $supportEmail parameter from processApiResults() signature.
    * Replace all $this->buildErrorMessage() calls with $this->allAPIFunction->buildErrorMessage().
    * Refactor outputSearch() to use AllApi prepareSearchContext() and resolveSearchTemplate().
    * Refactor outputDetails() to use AllApi resolveDetailTemplate(), ensureLayoutFields() (with callback for processBirthdate), and ensureIconFields().
+ /public/templates/includes/class-pet-match-pro-field-exclusion-filter-trait.php
    * Remove leftover debug error_log() from resolveFieldLabel().

Version 7.7.7 - February 28, 2026
+ /public/templates/af/featured-search-compact.php
    * New compact template for use in sidebars and footers. 
+ /public/templates/af/featured-search-carousel.php
    * New horizontal slider template for homepage hero sections.
+ /public/css/pet-match-pro-styles.css
    * New CSS for featured compact and carousel templates. 
+ /public/js/pet-match-pro-public.js
    * Add code for Featured Search Carousel template. 
+ /public/templates/pp/featured-search-compact.php
    * New compact template for use in sidebars and footers. 
+ /public/templates/pp/featured-search-carousel.php
    * New horizontal slider template for homepage hero sections.
+ /public/templates/rg/adopt-search-compact.php
    * New compact template for use in sidebars and footers. 
+ /public/templates/rg/adopt-search-carousel.php
    * New horizontal slider template for homepage hero sections.
+ /includes/wizard/class-pet-match-pro-setup-wizard.php
    * New method for setup wizard.
+ /admin/js/pet-match-pro-wizard.js
    * New scripts for Setup Wizard.
+ /admin/css/pet-match-pro-wizard.css
    * New styles for Setup Wizard
+ /admin/class-pet-match-pro-admin-settings.php
    * Add Wizard Group to the Tools Tab.
    * Filter template selectio for free license level. 
+ /admin/partials/pmp-option-levels-tools.php
    * Add level entries for the Tools Tab Wizard Group. 
+ /admin/partials/pmp-admin-info.php
    * Add help text for the Tools Tab Wizard Group. 
+ /pet-match-pro.php
    * Add Wizard constants.
    * Register Setup Wizard.
    * Correct plugin Settings link displayed in WordPress Plugins. 
+ /assets/icon-25x25__.png
    * Renamed to icon-25x25-color.png
+ /includes/class-pet-match-pro-deactivator.php
    * Fix broken constant references.
    * Add wizard_complete and license-file to both removePluginData() and uninstall() cleanup lists.
+ /includes/class-pet-match-pro-activator.php
    * Remove defaults for LOCATION_EXCLUSION, LOCATION_FILTER, LOCATION_OTHER, and LOCATION_SHELTER.
+ /admin/css/pet-match-pro-admin.css
    * Style active admin tab. 
+ /admin/partials/*/pmp-instructions-details.html
    * Make template parameter a paid feature. 
+ /admin/partials/pmp-option-levels-general.php
    * Add level entries for premium shortcode parameters.
+ /includes/class-pet-match-pro-all-api.php
    * Restrict use of shortcode parameters based on license level.  
+ /includes/af/class-pet-match-pro-af-api.php
    * Display appropriate error if template= shortcode parameter used with free license level.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Display appropriate error if template= shortcode parameter used with free license level.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Display appropriate error if template= shortcode parameter used with free license level.

Version 7.7.6 - February 26, 2026
+ /includes/af/class-pet-match-pro-af-api.php
    * Implement API caching. 
    * Implement standardized error logging. 
    * Correct processing of search form sort field/order parameters.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Implement standardized error logging. 
    * Correct processing of search form sort field/order parameters.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct processing of search form orderby parameter.
+ /includes/class-pet-match-pro-all-api.php
    * Correct processing of search form orderby parameter.
    * Set sortorder to generalOptions[Settings::SORTORDER] when not present in url or shortcode.
    * Add data-sort-field/data-sort-order-field/data-filter="{fieldname}" to form-builder attributes to facilitate client side filtering/sorting. 
    * Correct the way the search form defaults filter field labels. 
    * Inject $this into DetailFunctions via setAllApiFunction() in buildAnimalIcons().
    * Support >=10 exclusion/filter values.
+ /includes/cache/class-pet-match-pro-api-cache.php
    * Format time saved to display milliseconds when under 1 second.
+ /pet-match-pro.php
    * Add CLIENT_FEATURES_ENABLED constant.
    * Add new icon color setting constants. 
+ /admin/partials/pmp-option-levels-general.php
    * Add premium level entry for CLIENT_FEATURES_ENABLED.
+ /admin/partials/pmp-admin-info.php
    * Add help text for CLIENT_FEATURES_ENABLED.
    * Add help text for icon color settings. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Add CLIENT_FEATURES_ENABLED setting in Performance Group of General Tab.
    * New JS snippet to format cache info instructions across both columns in the Performance Group of the General Tab.
    * Correct API Diagnostics for RescueGroups.
    * Dynamically generate the API Diagnostics Method Types for each partner. 
    * Render Conversion by Traffic Source, Search Position Impact and Declining Click Rates to Analytic Dashboard.
    * Add Narrative Insights to Analytics Tab - Peak engagement day/hour, Best-converting traffic source, Position impact on CTR, Time-to-action speed, Multi-action visitor count, Repeat visitor conversion lift and Stale listing warnings with animal names.
    * Add icon color settings. 
+ /public/class-pet-match-pro-public.php
    * Pass client_features_enabled via wp_localize_script.
    * Add new search shortcode parameter client="enable/disable" to override CLIENT_FEATURES_ENABLED setting. 
    * New method extractStringFromDetail() to safely handle whatever animalDetail() returns (string, numeric, array with value key, flat array).
    * Correctly pull the RescueGroups photo.
+ /public/js/pet-match-pro-public.js
    * Check flag before PMPFilterSort intercepts clicks.
    * ajaxSortEnabled -> clientFeaturesEnabled, reads pmpPublic.client_features_enabled
    * Shortcode override: checks data-client-features on outer container - 'enable'/'disable' wins over global setting, '' defers to it.
    * filterSelects now covers both .pmp-filter-select (horizontal) and [data-filter] (form-based).
    * New sortSelects for [data-sort-field] and [data-sort-order-field] - intercepts onChange, updates currentSort, prevents form submit.
    * Form submit itself is also blocked when client features are enabled.
    * All interception gated on clientFeaturesEnabled - free users get normal form submit behavior unchanged.
    * Initial sort state seeded from sort dropdowns if no active sort button found.
    * Track search impressions after client-side filtering/sorting. 
    * Exclude filtered animals from impression tracking. 
    * Update search result count after client-side filtering. 
+ /includes/class-pet-match-pro-form-builder.php
    * Add data_attrs to form fields for use n client-side filtering/sorting. 
+ /public/templates/af/featured-search-default.php
    * Add data attributes for all field values to search card div to support client filtering/sorting. 
    * Remove location exclusion/filtering and replace with field logic.
+ /public/templates/af/universal-search-default.php
    * Add data attributes for all field values to search card div to support client filtering/sorting. 
+ /public/templates/af/universal-search-filter-widget.php
    * Add data attributes for all field values to search card div to support client filtering/sorting. 
+ /public/templates/pp/adopt-search-default.php
    * Add data attributes for all field values to search card div to support client filtering/sorting. 
+ /public/templates/pp/featured-search-default.php
    * Add data attributes for all field values to search card div to support client filtering/sorting. 
    * Remove location exclusion/filtering and replace with field logic.
+ /public/templates/pp/found-search-default.php
    * Add data attributes for all field values to search card div to support client filtering/sorting. 
+ /public/templates/pp/lost-search-default.php
    * Add data attributes for all field values to search card div to support client filtering/sorting. 
+ /public/templates/pp/universal-search-default.php
    * Add data attributes for all field values to search card div to support client filtering/sorting. 
+ /public/templates/rg/adopt-search-default.php
    * Add data attributes for all field values to search card div to support client filtering/sorting. 
+ /public/templates/rg/adopt-search-filter-horizontal.php
    * Add data attributes for all field values to search card div to support client filtering/sorting. 
    * Remove methods to build horizontal filter/sort buttons, move to search trait.
+ /includes/class-pet-match-pro-all-api.php
    * Add support for search shortcode parameter client.
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Add support for search shortcode parameter client.
    * Add methods to build horizontal filter/sort buttons.
    * Correct display of label for horizontal filters when no fields are present.
    * New mehod getLabelIconUrl($fieldKey) delegates to AllAPI::getImageUrl().
+ /public/css/pet-match-pro-styles.css
    * Add .pmp-filter-hidden { display: none !important; } alongside .pmp-pagination-hidden.
    * Add base .pmp-svg-icon class + 115 mask-image rules, span-compatible selectors for icon colors. 
+ /public/templates/pp/adopt-search-filter-horizontal.php
    * Remove methods to build horizontal filter/sort buttons, move to search trait.
+ /includes/rg/partials/pmp-field-values-adopt.php
    * Add default Search Filter label for name field. 
+ /includes/analytics/class-pet-match-pro-analytics-db.php
    * Correct issue w/ modifying table if it already exists. 
    * New methods getPeakEngagementTimes(), getSourceConversionRates(), getPositionImpact(), getTimeToAction(), getMultiActionVisitors(), getRepeatVisitorConversion() and getStaleListings().
+ /public/templates/includes/class-pet-match-pro-poster-template-trait.php
    * Remove location exclusion/filtering and replace with field functionality.
+ /public/templates/rg/adopt-similar.php
    * Remove filter button label for similar animals when using client-side filtering. 
    * Display sort buttons when using client-side filtering. 
+ /public/templates/rg/adopt-celebration-similar.php
    * New template to celebration adoptions. 
+ /includes/class-pet-match-pro-seo.php
    * Properly build the pretty detail url or all partners.
    * Determine the appropriate detail method for each partner. 
    * Default OG image to /includes/images/photo-animal-logo.jpg
+ /includes/analytics/class-pet-match-pro-analytics-insights.php
    * Update insights with new DB methods. 
+ /admin/partials/pmp-option-levels-color.php
    * Add level entries for icons color settings. 
+ /public/partials/pet-match-pro-public-color-css.php
    * Add selectors, added buildIconThemeOverrideCss()entries for icon colors.
+ /includes/*/class-pet-match-pro-*-detail-functions.php
    * New setAllApiFunction() setter; getIconUrl() delegates to AllAPI::getImageUrl() with fallback; removed $imagesPath and $imagesDir properties.
    * Convert buildIconHtml() from <img> -> <span> to support icon colors. 
+ /includes/*/class-pet-match-pro-*-api.php
    * Add $this->animalDetailFunction->setAllApiFunction($this->allAPIFunction) right after AllAPI is instantiated to centralize icon processing. 
+ /public/templates/af/universal-search-default.php
    * Use central icon processing. 
    * Use getFieldLabel() vs. local field mapping.
+ /public/templates/af/universal-search-*filter*.php
    * Use central icon processing. 
    * Use getFieldLabel() vs. local field mapping.
    * Convert icons from <img> to <span> to support icon colors. 
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Convert buildOverlays() from <img> -> <span> for icon colors.
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Convert getLabelIconUrl() -> getLabelIconClass(), search overlays from <img> -> <span> for icon colors.
+ /public/templates/pp/adopt-celebration-similar.php
    * New template to celebration adoptions. 
+ /public/templates/includes/class-pet-match-pro-field-exclusion-filter-trait.php

Version 7.7.5 - February 21, 2026
+ /admin/class-pet-match-pro-admin-settings.php
    * Correct bug in the creation of the table to store analytics data.
    * Analytics Insights group to Analytics Tab. 
+ /includes/analytics/class-pet-match-pro-analytics-insights.php
    * New Insights class method. 
+ /includes/analytics/class-pet-match-pro-analytics-db.php
    * Add three new methods: getAnimalsNeedingAttention(), getSpeciesEngagement(), getPriorPeriodStats().
    * Add columns to track icons/overlays. 
+ /admin/css/pet-match-pro-admin.css
    * Add Analytics Insights Group styles. 
+ /pet-match-pro.php
    * Register the new Analytics Insights method. 
    * Add DB_ANALYTICS_TYPE_ACTION_VIDEO_PLAY = 'video_play' constant.
    * Add Shortcodes::OVERLAY_FILTER = 'overlay_filter'. 
    * Add DB_ANALYTICS_HAS_VIDEO = 'has_video' constant.
    * Add DB_ANALYTICS_ICON_COUNT = 'icon_count' constant.
    * DB_ANALYTICS_OVERLAY_COUNT = 'overlay_count' constant.
+ /includes/analytics/class-pet-match-pro-analytics-ajax.php
    * Revise handleGetDashboard() to instantiate AnalyticsInsights to append the full insights payload (funnel, narrative, attention, trends) to the JSON response.
    * Reads 3 new icon/overlay fields from $_POST and passes to tracker.
+ /includes/analytics/class-pet-match-pro-analytics-tracker.php
    * Add 'video_play' => 'Video Play' to getActionTypes() so it shows correctly in the Action Breakdown table.
    * Add getCardDataAttributes(array $resultArray) string method to return data-has-video="1", data-icon-count="N" and data-overlay-count="N" for templates to add to card wrapper.
+ /public/js/pet-match-pro-public.js
    * Extend bindActionClicks selector to include a[data-action-type="video_play"] and checks $this.data('action-type') first before the href/class detection chain. Video plays now fire as action_click events with action_type = 'video_play', flowing through the same AJAX handler as all other actions.
    * All 3 detail_view fire points now read and send has_video, icon_count, overlay_count.
+ /includes/class-pet-match-pro-all-api.php
   * AddbuildEnrichmentDataAttributes() for icon/overlay data attributes.
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Add data-action-type="video_play" to the video play anchor.
    * New getDetailDataAttributes() method for the detail container div.
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
   * Enable icons for all AnimalsFirst method types. 
   * Override overlays shortcode parameter when overlay_filter is used. 
   * New getCardDataAttributes() method templates call on the card wrapper div.
+ /includes/af/class-pet-match-pro-af-detail-functions.php
   * Override overlays shortcode parameter when overlay_filter is used. 
   * Add video to icon map. 
+ /includes/rg/class-pet-match-pro-rg-detail-functions.php
   * Override overlays shortcode parameter when overlay_filter is used. 
   * Add video to icon map. 
+ /includes/rg/class-pet-match-pro-rg-api.php
   * Add videos to icon map. 
+ /public/css/pet-match-pro-styles.css
   * Add margin below search card icons.
   * Restrict pet icons to a width of 30px.
+ /public/templates/af/*-search-*.php
   * Add icon/overlay data attributes to search card div. 
+ /public/templates/pp/*-search-*.php
   * Add icon/overlay data attributes to search card div. 
+ /public/templates/rg/adopt*.php
   * Add icon/overlay data attributes to search/detail card div. 
+ /public/templates/pp/adopt-*.php
   * Add icon/overlay data attributes to detail card div. 
+ /public/templates/pp/found-*.php
   * Add icon/overlay data attributes to detail card div. 
+ /public/templates/pp/lost-*.php
   * Add icon/overlay data attributes to detail card div. 
+ /public/templates/pp/universal-details-*.php
   * Add icon/overlay data attributes to detail card div. 
+ /public/templates/af/adopt-*.php
   * Add icon/overlay data attributes to detail card div. 
+ /public/templates/af/found-*.php
   * Add icon/overlay data attributes to detail card div. 
+ /public/templates/af/lost-*.php
   * Add icon/overlay data attributes to detail card div. 
+ /public/templates/af/universal-details-*.php
   * Add icon/overlay data attributes to detail card div. 

Version 7.7.4 - February 19, 2026
+ /pet-match-pro.php
    * Add constants for SEO functionality. 
    * Add constant for PetPoint Adoptable method. 
+ /includes/class-pet-match-pro-seo.php
    * New SEO class manager. 
+ /public/class-pet-match-pro-public.php
    * Expose apiCient for SEOManager.
+ /admin/partials/pmp-option-levels-analytics.php
    * Add level entries for SEO functionality. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Add SEO settings to Analytics Tab. 
+ /includes/class-pet-match-pro-all-api.php
    * Use new PetPoint Adoptable method constant. 
    * Build SEO pretty urls for search and detail links when enabled 
+ /public/templates/af/*-search-*.php
    * Remove buildDetailUrl(), replace with $this->apiFunction->buildDetailsURL().
+ /public/templates/pp/*-search-*.php
    * Remove buildDetailUrl(), replace with $this->apiFunction->buildDetailsURL().
    * Use new PetPoint Adoptable method constant. 
+ /public/templates/rg/adopt-search-*.php
    * Remove buildDetailUrl(), replace with $this->apiFunction->buildDetailsURL().
+ /includes/images/photo-animal-celebration.jpg
    * New image as default celebration photo. 
+ /public/partials/pet-match-pro-public-color-css.php
    *Add entries for celebration page.

Version 7.7.3 - February 17, 2026
+ /pet-match-pro.php
    * Add constant for Tools Tab.
+ /admin/partials/pmp-option-levels-tools.php
    * New file for Tools Tab level settings for preferred license.
+ /admin/partials/pmp-admin-info.php
    * Help text for Tools Tab settings. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Add Tools Tab to export/import settings and provide system info.
+ /admin/js/pet-match-pro-admin.js
    * Add initializeToolsControls() with export download via Blob, file preview with validation, import with confirm dialog, system info AJAX render, and clipboard copy.
+ /admin/css/pet-match-pro-admin.css
    * Add Tools Tab classes: checkboxes, preview box, sysinfo tables, status messages, upgrade notice.
+ /public/class-pet-match-pro-public.php
    * Add $this->enqueueStylesScripts() to handleSearchFiltersShortcode() so styles load when [pmp-search-filters] is used standalone without [pmp-search].
    * Remove commented debug blocks.
+ /admin/partials/pmp-option-levels-general.php
    * Enable Featured No Animals Found message for AnimalsFrst.

Version 7.7.2 - February 16, 2026
+ /includes/class-pet-match-pro-filter-override-manager.php
    * Revise to support exclusion/filtering v. location_site setting for AnimalsFirst. 
    * Loops staticApiFields array pulling title from API cache.
    * Expand getAnimalsFirstValues() to return normalized values for all new fields.
+ /admin/class-pet-match-pro-admin-settings.php
    * Correct exclusion count for AnimalsFirst Site Filter Values entries. 
    * Add functionality to copy exclusion/filter settings between methods.
    * Support exclusion/filter for partner-specific method types. 
    * Add settings for method specific sort field/order defaults.
    * Add Combination Methods group to the Filters Tab.
    * Change Filters Tab groups from Filters to Method. 
+ /admin/js/pet-match-pro-admin.js
    * Add code to copy exclusion/filter settings between methods.
+ /admin/css/pet-match-pro-admin.css
    * Add CSS for copy exclusion/filter settings between methods.
    * Add CSS for new sort settings by method title.
+ /public/css/pet-match-pro-styles.css
    * Add CSS to size and align pet icons. 
+ /public/templates/af/featured-search-default.php
    * Display default labels when no custom labels exist. 
    * Place banner outside the photo section to prevent overlay blocking banner. 
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Display currency prefix for zero values. 
+ /public/templates/af/*-search-*.php
    * Apply exclusion/filtering logic to all fields. 
+ /public/templates/pp/*-search-*.php
    * Apply exclusion/filtering logic to all fields. 
+ /public/templates/af/adopt-search-filter-horizontal.php
    * Delete template, convert to universal. 
+ /public/templates/af/universal-search-filter-horizontal.php
    * New search template for all method types. 
    * Support new admin sort filter/order settings. 
    * Add data-method-type to the results container.
+ /public/templates/af/adopt-details-filter-horizontal.php
    * Delete template, convert to universal. 
+ /public/templates/af/universal-details-filter-horizontal.php
    * New search template for all method types. 
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Update horizontal navigation with partner specific animal id field. 
    * Display default description when value is empty. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Add horizontal detail shortcode parameter fields to animalDetails. 
    * Add location to essentialFields to support foster location processing.  
    * Implement new lost_found combination setting.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Add horizontal detail shortcode parameter fields to animalDetails. 
+ /includes/class-pet-match-pro-all-api.php
    * Display other "Not Configured" button as a button. 
    * Remove nonce from filter form attributes. 
    * Add method specific email subjects for email conversion button. 
    * Pass $animalDetails through to buildCallButton, buildMeetGreetButton, buildFosterAppButton.
    * Update email and call buttons based on animal in foster location. 
    * Disable meet_greet conversion button when animal in foster location. 
    * Convert foster_app button to donate/sponsorship when animal in foster location. 
    * Add alias mapping for commo shortcode detail fields (description, breed, gender, name, age and color).
    * Use partner-specific location field in isAnimalInFoster() vs. hardcoded 'location'.
    * Validate shortcode type parameter values against the partner list and default to admin setting. 
+ /public/templates/af/adopt-details-filter-horizontal-similar.php
    * Update to use field-based exclusion/filtering logic.
    * Align shortcode functionality with PP template.
+ /public/templates/af/adopt-details-filter-horizontal-similar.php
    * Pass sortfield value for filter parameter to similar search shortcode.
    * Exclude filter fields from admin configured filters via exclude_filters.
+ /public/templates/rg/adopt-details-filter-horizontal-similar.php
    * Pass sortfield value for filter parameter to similar search shortcode.
    * Exclude filter fields from admin configured filters via exclude_filters.
+ /pet-match-pro.php
    * Add constants for AnimalsFirstFields: STATUS_SUBTYPE_CAT, STATUS_SUBTYPE_DOG.
    * Add setting constant for combination setting. 
+ /includes/af/partials/pmp-field-levels-*.php
    * Add filter, search result and detail entries for new API fields. 
+ /includes/af/partials/pmp-field-values-*.php
    * Add filter, search result and detail entries for new API fields. 
+ /includes/class-pet-match-pro-form-builder.php
    * Remove name="submit" from submit button input to keep it off the url parameters. 
+ /admin/partials/pmp-option-levels-general.php
    * Add levels for method specific sort field/order settings.
+ /admin/partials/pmp-admin-info.php
    * Add help text for method specific sort field/order settings.
+ /public/templates/af/universal-search-filter-horizontal.php
    * Support new admin sort filter/order settings. 
    * Add data-method-type to the results container.
+ /public/templates/rg/adopt-search-filter-horizontal.php
    * Support new admin sort filter/order settings. 
    * Add data-method-type to the results container.
+ /public/templates/includes/class-pet-match-pro-field-exclusion-filter-trait.php
    * Lookup fallback filter value in fallback field when primary is not configured as a filter. 
+ /admin/partials/af/pmp-option-levels-filter.php
    * Add levels for lost/found label setting. 
+ /includes/af/class-pet-match-pro-af-options.php
    * Add lost/found method and label settings to Filters Tab. 
    * Change Filters Tab groups from Filters to Method. 
+ /admin/partials/pp/pmp-option-levels-filter.php
    * Add levels for lost/found method setting. 
+ /admin/partials/pp/pmp-admin-info.php
    * Add help text for lost/found method setting. 
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Add lost/found method setting to Filters Tab. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Implement new lost_found combination setting.

Version 7.7.1 - February 11, 2026
+ /includes/af/class-pet-match-pro-af-options.php
    * Remove sort calls and methods.
+ /includes/af/class-pet-match-pro-af-api.php
    * Remove sortOptions property + loading code.
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Remove sort options call and method
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Remove Sort Field and Sort Order blocks.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Remove PMPAdoptSortOptions property + loading code.
+ /includes/class-pet-match-pro-filter-override-manager.php
    * Add the list type sort by to the Filter Values Tab. 
+ /includes/pp/partials/pmp-field-values.php
    * Add filter_orderby_list with ID, Name, Sex values.
+ /public/templates/rg/adopt-search-default.php
    * Update to use field-based exclusion/filtering logic.
+ /public/templates/rg/adopt-search-filter-horizontal.php
    * Update to use field-based exclusion/filtering logic.
    * Separate sort/filter logic.
    * Use helper methods. 
    * Use storeAnimalIdsInSession() to enableprev/next navigation on detail pages.
    * Use buildAnimalCard().
    * Use buildDetailsSection().
    * Constructor cleanup.
    * Use $templateContext->getResultsPerRow().
    * Only apply status button treatment to the last field when present. 
+ /public/templates/pp/universal-search-filter-horizontal.php
    * Only apply status button treatment to the last field when present. 
+ /public/templates/rg/adopt-cpa.php
    * Update to use field-based exclusion/filtering logic.
+ /public/templates/rg/adopt-details-filter-horizontal.php
    * Update to use field-based exclusion/filtering logic.
    * Use Dynamic getMethodType() vs. hardcode RESCUEGROUPS_ADOPT.
    * Use shouldSkipFieldForSpecies() - declawed skipped for non-cats.
    * Use getDefaultTitle() - method-aware titles.
    * Use getResolvedMethodType() - public accessor.
    * Implement method-aware renderButtonsColumn() - adopt gets adoption_app/donate, lost/found gets email/call.
    * Species skip calls added to renderQuickFields(), renderStatsRow(), renderStatsFull(), renderTitleSection().
    * Align HTML layout w/ PP: separate thumbs-row, video player support, conditional instructions placement (adopt->below stats, lost/found->below description).
    * Add container class pmp-details-method-{type} and data-method-type uses resolved type.
+ /public/templates/rg/adopt-details-filter-horizontal-similar.php
    * Update to use field-based exclusion/filtering logic.
    * Align shortcode functionality with PP template.
+ /public/templates/af/adopt-search-filter-horizontal.php
    * Update to use field-based exclusion/filtering logic.
+ /public/templates/af/featured-search-default.php
    * Update to use field-based exclusion/filtering logic.
+ /public/templates/af/universal-search-default.php
    * Update to use field-based exclusion/filtering logic.
+ /public/templates/af/universal-search-filter-widget.php
    * Update to use field-based exclusion/filtering logic.
+ /public/templates/af/universal-search-no-filter.php
    * Update to use field-based exclusion/filtering logic.
+ /public/templates/af/adopt-details-filter-horizontal.php
    * Update to use field-based exclusion/filtering logic.
+ /public/templates/af/adopt-details-filter-horizontal-similar.php
    * Update to use field-based exclusion/filtering logic.
+ /public/templates/af/adopt-profile-3-column.php
    * Update to use field-based exclusion/filtering logic.
+ /public/templates/af/adopt-profile-3-column-similar.php
    * Update to use field-based exclusion/filtering logic.
+ /admin/partials/rg/pmp-option-levels-filter.php
    * Update to use field-based exclusion/filtering logic.
+ /includes/class-pet-match-pro-all-api.php
    * Remove setSearchPageSession(), use JS solution. 
    * Clean buildReturnLink(), no inline style or script.
+ /public/js/pet-match-pro-public.js
    * Add referrer check in $(document).ready, reveals Return to Search button if same-domain referrer exists.
+ /public/css/pet-match-pro-styles.css
    * Add CSS to hide .pmp-details-button-return by default with display: none.
    * Add CSs to style thumbs in horizontal detail template. 
+ /public/templates/af/*-search-*.php
    * Remove call to setSearchPageSession().
+ /public/templates/pp/*-search-*.php
    * Remove call to setSearchPageSession().
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Correct to array-to-string issues, Line 1280: $animalKeyValue & Line 1296: $resultArray[$animalKey].
+ /public/templates/pp/universal-details-filter-horizontal*.php
    * Remove the display of the animal description for lost & found method types. 

Version 7.7.0 - February 10, 2026
+ /includes/class-pet-match-pro-all-api.php
    * Remove location methods.
    * Add new field exclusion and filtering methods. 
+ /includes/class-pet-match-pro-field-filter-config.php
    * New method for immutable value objects: fieldKey, fallbackField, useFallback, exclusions, filters, otherLabel.
    * Support field aliases. 
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Remove location methods.
    * Add buildLocationFilterConfig() that returns a FieldFilterConfig, calls generic engine. 
    * Require FieldFilterConfig and FieldExclusionFilterTrait files. 
    * resolveSiteLocationValue() renamed to resolveFilteredFieldValue().
    * filterAnimalsByLocation() renamed to filterAnimalsByConfiguredFields()
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Add FieldFilterConfig + generic display resolver from new trait.
    * Require FieldFilterConfig and FieldExclusionFilterTrait files. 
+ /public/templates/includes/class-pet-match-pro-field-exclusion-filter-trait.php
    * Add filterAnimalsByField() engine, FieldFilterConfig value object and generic resolver (primary + fallback field).
    * getFieldLabel() now returns ?string - null when no custom label exists.
    * Support field aliases. 
+ /pet-match-pro.php
    * Remove constants for location/site settings. 
    * Add constants for primary and fallback exclusion/filtering settings.  
    * Add field alias constants for all partners.    
+ /admin/class-pet-match-pro-admin-settings.php
    * Convert location exclusion/filtering to primary/fallback with field selection. 
    * Support primary/fallback field aliases. 
+ /admin/partials/pmp-admin-info.php
    * Remove help text for location/site settings. 
    * Add help text for primary and fallback exclusion/filtering settings. 
+ /admin/partials/*/pmp-admin-info.php
    * Remove location_shelter help text. 
+ /admin/partials/pmp-option-levels-general.php
    * Remove location/site levels. 
    * Add primary and fallback exclusion/filtering level entries.     
+ /admin/js/pet-match-pro-admin.js
    * Replace initializeDynamicLocationFields / regenerateLocationFields with initializeDynamicFieldFilterFields / regenerateFieldFilterFields using data-* attributes.
+ /admin/css/pet-match-pro-admin.css
    * Add CSS for sub-accordion and field filter container styles.
+ /includes/class-pet-match-pro-all-api.php
    * LOCATION_SITE -> generic FIELD_PRIMARY lookup; fieldExclusions()/fieldFilters() now take $methodType param with new key pattern field_exclusion_{fieldType}_{methodType}_N
+ /includes/af/class-pet-match-pro-af-api.php
    * Remove location/site logic. 
    * Use new field/method type other value for filter processing. 
    * Replace location_site check with primary/fallback per method type.      
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Remove location/site logic. 
    * Use new field/method type other value for filter processing. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Remove location/site logic. 
    * Use new field/method type other value for filter processing. 
+ /includes/pp/partials/pmp-field-levels-found.php
    * Correct location levels. 
+ /includes/pp/partials/pmp-field-values-found.php
    * Correct location levels. 
+ /includes/pp/partials/pmp-field-levels-lost.php
    * Correct location levels. 
+ /includes/pp/partials/pmp-field-values-lost.php
    * Correct location levels. 
+ /public/templates/pp/*-search-*.php
    * Correct custom label processing.
+ /public/templates/pp/adopt-search-filter-horizontal.php
    * resolveSiteLocationValue() renamed to resolveFilteredFieldValue().
    * filterAnimalsByLocation() renamed to filterAnimalsByConfiguredFields()

Version 7.6.9 - February 7, 2026
+ /public/templates/includes/class-pet-match-pro-poster-template-trait.php
    * Use custom labels and format description with line breaks. 
+ /public/css/pet-match-pro-styles.css
    * Add CSS styles for poster description. 
    * Add CSS to properly style AnimalsFirst & PetPoint horizontal search template. 
    * Add CSS to style horizontal search template accent and result status box. 
    * Remove overlay offset wen banner is displayed in search results.
    * Add CSS to style universal detail horizontal template pagination. 
+ /admin/partials/activate_license_form.php
    * Prevent Activate License button from shadowing the form's submit method.
+ /public/templates/af/found-default.php
    * Add instruction functionality. 
+ /public/templates/af/found-poster.php
    * Add instruction functionality. 
+ /public/templates/af/lost-default.php
    * Add instruction functionality. 
+ /public/templates/af/lost-poster.php
    * Add instruction functionality. 
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Add getFilterFields() and getExcludedFilters() for horizontal search templates. 
    * Revise horizontal methods to accept method type as a parameter. 
    * Add support for sort_only shortcode parameter. 
    * Add formatGender() as protected method using SEX_MAP plus lowercase/full-word variants for AF/RG.
    * Normalize sort field keys.  
+ /public/templates/af/adopt-search-filter-horizontal.php
    * New search template.
+ /pet-match-pro.php
    * Add constants for horizontal search template colors. 
    * Add constant for SEX_MAP (code to name).
+ /admin/class-pet-match-pro-admin-settings.php
    * Add settings for horizontal search template colors. 
    * Add universal-detail* templates to template options for lost and found.
    * Add email and phone settings for adoption of a foster animal. 
+ /admin/partials/pmp-admin-info.php
    * Add help text for horizontal search template colors. 
    * Add help text for adoption of a foster animal email and phone settings. 
+ /admin/partials/pmp-option-levels-contact.php
    * Add levels for adoption of a foster animal email and phone settings. 
+ /public/partials/pet-match-pro-public-color-css.php
    * Add entries for horizontal search template colors CSS. 
+ /admin/partials/pmp-option-levels-color.php
    * Add level entries for horizontal search template colors. 
+ /public/templates/rg/adopt-search-filter-horizontal.php
    * Use getFilterFields() and getExcludedFilters() from trait and color settings for result formatting.
    * Use Filter Values Tab values for horizontal template sort options.  
+ /public/templates/pp/adopt-search-filter-horizontal.php
    * Use getFilterFields() and getExcludedFilters() from trait and color settings for result formatting.  
    * Delete template, convert to universal.   
+ /public/templates/pp/universal-search-filter-horizontal.php
    * New template to support all method types. 
    * Correct detail page URL for the method type. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Read Filter Values from $_POST['filters_json']. 
+ /admin/js/pet-match-pro-admin.js
    * Create single post field when processing Filter Value saves. 
+ /includes/pp/class-pet-match-pro-pp-detail-functions.php
    * Add fallback label value for featured icon. 
+ /includes/rg/class-pet-match-pro-rg-detail-functions.php
    * Add fallback label value for all boolean icons. 
+ /public/templates/pp/found-search-default.php
    * Use sex lookup in SearchTemplateTrait.
+ /public/templates/pp/lost-search-default.php
    * Use sex lookup in SearchTemplateTrait.
+ /public/templates/af/universal-search-*.php
    * Use sex lookup in SearchTemplateTrait.
+ /public/templates/pp/universal-search-default.php
    * Use sex lookup in SearchTemplateTrait.
+ /public/templates/pp/featured-search-default.php
    * Use sex lookup in SearchTemplateTrait.
+ /public/templates/includes/class-pet-match-pro-field-helpers.php
    * New method with sex code lookup shared across search and detail traits.
+ /public/templates/pp/adopt-search-filter-horizontal.php
    * Change shortcode template to universal. 
+ /public/templates/pp/adopt-details-filter-horizontal*.php
    * Make some of the custom shortcode parameters optional (see instructions).
+ /public/templates/pp/adopt-details-filter-horizontal.php
    * Make some of the custom shortcode parameters optional (see instructions).
    * Delete template, convert to universal.  
+ /public/templates/pp/*-similar.php
    * Correct search shortcode creation. 
+ /public/js/pet-match-pro-public.js
    * Correct sorting of date values.
+ /includes/class-pet-match-pro-filter-override-manager.php
    * Add PetPoint fields sex, all OK, on hold, special needs and the search types field to Filter Values Tab. 
    * Add RecueGroups fields Sex, all OK, Special Needs, Declawed, Foster, Special Diet, Yard and Size.
+ /public/templates/pp/universal-details-filter-horizontal*.php
    * New template to support all method types. 

Version 7.6.8 - January 31, 2026
+ /includes/af/class-pet-match-pro-af-detail-functions.php
    * Add icon support for altered. 
+ /public/templates/af/featured-search-default.php
    * Add image overlay functionality.
    * Add currency support for adoption_fee.
+ /public/templates/af/universal-search-default.php
    * Add image overlay functionality for adopt method type.
    * Add currency support for adoption_fee.
+ /public/templates/af/universal-search-filter-widget.php
    * Add image overlay functionality for adopt method type.
    * Add currency support for adoption_fee.
+ /public/templates/af/universal-no-filter.php
    * Add image overlay functionality for adopt method type.
    * Add currency support for adoption_fee.
+ /admin/class-pet-match-pro-admin-settings.php
    * Correct list of values for default metho type in General Tab.
+ /includes/af/class-pet-match-pro-af-options.php
    * Add Sort Order fields to the Filters Tab for each method type. 
    * Add ability to copy Label Tab setting values. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Limit sortfield and sortorder values to those checked in Admin Filters Tab. 
+ /admin/css/pet-match-pro-admin.css
    * Add CSS entries to style label copy options.   
+ /admin/js/pet-match-pro-admin.js
    * Add script to copy label options.   
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Add ability to copy Label Tab setting values. 
+ /includes/af/partials/pmp-field-values-*.php
    * Correct default label for KENNEL_LOCATION.
+ /public/templates/pp/featured-search-default.php
    * Correct bug processing currency fields. 
    * Use correct grid class. 
+ /public/css/pet-match-pro-styles.css
    * Correct CS for horizontal detail template pagination. 
    * Add CSS to display icon overlays below the search image banner. 
    * Add CSS to override theme anchor text decoration. 
+ /public/css/bootstrap.min.css
    * Delete file, no longer used.
+ /public/css/fontawesome_all.css
    * Delete file, no longer used.
+ /public/css/pet-match-pro-public.css
    * Delete file, no longer used.
+ /public/css/pet-match-pro-public.min.css
    * Delete file, no longer used.
+ /public/js/bootstrap.min.js
    * Delete file, no longer used.
+ /public/js/js.js
    * Delete file, no longer used.
+ /public/js/paginathing.js
    * Delete file, no longer used.
+ /public/js/popper.min.js
    * Delete file, no longer used.
+ /public/js/tableManager.js
    * Delete file, no longer used.
+ /public/partials/pet-match-pro-public-display.php
    * Delete file, no longer used.
+ /clear.svg
    * Delete file, never used.
+ /rain.svg
    * Delete file, never used.
+ /public/templates/af/adopt-conversion.php
    * Add instructions.
+ /public/templates/af/adopt-conversion-no-app.php
    * Add instructions.
+ /public/templates/af/adopt-conversion-poster.php
    * Add instructions.
+ /public/templates/af/adopt-conversion-similar.php
    * Add instructions.
    * Correct filter lookup for similar search. 
+ /public/templates/af/adopt-default.php
    * Add instructions.
+ /public/templates/af/adopt-profile-3-column.php
    * Add instructions.
    * Add currency formatting.
+ /public/templates/af/adopt-profile-3-column-similar.php
    * Add instructions.
    * Add currency formatting.
    * Correct filter lookup for similar search. 
+ /public/templates/af/adopt-wide.php
    * Add instructions and social share.
+ /public/templates/rg/adopt-cpa.php
    * Remove local method to format currency fields.
+ /public/css/pet-match-pro-styles.css
    * Remove AF CSS styling for currency prefix. 
+ /public/templates/pp/adopt-conversion-similar.php
    * Correct filter lookup for similar search. 
+ /public/templates/pp/adopt-details-filter-horizontal-similar.php
    * Correct filter lookup for similar search. 
+ /public/templates/pp/adopt-similar.php
    * Correct filter lookup for similar search. 
+ /public/templates/rg/adopt-details-filter-horizontal-similar.php
    * Correct filter lookup for similar search. 
+ /public/templates/pp/adopt-wide.php
    * Add social share. 
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Add line breaks to descriptions if they do not exist. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Update SearchFilters() method to use FilterOverrideManager.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Update SearchFilters() method to use FilterOverrideManager.

Version 7.6.7 - January 30, 2026
+ /includes/rg/class-pet-match-pro-rg-detail-functions.php
    * Add icon support for altered, microchipped, fence, foster, adoption_pending, hypoallergenic, foster and yard. 
+ /includes/images/icon-adoption-pending.png
    * New icon file. 
+ /includes/images/icon-hypoallergenic.png
    * New icon file. 
+ /includes/images/icon-foster.png
    * New icon file. 
+ /public/templates/rg/adopt-search-default.php
    * Add image overlay functionality and currency prefix.
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Add AF/RG adoption_fee to currency fields. 
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Add AF/RG adoption_fee to currency fields. 
+ /public/templates/includes/class-pet-match-pro-poster-template-trait.php
    * New method formatPosterCurrencyValue() to format currency fields. 
    * Add AF/RG adoption_fee to currency fields. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Pass all icon fields to detail templates. 
    * Support shortcode parameters in the pmp-search shortcode for all filter fields.  
    * Always apply shortcode parameters to $urlParms without a conditional check. 
+ /public/templates/rg/adopt-cpa.php
    * Add icon and instruction display. 
+ /public/templates/rg/adopt-default.php
    * Add instruction display. 
+ /public/templates/rg/adopt-similar.php
    * Add instruction display. 
    * Correct bug to exclude correct animal from similar results. 
+ /public/templates/rg/adopt-search-filter-horizontal.php
    * New search template. 
+ /public/templates/rg/adopt-details-filter-horizontal.php
    * New detail template. 
+ /public/templates/rg/adopt-details-filter-horizontal-similar.php
    * New detail template. 
    * Use age to agegroup mapping when building similar search shortcode. 
+ /public/templates/pp/adopt-search-filter-horizontal.php
    * Revise label processing hierarchy. 
+ /public/templates/pp/adopt-details-filter-horizontal-similar.php
    * Revise label processing hierarchy. 
+ /includes/class-pet-match-pro-all-api.php
    * New methods to remove whitespace when processing detail shortcode parameters. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Call new all-api method to remove whitespace when processing detail shortcode parameters. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Call new all-api method to remove whitespace when processing detail shortcode parameters. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Call new all-api method to remove whitespace when processing detail shortcode parameters. 
+ /pet-match-pro.php
    * Add constants for age to agegroup mapping.
+ /admin/partials/rg/pmp-field-values.php
    * Add default age to agegroup mapping. 
+ /admin/partials/rg/pmp-admin-info.php
    * Add help text for age to agegroup mapping in Filters Tab.
+ /admin/partials/rg/pmp-option-levels-filter.php
    * Add levels for age to agegroup mapping in Filters Tab.
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Add age to agegroup mapping settings in Filters Tab.
+ /includes/class-pet-match-pro-filter-override-manager.php
    * Revise 'Age' group in Filter Values to 'Age Group' for RescueGroups.
+ /includes/af/class-pet-match-pro-af-api.php
    * Always apply shortcode parameters to $urlParms without a conditional check. 

Version 7.6.6 - January 27, 2026
+ /public/partials/pet-match-pro-public-color-css.php
    * Add more specific entries for search form submit button CSS. 
+ /public/css/pet-match-pro-styles.css
    * Add CSS to style search filter form submit button under value fields. 
    * Add CSS to style pmp-search-no-results across all grids and increase font-size. 
    * Add CSS to style the instructions for the adopt-wide template. 
+ /public/templates/pp/adopt-search-default.php
    * Add image overlay functionality.
+ /public/templates/pp/featured-search-default.php
    * Add image overlay functionality.
+ /public/templates/pp/universal-search-default.php
    * Add image overlay functionality for adopt method type.
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Add $this->buildOverlays() call in renderMainImage() and renderMainImageWide().
    * Add currency symbol before price fields. 
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Add currency symbol before price fields. 
+ /public/templates/pp/adopt-conversion*.php
    * Add instruction functionality. 
+ /public/templates/pp/adopt-default.php
    * Add instruction functionality. 
+ /public/templates/pp/adopt-wide.php
    * Add instruction functionality. 
+ /public/templates/pp/found-default.php
    * Add instruction functionality. 
+ /public/templates/pp/found-poster.php
    * Add instruction functionality. 
+ /public/templates/pp/lost-default.php
    * Add instruction functionality. 
+ /public/templates/pp/lost-poster.php
    * Add instruction functionality. 
+ /public/templates/pp/adopt-details-filter-horizontal.php
    * Correct to be based on detail vs. search template.
+ /public/templates/pp/found-search-default.php
    * Remove ability to display icons. 
+ /public/templates/pp/lost-search-default.php
    * Remove ability to display icons. 
+ /pet-match-pro.php
    * Add constants for currency and instruction color settings.
+ /admin/partials/pmp-option-levels-color.php
    * Add level entries for new currency and instruction color settings. 
+ /admin/partials/pmp-option-levels-general.php
    * Add levels for new currency and instruction color settings.
+ /admin/partials/pmp-admin-info.php
    * Add help text for new currency and instruction color settings. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Add new currency and instruction color settings. 

Version 7.6.5 - January 26, 2026
+ /includes/class-pet-match-pro.php
    * Display admin notice when license validation fails AND a key was provided.
+ /pet-match-pro.php
    * Add constants for new color settings.
    * Add constant for Overlay Title Source setting. 
+ /admin/partials/pmp-option-levels-color.php
    * Add level entries for new default color settings. 
+ /admin/partials/pmp-option-levels-general.php
    * Add levels for new instruction settings.
    * Add levels for new Overlay Title Source settings.
+ /admin/partials/pmp-admin-info.php
    * Add help text for new default color settings. 
    * Add help text for new instruction settings.
    * Add help text for new Overlay Title Source settings.
+ /admin/class-pet-match-pro-admin-settings.php
    * Add new default color settings. 
    * Add RG refresh button + AJAX handler.
    * Delete color override transient when colors sad to regenerate color overrides file. 
    * Add instruction settings to display on method type animal detail pages.
    * Add new Overlay Title Source settings.
+ /public/partials/pet-match-pro-public-color-css.php
    * Add :root variable output for new default color settings. 
+ /public/css/pet-match-pro-styles.css
    * Replace hard-coded colors with new :root variables from default color settings. 
    * Change overlay corner position icon flow to side of image vs. across center. 
    * Add CSS to style instructions. 
+ /admin/partials/rg/pmp-field-values.php
    * Remove static breeds, add static size values.
+ /includes/rg/partials/pmp-field-levels-adopt.php
    * Add filter levels for BREED_PRIMARY, COLOR_PRIMARY, SIZE_POTENTIAL_GENERAL.
+ /includes/rg/partials/pmp-field-values-adopt.php
    * Add labels for breed, color, size filters
+ /includes/class-pet-match-pro-filter-override-manager.php
    * Update getRescueGroupsValues() to read from cache, add color/size configurable fields.
+ /public/js/pet-match-pro-public.js
    * Add JavaScript handler for RG refresh button.
+ /admin/pet-match-pro-admin.css
    * Add CSS for RG refresh button. 
    * Change orientation overlay corner position icon display. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Pass species and all filter fields to the detail template to support similar detail templates. 
    * Add sex shortcode parameter handling with "Male"->"M", "Female"->"F".
    * Add shortcode parameter handling for all search filter fields.
    * Add helper methods to lookup speciesid,breedid and agegroup to pass search parameter values. 
+ /public/templates/pp/adopt-conversion-similar.php
    * Correct processing of similar search shortcode.
+ /public/templates/pp/adopt-details-filter-horizontal-similar.php
    * New detail template. 
+ /public/templates/pp/adopt-details-filter-horizontal-similar.php
    * Re-orient output to match similar template. 
    * Display instructions. 
+ /public/partials/pet-match-pro-public-color-css.php
    * Apply search filter submit button styles to horizontal search sort buttons. 
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Add method to display instruction settings.
    * Store Return to Search URL as WordPress transient vs. i PHP session.
+ /incudes/pp/class-pet-match-pro-pp-detail-functions.php
    * Revise to use new Overlay Title Source settings.
+ /public/js/pet-match-pro-public.js
    * New PMPFilterSort module to allow client-side filtering/sorting of search results. 
+ /public/templates/includes/class-pet-match-pro-public-ajax.php
    * New file to register public AJAX handler. 
+ /public/templates/pp/adopt-search-filter-horizontal.php
    * Add client-side filtering/sorting with AJAX to update animalids passed to detail template for navigation. 
+ /public/class-pet-match-pro-public.php
    * Add nonce to pmpPublic localization.
    * Add initializeAjaxHandler() method that loads the AJAX class.
+ /incudes/af/class-pet-match-pro-af-detail-functions.php
    * Revise to use new Overlay Title Source settings.
    * Limit declawed icon/overlay to cats only.
+ /incudes/rg/class-pet-match-pro-rg-detail-functions.php
    * Revise to use new Overlay Title Source settings.
    * Limit declawed icon/overlay to cats only
+ /includes/af/class-pet-match-pro-af-api.php
    * Pass species and all filter fields to the detail template to support similar detail templates. 
    * Add shortcode parameter handling for all search filter fields.
    * Pass all shortcode parameters to detail templates. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Pass species and all filter fields to the detail template to support similar detail templates. 
    * Add shortcode parameter handling for search filter fields.
    * Pass all shortcode parameters to detail templates. 
    * Add value normalization in getFilterValues() for all yes/no and sex fields. 
+ /includes/class-pet-match-pro-all-api.php
    * Check location value type before trying to convert to lower case. 

Version 7.6.4 - January 24, 2026
+ /pet-match-pro.php
    * Add constants for icon overlay functionality. 
+ /admin/partials/pmp-option-levels-general.php
    Add level entries for icon overlay functionality.
+ /admin/partials/pmp-admin-info.php
    * Add help text for icon overlay settings. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Add settings to General Tab Display group for icon overlay functionality. 
    * Display Organization ID, Search Result Limit and Sort Order settings based on integration partner. 
+ /admin/css/pet-match-pro-admin.css
    * Add CSS to style icon overlay settings. 
+ /public/css/pet-match-pro-styles.css
    * Add CSS for icon overlays.
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Add helper methods to support icon overlays. 
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Add helper methods to support icon overlays. 
+ /incudes/af/class-pet-match-pro-af-detail-functions.php
    * Add getOverlayIcons() to support display of icon overlays. 
    * Add icon support for featured field. 
+ /incudes/pp/class-pet-match-pro-pp-detail-functions.php
    * Add getOverlayIcons() to support display of icon overlays. 
    * Add icon support for featured and onhold fields. 
+ /incudes/rg/class-pet-match-pro-rg-detail-functions.php
    * Add getOverlayIcons() to support display of icon overlays. 
+ /public/templates/pp/adopt-search-filter-horizontal.php
    * Replace hardcoded overlay with trait method.  
+ /public/templates/pp/adopt-details-filter-horizontal.php
    * Replace hardcoded overlay with trait method.  
+ /includes/images/icon-onhold.png
    * New icon file. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Add icon support for featured and onhold fields. 
    * Pass all detail shortcode parameters to the detail template. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Add icon support for featured field. 
    * Pass all detail shortcode parameters to the detail template. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Add icon support for featured field. 
    * Pass all detail shortcode parameters to the detail template. 
+ /includes/class-pet-match-pro-all-api.php
    * Add support for detail button icons. 
    * Add support for custom detail button labels.
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Add support for detail button icons. 
    * Add support for custom detail button labels.
+ /public/templates/pp/adopt-search-filter-horizontal.php
    * Add support for detail button icons. 
    * Add support for custom detail button labels.
+ /public/partials/pet-match-pro-public-color-css.php
    * Add support to apply detail button color settings to pagination navigation buttons. 

Version 7.6.3 - January 23, 2026
+ /pet-match-pro.php
    * Add constant for new video parameter to pmp-detail shortcode. 
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Add renderVideoPlayer() method.
+ /includes/af/class-pet-match-pro-af-api.php
    * Pass video parameter to detail templates.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Pass video parameter to detail templates.
+ /public/templates/af/adopt-profile-3-column.php
    * Render video player based on shortcode value. 
+ /public/templates/af/adopt-profile-3-column-similar.php
    * Render video player based on shortcode value. 
+ /public/templates/af/adopt-converion-*.php
    * Render video player based on shortcode value. 
+ /public/templates/af/adopt-default.php
    * Render video player based on shortcode value. 
+ /public/templates/rg/adopt-cpa.php
    * Render video player based on shortcode value. 
+ /public/templates/rg/adopt-default.php
    * Render video player based on shortcode value. 
+ /public/templates/rg/adopt-similar.php
    * Render video player based on shortcode value. 
+ /public/templates/rg/universal-details-poster.php
    * Correct bug referencing detail and poster traits/templates.  
+ /public/templates/rg/adopt-search-default.php
    * Correct bug in passing lastitem value to the pagination javascript.  
    * Revise to require separator shortcode parameter for inline result values. 
+ /admin/partials/rg/pmp-instructions-search.html
    * Revise separator parameter instructions.  
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * New trait method useInlineMode() to check for separator shortcode parameter. 
+ /public/templates/af/featured-search-default.php
    * Revise to require separator shortcode parameter for inline result values. 
+ /public/templates/af/universal-search-default.php
    * Revise to require separator shortcode parameter for inline result values. 
+ /admin/partials/af/pmp-instructions-search.html
    * Revise separator parameter instructions.  
+ /public/templates/pp/featured-search-default.php
    * Revise to require separator shortcode parameter for inline result values. 
+ /public/templates/pp/universal-search-default.php
    * Revise to require separator shortcode parameter for inline result values. 
+ /public/templates/pp/adopt-search-default.php
    * Revise to require separator shortcode parameter for inline result values. 
+ /public/templates/pp/found-search-default.php
    * Revise to require separator shortcode parameter for inline result values. 
+ /public/templates/pp/lost-search-default.php
    * Revise to require separator shortcode parameter for inline result values. 
+ /admin/partials/pp/pmp-instructions-search.html
    * Revise separator parameter instructions.  
+ /public/css/pet-match-pro-styles.css
    * Add CSS for video player.
+ /admin/partials/af/pmp-instructions-details.html
    * Add instructions for video parameter.
+ /admin/partials/rg/pmp-instructions-details.html
    * Add instructions for video parameter.
+ /admin/class-pet-match-pro-admin-settings.php
    * Correct bug with the display of partner-specific settings.  
    * Display Partner API Source, On Hold Status and Preferred Method Type Label settings based on integration partner. 
+ /admin/partials/pmp-option-levels-general.php
    * Correct poster template entries to remove extra underscore.  
    * Add entries to display Partner API Source, On Hold Status and Preferred Method Type Label settings based on integration partner. 
+ /incudes/rg/class-pet-match-pro-rg-detail-functions.php
    * Correct bug in processing missing label options. 

Version 7.6.2 - January 21, 2026
+ /pet-match-pro.php
    * Allow up to 18 thumbs on detail pages. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Correct bug hard-coding max detail thumbs. 
+ /includes/class-pet-match-pro-activator.php
    * Use fields constants.
    * Correct key values used to default admin settings. 
+ /public/css/pet-match-pro-styles.css
    * Correct display of video thumbnail. 
    * Replace all hardcoded image URLs with CSS variables (var(--pmp-icon-xxx)).
    * Remove hardcoded social share icon overrides.
+ /includes/images/Female.png
    * Delete icon file. 
+ /includes/images/icon_*.jpg
    * Delete icon files.
+ /includes/images/icon_*.png
    * Rename to icon-*.*
+ /incudes/class-pet-match-pro-all-api.php
    * Add getImageUrl(), getImageCssVariables(), outputImageCssVariables() methods to allow them-based image overrides.
    * Added social share icon overrides for 
+ /incudes/af/class-pet-match-pro-af-detail-functions.php
    * Pet icon hyphen naming, use of unified images folder.
+ /incudes/pp/class-pet-match-pro-pp-detail-functions.php
    * Pet icon hyphen naming, use of unified images folder.
+ /incudes/rg/class-pet-match-pro-rg-detail-functions.php
    * Pet icon hyphen naming, use of unified images folder.
+ /public/class-pet-match-pro-public.php
    * Hook outputImageCssVariables() to output dynamic image path CSS in page heading. 

Version 7.6.1 - January 20, 2026
+ /pet-match-pro.php
    * Add new constant for setting to manage AnimalsFirst location field processing.
    * Add constants for AnimalsFirst API field processing. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Add setting to manage AnimalsFirst location field processing. 
+ /admin/partials/pmp-admin-info.php
    * Add help text for setting to manage AnimalsFirst location field processing. 
+ /includes/class-pet-match-pro-filter-override-manager.php
    * Enhance to allow dynamic location or site field value management based on AnimalsFirst location field setting. 
    * Use constants vs. hard coded field values. 
+ /admin/css/pet-match-pro-admin.css
    * Add CSS entries to style manage AnimalsFirst Site filter values.  
+ /admin/js/pet-match-pro-admin.js
    * Add script to manage AnimalsFirst Site filter values.  
+ /public/templates/af/found*.phpunversal-search
    * Code modernization.
+ /public/templates/af/lost*.php
    * Code modernization.
+ /admin/class-pet-match-pro-admin.php
    * Add localization settings for AnimalsFirst Site filter values.  
    * Enqueue jQuery Sortable for use in managing AnimalsFirst Site filter values. 
+ /includes/af/partials/pmp-field-values.php
    * Move type field from array filter to text filter. 
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Add filterAnimalsByLocation with helper methods to process location exclusions and filters.
    * Skip declawed field if species <> cat.
+ /public/templates/includes/class-pet-match-pro-detail-template.php
    * Add resolveLocationDisplayValue with helper method to process location exclusions and filters.
    * Skip declawed field if species <> cat.
+ /public/templates/af/universal-search-*.php
    * Add logic to utilize location exclusions and filters on the General Tab.
    * Skip declawed field if species <> cat.
    * Display search count when no sub-title is provided in shortcode. 
+ /public/templates/af/featured-search-default.php
    * Add logic to utilize location exclusions and filters on the General Tab.
+ /includes/class-pet-match-pro-all-api.php
    * Case-insensitive matching in locationLabel(). 
    * Skip declawed field if species <> cat.
+ /includes/af/class-pet-match-pro-af-api.php
    * Pass location field to Details Templates when AnimalsFirst location field setting is enabled. 
    * Correct issues building api page number parameters to reach max search count. 
+ /public/templates/af/adopt-profile-3-column*.php
    * Add logic to utilize location exclusions and filters on the General Tab.
    * Skip declawed field if species <> cat.
+ /public/templates/includes/class-pet-match-pro-poster-template-trait.php
    * Use resolveLocationDisplayValue() from BaseDetailTemplateAdd filterAnimalsByLocation with helper methods to process location exclusions and filters.
    * Skip declawed field if species <> cat.

Version 7.5.6 - January 17, 2026
+ /public/templates/pp/adopt-search-default.php
    * Remove label from name field. 
+ /public/templates/pp/adopt-search-filter-horizontal.php
    * New PetPoint search template to display filter fields horizontally. 
+ /public/templates/pp/adopt-details-filter-horizontal.php
    * New PetPoint detail template to display filter fields horizontally. 
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Correct bug processing icon logic. 
+ /admin/partials/pp/pmp-instructions-details.html
    * Add shortcodes for adopt-details-filter-horizontal.php template. 
+ /admin/partials/af/pmp-instructions-details.html
    * Add missing shortcode parameters.
+ /admin/partials/rg/pmp-instructions-details.html
    * Correct instructions. 
+ /pet-match-pro.php
    * Add shortcode constants for adopt-details-filter-horizontal.php template. 
    * Add constants for new filter horizontal template color settings.
+ /includes/class-pet-match-pro-pp-api.php
    * Add processing of new shortcodes for adopt-details-filter-horizontal.php template. 
+ /public/css/pet-match-pro-styles.css
    * Add styles for PetPoint horizontal templates. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Add new color settings for filter horizontal template.
    * Add Enable Search Label settings to General Tab (from partner Filters Tab). 
+ /public/partials/pet-match-pro-public-color-css.php
    * Process new color settings for filter horizontal template to css overrides files.
+ /admin/partials/pmp-admin-info.php
    * Add help text for new filter horizontal template color settings.
    * Add help text for Enable Search Label settings moved to General Tab (from partner Filters Tab)
+ /admin/partials/pmp-option-levels-color.php
    * Add level entries for new filter horizontal template color settings.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Always pass icon fields to detail templates. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Always pass icon fields to detail templates. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Always pass icon fields to detail templates. 
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * Add getIconFieldsToExclude function to remove icon fields from results when displayed as icons. 
+ /public/templates/af/universal-search-*.pp
    * Use field constants for defining available fields. 
    * Use new search trait function to remove icon fields from results when displayed as icons.
+ /includes/*/class-pet-match-pro-*-options.php
    * Remove the Search Labels settings. 
+ /admin/partials/*/pmp-admin-info.php
    * Remove Help Text for Search Labels settings. 
+ /includes/class-pet-match-pro-all-api.php
    * Changed showLabels() to read from generalOptions instead of filterOptions. 
+ /admin/partials/pmp-option-levels-general.php
    * Add level/enable entries for Enable Search Label settings. 
+ /public/templates/af/found-*.pp
    * Code modernization. 
+ /public/templates/af/lost-*.pp
    * Code modernization. 
+ /public/templates/includes/class-pet-match-pro-poster-template-trait.php
    * Revise for AnimalsFirst field constants. 

Version 7.5.5 - January 15, 2026
+ /public/templates/af/adopt-default.php
    * Code modernization. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Add instance caching.
+ /public/templates/includes/class-pet-match-pro-base-detail-template.php
    * Correct bug with video processing. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct bug processing details shortcode parameters.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Correct bug processing details shortcode parameters.
+ /public/templates/af/adopt-conversion-with-app.php
    * Delete file, use adopt-conversion.php
+ /public/templates/af/adopt-conversion*.php
    * Code modernization.
+ /public/templates/af/adopt-conversion-similar.php
    * New template to display similar animals for adoption after the animal details. 
+ /public/templates/af/universal-details-poster.php
    * Revise to make universal across partners. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Include shortcode-specified fields for processBirthdate to display as age in years. 
    * Use poster template settings.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Include shortcode-specified fields for processBirthdate to display as age in years. 
    * Use poster template settings.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Include shortcode-specified fields for processBirthdate to display as age in years. 
    * Use poster template setting.
+ /admin/partials/pmp-admin-info.php
    * Make age_in_years help partner specific based on the field used. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Make age_in_years label partner specific based on the field used. 
+ /public/templates/includes/class-pet-match-pro-poster-template-trait.php
    * Revise to support AnimalsFirst photo structure.  
+ /public/templates/af/adopt-wide.php
    * Code modernization.
+ /public/templates/pp/adopt-wide.php
    * Use new html structure. 
+ /public/templates/af/adopt-profile-3-column*.php
    * Code modernization.
+ /includes/class-pet-match-pro-all-api.php
    * Support passing a parameter for a prefix class to the functions building detail page buttons.  
+ /public/includes/class-pet-match-pro-base-detail-template.php
    * Pass parameter for a prefix class to the functions building detail page buttons.  
+ /public/partials/pet-match-pro-public-color-css.php
    * Add color overrides for AnimalsFirst details profile buttons.  

Version 7.5.4 - January 9, 2026
+ /includes/af/class-pet-match-pro-af-api.php
    * Code modernization.
+ /public/templates/af/*-search-*.pp
    * Code modernization.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Remove any spaces when processing shortcode parameters.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Remove any spaces when processing shortcode parameters.
+ /includes/class-pet-match-pro-all-api.php
    * Correct bug preventing the search form selection of shortcode parameters (e.g. species = 'dog').
+ /public/templates/pp/*search*.pp
    * Correct bug displaying no animals meet search criteria message. 
    * Correct bug to prioritize results per row settings.
    * Correct bug to prioritize display icons settings.
    * Revise to centralize shortcode parameter processing in the search trait. 
+ /public/templates/rg/adopt-search-default.pp
    * Correct bug to prioritize results per row settings.
    * Correct bug to prioritize display icons settings.
    * Revise to centralize shortcode parameter processing in the search trait. 
+ /petmatch-pro.php
    * Add constant for AnimalsFirst color_pattern field. 
+ /admin/partials/af/pmp-field-levels*.php
    * Add level for AnimalsFirst color_pattern field. 
+ /admin/partials/af/pmp-field-values*.php
    * Add label for AnimalsFirst color_pattern field. 
+ /public/css/pet-match-pro-styles.css
    * Add css for universal search widget template. 
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * New function to get search settings with admin fallback. 
    * Centralize processing of shortcode icon/icons parameters.  
    * Centralize processing of shortcode labels parameter. 

Version 7.5.3 - January 7, 2026
+ /includes/af/class-pet-match-pro-af-options.php
    * Code modernization.
+ /admin/partials/af/pmp-admin-info.php
    * Code modernization.
    * Add help text for method specific poster template selection in General Tab.
+ /admin/partials/af/pmp-option-levels-filter.php
    * Code modernization.
    * Add settings for poster templates and list/preferred method use. 
+ /admin/partials/af/pmp-option-levels-labels.php
    * Code modernization.
+ /admin/partials/pmp-option-levels-general.php
    * Add level settings for method specific poster template settings in General Tab.
+ /includes/af/pmp-field-levels-*.php
    * Code modernization.
+ /includes/af/pmp-field-values-*.php
    * Code modernization.
+ /admin/class-pet-match-pro-admin-settings.php
    * Add missing function registerPreferredTemplateField.
    * Revise loadAdminInfo to be 'lazy'.
    * Correct bug preventing the display of AF Sort option values in the General Tab.
    * Add settings for method specific poster template selection to General Tab.
    * Add logic for dynamic AnimalsFirst values for Filter Values Tab.
+ /public/templates/common/
    * Remove folder.
+ /public/templates/*/universal-details-poster.php
    * New template file supporting all method types (rename of /common/details/poster.php).
+ /admin/partials/pmp-option-levels-general.php
    * Add level entries for method specific poster page templates. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Remove /common/ check from getTemplateDirectory function.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Remove /common/ check from getTemplateDirectory function.
+ /pet-match-pro.php
    * Add constants for method specific poster templates.
+ /admin/class-pet-match-pro-functions.php
    * Add functions for method specific poster templates.
+ /includes/af/class-pet-match-pro-af-options.php
    * Changes to display appropriate settings for integration partner. 
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Changes to display appropriate settings for integration partner. 
+ /admin/partials/pp/pmp-option-levels-filter.php
    * Add settings for poster templates and list/preferred method use. 
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Changes to display appropriate settings for integration partner. 
    * Correct bug with display of help text.
+ /admin/partials/rg/pmp-option-levels-filter.php
    * Add settings for poster templates and list/preferred method use. 
+ /admin/css/pet-match-pro-admin.css
    * Style Filter Values Tab for AnimalsFirst.
+ /admin/js/pet-match-pro-admin.js
    * Scripts to support dynamic AnimalsFirst values for Filter Values Tab.
+ /includes/class-pet-match-pro-filter-override-manager.php
    * Add logic for dynamic AnimalsFirst values.
+ /admin/partials/pp/pmp-admin-info.php
    * Add help text for method specific poster template selection in General tab.
+ /admin/partials/rg/pmp-admin-info.php
    * Add help text for method specific poster template selection in General tab.

Version 7.5.2 - December 29, 2025
+ /admin/partials/pp/pmp-option-levels-filter.php
    * Remove level entr for lost_found combo setting, moved to General tab as template setting. 
+ /includes/pp/class-pet-match-pro-options.php
    * Remove lost_found combo setting, moved to General tab as template setting. 
+ /admin/partials/pmp-option-levels-general.php
    * Add level settings for lost_found template setting. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Add level settings for lost_found template setting to General tab.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Updated createLostFoundSearch() to use new template setting + pass $detailUrls array; simplified determineLostFoundPage(). 
+ /public/tempates/pp/universal-search-default.php
    * Added $detailUrls parameter; updated buildDetailUrl() to check animal's Type field ('Lost' -> lost page, else -> found page). 
    * Add field-specific classes to label and value containers.
    * Add ability to display search results with separator.
+ /includes/class-pet-match-pro-all-api.php
    * Revise processTemplate() to use the new lost_found template key.  
+ /public/tempates/pp/found-search-default.php
    * Add field-specific classes to label and value containers.
    * Properly convert age to years based on appropriate admin setting.
    * Correct logic to display search results with separator.
+ /public/tempates/pp/lost-search-default.php
    * Add field-specific classes to label and value containers.
    * Properly convert age to years based on appropriate admin setting.
    * Correct logic to display search results with separator.
+ /public/tempates/pp/adopt-search-default.php
    * Correct logic to display search results with separator.
+ /public/tempates/rg/adopt-search-default.php
    * Add field-specific classes to label and value containers.
    * Correct logic to display search results with separator.
+ /public/css/pet-match-pro-styles.css
    * Add class to display search results inline with separator. 

Version 7.5.1 - December 29, 2025
+ /pet-match-pro.php
    * New constant for filter value/label customization. 
    * Remove orderby_labels setting constant. 
+ /includes/class-pet-match-pro-filter-override-manager.php
    * New class to manager filter/label customization.
+ /admin/partials/pmp-option-levels-filters-customize.php
    * New file to manage access to new admin tab for filter value/label customization. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Add new admin tab for filter value/label customization.
    * Remove orderby_labels settings from General tab (now included in filter value/label customization).
+ /admin/css/pet-match-pro-admin.css
    * Add styles for new filter value/label customization tab.
+ /admin/js/pet-match-pro-admin.js
    * Add scripts for new filter value/label customization tab.
+ /admin/partials/pmp-option-levels-general.php
    * Remove orderby_labels level constant. 
+ /admin/partials/pmp-admi-info.php
    * Remove orderby_labels level constant. 
+ /admin/class-pet-match-pro-functions.php
    * Revise logic for custom order_by values from use of Generl Tab orderby_labels settings to filter value/label customization.
+ /includes/class-pet-match-pro-all-api.php
    * Update to use filter value/label customization when building values for search form.

Version 7.4.1 - December 15, 2025
+ /includes/core/class-pet-match-pro-api-result.php
    * New results wrapper class to standardize error messaging.
+ /includes/core/class-pet-match-pro-error-logger.php
    * New class to implement centralized error logging.
+ /admin/partials/pmp-option-levels-analytics.php
    * New file controlling access to admin Analytics tab.
+ /admin/class-pet-match-pro-admin-settings.php
    * Support the use of either plugin or theme-based templates.
+ /includes/pp/class-pet-match-pro-pp-detail-functions.php
    * Support theme-based /includes/images overrides for premium licenses. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Support detail and search theme-based template overrides for premium licenses.
    * Support theme-based /includes/images overrides for premium licenses. 
+ /includes/rg/class-pet-match-pro-rg-detail-functions.php
    * Support theme-based /includes/images overrides for premium licenses. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Support detail and search theme-based template overrides for premium licenses.
    * Support theme-based /includes/images overrides for premium licenses. 

Version 7.3.1 - November 25, 2025
+ /admin/partials/activate_license_form.php
    * Modernization of code. 
+ /admin/partials/deactivate_license_form.php
    * Modernization of code. 
+ /admin/partials/pet-match-pro-admin-activation.php
    * Modernization of code. 
+ /admin/partials/*/pp-instructions-details.html
    * Add the social share parameter to the instructions.
+ /includes/class-pet-match-pro.php
    * Modernization of code. 
+ /admin/license/class-pet-match-pro-license.php
    * Additional modernization of code. 
+ /public/templates/includes/class-pet-match-pro-poster-template.php
    * New base class for poster detail templates.
+ /public/templates/pp/details-poster.php
    * Use of modern base poster details class. 
+ /pet-match-pro.php
    * New constants to support dynamic location exclusion and filter max. 
    * New constants to support API caching.
    * New constants to support analytics/click tracking.
+ /admin/js/pet-match-pro-admin.js
    * Add dynamic location field AJAX handling. 
    * Add API cache control button handlers.
+ /admin/class-pet-match-pro-admin-settings.php
    * Complete modernization of admin look. 
    * Allow dynamic selection of the number of location exclusion and filters.
    * Add performance accordian o General tab, API cache setting fields and AJAX handlers for API caching. 
+ /includes/class-pet-match-pro-all-api.php
    * Extend location methods to support use of double digit location field count.
+ /includes/cache/class-pet-match-pro-api-cache.php
    * API stats tracking.
+ /includes/cache/class-pet-match-pro-cached-api-client.php
    * Wrapper to implement API caching for every integration partner.
+ /admin/css/per-match-pro-admin.css
    * Add General Tab Performance Group styles.
    * Add Analytics Tab styles.
+ /includes/analytics/class-pet-match-pro-analytics-db.php
    * New file supporting analytics/click tracking database functions.
+ /includes/analytics/class-pet-match-pro-analytics-tracker.php
    * New file supporting analytics/click tracking event recording.
+ /includes/analytics/class-pet-match-pro-analytics-ajax.php
    * New file supporting analytics/click tracking front-end AJAX event handlers.
+ /includes/analytics/class-pet-match-pro-analytics-admin.php
    * New file supporting the new Analytics tab in the admin.
+ /public/js/pet-match-pro-public.js
    * Add analytics tracking. 
+ /admin/partials/PetMatchPro.php
    * Delete file, consolidate license handling to admin.
+ /admin/partials/pet-match-pro-admin-activation.php
    * Delete file, consolidate license handling to admin settings.

Version 7.1.0 - November 2, 2025
+ /pet-match-pro.php
    * Modernization of code. 
+ /uninstall.php
    * Modernization of code. 
+ /public/class-pet-match-pro-public.php
    * Modernization of code. 
+ /public/partials/pet-match-pro-public-color-css.php
    * Modernization of code. 
+ /includes/class-pet-match-pro-activator.php
    * Modernization of code. 
+ /includes/class-pet-match-pro-deactivator.php
    * Modernization of code. 
+ /includes/class-pet-match-pro-i18n.php
    * Modernization of code. 
+ /includes/class-pet-match-pro-form-builder.php
    * Modernization of code. 
+ /includes/pp/*.php
    * Modernization of code. 
+ /includes/pp/partials/*.php
    * Modernization of code. 
+ /admin/class-pet-match-pro-admin.php
    * Modernization of code. 
+ /admin/class-pet-match-pro-functions.php
    * Modernization of code. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Modernization of code. 
+ /admin/license/class-pet-match-pro-license.php
    * Modernization of code. 
+ /admin/partials/pmp-admin-info.php
    * Modernization of code. 
+ /admin/partials/pmp-option-levels*.php
    * Modernization of code. 
+ /admin/partials/pp/*.php
    * Modernization of code. 
+ /admin/js/pet-match-pro-admim.js
    * Modernization of code. 
    * New function to display hover text for disabled settings.
    * New functions to dynamically change title colors on Colors tab when a value is entered.
+ /public/templates/includes/class-pet-match-pro-search-template-trait.php
    * New features trait for search templates.
+ /public/templates/pp/adopt-search-default.php
    * Use of modern base search template.
+ /public/templates/pp/universal-search-default.php
    * Use of modern base search template.
+ /public/templates/includes/class-pet-match-pro-base-detail-template.php
    * New base class for detail templates.
+ /public/templates/includes/class-pet-match-pro-poster-template-trait.php
    * New features trait for poster detail templates.
+ /public/templates/pp/adopt-conversion-poster.php
    * Use of modern base default template.
+ /public/templates/pp/adopt-default.php
    * Use of modern base default template.
+ /public/css/pet-match-pro-styles.cssp
    * Modernization of code plus new styles for search and detail templates. 

Version 6.2.5 - October 2, 2025
+ /pet-match-pro.php
    * Add constants for 'admin', 'api', 'class', 'detail', 'field', 'filter', 'functions', 'info', 'levels', 'license', 'min', 'options', 'public', 'settings', 'styles' and 'values' in file names.
    * Add constants for social share plugin directory and file name.
    * Add constants for search method calling source (admin or shortcode).
    * Add constant for details method calling source (front-end).
+ /admin/class-pet-match-pro-admin.php
    * Remove extraneous comments. 
    * Standardize name, slug and version variable names.
    * Use of license directory and new file name constants.
    * Use of PetMatchPro proper constant. 
+ /admin/class-pet-match-pro-functions.php
    * Use of new file name constants.
+ /admin/class-pet-match-pro-admin-settings.php
    * Remove extraneous comments. 
    * Standardize name, slug and version variable names.
    * Use of new file name constants.
    * Escape and translate button label to apply changes to General Settings.
    * Escape and translate help text for settings.
+ /public/class-pet-match-pro-public.php
    * Standardize name, slug and version variable names.
    * Use of new file name constants.
+ /includes/class-pet-match-pro-includes.php
    * Standardize name, slug and version variable names.
    * Use of new file name constants.
+ /includes/class-pet-match-pro-all-api.php
    * Use method type constants. 
    * Use of new file name constants.
+ /includes/af/class-pet-match-pro-af-options.php
    * Use of new file name constants.
    * Escape and translate help text for settings.
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Use of new file name constants.
    * Escape and translate help text for settings.
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Use of new file name constants.
    * Escape and translate help text for settings.
+ /includes/af/class-pet-match-pro-af-detail-functions.php
    * Use of new file name constants.
+ /includes/pp/class-pet-match-pro-pp-detail-functions.php
    * Use of new file name constants.
+ /includes/rg/class-pet-match-pro-rg-detail-functions.php
    * Use of new file name constants.
+ /includes/af/partials/pmp-field-values.php
    * Use level_types constant to name array of level type values.
+ /includes/pp/partials/pmp-field-values.php
    * Use primary breed constant to name cat and dog species arrays.
    * Use level_types constant to name array of level type values.
+ /includes/pp/partials/pmp-field-values.php
    * Use primary breed constant to name cat and dog species arrays.
+ /includes/af/class-pet-match-pro-af-api.php
    * Use of new file name constants.
    * Use new constants for search method calling source (admin or shortcode).
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Use of new file name constants.
    * Use new names for cat and dog breed arrays.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Use of new file name constants.
    * Use new constants for search method calling source (admin or shortcode).

Version 6.2.4 - September 17, 2025
+ /public/partials/pet-match-pro-public-color-css.php
    * Add initialization of integration partner specific result details title color style for all method types. 

Version 6.2.3 - August 14, 2025
+ /includes/af/class-pet-match-pro-af-api.pp
    * Properly declare variable $partialsAdminDir.
+ /includes/rg/class-pet-match-pro-rg-api.pp
    * Properly declare variable $partialsAdminDir.
+ /public/templates/af/universal-search.php
    * Rename to universal-search-default.php
+ /public/templates/pp/universal-search.php
    * Rename to universal-search-default.php
+ /includes/af/class-pet-match-pro-af-api.pp
    * Cast $speciesArray as an array for PHP 8.X.
+ /includes/pp/class-pet-match-pro-pp-api.pp
    * Cast $speciesArray as an array for PHP 8.X.
+ /includes/rg/class-pet-match-pro-rg-api.pp
    * Cast $speciesArray as an array for PHP 8.X.
+ /includes/class-pet-match-pro-all-api.pp
    * Remove license restriction from procedure requestedSpecies as all filter parameters are now added to the URL.

Version 6.2.2 - August 14, 2025
+ /includes/af/class-pet-match-pro-all-api.pp
    * Correct error processing description shortcode substitutions.  

Version 6.2.1 - August 13, 2025
+ /pet-match-pro.php
    * Require PHP v8.0.
+ /includes/af/class-pet-match-pro-af-api.pp
    * Revise createSearch procedure to process species correctly when no results are returned from the Get Filters query.  

Version 6.2.0 - August 7, 2025
+ /admin/partials/af/pmp-instructions-search.html
    * Add shelter, status and good with values to search filter field options. 
    * Add featured to type options.
    * Add disclaimer and shelter to detail field values.
    * Update featured to featured_animal in details field list. 
+ /admin/partials/af/pmp-instructions-details.html
    * Add disclaimer and shelter to detail field values.
    * Update featured to featured_animal in details field list. 
+ /pet-match-pro.php
    * Add discaimer and shelter field constants.
    * Update good with field names. 
+ /includes/af/partials/pmp-field-levels-*.php
    * Remove level for filtering by location (now shelter). 
    * Add levels for filtering by shelter and good with fields. 
    * Add levels for disclaimer, good with and shelter fields as search and detail fields.
+ /includes/af/partials/pmp-field-values-*.php
    * Remove label/value for filtering by location (now shelter). 
    * Add labels/values for filtering by shelter and good with fields. 
    * Add labels/values for disclaimer, good with and shelter fields as search and detail fields.
+ /includes/af/partials/pmp-field-values.php
    * Add good with and shelter fields to the list of filter field arrays. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Update the search creation procedure to include shelter and good in the search url.
    * Update the search creation procedure to process the featured type as a search filter vs. a type value.
    * Remove procedure to output featured pets (use same procedure outputting search results).
    * Make urlParams a public variable to be references ion universal search templates.
+ /public/templates/af/universal-search-*.php
    * Add ability to filter search results by shelter.

Version 6.1.1 - August 1, 2025
+ /admin/class-pet-match-pro-admin-settings.php
    * Use $GLOBALS to pass display of activation license messages. 
+ /admin/partials/PetMatchPro.php
    * Use $GLOBALS to pass display of activation license messages. 
+ /admin/partials/activate_license_form.php
    * Use $GLOBALS to pass display of activation license messages. 
+ /admin/partials/af/pmp-instructions-search.html
    * Correct typo.
+ /public/templates/af/adopt-profile-3-colun*.pp
    * Revise main heading. 
+ /admin/license/class-pet-match-pro-license.php
    * Remove text translations. 
    * Correct errors in function _CheckWPPlugin: process response status vs. code and properly initialization error message. 
    * Set response status to Error vs. false.  

Version 6.1.0 - July 15, 2025
+ /admin/class-pet-match-pro-admin-settings.php
    * Remove the license key requirement from all settings to permit use from WordPress plugin repository w/o a PMP license key.
+ /includes/af/class-pet-match-pro-af-options.php
    * Remove the license key requirement from all settings to permit use from WordPress plugin repository w/o a PMP license key.
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Remove the license key requirement from all settings to permit use from WordPress plugin repository w/o a PMP license key.
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Remove the license key requirement from all settings to permit use from WordPress plugin repository w/o a PMP license key.

Version 6.0.7 - June 11, 2025
+ /includes/rg/class-pet-match-pro-rg-api.pp
    * Correct error processing videos when no videos exist. 
+ /public/class-pet-match-pro-public.php
    * Require integration partner include file before initializing new class. 

Version 6.0.6 - June 7, 2025
+ /public/templates/af/adopt-profile-3-column.php
    * Add animal description. 
+ /public/templates/af/adopt-profile-3-column-similar.php
    * Add animal description. 
+ /public/css/pet-match-pro-styles.css
    * Add styles for AnimalsFirst Profile Details Description.
+ /public/css/pet-match-pro-styles.min.css
    * Add styles for AnimalsFirst Profile Details Description.
+ /public/js/jquery-3.2.1.slim.min.js
    * Remove file, included in WordPress.
+ public/video/lib/adapter/*
    * Remove directory, jquery.js included in WordPress. 
+ public/video/src/adapter/*
    * Remove directory, jquery.js included in WordPress. 

Version 6.0.5 - May 30, 2025
+ /admin/class-pet-match-pro-admin-settings.php
    * Additional changes to properly escape output for translation. 
    * Unslash $_GET and $_POST variables.
+ /admin/class-pet-match-pro-functions.php
    * Additional changes to properly escape output for translation. 
+ /admin/partials/PetMatchPro.php
    * Unslash $_POST variables.
+ /public/templates/pp/details-poster.php
    * Additional changes to properly escape output for translation. 
+ /public/class-pet-match-pro-public.php
    * Additional changes to properly escape output for translation. 
+ /public/partials/pet-match-pro-public-color-css.php
    * Initialize WordPress file system global $wp_filesystem. 
+ /public/templates/af/details-poster.php
    * Unslash and sanitize $_GET and $_SESSION variables.
+ /public/templates/af/featured-search-default.php
    * Unslash and sanitize $_SERVER and $_POST variables.
+ /public/templates/af/universal-search-filter-widget.php
    * Unslash and sanitize $_SERVER, $_GET and $_POST variables.
+ /public/templates/af/universal-search-no-filter.php
    * Unslash and sanitize $_SERVER, $_GET and $_POST variables.
+ /public/templates/af/universal-search.php
    * Unslash and sanitize $_SERVER, $_GET and $_POST variables.
+ /public/templates/af/adopt-conversion.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/af/adopt-conversion-no-app.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/af/adopt-conversion-poster.php
    * Unslash and sanitize $_SERVER and $_GET variables.
+ /public/templates/af/adopt-conversion-with-app.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/af/adopt-default.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/af/adopt-3-column.php
    * Unslash and sanitize $_SERVER and $_GET variables.
+ /public/templates/af/adopt-3-column-similar.php
    * Unslash and sanitize $_SERVER and $_GET variables.
+ /public/templates/af/adopt-wide.php
    * Unslash and sanitize $_SERVER variable.
+ /public/templates/af/found-default.php
    * Unslash and sanitize $_SERVER variable.
+ /public/templates/af/found-poster.php
    * Unslash and sanitize $_SERVER and $_GET variables.
+ /public/templates/af/lost-default.php
    * Unslash and sanitize $_SERVER variable.
+ /public/templates/af/lost-poster.php
    * Unslash and sanitize $_SERVER and $_GET variables.
+ /public/templates/pp/adopt-conversion-no-app.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/pp/adopt-conversion-poster.php
    * Unslash and sanitize $_SERVER and $_GET variables.
+ /public/templates/pp/adopt-conversion-similar.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/pp/adopt-conversion.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/pp/adopt-conversion-with-app.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/pp/adopt-default.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/pp/adopt-wide.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/pp/found-default.php
    * Unslash and sanitize $_SERVER variable.
+ /public/templates/pp/found-poster.php
    * Unslash and sanitize $_SERVER and $_GET variables.
+ /public/templates/pp/lost-default.php
    * Unslash and sanitize $_SERVER variable.
+ /public/templates/pp/lost-poster.php
    * Unslash and sanitize $_SERVER and $_GET variables.
+ /public/templates/rg/adopt-cpa.php
    * Unslash and sanitize $_SERVER and $_GET variables.
+ /public/templates/rg/adopt-default.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/rg/adopt-similar.php
    * Unslash and sanitize $_SERVER variables.
+ /public/templates/rg/details-poster.php
    * Unslash and sanitize $_SESSION and $_GET variables.
+ /public/templates/pp/adopt-search-default.php
    * Unslash and sanitize $_SERVER and $_POST variables.
+ /public/templates/pp/details-poster.php
    * Unslash and sanitize $_GET and $_SESSION variables.
+ /public/templates/pp/featured-search-default.php
    * Unslash and sanitize $_SERVER and $_POST variables.
+ /public/templates/pp/found-search-default.php
    * Unslash and sanitize $_SERVER and $_POST variables.
+ /public/templates/pp/lost-search-default.php
    * Unslash and sanitize $_SERVER and $_POST variables.
+ /public/templates/pp/universal-search.php
    * Unslash and sanitize $_SERVER and $_POST variables.
+ /public/templates/rg/adopt-search-default.php
    * Unslash and sanitize $_SERVER variables.
+ /includes/for-master/PhpFormBuilder.php
    * Unslash and sanitize $_REQUEST variable.
+ /includes/class-pet-match-pro-all-api.php
    * Unslash and sanitize $_SERVER, $_GET and $_POST variables.
+ /admin/license/class-pet-match-pro-license.php
    * Unslash $_GET before sanitization.
    * Check $_SERVER variable existence before use. 
    * Unslash and sanitize $_SERVER variables before use.
+ /includes/af/class-pet-match-pro-af-api.pp
    * Unslash $_GET and $_POST before sanitization.
    * Unslash and sanitize $_SERVER and $_SESSION variables. 
+ /includes/pp/class-pet-match-pro-pp-api.pp
    * Unslash $_GET and $_POST before sanitization.
    * Unslash and sanitize $_SERVER and $_SESSION variables. 
+ /includes/rg/class-pet-match-pro-rg-api.pp
    * Additional changes to properly escape output for translation. 
    * Unslash $_GET and $_POST before sanitization.
    * Unslash and sanitize $_SERVER and $_SESSION variables. 
+ / includes/class-pet-match-pro-form-builder.php
    * Unslash and sanitize $_SERVER variables. 
    * Unslash and sanitize $_GET and $_POST variables.

Version 6.0.4 - May 29, 2025
+ /admin/class-pet-match-pro-admin.php
    * Use constants. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Additional changes to properly escape output for translation. 
+ /admin/class-pet-match-pro-functions.php
    * Additional changes to properly escape output for translation. 
+ /admin/license/class-petmatch-pro-license.php
    * Additional changes to properly escape output for translation. 
+ /admin/partials/pet-match-pro-admin-activation.php
    * Additional translation. 
+ /admin/partials/pmp-option-levels-instructions.php
    * Use constants. 
+ /admin/partials/af/pmp-option-levels-filter.php
    * Use constants. 
+ /admin/partials/af/pmp-option-levels-labels.php
    * Use constants. 
+ /admin/partials/pp/pmp-option-levels-filter.php
    * Use constants. 
+ /admin/partials/pp/pmp-option-levels-labels.php
    * Use constants. 
+ /admin/partials/rg/pmp-option-levels-filter.php
    * Use constants. 
+ /admin/partials/rg/pmp-option-levels-labels.php
    * Use constants. 
+ /public/templates/af/adopt-conversion.php
    * Properly escape output for translation. 
+ /public/templates/af/adopt-profile-3-column-similar.php
    * Properly escape output for translation. 
+ /public/templates/af/adopt-profile-3-column.php
    * Properly escape output for translation. 

Version 6.0.3 - May 24, 2025
+ /pet-match-pro.php
    * Add license GPLv3 or later to plugin heading.
    * Use 'PMP_PLUGIN_SLUG' constant for domain option in translations. 
    * Properly escape output for translation. 
+ /admin/partials/license/license-form-active.php
    * Properly escape output for translation. 
    * Use constants in output. 
+ /admin/partials/license/license-form.php
    * Properly escape output for translation. 
+ /admin/partials/activate_license_form.php
    * Properly escape output for translation. 
    * Use constants in output. 
+ /admin/partials/deactivate_license_form.php
    * Properly escape output for translation. 
    * Use constants in output. 
+ /admin/class-pet-match-pro-admin.php
    * Include the activation/deactivation class when loading dependencies.
+ /admin/partials/PetMatchPro.php
    * Redirect to referral url. 
    * Remove activation & deactivation forms. 
    * Remove admin menu option. 
+ /public/templates/partials/video-includes.php
    * Add stylesheet versions.
    * Add script arguments.
    * Use constants.
+ /admin/license/class-petmatch-pro-license.php
    * Properly escape output for translation. 
+ /includes/af/class-pet-match-pro-af-options.php
    * Properly escape output for translation. 
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Properly escape output for translation. 
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Properly escape output for translation. 
+ /admin/class-pet-match-pro-functions.php
    * Properly escape output for translation. 
+ /admin/index.php
    * Properly escape output for translation. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Check if session exists before starting. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Check if session exists before starting. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Check if session exists before starting. 
+ /admin/partials/pmp-option-levels.php
    * Use constants.

Version 6.0.2 - April 4, 2025
+ /admin/partials/PetMatchPro.php
    * Move constant initialization from class definition to function. 

Version 6.0.1 - April 3, 2025
+ /public/templates/af/adopt-profile-3-column*.php
    * Update comment on closing div for media. 
+ /public/css/pet-match-pro-styles*.css
    * pmp-details-profile-media class width to 1%.
+ /admin/class-pet-match-pro-admin-settings.php
    * Declare listMethodType as a private variable.
+ /includes/class-pet-match-pro-activator.php
    * Remove RescueGroups settings from General Tab initialization.
    * Add Label Tab values for AdoptionList type.

Version 6.0.0 - March 31, 2025
+ /pet-match-pro.php
    * Add additional color constants.
+ /admin/class-pet-match-pro-admin-settings.php
    * Use additional constants.

Version 5.9.8 - March 11, 2025
+ / pet-match-pro.php
    * Add constant 'ID'.
    * Add constant ''SHORTCODE_CASE_LOWER'.
    * Add constant 'ADOPTED_METHODTYPE_PETPOINT'.
    * Add constant 'ANIMALSFIRST_LIVE_SOURCE'.
+ /public/class-pet-match-pro-public.php
    * Use new constant 'ID'.
    * Use 'COLOR' constant. 
    * Use shortcode parameter constants when initializing the detail shortcode function petmatch_detail. 
    * Use shortcode parameter constants when initializing the option value shortcode function petmatch_option_value. 
+ /includes/class-pet-match-pro.php
    * Use tab constants when defining the admin hooks.
+ /includes/class-pet-match-pro-all-api.php
    * Use constants to define filterOptions.
    * Use new constant 'ADOPTED_METHODTYPE_PETPOINT' when setting call method parameters. 
+ / public/templates/af/*search*.php
    * Use general vs. AnimalsFirst method constant when define no search results error. 
+ / public/templates/pp/universal-search.php
    * Use general vs. PetPoint method constant when define no search results error. 
+ /admin/class-pet-match-pro-functions.php
    * Use additional constants.
+ /admin/class-pet-match-pro-admin-settings.php
    * Use additional constants.
+ /public/templates/pp/found-*.php
    * Add default value for jurisdiction in detail templates. 
+ /public/templates/pp/lost-*.php
    * Add default value for jurisdiction in detail templates.
+ /includes/af/class-pet-match-pro-af-api.php
    * Add logic to prevent Animal Details description from being mixed case.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Add logic to prevent Animal Details description from being mixed case.

Version 5.9.7 - February 27, 2025
+ /public/css/pet-match-pro-styles.css
    * Add pointer property to search result image and name.
+ /includes/pp/partials/pmp-field-values.php
    * Added new value for lost_found Search Types.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Update lost_found search to use new Search Type filter values. 
    * Update lost_found search to exclude lost search results when Search Types Found Reports Only or Animals in Custody are selected. 
    * Update logic to assign animal detail page url.
+ /includes/class-pet-match-pro-all-api.php
    * Initialize selected value as input to the form builder. 
+ /public/templates/pp/found-search-default.php
    * Update not to initialize filter values unless called by found type. 
+ /public/templates/pp/lost-search-default.php
    * Update not to initialize filter values unless called by lost type. 
+ /public/templates/pp/universal-search.php
    * Correct logic assigning details page url in search results. 
+ /public/js/pet-match-pro-public.js
    * Add search pagination to URL in browser history to enable return from animal details page.
+ /public/css/pet-match-pro-public.min.css
    * New file, minimized and compressed style sheet.
+ /public/css/pet-match-pro-styles.min.css
    * New file, minimized and compressed style sheet.
+ /public/js/pet-match-pro-public.min.js
    * New file, minimized and compressed script file.
+ /public/class-pet-match-pro-public.php
    * Use compressed css and javascript files. 
+ /pet-match-pro.php
    * New constant REFERRER.
    * Change REFERER constant to REFERRER.
+ /includes/af/class-pet-match-pro-af-api.php
    * Add $_SESSION entry for current page URL before displaying animal details template.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Add $_SESSION entry for current page URL before displaying animal details template.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Add $_SESSION entry for current page URL before displaying animal details template.
+ /public/templates/af/*.php
    * Revise the referrer logic to use the $_SESSION entry.
+ /public/templates/pp/*.php
    * Revise the referrer logic to use the $_SESSION entry.
+ /public/templates/rf/*.php
    * Revise the referrer logic to use the $_SESSION entry.

Version 5.9.6 - February 19, 2025
+ /public/templates/pp/found-search-default.php
    * Correct error in processing date search result.
    * Correct error to display shortcode details in the proper order.
+ /public/templates/pp/lost-search-default.php
    * Correct error in processing date search result.
    * Correct error to display shortcode details in the proper order.

Version 5.9.5 - February 4, 2025
+ /pet-match-pro.php
    * Change plugin slug constant to petmatchpro to match WordPress text domain.
    * Add constants to standardize contact settings.
    * Add constant for Pagination Page Limit.
+ /admin/partials/pmp-option-levels-contact.php
    *Use new constants.
+ /admin/partials/pmp-admin-info.php
    * Use new contact constants.
    * Use method and option constants.
    * Add help text for new General Option Pagination Page Limit. 
+ /admin/partials/pet-match-pro-admin-activation.php
    * Use constants.
    * Change text domain of settings sections/fields from 'pet-match-pro-plugin' to use constant.
+ /admin/PetMatchPro.php
    * Use constants.
    * Use new plugin slug constant.
+ /admin/class-pet-match-pro-admin-settings.php
    * Change text domain of settings sections from 'pet-match-pro-plugin' to use constant.
    * Use new contact constants.
    * Add General Option Pagination Page Limit. 
+ /includes/class-pet-match-pro-activator.php
    * Add default value for General Option Pagination Page Limit.
+ /includes/af/class-pet-match-pro-af-options.php
    * Change text domain of settings sections/fields from 'pet-match-pro-plugin' to use constant.
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Change text domain of settings sections/fields from 'pet-match-pro-plugin' to use constant.
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Change text domain of settings sections/fields from 'pet-match-pro-plugin' to use constant.
+ /includes/af/class-pet-match-pro-af-api.php
    * Use new contact constants.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Use new contact constants.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Use new contact constants.
+ /public/templates/af/*.php
    * Use new contact constants.
+ /public/templates/pp/*.php
    * Use new contact constants.
+ /public/templates/rg/*.php
    * Use new contact constants.
+ /public/templates/af/*search*.php
    * Add support for Pagination Page Limit.
+ /public/templates/pp/*search*.php
    * Add support for Pagination Page Limit.
+ /public/templates/rg/*search*.php
    * Add support for Pagination Page Limit.

Version 5.9.4 - January 31, 2025
+ /includes/partials/af/pmp-field-values.php
    * Add Level for Method Type lost_found_stray.
+ /includes/class-pet-match-pro-all-api.php
    * Update public function callMethod_Parameters to support Method Type lost_found_stray.

Version 5.9.3 - January 30, 2025
+ /includes/partials/af/pmp-field-levels-adopt.php
    * Allow filter by status with free license.
+ /includes/partials/af/pmp-field-levels-*.php
    * Add level entry for Location as a valid filter field. 
+ /includes/partials/af/pmp-field-values-*.php
    * Add label and value entries for Location as a valid filter field. 
+ /admin/partials/af/pmp-instructions-search.html
    * Include use of status parameter in search shortcode instructions. 
+ /pet-match-pro.php
    * Add preferred method type constants all and pending for AnimalsFirst.
    * Add constant for Search Shortcode Status parameter. 
+ /includes/partials/af/pmp-field-values.php
    * Add all and pending to the list of preferred method types. 
    * Add Location as a valid filter array field. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Add Location as a valid filter array field. 
    * Remove Animal from search results when calling search from Animal Details page.
+ /admin/class-pet-match-pro-admin-settings.php
    * Enable the display of Similar Animals for PetPoint & RescueGroups.
+ /public/templates/rg/adopt-similar.php
    * New Animal Detail template to display animals they may also like. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Remove Animal from search results when calling search from Animal Details page.

Version 5.9.2 - January 26, 2025
+ /pet-match-pro.php
    * New constants for PetPoint AdoptionList search type.  
    * New constant to rename admin Filter Options tab to Partner Options.
+ /includes/pp/partials/pmp-field-levels-adoptionlist.php
    * New file to support the PetPoint AdoptionList search type.
+ /includes/pp/partials/pmp-field-levels-list.php
    * New file to support the PetPoint AdoptionList search type.
+ /includes/pp/partials/pmp-field-values-list.php
    * New file to support the PetPoint AdoptionList search type.
+ /includes/pp/partials/pmp-field-values.php
    * Add entries to support the PetPoint AdoptionList search type.
+ /includes/class-pet-match-pro-all-api.php
    * Correct bug in public function finalizeItems processing shortcode details parameters.
    * Initialize default value for Search Filter text fields if one is provided. 
+ /admin/partials/pmp-option-levels-general.php
    * Add entries to support the PetPoint AdoptionList search type.
+ /admin/partials/pmp-admin-info.php
    * Add entries to support the PetPoint AdoptionList search type.
+ /admin/partials/pp/pmp-option-levels-labels.php
    * Add entries to support the PetPoint AdoptionList search type.
+ /admin/partials/pp/pmp-option-levels-filter.php
    * Add entries to support the PetPoint AdoptionList search type.
+ /admin/partials/pp/pmp-admin-info.php
    * Add entries to support the PetPoint AdoptionList search type.
+ /admin/class-pet-match-pro-admin-settings.php
    * Add entries to support the PetPoint AdoptionList search type.
    * Rename Filter Options to Partner Options. 
+ /admin/partials/af/pmp-intructions-search.html
    * Change Filter Options tab to Partner Options tab.
+ /admin/partials/af/pmp-intructions-deails.html
    * Change Filter Options tab to Partner Options tab.
+ /admin/partials/pp/pmp-intructions-search.html
    * Add instructions to support the PetPoint AdoptionList search type.
    * Change Filter Options tab to Partner Options tab.
+ /admin/partials/pp/pmp-intructions-deails.html
    * Change Filter Options tab to Partner Options tab.
+ /admin/partials/rg/pmp-intructions-search.html
    * Change Filter Options tab to Partner Options tab.
+ /admin/partials/rg/pmp-intructions-deails.html
    * Change Filter Options tab to Partner Options tab.
+ /includes/pp/class-pet-match-pro-detail-functions.php
    * Correct error in processing labels in the function Animal_Labels.
+ /includes/af/class-pet-match-pro-detail-functions.php
    * Correct error in processing labels in the function Animal_Labels.
+ /includes/rg/class-pet-match-pro-detail-functions.php
    * Correct error in processing labels in the function Animal_Labels.
+ /includes/af/class-pet-match-pro-af-api.php
    * Correct use of shortcode constant 'SHORTCODE_DETAILS_PARM' vs. 'SHORTCODE_DETAILS'.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Correct use of shortcode constant 'SHORTCODE_DETAILS_PARM' vs. 'SHORTCODE_DETAILS'.

Version 5.9.1 - January 22, 2025
+ /includes/af/class-pet-match-ro-af-api.php
    * New public function to display the Search Filters as a widget. 
+ /pet-match-pro.php
    * New constant for Search Form Widget. 
    * New Shortcode Subtitle constant.
    * New constants for the number of Similar Animals to Display Below Animal AnimalsFirst Details (default 12).
+ /includes/class-pet-match-pro-all-api.php
    * Update public function buildSearchForm to support the Search Filters widget.
    * Update public function processSearchParameters to include new subtitle option. 
+ /includes/af/partials/pmp-field-levels-*.php
    * Add Level entries for Search Filter Status.
+ /includes/af/partials/pmp-field-values-*.php
    * Add Value entries for Search Filter Status.
+ /public/css/pet-match-pro-styles.css
    * Add css for Search Filters widget.
+ /includes/class-pet-match-pro-form-builder.php
    * Add logic to support Search Filters widget. 
+ /admin/partials/pmp-option-levels-general.php
    * Add level for the number of Similar Animals to Display Below Animal AnimalsFirst Details.
+ /admin/partials/pmp-amin-if.php
    * Add help text for the number of Similar Animals to Display Below Animal Details.
+ /admin/class-pet-match-pro-admin-settings.php
    * Add new General Option for the number of Similar Animals to Display Below Animal AnimalsFirst Details.
+ /admin/partials/af/pmp-instructions-search.html
    * Add instruction for Subtitle. 
+ /public/templates/af/universal-search*.php
    * Add Subtitle to output display.
+ /admin/partials/pp/pmp-instructions-search.html
    * Add instruction for Subtitle. 
+ /public/templates/pp/*search*.php
    * Add Subtitle to output display.
+ /admin/partials/rg/pmp-instructions-search.html
    * Add instruction for Subtitle. 
+ /public/templates/rg/adopt-search-default.php
    * Add Subtitle to output display.

Version 5.8.4 - January 18, 2025
+ /pet-match-pro.php
    * Add new constant for AnimalsFirst max search page retrieval (250). 
    * Add featured method type for AnimalsFirst.
    * Update name of AnimalsFirst featured animal field. 
    * Add field constant for AnimalsFirst Intake Jurisdiction. 
+ /includes/af/partials/pmp-field-levels-*.php
    * Add Level entries for Intake Jurisdiction.
    * Add Level entries for Search Filter Intake Date.
+ /includes/af/partials/pmp-field-values-*.php
    * Add Label entries for Intake Jurisdiction.
    * Add Label entries for Search Filter Intake Date.
    * Add Value entries for Search Filter Intake Date.
+ /admin/partials/af/pmp-instructions-*.html
    * Add detail entry for Intake Jurisdiction.
+ /includes/af/class-pet-match-pro-af-api.php
    * Support search result retrievals greater than the 250 page limit.
    * Update to support featured animals. 
    * Update logic to display animal details. 
+ /includes/af/partials/pmp-field-values.php
    * Add featured to type levels and values. 
    * Add Intake Date to Text Filter Fields.
+ /public/templates/af/featured-search-default.php
    * New template for displaying featured animals.

Version 5.8.3 - January 17, 2025
+ /public/css/pet-match-pro-styles.css
    * Remove !important from search result grid layout to properly display on mobile. 
    * Remove !important from right padding of search result grid layout results to allow override in custom CSS.
    * Add CSS entries to properly size featured pet search results on mobile devices. 
    * Add CSS entries to support new AnimalsFirst search/detail templates. 
    * Revise width of media and result columns in detail templates. 
+ /public/templates/pp/found-*.php
    * Correct bug with displaying Found Phone Number.
+ /public/templates/pp/lost-*.php
    * Standardize initialization of Lost Phone Number & Email. 
+ / public/templates/af/*.php
    * Use Social Share Shortcode constant to call Shortcode. 
+ /admin/class-pet-match-pro-functions.hp
    * Correct bug in Filter_Option_Values function to use the Sort Field Label Setting to initialize label values. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct bug in the initialization of the default sort method for non adopt method types.
+ /pet-match-pro.php
    * Add new Shortcode setting for pagination of search results.
    * Add new Shortcode pmp-search-filters to separate the filter from the search as a widget.
    * Add new constant for AnimalsFirst max search page retrieval (250). 
+ /includes/class-pet-match-pro.php
    * Add a hook for the new pmp-search-filters Shortcode. 
+ /public/class-pet-match-pro-public.php
    * Add a function for the new pmp-search-filters Shortcode. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Support search result retrievals greater than the 250 page limit.  
+ /includes/class-pet-match-pro-all-api.php
    * Add pagination to public function processSearchParameters.
+ /pubic/templates/af/univeral-search-no-filter.php
    * New Search template that excludes the filter form.
+ /pubic/templates/af/adopt-profile-3-column.php
    * New Animal Detail template. 
+ /public/templates/af/universal-search.php
    * Update pagination to use public function processSearchParameters.
+ /public/templates/pp/*-search.php
    * Update pagination to use public function processSearchParameters.
+ /public/templates/rg/adopt-search-default.php
    * Update pagination to use public function processSearchParameters.
+ /admin/partials/af/pmp-instructions-search.html
    * Add pagination instruction.
+ /admin/partials/pp/pmp-instructions-search.html
    * Add pagination instruction.
+ /admin/partials/rg/pmp-instructions-search.html
    * Add pagination instruction.
+ /includes/af/class-pet-math-pro-af-detail-functions.php
    * Update General Age icon logic. 
+ /includes/images/*.png
    * New icon files to support AnimalsFirst and new search/detail templates.
+ /public/partials/pet-match-pro-public-color-css.php
    * New entries to support AnimalsFirst search/detail templates. 

Version 5.8.2 - January 8, 2025
+ /admin/class-pet-match-pro-admin.php
    * Use plugin constant in file name references. 
+ /admin/class-pet-match-pro-functions.php
    * Use constants. 
+ /admin/css/pet-match-pro-admin.css
    * Convert admin tabs to use pmp id prefix.
+ /admin/class-pet-match-pro-admin-settings.php
    * Convert admin tabs to use pmp id prefix.
    * Use constants in General settings. 
    * Add AnimalsFirst Preferred Search Template setting to General tab.
    * Use constants in Contact settings. 
    * Use constants in Instruction settings.
    * Add Search CTA button settings to Colors tab.
+ /admin/partials/pmp-admin-info.php
    * Add help text for AnimalsFirst Preferred Search Option.
    * Add help text for Search CTA button Color options.
+ /admin/partials/
+ /pet-match-pro.php
    * Add prefix constants for email, phone an URL.
    * Add instruction settings constant.
    * Add Search CTA button constants.
+ /public/partials/pet-match-pro-public-color-css.php
    * Use constants.
    * Initialize content to null prior to use.
    * Add entries for Search CTA button color options.
+ /admin/partials/pmp-option-levels-color.php
    * Add levels for Search CTA button options.
    * Use level constants.
+ /public/css/pet-match-pro-styles.css
    * Add CSS entries for the Search CTA button. 
    * Add a CSS entry to format featured animal search results.
    * Remove !important attribute from CSS entries w/ options included in the Colors tab. 

Version 5.8.1 - January 2, 2025
+ /pet-match-pro.php
    * Renamed Sponsor Link URL constant to resolve agent css display issue. 
    * Increased search result maximum default to 999. 

Version 5.7.4 - January 1, 2025
+ /public/class-et-match-pro-public.php
    * Allow use of pmp-search shortcode with a free license. 
+ /includes/class-pet-match-pro-all-api.php
    * Declare $searchDetailSource as a public variable.
+ /public/templates/af/*search.php
    * Update upgrade links to include field name in title. 
+ /public/templates/pp/*search.php
    * Update upgrade links to include field name in title. 
+ /public/templates/rg/*search.php
    * Update upgrade links to include field name in title. 

Version 5.7.3 - December 31, 2024
+ /incudes/pp/partials/pmp-field-values-found.php
    * Correct label typos.
+ /incudes/pp/partials/pmp-field-values-lost.php
    * Correct label typos.
+ /includes/class-pet-match-pro-all-api.php
    * Update logic to replace detail shortcodes in the default description. 
+ /public/templates/af/universal-search.php
    * Update logic for result pagination. 
+ /public/templates/pp/*-search-defaut.pp
    * Update logic for result pagination. 
+ /admin/css/pet-match-pro-admin.css
    * Add style to control the width of the option tabs.
+ /admin/partials/rg/*.html
    * Adjust row sizes to accommodate new tab width. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Add shortcode substitution for the default description when called via the pmp-detail shortcode. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Add shortcode substitution for the default description when called via the pmp-detail shortcode. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Add shortcode substitution for the default description when called via the pmp-detail shortcode. 

Version 5.7.2 - December 29, 2024
+ /includes/class-pet-match-pro-all-api.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
    * Update sort function to obtain the sortfield from the appropriate form submission method ($_GET vs. $_POST).
+ /includes/af/class-pet-match-pro-detail-functions.php
    * Use constants.
+ /includes/af/class-pet-match-pro-af-api.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /public/templates/af/universal-search.php
    * New file for adopt search template.
+ /public/templates/af/adopt-search-default.php
    * New file for adopt search template.
+/public/css/pet-match-pro-styles.css
    * Add styling options for 1 and 2 results per search row.
+ /admin/class-pet-match-pro-admin-settings.php
    * Convert partner method type constants to corresponding setting values.
    * Start process to convert field/level constants.
+ /admin/partials/pmp-option-levels-contact.php
    * Use setting constants.

Version 5.7.1 - December 22, 2024
+ /admin/partials/af/*.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /admin/partials/pp/*.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /admin/partials/rg/*.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /includes/af/partials/*.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /includes/pp/partials/*.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /includes/rg/partials/*.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /public/templates/af/*.php
    * Remove the jquery script from all files. 
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /public/templates/rg/*.php
    * Remove the jquery script from all files. 
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /includes/rg/partials/pmp-field-values.php
    * Add constants for dog and cat breeds.
+ /includes/af/partials/class-pet-match-pro-af-options.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /includes/pp/partials/class-pet-match-pro-pp-options.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /includes/rg/partials/class-pet-match-pro-rg-options.php
    * Remove static variables for dog and cat breeds.
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /includes/class-pet-match-pro-all-api.php
    * Correct issue processing integration partners w/o a featured method type. 
    * Correct issue processing search shortcode details values.
    * Correct issue with the search form anchor definition.
    * Correct issue with the search shortcode count parameter. 
+ /includes/class-pet-match-pro-form-builder.php
    * Update to support seach form field initialization via $_GET or $_POST.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
    * Convert search to use template.
+ /public/templates/rg/adopt-search-default.php
    * New file for adopt search template.

Version 5.6.2 - December 13, 2024
+ /includes/class-pet-match-pro-activator.php
    * Convert partner method type constants to corresponding setting values.
+ /includes/class-pet-match-pro-all-api.php
    * Convert partner method type constants to corresponding setting values.
+ /includes/class-pet-match-pro-form-builder.php
    * Use constants.
+ /includes/class-pet-match-pro-i18n.php
    * Use constants.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Convert partner method type constants to corresponding setting values.
+ /includes/pp/class-pet-match-pro-detail-functions.php
    * Use constants.
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Convert partner method type constants to corresponding setting values.
+ /includes/pp/partials/pmp-field-levels-adopt.php
    * Convert partner method type constants to corresponding setting values.
+ /includes/pp/partials/pmp-field-levels-found.php
    * Convert partner method type constants to corresponding setting values.
+ /includes/pp/partials/pmp-field-levels-lost.php
    * Convert partner method type constants to corresponding setting values.
+ /includes/pp/partials/pmp-field-values.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /includes/pp/partials/pmp-field-values-adopt.php
    * Convert partner method type constants to corresponding setting values.
+ /includes/pp/partials/pmp-field-values-found.php
    * Convert partner method type constants to corresponding setting values.
+ /includes/pp/partials/pmp-field-values-lost.php
    * Convert partner method type constants to corresponding setting values.
+ /includes/pp/partials/search-form.php
    * Use field/value constants.
+ /public/templates/pp/*.php
    * Convert partner method type constants to corresponding setting values.
    * Use field/value constants.
+ /public/templates/pp/universal-search.php
    * New search result template for use across all method types.

Version 5.6.1 - November 29, 2024
+ /includes/class-pet-match-pro-form-builder.php
    * Convert form to use $_GET vs. $_POST to prevent resubmission upon return after viewing animal detail page.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Modify PetPoint Search to initialize values from $_GET vs. $_POST.
+ /includes/class-pet-match-pro-activator.php
    * Use new setting constants.
+ /includes/class-pet-match-pro.php
    * Load translations at initialization vs. when plugin is loaded (new for WordPress 6.7).
    * Use constants to hook shortcodes.
+/admin/license/class-pet-match-pro-license.php
    * Set translation parameter to false when calling get_plugin_data to prevent premature translation. 
+ /pet-match-pro.php
    * Add constants for color options.
+ /admin/partials/pmp-admin-info.php
    * Use new setting constants.
+ /admin/partials/*/pmp-admin-info.php
    * Use new setting constants.
+ /admin/partials/PeMatchPro.php
    * Add version when registering admin styles.
+ /public/templates/pp/*.php
    * Update all templates with new constants.

Version 5.5.2 - November 24, 2024
+ /includes/class-pet-match-pro-all-api.php
    * New public function processSearchParameters to standardize the setting of search parameter values. 
    * Update processSearchParameters for new Banner Color options. 
    * Use new anchor tag to return focus after search form submission.
    * Use new setting constants.
+ /pet-match-pro.php
    * Add constant for max number of search results (249).
    * Add constant for max number of search results per page (3).
    * Add constant for max number of results per page ('SEARCH_COUNT_MAX_PAGE').
    * Add constants for use in settings. 
    * Add constants for anchor tag to return focus after search submission.
+ /includes/pet-match-pro.php
    * Add search shortcode parameter rows to override the max number of rows per page. 
    * Add search shortcode parameter icons to override the max number of icons per search result.
    * Use new constants.
    * Remove method specific search and detail shortcodes. 
+ /admin/partials/pp/pmp-instructions-search.html
    * Converted count search parameter to paid vs. free.
    * Add instruction for the new rows parameter to define the number of search result rows per page.
    * Add instruction for the new icons max for search result. 
+ /admin/partials/pmp-option-levels-general.php
    * Add level for the number of search result rows per page. 
    * Update settings to use constants.
    * Add level for search anchor tag option.
+ /public/templates/pp/*.php
    * Remove jquery script from detail templates.
+ /public/templates/pp/*-search-default.php
    * Add id to search form container for method type templates.
+ /admin/partials/pmp-option-levels-color.php
    * Add level entries for Search Result Banner color options. 
+ /admin/partials/pmp-admin-info.php
    * Add help entries for Search Result Banner color options. 
    * Add help for search anchor tag option.
+ /admin/class-pet-match-pro-admin-settings.php
    * Add Search Result Banner color options.
    * Add search anchor tag option to General tab.
+ /public/class-pet-match-po-public.php
    * Use new setting constants.
    * Remove functions for lost and found combination shortcode,use pet-search with type="lost_found".
    * Remove functions for method specific detail shortcodes, use pet-details with type parameter.
+ /public/partials/pet-match-pro-public-color-css.php
    * Add entries for Search Result Banner color options. 
+ /public/templates/pp/*-search-default.php
    * Update Search Templates with new Banner Color options.
+ /public/css/pet-match-pro-styles.css
    * Update default banner css classes.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Update SearchFilters function to remove species that are not specified in the shortcode species/speciesid parameter and add the entry from the shortcode to the speciesid filter options.
    * Remove support for the featured method shortcode, use pet-search with type="featured".
+ /includes/class-pet-match-po-activator.php
    * Add default value for search anchor tag option.

Version 5.5.1 - November 5, 2024
+ /pet-match-pro.php
    * Add constant to identify search template files.
    * Add constants to support addition of results per row as search shortcode parameter.
    * Add constant with a blank as the default no label search results separation character.
    * Add constants to support the use of shortcode parameters to control the display (icon) and max number (icons) of search result icons.
+ /admin/partials/pmp-option-levels-general.php
    * Add level settings for search templates.
+ /admin/partials/pmp-admin-info.php
    * Add help text for new General Tab search template settings.
+ /admin/partials/pmp-options-levels-general.php
    * Add level entries for search result banner, results per row and no label search results separation character. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Add search template settings.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Add support for use of search template. 
    * Add support for use of template parameter in detail shortcode.
    * Add support for search shortcode parameter results per row.
+ /public/templates/pp/*-search-default.php
    * New method specific search templates.
+ /public/templates/pp/featured-search-default.php
    * New featured pets search template.
+ /public/css/pet-match-pro-styles.css
    * New entries to support search templates. 
+ /includes/pp/partials/php-field-values-*.php
    * Localize partials directory to support calling from static activation function. 
+ /includes/rg/partials/php-field-values-adopt.php
    * Localize partials directory to support calling from static activation function. 

Version 5.4.5 - October 26, 2024
+ /pet-match-pro.php
    * Add constant for the static shortcode parameters. 
+ /includes/class-pet-match-pro-all.php
    * Update public function callMethod_Parameters to support pmp-search and pmp-detail shortcodes for PetPoint.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Update to support pmp-search and pmp-detail shortcodes.
    * Use constants for static shortcode parameters.
+ /includes/af/class-pet-match-pro-af-api.php
    * Use constants for static shortcode parameters.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Use constants for static shortcode parameters.
+ /includes/pp/partials/pmp-field-values.php
    * Add value and level type array values for pmp-search and pmp-detail shortcodes.
+ /admin/partials/pmp-admin-info.php
    * Revise help text for the General tab setting default method type. 
+ /admin/partials/pmp-option-levels-instructions.php
    * Add level entries for PetPoint pmp-search and pmp-detail shortcodes.
    * Remove entries for PetPoint method specific and featured shortcodes.
+ /admin/partials/pmp-instructions-customization.html
    * New file for customization instructions.
+ /admin/partials/pp/pmp-instructions-details-*.html
    * Delete method specific animal detail instruction files.
+ /admin/partials/pp/pmp-instructions-search-*.html
    * Delete method specific search instruction files.
+ /admin/partials/pp/pmp-instructions-featured-adopt*.html
    * Delete method specific featured animal instruction file
+ /admin/class-pet-match-pro-admin-settings.php
    * Enable General tab setting default method type for PetPoint.
    * Add instructions for plugin customization.
+ /admin/css/pet-match-pro-admin.min.css
    * Delete unused file. 
+ /admin/css/pet-match-pro-admin.min_1.css
    * Delete unused file. 
+ /admin/css/pet-match-pro-admin_1.css
    * Delete unused file. 

Version 5.4.4 - October 21, 2024
+ /pet-math-pro.php
    * Add new constants to parameterize fetching records from integration partners.
+ /includes/class-pet-match-pro-all-api.php
    * New public function to process requested species.
    * New public function to finalize filter and detail parameters.
    * New public function to set global filter variables.
+ /includes/af/class-pet-match-pro-af.php
    * Use new constants when fetching records.
    * Use new function to process requested search species. 
    * Use new public function to finalize search filter and detail parameters.
    * Use new public function to set search global filter variables.
+ /includes/pp/class-pet-match-pro-pp.php
    * Use new constants when fetching records.
    * Use new function to process requested search species. 
    * Use new public function to finalize search filter and detail parameters.
    * Use new public function to set search global filter variables.
+ /includes/rg/class-pet-match-pro-rg.php
    * Use new constants when fetching records.
    * Use new function to process requested search species. 
    * Use new public function to finalize search filter and detail parameters.
    * Use new public function to set search global filter variables.

Version 5.4.3 - October 19, 2024
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Standardize the way URL and shortcode parameters are processed.
    * Standardize setting search filter and details fields from admin when not specified in the shortcode.
    * Add use of dynamic colors css by removing echo of static colors css file pet-match-pro-public-color-css.php.
+ /includes/rg/partials/pmp-field-values.php
    * Standardize the naming of field value arrays.
    * Apply constants to the naming of field value arrays.
+ /includes/rg/partials/pmp-field-values-adopt.php
    * Use new standard field value array names.
+ /public/class-pet-match-pro-pulic.php
    * Dynamically generate and enqueue a colors style sheet.
+ /public/css/pet-match-pro-color-overrides.css
    * New CSS file to contain CSS from Colors and General tab settings.
+ /public/partials/pet-match-pro-public-color-css.php
    * Convert to write css to file vs. echo.
+ /public/css/pet-match-pro-color.css
    * New dynamic css file for plugin colors.
+ /includes/af/class-pet-match-pro-af-api.php
    * Add use of dynamic colors css by removing echo of static colors css file pet-match-pro-public-color-css.php.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Add use of dynamic colors css by removing echo of static colors css file pet-match-pro-public-color-css.php.
+ /public/class-pet-match-pro-public.php
    * Remove use of function to echo CSS from General tab. 
+ /includes/af/class-pet-match-pro-af-options.php
    * Remove display_form function (unused).
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Remove display_form function (unused).


Version 5.4.2 - October 7, 2024
+ /includes/af/class-pet-match-pro-af-api.php
    * Standardize the way URL and shortcode parameters are processed.
    * Standardize setting search filter and details fields from admin when not specified in the shortcode.

Version 5.4.1 - October 3, 2024
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Standardize the way URL and shortcode parameters are processed.
    * Standardize setting search filter and details fields from admin when not specified in the shortcode.

+ /includes/pp/partials/pmp-field-values.php
    * Standardize the naming of field value arrays.
    * Apply constants to the naming of field value arrays.
+ /includes/pp/partials/pmp-field-values-*.php
    * Use new standard field value array names.

Version 5.3.6 - September 23, 2024
+ /pet-match-pro.php
    * Add additional global variables to facilitate code standardization.    
    * Add constants for default icon and thumbnail maximums.
+ /includes/class-pet-match-pro-all-api.php
    * Standardize the animal search shortcode parameter labels="enable/yes/1/on".
    * Replace hard-coded method type references with integration partner specific constants.
+ /includes/af/class-pet-match-pro-af-api.php
    * Add support for animal search shortcode parameter filter="disable".
    * Standardize value for label processing in search output.
    * Correct processing of orderby logic. 
    * Add PMP logo as default image in search results.
    * Add PMP logo as default image in animal details.
    * Remove _adopt method type suffix from all icon and thumbnail level settings.
    * Use new icon and thumbnail maximum constants.
+ /admin/partials/af/pmp-instructions-search.html
    * Add instruction for animal search shortcode parameter filter="disable".
    * Add instruction for animal search shortcode parameter labels="enable/disable".
    * Add instruction for animal search shortcode parameter sortorder="asc/desc".
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Add support for animal search shortcode parameter filter="enable/disable".
    * Standardize value for label processing in search output.
    * Correct processing of orderby logic. 
    * Add PMP logo as default image in search results.
    * Add PMP logo as default image in animal details.
    * Remove _adopt method type suffix from all icon and thumbnail level settings.
    * Use new icon and thumbnail maximum constants.
+ /admin/partials/pp/pmp-instructions-search-*.html
    * Add instruction for animal search shortcode parameter filter="disable".
    * Add instruction for animal search shortcode parameter labels="enable/disable".
    * Add instruction for animal search shortcode parameter orderby.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Correct bug processing labels for the display of animal details.
    * Add support for animal search shortcode parameter filter="enable/disable".
    * Standardize value for label processing in search output.
    * Correct processing of orderby logic. 
    * Add PMP logo as default image in search results.
    * Add PMP logo as default image in animal details.
    * Remove _adopt method type suffix from all icon and thumbnail level settings.
    * Use new icon and thumbnail maximum constants.
+ /admin/partials/rg/pmp-instructions-search-adopt.html
    * Add instruction for animal search shortcode parameter filter="disable".
    * Add instruction for animal search shortcode parameter labels="enable/disable".
    * Add instruction for animal search shortcode parameter sortfield="enable/disable".
    * Add instruction for animal search shortcode parameter sortorder="asc/desc".
+ /admin/partials/pmp-option-levels-general.php
    * Remove _adopt method type suffix from all icon and thumbnail level settings.
+ /admin/partials/pmp-admin-info.php
    * Remove _adopt method type suffix from all icon and thumbnail settings.
+ /admin/class-pet-match-pro-admin-settings.php
    * Remove _adopt method type suffix from all icon and thumbnail settings.
+ /public/templates/af/adopt-*.php
    * Remove _adopt method type suffix from all icon and thumbnail settings.
    * Use new icon and thumbnail maximum constants.
+ /public/templates/af/found-*.php
    * Remove _adopt method type suffix from all thumbnail settings.
    * Use new thumbnail maximum constants.
+ /public/templates/af/lost-*.php
    * Remove _adopt method type suffix from all thumbnail settings.
    * Use new thumbnail maximum constants.
+ /public/templates/pp/adopt-*.php
    * Remove _adopt method type suffix from all icon and thumbnail settings.
    * Use new icon and thumbnail maximum constants.
+ /public/templates/pp/found-*.php
    * Remove _adopt method type suffix from all thumbnail settings.
    * Use new thumbnail maximum constants.
+ /public/templates/pp/lost-*.php
    * Remove _adopt method type suffix from all thumbnail settings.
    * Use new thumbnail maximum constants.
+ /public/templates/rg/adopt-*.php
    * Remove _adopt method type suffix from all icon and thumbnail settings.
    * Use new icon and thumbnail maximum constants.
+ /includes/class-pet-match-pro-activator.php
    * Remove _adopt method type suffix from all icon and thumbnail settings.
    * Use new icon and thumbnail maximum constants.

Version 5.3.5 - September 17, 2024
+ /petmatchpro.php
    * Define constants for all PetPoint data fields.
    * Add constants for PetPoint method types.
    * Rename/group PetPoint constants as appropriate.
+ /includes/pp/partials/pmp-field-levels-*.pp
    * Update level definitions to use constants.
+ /includes/pp/partials/pmp-field-values-*.pp
    * Update label definitions to use constants.
+ /admin/partials/pp/pmp-admin-info.php
    * Update help text keys and content to use constants.
    * Add help text for new Color Tab text link options.
+ /admin/partials/pp/pmp-option-levels-filter.php
    * Update references to use new constants.
+ /admin/partials/pp/pmp-option-levels-labels.php
    * Update references to use new constants.
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Rewrite initialize_label_options function to dynamically generate label setting fields from pmp-field-values-*.php.
+ /includes/pp/class-pet-match-pro-pp-detail-functions.php
    * Update references to use new constants.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Update references to use new constants.
    * Define public variable $searchOutput.
+ /public/templates/pp/*.php
    * Update all templates to use new level and field names.
    * Update require files to use public path variable.
+ /public/class-pet-match-pro-public.php
    * Revise short code logic to display null for all short codes when called from a page builder.
+ /includes/class-pet-match-ro-all-api.pp
    * Correct bug in function showLabels when display search labels option is not enabled.
+ /includes/class-pet-match-pro-activator.php
    * Require method type field value files vs. require_once. 
    * Add default entries for new Color Tab text link options.
    * Revise AnimalsFirst default label processing. 
+ /admin/class-pet-match-pro-settings.php
    * Add Color Tab options for Text Links. 
+ /admin/partials/pmp-option-levels-color.php
    * Add level entries for new Color Tab text link options.
+ /includes/af/class-pet-match-pro-af-options.php
    * Correct code to display shared search/detail labels for all method types.

Version 5.3.4 - August 5, 2024
+ /petmatchpro.php
    * Define constants for all RescueGroups data fields.
    * Add constants for RescueGroups method types.
    * Rename/group RescueGroups constants as appropriate.
+ /includes/rg/partials/pmp-field-levels-*.pp
    * Update level definitions to use constants.
+ /includes/rg/partials/pmp-field-values-*.pp
    * Update label definitions to use constants.
    * Remove reverse Species value arrays.
+ /admin/partials/rg/pmp-admin-info.php
    * Update help text keys and content to use constants.
+ /admin/partials/rg/pmp-option-levels-filter.php
    * Update references to use new constants.
+ /admin/partials/rg/pmp-option-levels-labels.php
    * Update references to use new constants.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Update references to use new constants.
+ /includes/rg/class-pet-match-pro-rg-detail-functions.php
    * Update references to use new constants.
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Rewrite initialize_label_options function to dynamically generate label setting fields from pmp-field-values-adopt.php.
+ /public/templates/rg/*.php
    * Update all templates to use new level and field names.
    * Update require files to use public path variable.

Version 5.3.3 - August 4, 2024
+ /includes/public/class-pet-match-pro-public.php
    * Initialize Integration Partner to PetPoint when no General Settings exist.
+ /includes/class-pet-match-pro.php
    * Initialize Integration Partner to PetPoint when no General Settings exist.
+ /admin/class-petmatch-pro-filter-functions.php
    * Add callback and other functions removed from other classes. 
    * Renamed file to class-petmatch-pro-functions.php
+ /admin/class-pet-match-pro-admin.php
    * Remove initialization of Integration Partner. 
    * Change file name for admin functions to class-pet-match-pro-functions.php
+ /admin/class-pet-match-pro-admin-settings.php
    * Initialize Integration Partner to PetPoint when no General Settings exist.
    * Default references to General Tab settings as appropriate.
    * Remove callback and general array functions and reference functions in class-petmatch-pro-functions.php.
+ /includes/class-pet-match-pro-all-api.php
    * Initialize Integration Partner to PetPoint when no General Settings exist.
    * Update function locationExclusions to assign location constant prior to processing General Tab options.
+ /includes/class-pet-match-pro-detail-functions.php
    * Initialize Integration Partner to PetPoint when no General Settings exist.
    * Initialize other General Tab options to blank if General Tab has not been initialized. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Initialize Integration Partner to PetPoint when no General Settings exist.
    * Initialize other General Tab options as appropriate if General Tab has not been initialized. 
    * Change file name for admin functions to class-pet-match-pro-functions.php
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Initialize Integration Partner to PetPoint when no General Settings exist.
    * Initialize other General Tab options as appropriate if General Tab has not been initialized. 
    * Change file name for admin functions to class-pet-match-pro-functions.php
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Initialize Integration Partner to PetPoint when no General Settings exist.
    * Initialize other General Tab options as appropriate if General Tab has not been initialized. 
    * Change file name for admin functions to class-pet-match-pro-functions.php
+ /includes/af/class-pet-match-pro-af-options.php
    * Remove callback and general array functions and reference functions in class-petmatch-pro-functions.php.
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Remove callback and general array functions and reference functions in class-petmatch-pro-functions.php.
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Remove callback and general array functions and reference functions in class-petmatch-pro-functions.php.

Version 5.3.2 - July 31, 2024
+ /petmatchpro.php
    * Define constants for all AnimalsFirst data fields.
    * Add constants for AnimalsFirst method types.
    * Rename/group AnimalsFirst constants as appropriate.
    * Removed constant 'ANIMALSFIRST_TYPE_PREFERRED' and transitioned to General Tab setting.
+ /admin/class-pet-match-pro-admin-settings.php
    * New AnimalsFirst setting for the label value for the Preferred Method Type. 
    * Replace all references of MethodTypes with new constants.
+ /admin/partials/pmp-admin-info.php
    * Replace all references of MethodTypes with new constants.
+ /admin/partials/af/pmp-admin-info.php
    * Update help text keys and content to use constants.
+ /admin/partials/af/pmp-option-levels-filter.php
    * Update references to use new constants.
+ /admin/partials/af/pmp-option-levels-labels.php
    * Update references to use new constants.
+ /includes/af/class-pet-match-pro-af-options.php
    * Replace all references of MethodTypes with new constants.
    * Add lines between method type sections in the Filters tab.
+ /includes/af/class-pet-match-pro-af-api.php
    * Replace all references of MethodTypes with new constants.
+ /includes/af/partials/pmp-field-levels-*.pp
    * Update level definitions to use constants.
+ /includes/af/partials/pmp-field-values-*.pp
    * Update label definitions to use constants.
+ /public/templates/af/*.php
    * Replace all references of MethodTypes with new constants.
+ /public/templates/af/details-poster.php
    * Correct issues with the display of the animal description and QR code.
+ /public/class-pet-match-pro-public.php
    * Update pmp-detail shortcode function to prevent processing of the description field for all integration partners.

Version 5.3.1 - July 22, 2024
+ /petmatchpro.php
    * Define constants for all AnimalsFirst data fields.
    * Add constants for AnimalsFirst method types.
    * Rename/group AnimalsFirst constants as appropriate.
+ /includes/af/partials/pmp-field-levels-*.pp
    * Update level definitions to use constants.
+ /includes/af/partials/pmp-field-values-*.pp
    * Update label definitions to use constants.
+ /includes/af/class-pet-match-pro-af-api.php
    * Update references to use new breed and color constants.
+ /public/templates/af/*.php
    * Update all templates to use new level and field names.
    * Update require files to use public path variable.

Version 5.2.3 - July 18, 2024
+ /admin/class-pet-match-pro-filter-functions.php
    * Use the default label when the label on the admin Labels tab is empty. 
    * Use the default label when the label for the search form in the admin Labels tab is empty. 
+ /includes/class-pet-match-pro-all-api.php
    * Remove the Submit button from the search form when no entries are provided. 
    * Return the location value when no filters are configured. 
+ /includes/af/class-pet-match-pro-detail-functions.php
    * Use the default label when the label on the admin Labels tab is empty. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Use the default label when outputting animal details if the label on the admin Labels tab is empty.
    * Use the default label for the search filter fields if the label on the admin Labels tab is empty.
    * Update logic to remove the last label separator from the search results.
+ /includes/pp/class-pet-match-pro-detail-functions.php
    * Use the default label when the label on the admin Labels tab is empty. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Use the default label when outputting animal details if the label on the admin Labels tab is empty.
    * Use the default label for the search filter fields if the label on the admin Labels tab is empty.
    * Update key value for other location filter. 
    * Use constants as appropriate. 
    * Standardize/simplify logic as appropriate. 
    * Update logic to remove the last label separator from the search results.
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Remove static array values for filter values for dog and cat breeds.
+ /includes/pp/partials/pmp-field-values.php
    * Add value entries for breeds, ok and sex fields. 
+ /includes/rg/class-pet-match-pro-detail-functions.php
    * Use the default label when the label on the admin Labels tab is empty. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Use the default label when outputting animal details if the label on the admin Labels tab is empty.
    * Update key value for other location filter. 
    * Update logic to remove the last label separator from the search results.
+ /public/templates/af/details-poster.php
    * Change 'adoptable' method to 'adopt'.
+ /admin/class-pet-math-po-admin-settings.hp
    * Add instruction for featured pet. 
    * Add key for instruction title and instructions. 

Version 5.2.2 - July 8, 2024
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Change 'adoptable' to 'adopt' in all key values. 
    * Remove location settings from Filters tab (moved to General tab).
    * Move Filter tab Details on Search setting below Sort Order.
    * Revise filter and sort options to  use new functions to define values. 
    * Update Filter tab settings to return key and label as appropriate. 
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Change 'adoptable' to 'adopt' in all key values. 
    * Move Filter tab Details on Search setting below Sort Order for all methods.
    * Move Found Filter tab settings above lost.
    * Revise filter and sort options to use new functions to define values. 
    * Update Filter tab settings to return key and label as appropriate. 
+ /admin/partials/rg/pmp-admin-info.php
    * Change 'adoptable' to 'adopt' in all key values.
    * Remove help text for location fields.
+ /admin/partials/pp/pmp-admin-info.php
    * Change 'adoptable' to 'adopt' in all key values.
    * Remove help text for location fields.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Change 'adoptable' to 'adopt' in all references. 
    * Change location other reference from Filters to General tab setting.
    * Revise filter and sort options to  use new functions to define values.
    * Revise searchFilters function to support shortcode with empty filter parameter to exclude the filter form.
    * Revise searchFilters function to support use of admin settings to define filter fields when filter parameter is excluded from shortcode.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Change 'adoptable' to 'adopt' in all references. 
    * Change location other reference from Filters to General tab setting.
    * Revise filter and sort options to  use new functions to define values.
    * Revise searchFilters function to support shortcode with empty filter parameter to exclude the filter form.
    * Revise searchFilters function to support use of admin settings to define filter fields when filter parameter is excluded from shortcode.
    * Update searchFilters function to use new search filters prefix.
+ /includes/rg/partials/pmp-field-levels-adopt.php
    * Update search result level prefix to 'level_seach_result'.
+ /includes/pp/partials/pmp-field-levels-adopt.php
    * Update search result level prefix to 'level_seach_result'.
+ /includes/rg/partials/pmp-field-values-adopt.php
    * Update prefix for search filter label and value fields to 'label_search_filter'.
    * Update prefix for search results to 'label_search_result'.
    * Add entries for search_sort.
+ /includes/pp/partials/pmp-field-values-adopt.php
    * Update prefix for search filter label and value fields to 'label_search_filter'.
    * Update prefix for search results to 'label_search_result'.
    * Add entries for search_sort.
+ /includes/pp/partials/pmp-field-values-found.php
    * Update prefix for search filter label and value fields to 'label_search_filter'.
    * Update prefix for search results to 'label_search_result'.
    * Add entries for search_sort.
+ /includes/pp/partials/pmp-field-values-lost.php
    * Update prefix for search filter label and value fields to 'label_search_filter'.
    * Update prefix for search results to 'label_search_result'.
    * Add entries for search_sort.
+ /includes/pp/partials/pmp-field-levels-adopt.php
    * Update prefix for search filter levels to 'level_search_filter'.
    * Update prefix for search filter value levels to 'level_search_sort'.
+ /includes/pp/partials/pmp-field-levels-found.php
    * Update prefix for search filter levels to 'level_search_filter'.
    * Update prefix for search filter value levels to 'level_search_sort'.
+ /includes/pp/partials/pmp-field-levels-lost.php
    * Update prefix for search filter levels to 'level_search_filter'.
    * Update prefix for search filter value levels to 'level_search_sort'.
    * Update prefix for search result levels to 'level_search_result'.
+ /public/templates/rg/adoptable-*.php
    * Change 'adoptable' to 'adopt' in all key and variable references. 
    * Rename files to adopt-*.php.
+ /public/templates/pp/adoptable-*.php
    * Change 'adoptable' to 'adopt' in all key and variable references. 
    * Rename files to adopt-*.php.
+ /public/templates/rg/details-poster.php
    * Change 'adoptable' to 'adopt' in all key and variable references. 
    * Update QR code link to adoption application. 
+ /public/templates/pp/details-poster.php
    * Change 'adoptable' to 'adopt' in all key and variable references. 
    * Update QR code link to adoption application. 
+ /public/templates/af/details-poster.php
    * Update QR code link to adoption application. 
+ /public/templates/af/adoptable-*.php
    * Rename files to adopt-*.php.
+ /admin/class-pet-match-pro-admin-settings.php
    * Filter method details template lists in the General settings by the method type, showing all if no match. 
    * Correct display of General Tab Sort Order for PetPoint.
+ /includes/class-pet-match-pro-all-api.php
    * Update buildSearchForm to accept no search filter options. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Revise searchFilters function to support shortcode with empty filter parameter to exclude the filter form.
    * Revise searchFilters function to support use of admin settings to define filter fields when filter parameter is excluded from shortcode.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Revise searchFilters function to support shortcode with empty filter parameter to exclude the filter form.
    * Revise searchFilters function to support use of admin settings to define filter fields when filter parameter is excluded from shortcode.

Version 5.2.1 - July 6, 2024
+ /pet-math-pro.php
    * Remove AnimalsFirst filter array/field constants and add a constant for the preferred type suffix. 
+ /includes/af/partials/pmp-field-values.pmp
    * Add entries for the filter array and field values. 
    * Update the name of the preferred value types array to use the new preferred type suffix. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Transition from use of filters array constant to array included in field values file. 
    * Change 'adoptable' to 'adopt' in all references. 
+ /public/class-pet-match-pro-public.php
    * Change search method 'AdoptableSearch' to 'adoptSearch'.
+ /includes/af/class-pet-match-pro-af-options.php
    * Change 'adoptable' to 'adopt' in all key values. 
    * Update preferred filter and label keys and labels to use new preferred type suffix.
    * Add filter tab options for preferred search types.
+ /admin/partials/af/pmp-admin-info.php
    * Change 'adoptable' to 'adopt' in all key values.
+ /admin/partials/af/pmp-option-levels-filter.php
    * Change other level keys to use the suffix 'preferred'.
+ /admin/partials/af/pmp-option-levels-labels.php
    * Change other level keys to use the suffix 'preferred'.
+ /admin/partials/pmp-admin-info.php
    * Change 'adoptable' to 'adopt' in all key values.
    * Add admin help text entries for preferred search types. 
    * Update preferred type suffix to new constant. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Reverse order of lost and found no animal found message settings.
    * Add no animals found setting for preferred search types.
    * Change 'adoptable' to 'adopt' in all key values and labels.
    * Update preferred type suffix to new constant. 
+ /admin/partials/pmp-option-levels-general.php 
    * Remove enable level keys for preferred type content.
    * Configure Preferred content with dynamic level suffix. 
+ /admin/class-pet-match-pro-filter-functions.php
    * Add support for preferred search types to Filter_Option_Values function.
+ /includes/af/partials/pmp-field-levels-preferred.php
    * New file for level entries for premium search types.
+ /includes/af/partials/pmp-field-values-preferred.php
    * New file for label and value entries for premium search types.
+ /includes/class-pet-match-pro-all-api.php
    * Update callMethod_Parameters function for preferred search types. 

Version 5.1.1 - June 30, 2024
+ /pet-match-pro.php
    * Revise the level and filter prefixes.
+ /admin/class-pet-match-pro-filter-functions.php
    * New function Filter_Option_Values to populate options on the admin filters tab from the updated prefix values. 
    * Remove previous functions to display sort filters, fields and order values. 
    * Update function Search_Filter_Values for use of Filter_Option_Values and new level, level and value prefixes.
+ /admin/class-petmatch-pro-admin-settings.php
    * Update to use Filter_Option_Values function display order by and sort order fields on General tab. 
    * Update logic for AnimalsFirst default type method on General tab.
+ /admin/partials/af/pmp-admin-info.php
    * Revise for new search filter prefix. 
    * Update key for AnimalsFirst default type method help string.
+ /admin/partials/pmp-option-levels-general.php
    * Update key for AnimalsFirst default type method level.
+ /includes/class-pet-match-pro-all-api.php
    * Revise for new search filter prefix. 
    * New function callMethod_Parameters to assign Method related variables. 
    * Update callMethod_Parameters to use default type method from General tab.
+ /includes/class-pet-match-pro-form-builder.php
    * Revise for new search filter prefix. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Revise to use new function to populate Method variables.  
+ /includes/af/class-pet-match-pro-af-options.php
    * Revise to use new function to populate Filters tab.  
+ /includes/af/partials/pmp-field-values.php
    * Update keys for type level and value arrays.
    * Remove unnecessary species arrays. 
+ /includes/af/partials/pmp-field-levels-*.php
    * Revise filter, sort and order level prefixes. 
+ /includes/af/partials/pmp-field-values-*.php
    * Revise filter, sort and order label and value prefixes. 
+ /includes/rg/partials/pmp-field-levels-adopt.php
    * Revise filter, sort and order level prefixes. 
+ /includes/rg/partials/pmp-field-values-adopt.php
    * Revise filter, sort and order label and value prefixes. 

Version 5.0.5 - June 21, 2024
+ /includes/class-pet-match-pro-af-api.php
    * Update error handling on animal detail shortcode function. 
+ /includes/class-pet-match-pro-pp-api.php
    * Update error handling on animal detail shortcode function.
+ /includes/class-pet-match-pro-rg-api.php
    * Update error handling on animal detail shortcode function. 
+ /admin/partials/pmp-option-levels-instructions.php
    * Updated for AnimalsFirst.
+ /admin/partials/pp/pmp-instructions-animal-details.html
    * Rename to pmp-instructions-animal-detail.html
+ /admin/partials/pp/pmp-instructions-detail-adopt.html
    * Rename to pmp-instructions-details-adopt.html
+ admin/partials/pp/pmp-instructions-detail-found.html
    * Rename to pmp-instructions-details-found.html
+ /admin/partials/pp/pmp-instructions-detail-lost.html
    * Rename to pmp-instructions-details-lost.html
+ /admin/partials/rg/pmp-instructions-animal-details.html
    * Rename to pmp-instructions-animal-detail.html
+/admin/partials/rg/pmp-instructions-detail-adopt.html
    * Rename to pmp-instructions-details-adopt.html
+ /admin/class-pet-match-pro-admin-settings.php
    * Update with new instruction file names.
+ /admin/class-pet-match-pro-filter-functions.php
    * Update function to restrict search sort order values based on admin configuration and license type.

Version 5.0.4 - June 9, 2024
+ /includes/class-pet-match-pro.php 
    * Add new shortcode [pmp-search].
+ /public/class-pet-match-pro-public.php
    * Add function for new pmp-search shortcode.
+ /includes/class-pet-match-pro-af-api.php
    * Update search feature to support new pmp-search shortcode.
    * Removed function for [pmp-lost-found-search] shortcode.

Version 5.0.3 - May 13, 2024
+ /admin/class-pet-match-pro-admin-settings.php
    * Support AnimalsFirst API v2.
+ /includes/class-pet-match-pro-af-api.php
    * Support AnimalsFirst API v2.
+ /includes/af/partials/*.php
    * Support AnimalsFirst API v2.

Version 5.0.2 - April 19, 2023 
+ /includes/af/class-pet-match-pro-af-options.php
    * Remove static search filter definitions to support dynamic values from AnimalsFirst. 
+ /includes/af/class-pet-match-pro-af-api.php
    * Update to pull search filter values from AnimalsFirst. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Add logic to disable General tab setting to display age in years by Integration Provider.
+ /admin/partials/pmp-option-levels-general.php
    * Add configuration to disable General tab setting to display age in years by Integration Provider.
+ /pet-match-pro.php
    * Add PAGE_BUILDER constant.
    * Add INTEGRATION_PARTNERS constant.
+ /public/class-pet-match-pro-public.php
    * Update Detail shortcodes to use the appropriate ID field for the Integration Provider.
    * Update to use PAGE_BUILDER constant. 
+ /admin/class-pet-match-pro-admin-settings.php
    * Use INTEGRATION_PARTNERS constant.

Version 5.0.1 - April 14, 2023 
+ /pet-match-pro.php
    * Add variable for AnimalsFirst Integration Partner.  
+ /public/class-pet-match-pro-public.php
    * Add support for AnimalsFirst Integration Partner.  
+ /public/templates/af
    * New directory for AnimalsFirst Integration Partner.
+ /public/templates/af/adoptable-deafult.php
    * New file for AnimalsFirst Integration Partner.
+ /includes/class-pet-match-pro.php
    * Add support for AnimalsFirst Integration Partner.  
+ /includes/class-pet-match-pro-activator.php
    * Add support for AnimalsFirst Integration Partner.  
    * Update for location settings move to General tab.
+ /includes/af/class-pet-match-pro-af-options.php
    * New file for AnimalsFirst Integration Partner.
+ /includes/af/partials
    * New directory for AnimalsFirst Integration Partner.
+ /includes/af/partials/pmp-field-levels-adopt.php
    * New file for AnimalsFirst Integration Partner.
    * Add defaults for Location settings.
+ /includes/af/partials/pmp-field-values.php
    * New file for AnimalsFirst Integration Partner.
+ /admin/class-pet-match-pro-admin-settings.php
    * Add support for AnimalsFirst Integration Partner.  
    * Add ALL Location settings. 
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Remove location settings.
    * Reverse lost and found settings on the Filyter Options tab.
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Remove location settings.
+ /admin/class-pet-match-pro-filter-functions.php
    * Add support for AnimalsFirst Integration Partner.  
+ /admin/partials/pmp-option-levels-general.php
    * Add level for AnimalsFirst Status.
    * Add levels for Location settings moved from Integration Partner Filter Options tab.
+ /admin/partials/rg/pmp-option-levels-filter.php
    * Remove location levels.
+ /admin/partials/rg/pmp-admin-info.php
    * Remove location help text.
+ /admin/partials/pp/pmp-option-levels-filter.php
    * Remove location levels.
    * Remove icon levels left erroneously from a previous revision.
+ /admin/partials/pp/pmp-admin-info.php
    * Remove location help text.
+ /admin/partials/af
    * New directory for AnimalsFirst Integration Partner. 
+ /admin/partials/af/pmp-admin-info.php
    * New file for AnimalsFirst Filter and Label field information.     
+ /admin/partials/af/pmp-option-levels-filter.php
    * New file for AnimalsFirst Integration Partner. 
+ /admin/partials/php-admin-info.php
    * Add help text for AnimalsFirst status.
    * Add help text for all Location settings.
+ /admin/partials/pmp-option-levels-instructions.php
    * Add support for AnimalsFirst Integration Partner. 
+ /includes/af/class-pet-match-pro-af-detail-functions.php
    * New file for AnimalsFirst Integration Partner.
+ /admin/partials/af/pmp-option-levels-labels.php
    * New file for AnimalsFirst Integration Partner. 
+ /includes/af/class-pet-match-pro-af-api.php
    * New file for AnimalsFirst Integration Partner.

Version 4.8.2 - April 12, 2024
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Increase location count to 5 vs. 3.
+ /admin/partials/pp/pmp-admin-info.php
    * Add help field values for the additional location fields.
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Increase location count to 5 vs. 3.
+ /admin/partials/rg/pmp-admin-info.php
    * Add help field values for the additional location fields.
+ /includes/class-pet-match-pro-all-api.php
    * Update locationFilters function to process all locations configured.

Version 4.8.1 - April 6, 2024
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Add label values for location filters.
    * Add location exclusion fields.
+ /admin/partials/pp/pmp-admin-info.php
    * Add help field values for new label values for location filters.
    * Add help field values for new location exclusions. 
+ /includes/class-pet-match-pro-all-api.php
    * Add functions to set and process animal locations filters.
    * Add function to set animal location exclusions. 
    * Update error processing of search results. 
    * Add function to process shortcode replacement in default description across integration partners.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Update logic to process animal locations permitting partial location matches and changes in the location label for searches, details and animal details.
    * Optimize use of variables and constants.
    * Add logic to exclude results by location.
    * Add icons to search results.
    * Use new function for default description.
+ /public/templates/pp/*.php
    * Update details templates with new location logic.
    * Add icons to detail templates.
+ /public/pp/pet-match-pro-styles.css
    * Add style to format search results.
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Add label values for location filters.
    * Add location exclusion fields.
+ /admin/partials/rg/pmp-admin-info.php
    * Add help field values for new label values for location filters.
    * Add help field values for new location exclusions. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Update logic to process animal locations permitting partial location matches and changes in the location label for animal details and animal details. 
    * Optimize use of variables and constants.
    * Add logic to exclude results by location.
    * Use new function for default description.
+ /public/templates/rg/*.php
    * Update details templates with new location logic.
+ /pet-match-pro.php
    * Add additional RescueGroups field constants to support code optimization.
+ /admin/partials/pp/pmp-option-levels-filter-php
    * Add level for location exclusions from results.
+ /admin/partials/rg/pmp-option-levels-filter-php
    * Add level for location exclusions from results.
+ /includes/pp/class-pet-match-pro-pp-detail-functions.php
    * Update logic to support search & detail icons.
+ /includes/rg/class-pet-match-pro-rg-detail-functions.php
    * Update logic to support search & detail icons.

Version 4.7.3 - March 31, 2024
+ /admin/class-pet-match-pro-admin-settings.php
    * Only include color css styles when the Colors tab is active.  
    * Add unique setting to General tab to control the max number of thumbnails displayed on the animal detail pages.
+ /admin/partials/pmp-option-levels-general.php
    * Add level value for new detail thumbnail max General setting. 
+ /admin/partials/pmp-admin-info.php
    * Add help field value for new detail thumbnail max General setting. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct issue displaying animal details when key value does not exist.
    * Update to use new thumbnail max setting.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Update to use new thumbnail max setting.
    * Correct error when displaying animal detail that does not exist.
+ /public/templates/pp/*.php
    * Update detail templates to use new thumbnail max setting.
+ /public/templates/rg/*.php
    * Update detail templates to use new thumbnail max setting.
+ /includes/class-pet-match-pro-activator.php
    * Assign default value to new thumbnail max setting.
+ /admin/partials/pet-match-pro-public-color-css.php
    * Extend the Adoptable Details Title class to paragraphs and spans.
 
Version 4.7.2 - March 23, 2024
+ /public/class-pet-match-pro-public.php
    * Variable and code clean-up.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct error with return of no results from details shortcode. 
+ /includes/class-pet-match-pro.php
    * Variable and code clean-up.
+ /pet-match-pro.php
    * Add additional constants to support code clean-up.
+ /admin/class-pet-match-pro-admin-settings.php
    * Add Color settings to support posters. 
+ /admin/partials/pmp-option-levels-color.php
    * Add level settings to support colors for posters.
+ /admin/partials/pmp-admin-info.php
    * Add help text to support colors for posters.
+ /public/partials/pet-match-pro-public-color-css.php
    * Add color settings for posters.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Correct issue displaying animal details when ID does not exist.  
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct issue displaying animal details when ID does not exist. 
+ /public/templates/rg/adoptable-cpa.php
    * Correct issue displaying animal details when ID does not exist.      
+ /public/templates/rg/adoptable-default.php
    * Correct issue displaying animal details when ID does not exist.      
+ /public/templates/rg/details-poster.php
    * Correct issue displaying animal details when ID does not exist. 
+ /public/templates/pp/*.php
    * Correct issue displaying animal details when ID does not exist. 
+ /public/class-pet-match-pro-public.php
    * Correct an issue with the use of replace option with the animal detail shortcode.
+ /admin/partials/pp/pmp-instructions-animal-details.html
    * Added instruction for use of the replace option with the animal detail shortcode.
+ /admin/partials/rg/pmp-instructions-animal-details.html
    * Added instruction for use of the replace option with the animal detail shortcode.
+ /includes/pp/class-pet-match-pro-detail-functions.php
    * Correct issue in the processing of animal labels.

Version 4.7.1 - March 20, 2024
+ /admin/class-pet-match-pro-admin-settings.php
    * Add Colors tab to simplify the customization of the front-end. 
+ /admin/partials/pmp-option-levels-color.php
    * New file for Admin Colors tab. 
+ /admin/partials/pmp-admin-info.php
    * Add help text for Colors tab.
+ /includes/class-pet-match-pro.php
    * Register the new Colors tab.
+ /public/partials/pet-match-pro-public-color-css.php
    * New file to set color styles from Colors tab.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Update to include the color styles for search and detail output.
    * Correct issue with formatting of search results.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Update to include the color styles for search and detail output.

Version 4.6.5- March 18, 2024
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct error with display of featured pet.
    * Correct error displaying animal details shortcode values in the Default Animal Description setting.
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Correct name of Adoptable Animal Name label field.
+ /admin/partials/pp/pmp-admin-info.php
    * Correct name of Adoptable Animal Name label field.

Version 4.6.4- March 17, 2024
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct error with return of no combined lost/found search results.
    * Correct error when calling shortcodes from admin page builder.
    * Correct error with deprecated reference to variables.
    * Correct error displaying animal details shortcode values in the Default Animal Description setting.
    * Correct error converting age to years.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Correct error displaying animal details shortcode values in the Default Animal Description setting.
+ /includes/class-pet-match-pro-all.php
    * Correct error with use of Search Feature Link setting.
+ /public/templates/pp/*
    * Correct error with initialize of Max Detail Icons setting.
+ /public/templates/rg/*
    * Correct error with initialize of Max Detail Icons setting.
+ /admin/class-pet-match-pro-settings.php
    * Correct error with deprecated reference to variables.
+ /public/class-pet-match-pro-public.php
    * Correct issue calling shortcodes from admin page builder.

Version 4.6.3 - March 15, 2024
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct error with return of no combined lost/found search results.

Version 4.6.2 - March 14, 2024
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct error with return of no combined lost/found search results.

Version 4.6.1 - March 12, 2024
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct error with return of no search results.
+ /public/templates/pp/details-poster.php
    * Correct errors with use across multiple methods.
+ /public/templates/rg/details-poster.php
    * Apply appropriate changes made to PetPoint version. 

Version 4.5.5 - March 11, 2024
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct error with return of no search results.

Version 4.5.4 - March 10, 2024
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Correct error with the identification of filter options by method type for the creation of a search. 
+ /public/templates/rg/adoptable-cpa.php
    * Correct error with return to search URL.
+ /public/templates/rg/adoptable-default.php
    * Correct error with return to search URL.
+ /public/templates/pp/*.php
    * Correct error with return to search URL.
+ /includes/class-pet-match-pro-all-api.php
    * Correct error with function to show details.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Correct error with return of no search results.

Version 4.5.2 - March 9, 2024
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Add support for count parameter on adoptable search shortcode. 
+ /admin/partials/pp/pmp-instructions-search-adopt.html
    * Add instructions for use of count parameter.
+ /admin/partials/pp/pmp-instructions-search-found.html
    * Add instructions for use of count parameter.
+ /admin/partials/pp/pmp-instructions-search-lost.html
    * Add instructions for use of count parameter.
+ includes/rg/class-pet-match-pro-rg-api.php
    * Add support for count parameter on adoptable search shortcode. 
+ /admin/partials/rg/pmp-instructions-search-adopt.html
    * Add instructions for use of count parameter.
+/public/class-pet-match-pro-public.php
    * Correct issue with the assignment of options with blank values.

Version 4.4.2 - March 2, 2024
+ /admin/class-pet-match-pro-admin-settings.php
    * Remove activation defaults. 
+ /includes/class-pet-match-pro-activator.php
    * Update activation defaults.

Version 4.4.1 - March 1, 2024
+ /includes/class-pet-match-pro-all-api.php
    * New class to consolidate shared functions across all integration partners.
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Update to support common function class. 
+ /public/templates/pp/*.php
    * Update to support common function class. 
    * Replace direct field references with constants.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Update to support common function class. 
+ /public/templates/rg/*.php
    * Update to support common function class. 
+ /admin/partials/pp/pmp-option-levels-filter.php
    * Add security level (premium) to display search and detail icons.
+ /admin/partials/pp/pmp-admin-info.php
    * Add field help for Adoption search result and detail icon settings.
+ /admin/class-pet-match-pro-admin-settings.php
    * Enable the setting to control the number of thumbnail images on a detail page for all integration partners.
+ /admin/partials/pmp-admin-info.php
    * Enable the setting to control the number of thumbnail images on a detail page for all integration partners.
+ /admin/partials/rg/pmp-admin-info.php
    * Remove icon help text to transition to General tab.
+ /admin/partials/pmp-admin-info.php
    * Add help text for icon fields in General tab.
+ /admin/partials/rg/pmp-options-levels-filter.php
    * Remove icon levels to transition to General tab.
+ /admin/partials/pmp-options-levels-general.php
    * Add icon levels for transition to General tab.


Version 4.3.2 - February 28, 2024
+ /admin/class-pet-match-pro-admin-settings.php
    * Add General tab settings for search feature link to display next to Search Submit button.   
+ /admin/partials/pmp-option-levels-general.php
    * Add level for new General tab search feature link.
+ /admin/partials/pmp-admin-info.php
    * Add help text for the General tab search feature link.
+ /public/css/pet-match-pro-styles.css
    * Add styles to support Search form feature button.
+ /includes/class-pet-match-pro-form-builder.php
    * Add support for Search form feature button. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Add support for Search form feature button. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Add support for Search form feature button. 

Version 4.3.1 - February 27, 2024
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Add foster home and shelter locations to Filter tab.  
+ /admin/partials/rg/pmp-option-levels-filter.php
    * Add level for new Filter tab locations.
+ /admin/partials/rg/pmp-admin-info.php
    * Add help text for the Filter tab foster and shelter fields. 
+ /public/templates/rg/*.php
    * Change templates to use the foster and shelter locations
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Add foster home and shelter locations to Filter tab. 
+ /admin/partials/pp/pmp-option-levels-filter.php
    * Add level for new Filter tab locations.
+ /admin/partials/pp/pmp-admin-info.php
    * Add help text for the Filter tab foster and shelter fields. 
+ /public/templates/pp/*.php
    * Change templates to use the foster and shelter locations

Version 4.2.3 - February 25, 2024
+ /includes/rg/class-pet-match-pro-rg-detail-functions.php
    * Add function to display result icons. 
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Trim special characters from details provided in shortcode. 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Trim special characters from details provided in shortcode. 
+ /includes/public/templates/rg/adoptable-default.php
    * Implement General setting to restrict the photos on the detail page.

Version 4.2.2 - February 22, 2024
+ /includes/rg/class-pet-match-pro-rg-options.php
    * Add Filter Tab settings to display icons in Adoption Search results and detail pages.
+ /admin/partials/rg/pmp-option-levels-filter.php
    * Add security level (premium) to display search and detail icons.
+ /admin/partials/rg/pmp-admin-info.php
    * Add field help for Adoption search result and detail icon settings.

Version 4.2.1 - February 20, 2024
+ /admin/class-pet-match-pro-admin-settings.php
    * Change the purpose of the Sponsorship link in the Contact tab for supporting an animal to the organization, removing adoptable from setting.
    * Add a setting to Contact tab to provide an image for use when promoting the organization sponsor.
    * Add Donation link to Contact tab.
+ /admin/partials/pmp-admin-options.php
    * Add/alter to support changes to Contact tab above.
+ /admin/partials/pmp-option-levels-contact.php
    * Add/alter to support changes to Contact tab above.
+ /public/css/pet-match-pro-styles.php
    * Add formatting for sponsorship image.

Version 4.1.3 - February 19, 2024
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Additional changes to resolve errors with lost & found search combination shortcode.

Version 4.1.2 - February 16, 2024
+ /includes/pp/class-pet-match-pro-pp-api.php
    * Changes to resolve errors with lost & found search combination shortcode.
    * Correct error processing when no data is received from PetPoint. 
 
Version 4.1.1 - February 1, 2024 
+ /includes/rg/class-pet-match-pro-rg-api.php
    * Changes to support Rescue Groups Integration Partner.

Version 4.0.1 - December 4, 2023 
+ /pet-match-pro.php
    * Add variable for Rescue Groups Integration Partner.  
+ /public/class-pet-match-pro-public.php
    * Add support for Rescue Groups Integration Partner.  
+ /public/templates/rg
    * New directory for Rescue Groups Integration Partner.
+ /public/templates/rg/adoptable-deafult.php
    * New file for Rescue Groups Integration Partner.
+ /includes/class-pet-match-pro.php
    * Add support for Rescue Groups Integration Partner.  
+ /includes/class-pet-match-pro-activator.php
    * Use PetPoint constant.
+ /includes/pp/class-pet-match-pro-pp-options.php
    * Renamed from class-pet-match-pro-pp-options.php to include both PetPoint Filter and Label options.
+ /includes/rg/class-pet-match-pro-rg-api.php
    * New file for Rescue Groups Integration Partner.
+ /includes/rg/class-pet-match-pro-rg-options.php
    * New file for Rescue Groups Integration Partner.
+ /includes/rg/class-pet-match-pro-rg-detail-functions.php
    * New file for Rescue Groups Integration Partner.
+ /includes/rg/partials
    * New directory for Rescue Groups Integration Partner.
+ /includes/rg/partials/pmp-field-levels-adopt.php
    * New file for Rescue Groups Integration Partner.
+ /includes/rg/partials/pmp-field-values-adopt.php
    * New file for Rescue Groups Integration Partner.
+ /includes/rg/partials/pmp-field-values.php
    * New file for Rescue Groups Integration Partner.
+ /admin/class-pet-match-pro-admin-settings.php
    * Add support for Rescue Groups Integration Partner. 
+ /admin/partials/pmp-option-levels-filter.php
    * Move to /admin/partials/pp/.
+ /admin/partials/rg/pmp-option-levels-filter.php
    * New file for Rescue Groups Integration Partner. 
+ /admin/partials/pmp-option-levels-labels.php
    * Move to /admin/partials/pp/ and remove Lost & Found.
+ /admin/partials/rg/pmp-option-levels-labels.php
    * New file for Rescue Groups Integration Partner. 
+ /admin/partials/pmp-admin-info.php
    * Remove PetPoint Filter and Label field information. 
+ /admin/partials/pp/pmp-admin-info.php
    * New file for PetPoint Filter and Label field information. 
+ /admin/partials/rg/pmp-admin-info.php
    * New file for RescueGroups Filter and Label field information.     
+ /admin/class-pet-match-pro-filter-functions.php
    * Add support for Rescue Groups Integration Partner.  
+ /admin/partials/pmp-option-levels-general.pmp
    * Remove Lost & Found settings for Rescue Groups Integration Partner. 
+ /admin/partials/pmp-option-levels-contact.pmp
    * Remove Lost & Found settings for Rescue Groups Integration Partner.
+ /admin/partials/pmp-option-levels-instructions.pmp
    * New file to remove Lost & Found instructions for Rescue Groups Integration Partner.
+ /admin/partials/rg
    * New directory for Rescue Groups Integration Partner.

Version 3.3.2 - November 13, 2023 
+ /public/templates/pp/adoptable-conversion.php    
    * Correct error preventing the use of the Contact Option Adoption Application URL.

Version 3.2.1 - October 28, 2023
 + Additional changes to support distribution via the WordPress repository. 

Version 3.1.2 - September 27, 2023
 + /included/pp/class-pet-match-pro-pp-api.php
    * Changes to text as filter values and to display animal details when a video is present. 

Version 3.1.1 - September 3, 2023
 + Changes to support distribution via the WordPress repository. 

Version 3.0.1 - July 23, 2023
 + /README.txt
    * Revised as appropriate.
 + /included/pp/class-pet-match-pro-pp-api-0.php
    * Revert file_get_contents to use of wp_remote_get and wp_remote_retrieve_body.
 + /included/pp/class-pet-match-pro-pp-api.php
    * Revert file_get_contents to use of wp_remote_get and wp_remote_retrieve_body.
 + /admin/css/pet-match-pro-admin_1.css
    * Remove @import of Google Lato font. 
 + /admin/css/pet-match-pro-admin.min_1.css
    * Remove @import of Google Lato font. 
 + /public/templates/pp/video-load.php
    * Use wp_enqueue_script to reference jQuery, BootStrap and CloufFlare Popper.
 + /includes/pp/class-pet-match-pro-contact-options.php
    * Use WP function esc_html to escape html data. 

Version 2.9.9 - April 25, 2023
  + /includes/pp/class-pet-match-pro-pp-api.php
    * Corrected deprecated code for use with PHP 8.X when displaying a search filter with a free subscription.

Version 2.9.8 - April 22, 2023
  + /admin/class-pet-match-pro-admin-settings.php
    * Corrected bug related to the display of values on the labels tab with a free license.   

Version 2.9.7 - April 20, 2023
  + /includes/pp/class-pet-match-pro-pp-api.php
    * Corrected deprecated code for use with PHP 8.X.
    * Ignore animals details specified in the shortcode that do not exist. 
  + /public/templates/pp/adoptable-default.php
    * Default values for species and animal name when not provided in the details. 
    * Corrected deprecated code for use with PHP 8.X.
  + /public/css/pet-match-pro-styles.css
    * Adjusted label and result display widths for both search results and details.

Version 2.9.6 - April 12, 2023
  + /public/class-pet-match-pro-public.php
    * Corrected issues with short code functions in the admin interface.
  + /admin/class-pet-match-pro-admin-settings.php
    * Corrected bug related to obsolescence of $api_activated.  
  + /admin/class-pet-match-pro-filter-functions.php
    * Corrected bugs in defining Search_Filter_Values when no custom labels exist.
  + /admin/partials/activate_license_form.php
    * Added plugin version number. 
  + /admin/partials/deactivate_license_form.php
    * Added plugin version number. 
  + /includes/pp/class-pet-match-pro-pp-api.php
    * Corrected bugs related to the display of found and lost search filter with a free license.
    * Corrected bugs related to creating an onclick parameter when a GA tracking method does not exist. 
    * Corrected bug related to the display of the search form filter options. 
  + /includes/pp/class-pet-match-pro-pp-option.php
    * Set empty values for admin search_sort options for all methods. 
    * Corrected issue with check box callback function.
  + /includes/pp/partials/pmp-field-levels-lost.php
    * Changed level_value_filter_orderby_datelast_lost from 2 to 3.
  + /includes/pp/partials/pmp-field-levels-found.php
    * Changed level_value_filter_orderby_datelast_found from 2 to 3.

Version 2.9.5 - April 1, 2023
  + /includes/pp/class-pet-match-pro-pp-api.php
    * Corrected bug related to the inclusion of the detail template preventing the display of animal details. 
    * Enhanced the adoptable featured short code to perform similar to search results.

Version 2.9.4 - March 20, 2023
  + /admin/partials/pmp-instructions-admin-settings.html
    * New file with admin settings shortcode instructions. 
  + /admin/partials/pmp-option-levels.php
    * New file with admin tab security levels.  
  + /public/class-pet-match-pro-public.php
    * Require paid license for use of admin shortcode with filter and label setting options. 

Version 2.9.3 - March 14, 2023
  + /pet-match-pro.php
    * Added new constant to manage PetPoint media. 
  + /includes/pp/partials/pmp-field-levels-found.php
    * Added level entry for conversion buttons. 
  + /includes/pp/partials/pmp-field-levels-lost.php
    * Added level entry for conversion buttons. 
  + /publuc/css/pet-match-pro-styles.css
    * Added entries to format the conversion button in wide adoption detail template.
  + /public/templates/pp/
    * Updated conversion button levels in all templates.

Version 2.9.2 - March 11, 2023
  + /pet-match-pro.php
    * Added new constant to manage PetPoint media. 
  + /includes/pp/partials/pmp-field-levels-adopt.php
    * Allow display of photos in search result details. 
  + /includes/pp/class-pet-match-pro-pp-detail-functions.php
    * Remove media file options from pet details (displayed by default). 
  + /admin/class-pet-match-pro-filter-functions.php
    * Remove media file options from search details (displayed by default).

Version 2.9.1 - March 7, 2023
  + /pet-match-pro.php
    * Added new constants to manage plugin activation.  
  + /includes/pp/class-pet-match-pro-pp-api.php
    * Corrected bugs related to activation without the selection of filter options.  
  + /public/css/pet-match-pro-styles.css
    * Added class pmp-red to style text red. 
  + /public/pp/templates/*
    * Corrected all templates to display an error when no details are provided for display.

Version 2.8.3 - March 4, 2023
  + /pet-match-pro.php
    * Added function to prevent admin warning of previously sent headers. 
  + /includes/pp/class-pet-match-pro-pp-api.php: 
    * Corrected bugs preventing the display of search results and animal details with a free subscription. 
  + /admin/class-pet-match-pro-filter-functions.php
    * Default subscription to free when one is not found in the settings. 
  + /includes/pp/partials/pmp-field-levels-adopt.php
    * Corrected filter field levels. 
    * Restricted adoption search filter to dogs or cat for free subscriptions. 

Version 2.8.2 - February 24, 2023
  + /admin/class-pet-match-pro-admin-settings.php
    * Added an Instructions tab. 
  + /admin/partials/pmp-instructions-installation.html
    * New file with plugin installation instructions.
  + /admin/partials/pp/pmp-instructions-search-adopt.html
    * New file with instructions for use of the adoptable search shortcode.
  + /admin/partials/pp/pmp-instructions-detail-adopt.html
    * New file with instructions for use of the adoptable detail shortcode.
  + /admin/css/pet-match-pro-admin.css
    * New entries to format instructions.
  + /includes/pp/partials/pmp-field-values.php
    * Added array defining free search species.
  + /includes/pp/class-pet-match-pro-pp-api.php
    * Restrict free license type to free search species.

Version 2.7.1 - February 4, 2023
  + /includes/pp/class-pet-match-pro-pp-detail-functions.php: 
    * Added a new function Animal_Labels to output the labels for a specified array and context.   
  + /includes/pp/class-pet-match-pro-pp-api.php: 
    * Corrected a bug preventing the display of search result labels.   
  + /admin/partials/pmp-option-levels-filter.php
    * Renamed all the level settings.
  + /admin/partials/pmp-option-levels-contact.php
    * Renamed all the level settings.
  + /includes/pp/class-pet-match-pro-pp-option.php
    * Corrected a bug displaying an error at the bottom of the Labels tab in the admin. 
    * Updated to use the new filter and contact level setting values.

Version 2.6.2 - January 1, 2023
  + pet-match-pro.php: Defined global variables for use in securing content based on license type.
  + /admin/class-pet-match-pro-filter-functions: New class with functions to process animal search features.
  + /admin/class-pet-match-pro-admin.php: 
    * Updated to use new global variables to include files. 
    * Updated to include /admin/class-pet-match-pro-filter-functions.php
  + /includes/pp/class-pet-match-pro-admin.php: 
    * Updated to use new global variables to include files. 
    * Updated to include /admin/class-pet-match-pro-pp-api.php
  + /admin/class-pet-match-pro-admin-settings.php: 
    * Renamed many of the general settings to align with a consistent naming convention. 
    * Updated the default general and contact options. 
  + /includes/pp/class-pet-match-pro-pp-api.php: 
    * Updated to reflect the name changes in general settings.  
  + /includes/class-pet-match-pro.php: 
    * Updated to use the new global variables.  
  + /public/class-pet-match-pro-public.php: 
    * Updated to use the new global variables.  
  + /includes/class-pet-match-pro-activator.php: 
    * Updated to reflect the renamed general settings  

Version 2.5.2 - December 6, 2022
  + /includes/pmp-option-levels-general.php: New file to secure general tab options based on license type.
  + /includes/pmp-option-levels-contact.php: New file to secure contact tab options based on license type.
  + /includes/pmp-option-levels-filter.php: New file to secure filter tab options based on license type.
  + /includes/pmp-option-levels-labels.php: New file to secure labels tab options based on license type.
  + /includes/class-pet-match-pro.php: Enable actions for contact, label and filter initialization procedures. 
  + /includes/pp/class-pet-match-pro-pp-option.php: Secure admin filter tab options based on license type. 
  + /admin/class-pet-match-pro-admin-settings.php:
    * Secure general tab settings based on license type. 
    * Move contact options to dedicated initialization procedure and secure based on license type.
    * Move filter options to dedicated initialization procedure and secure based on license type.

Version 2.5.1 - December 4, 2022
  + /admin/partials/activate_license_form.php: Enhanced the activation instructions. 
  + /admin/partials/deactivate_license_form.php: Enhanced the activation instructions.
  + /admin/css/pet-match-pro-admin.css: Added styles used in the activation/deactivation changes.

Version 2.4.11 - November 27, 2022
  + /includes/pp/class-pet-match-pro-pp-api.php: Restrict search filter and result values based on license type. 
  + /includes/class-pet-match-pro-form-builder.php: Restrict search filter and result values based on license type.
  + /admin/class-pet-match-pro-admin-settings.php: Added the contact tab setting social_share to control the display of the icons on the animal detail pages.
  + /includes/pmp-admin-info.php: Added the help text for the social_share admin setting. 

Version 2.4.10 - November 23, 2022
  + /includes/class-pet-match-pro.php: Define new shortcode pmp-social-share.
  + /public/class-pet-match-pro-public.php: Add function for pmp-social-share shortcode. 
  + /public/css/pet-match-pro-styles.css: Style pmp-social-share shortcode results.
  + /includes/pmp-field-levels-adopt.php: Restrict social share icons from use with free licenses. 

Version 2.4.9 - November 15, 2022
  + /public/class-pet-match-pro-public.php: 
    * Restrict the use of lost, found and featured shortcodes to paid license types. 
    * Restrict use of pmp_detail shortcode to paid license types. 

Version 2.4.8 - November 14, 2022
  + /includes/class-pet-match-pro-form-builder.php: Added class attribute to form Submit button. 
  + /public/templates/pp/adoptable-conversion-no-app.php: Corrected container id for call-us button. 

Version 2.4.7 - November 13, 2022
  + /includes/pp/class-pet-match-pro-pp-api.php: 
      * Restrict use of template option on detail shortcodes to paid license types. 
  + /admin/class-pet-match-pro-admin-settings.php:
      * Removed poster_details_template, required to use template option on details shortcode. 

Version 2.4.5 - November 6, 2022
  + /public/templates/pp/adoptable-default.php, lost-default.php and found-default.php: Added onclick behavior to thumbnail images and buttons.
  + /public/css/pet-match-pro-styles.css: Corrected issues w/ styling for detail pages.
  + /admin/class-pet-match-pro-admin-settings.php:
      * Limit use of theme-based template directory to paid license types.
      * Only allow use of templates containing 'default' for free license type.
  + /public/class-pet-match-pro-public.php: Do not include the theme-based style sheet with a free license. 
  + /includes/pp/class-pet-match-pro-pp-api.php: 
      * Include CSS from admin in search and detail pages for all paid license types.
  + /public/templates/pp/*.php: Removed the inclusion of the style sheet and corrected a bug preventing the display of anything after the declawed key.

Version 2.4.1 - November 2, 2022
  + /includes/class-pet-match-pro.php: Added options PMP_License_Type and PMP_License_Type_ID to manage premium features.
  + /admin/class-pet-match-pro-admin-settings.php: 
      * Added General Option Google Analytics Tracking Method for Preferred Level and excluded Custom Css from Free Level. 
      * Added the PetMatchPro file path to the call to CheckWPPlugin to support update distribution. 
      * Updated PetMatchPro icon. 
      * Added the contact setting for website support.
  + /includes/pmp-admin-info.php: Configured hover text for new GA Tracking Method website support settings.
  + /includes/pp/class-pet-match-pro-api.php: 
      * Added onclick attribute for search result links. 
      * Update message displayed on front-end to use website support contact email when data is not available from integration partner.
  + /public/templates/pp/adoptable-preferred.php: New Adoptable Details template with Google onclick attribute. 
  + pet-match-pro.php: Included the definition of the plugin file path to support update distribution.
  + /admin/license/class-pet-match-pro-license.php: Added declaration of private variable $isEncryptUpdate to support update distribution. 
  + /includes/class-pet-match-pro-form-builder.php: Added onclick attribute to form Submit button based on license level. 
  + /assets/*.png: Added plugin banner and icon files.
  + /admin/license/class-pet-match-pro-license.php: Configured plugin update logo.

Version 2.3.3 - October 12, 2022
  + /admin/class-pet-match-pro-admin-settings.php: Code formtting.
  + /admin/css/pet-match-pro-admin.css: Added the class pmp-license-info-title format the license deactivation form.
  + /admin/partials/deactivate_license_form.php: Formatted the deactivation form. 
  + /admin/partials/activate_license_form.php: Formatted the activation form.
Version 2.3.2 - October 11, 2022
  + /admin/license/class-pet-match-pro-license.php: Now returns the license parameter to manage license types.  
Version 2.3.1 - September 30, 2022
  + pet-match-pro.php: Updated version number.
  + /includes/class-pet-match-pro.php: Added new shortcode pmp-poster-details to display a poster from a details page.
  + /public/class-pet-match-pro-public.php: Added the function petmatch_poster_details to process the new shortcode. 
  + /includes/pp/class-pet-match-pro-pp-api.php: Added the function outputDetailsPoster to display the poster. 
  + /public/templates/details-poster-default.php: New file to format the poster page.

Version 2.2.2 - September 28, 2022
  + Added options labels="on,yes,1" to all search and featured shortcodes.
  + Corrected bug with featured shortcode to display age appropriately based on setting.
  + Corrected bug to properly display search results for featured shortcode.
  + Corrected bug with the display of orderby values by method.

Version 2.1.1 - September 25, 2022
  + Bug fix to use the orderby label if configured.
  + Configure orderby values by method.
  + General setting option to use labels settings for orderby values.

Version 2.0.2 - September 23, 2022
  + Cleaned up the field labels on the "Labels" tab in the admin to make them consistent across search methods.

Version 2.0.1 - September 14, 2022
  + Added shortcode [petmatch_lost_found_search] to combine the search results for lost and found animals from PetPoint.
