*** Changelog ***

= 1.5.6 - 26-08-2026 =
* Fixed the pre-swap package validator permanently blocking updates for any consumer that ships no `vendor/` directory. When `critical_paths` is not declared, `SE_License_SDK_Updater::get_critical_paths()` fell back to a blind `[ 'vendor/autoload.php' ]` — so a pro plugin with no runtime Composer dependencies (or one that vendors elsewhere) had every update rejected with "the downloaded {slug} package is incomplete (missing vendor/autoload.php)", for a file it never shipped. The fallback is now derived from the installed build: `vendor/autoload.php` is only required when the installed copy actually has one. Protection is unchanged for consumers that do ship `vendor/`, and an explicitly declared `critical_paths` is unaffected.
* Note for consumers hit by this before upgrading: the validator runs from the *installed* build, before WordPress swaps the folder, so neither this SDK fix nor a later `critical_paths` declaration can rescue a site whose installed build predates it unless that site's elected SDK copy is 1.5.6+. Ship an inert `vendor/autoload.php` stub inside the release zip to unblock those installs (never a real Composer autoloader — a second SDK copy would race the host's through the version election).

= 1.5.5 - 30-07-2026 =
* Added an offline/server-unreachable license grace period. `SE_License_SDK_Client::request()` now tags transport-level failures (DNS/timeout/TLS/blocked request, plus 5xx/408/429) with `transport_error`, and `SE_License_SDK_License::check_license_status()` distinguishes "server unreachable" from a genuine rejection: a previously-valid license keeps working until a grace window (default 14 days since the last successful verification) elapses, instead of a transient outage deactivating a paying customer. New `license_grace_period` init arg + `{hook}_license_grace_period` filter, and `SE_License_SDK_License::is_in_grace()`.
* Added documented license lifecycle events on each product's hook namespace: `license_activated`, `license_deactivated`, `license_grace_expired`, `license_check_deferred`, `update_installed`, `update_failed`. Subscribe via `$client->add_action( 'license_activated', $cb )`.
* Added a manual-update fallback: when an automatic install fails, the React License panel now shows step-by-step download-from-your-account + upload-manually instructions (no direct link) with the dashboard/upload URLs, instead of a dead-end error.
* Added an expiring/expired license renewal admin notice (site-wide, dismissible/snoozable) with a Renew button, so a lapse is noticed before Pro features quietly stop.
* Added WP-CLI commands: `wp se-license list|status|activate|deactivate|check-update|install <slug>` for every SDK-registered product. New `SE_License_SDK::get_all_registered()` / `get_registered_by_slug()`.
* Completed theme support: theme updates now get the same pre-swap package validation, core-dependency gate and previous-version/rollback recording as plugins (`source_belongs_to_this_plugin()` and `record_previous_version()` are theme-aware); the license screen moves under Appearance for themes. README documents theme integration.
* Added multisite/network handling: network-activated products store one license at the network level (`get_site_option`/`update_site_option`), render their license screen + notices in the Network Admin with `manage_network_options`, and update injection now reaches the Network Admin plugins screen. New `SE_License_SDK_Client::is_network_activated()`.
* i18n: fixed stray wrong text domains (`woo-feed`, `se-license` → `storeengine-sdk`), the SDK now loads its own `storeengine-sdk` translations (PHP + JS via `wp_set_script_translations`), and ships a `languages/storeengine-sdk.pot` template.
* Docs: documented the Composer `autoload_files` de-dup pitfall (a newer bundled copy can be skipped when multiple plugins bundle the SDK) and the direct-`require` remedy, in both the README and `init.php`.

= 1.5.4 - 10-07-2026 =
* Added a "maximum activations reached" recovery flow (Freemius-style). When the license server rejects an activation with `license-activation-limit-reached` it now returns the list of the user's active sites; the React License panel opens a picker (`LimitReachedModal`) where the user selects which site(s) to deactivate to free a seat, then re-submits the activation with `deactivate_activations` and takes the freed seat — no more dead-end error. Plumbed through `SE_License_SDK_License::activate_client_license()` (new `deactivate_activations` post field + `get_error_code()` / `get_error_data()` accessors) and `SE_License_SDK_Rest_API::activate_license()` (surfaces the limit case as a 409 carrying the site list). Server side (`license-management` addon): the `activate-license` endpoint gained a `deactivate_activations` schema field, frees the requested seats before re-checking the limit, and includes the active-site list in the 409 payload.
* Added a core/free-plugin dependency gate so a pro update never out-runs the free plugin it depends on — important now that wordpress.org holds plugin updates for ~24h of review, which can leave new pro running against old free. New `requires_core` SDK config option (slug, or `[ slug, basename, name, min_version ]`) plumbed through `se_license_init()` / `SE_License_SDK_Client` (`getRequiresCore()`). New `SE_License_SDK_Core_Dependency` resolves the required version (license-server per-release answer wins over the static `min_version`), and: (a) the SDK installer (`updates/install`) updates the core plugin first, then blocks with an actionable "update the free plugin first / it can take up to 24h via WordPress.org / update manually" message if it still can't be satisfied; (b) `SE_License_SDK_Updater::gate_core_dependency()` hooks `upgrader_pre_install` to abort a native "Update now" / bulk / background auto-update while the core plugin is behind (rollbacks exempt); (c) an admin notice nags to update the core plugin when a pro update is pending. Entirely a no-op unless `requires_core` is declared. Server side (`license-management` addon): the `check-update` response gained a filterable `requires_core` field (`storeengine/license/software_requires_core`) so the required core version can be declared per product/release.

= 1.5.3 - 02-06-2026 =
* Added pre-swap update package validation so a failed or incomplete update is dismissed instead of leaving the host plugin half-installed and fataling on every load. `SE_License_SDK_Updater::validate_package_source()` hooks `upgrader_source_selection` at priority 20 (after the `SE_License_SDK_Install_Job` folder-normalizer at 10, so it inspects the final source dir) and returns a `WP_Error` when the extracted package is missing its main plugin file or any declared critical path. WordPress then never reaches `clear_destination` — and on WP 6.3+ restores the temp_backup — so the user keeps a working plugin. Covers both the native plugins.php "Update now" path and the SDK REST installer.
* Hardened the validator against false positives: it self-scopes to the host plugin via `$hook_extra` (REST `storeengine_sdk.slug`, native `plugin` / bulk `plugins`) and defaults to a no-op on any ambiguous context, so it never inspects or blocks another plugin's package.
* Added a `critical_paths` SDK config option (default `[ 'vendor/autoload.php' ]`, overridable per-site via the `{hook}critical_paths` filter) so each consumer can declare the bootstrap files whose absence would cause a fatal. Plumbed through `se_license_init()` and `SE_License_SDK_Client` (new `getCriticalPaths()` accessor).

= 1.5.2 - 26-05-2026 =
* Fixed manual install / rollback always re-installing the currently-installed version instead of the requested one. `SE_License_SDK_Client::request()` was building the outgoing body with `array_merge( $args['body'], [ ..., 'version' => $this->getProjectVersion(), ... ] )`, and because `array_merge()` lets the later array win on duplicate keys, any caller that put `version` in the body had it silently clobbered by the currently-installed plugin version. The `/software/get-package` proxy in `SE_License_SDK_Rest_API::resolve_package_url()` does exactly that — it sends `version` as the *target* version to fetch the zip for. So every rollback/manual-install request effectively asked the server for the currently-installed version, downloaded that zip, re-installed it on top of itself, and reported success without any actual version change.
* Swapped the merge order so caller-supplied body fields win over auto-added defaults. Matches the precedence the `license` field already used (via `if ( empty( $body['license'] ) )` guard).
* Added `upgrader_source_selection` filter in `SE_License_SDK_Install_Job::install_from_url()` that renames the working-dir source folder to match the host plugin's slug before `WP_Upgrader::install_package()` derives the destination. Protects against legitimate misnamed-folder uploads (older deployment uploads predating the upload-time `versions-controller.php` folder-name normalization) which would otherwise install into a sibling directory instead of the active plugin folder.
* Added deactivate-before-install in `install_from_url()` mirroring `Plugin_Upgrader::deactivate_plugin_before_upgrade()` so an actively-loaded plugin file is not held open during the overwrite. Avoids the silent "install reported success but nothing actually changed" symptom on Windows where the running PHP file cannot be replaced.
* Restored Manage License button text visibility — generic `.se-sdk-app a` link rule no longer overrides `.se-sdk-btn` button text colour (previously turned the white label into the same primary blue as the background).

= 1.5.1 - 26-05-2026 =
* Fixed `Fatal error: Class "SE_License_SDK_Update_State" not found` that could occur on hosts where multiple SDK copies coexist (Strauss-prefixed vendor folders, classmap-authoritative composer dumps, custom autoloaders). The SDK's own `spl_autoload_register` resolves classes relative to whichever plugin's `init.php` registered the version first — which is not always the plugin that loaded `SE_License_SDK_Updater.php`. The 1.5.0 release added three new classes (`Update_State`, `Install_Job`, `Upgrader_Skin`) and the autoload mismatch could lookup them in the wrong vendor folder.
* Added a defensive `require_once` for `SE_License_SDK_Update_State` before its two callsites in `SE_License_SDK_Updater::get_updates()` and `::record_previous_version()`. Loads the sibling file by `__DIR__` so it always matches the Updater that's running.
* Added the same defensive load in `SE_License_SDK_Client::update_state()` and `::new_install_job()` for the React panel REST handlers.
* Added the same defensive load in `SE_License_SDK_Install_Job::install_from_url()` for `SE_License_SDK_Upgrader_Skin`.

= 1.5.0 - 26-05-2026 =
* Added `str_starts_with`, `str_ends_with`, `str_contains` polyfills so the SDK runs correctly on PHP 7.4 (matches the declared `php: >=7.1` composer constraint).
* Added React-based License & Updates panel (`static/license-app.js` + `static/license-app.css`) that replaces the legacy PHP license form on the SDK's built-in admin page. Mounts via `wp.element` — no build step required.
* Added Status Hero card with installed → latest version, last-checked timestamp, and a "Check for updates" button.
* Added Update Available banner with one-click "Update to vX.Y.Z" backed by `Plugin_Upgrader`. Synchronous install with captured log streamed to a collapsible drawer.
* Added Rollback Banner: one-click revert to the previously installed version (recorded automatically via the `upgrader_process_complete` hook).
* Added Version History card with per-row Install / Roll back / Rollback-blocked actions. Honours per-version `allow_rollback` + `breaking_changes` flags set by the vendor.
* Added breaking-changes confirm modal inline in the version table.
* Added "Receive beta updates" toggle backed by the new `/settings/beta-channel` endpoint.
* Added new REST routes under `storeengine-sdk/v1/{slug}/`: `GET updates/status`, `POST updates/check-now`, `GET updates/versions`, `POST updates/install`, `POST updates/rollback`, `GET/POST settings/beta-channel`.
* Added `SE_License_SDK_Install_Job` that wraps `Plugin_Upgrader::run()` with a non-interactive skin, captures progress, persists the log to a 1h transient for post-mortem.
* Added `SE_License_SDK_Upgrader_Skin` — non-interactive skin that suppresses HTML output, captures feedback/errors as JSON, forces direct filesystem (no FTP prompt).
* Added `SE_License_SDK_Update_State` — single wp_option store for `previous_version`, `last_install_*`, `last_checked_at`, `beta_enabled` mirror.
* Added `SE_License_SDK_Updater::force_check()` — clears the transient and re-queries the server with `force=true`.
* Added `upgrader_process_complete` hook to record `previous_version` whenever WP (cron or otherwise) successfully updates the host plugin, enabling the one-click rollback shortcut.
* Stamps `last_checked_at` on every successful `/check-update` response so the UI can render "Checked 5 min ago" without a network round-trip.
* Reworked deactivation feedback modal: reduced to 5 reasons (no longer needed / found better / couldn't work / temporary / other), removed the required-selection gating, exposed an in-button spinner that preserves the primary brand colour while submitting.
* Switched deactivation feedback delivery to `navigator.sendBeacon` so the user is never blocked waiting for an AJAX round-trip before the plugin deactivates. Old AJAX path retained as fallback for browsers without sendBeacon.
* Submit & Deactivate with no reason now still fires telemetry (server enriches with admin name/email/site URL/usage_log) but never blocks deactivation; Skip & Deactivate skips telemetry entirely.
* Scoped 5 unscoped CSS selectors in `static/deactivation-modal.css` (`.feedback-message`, `.reasons`, `li`, `.mui`, `.mui-error`) to prevent style leakage onto unrelated WP admin pages.
* Removed the deactivate-on-WP-plugin-deactivate behaviour in `project_deactivation()`. The license stays active across WP plugin deactivate/reactivate cycles (matches ACF Pro / Yoast Premium / EDD behaviour). Seat is freed only when the user explicitly clicks Deactivate License in the SDK panel or truly uninstalls the plugin.
* Surfaced real WordPress auto-update state for the host plugin in the UI's "WP Auto-update" pill (was previously hardcoded to "Enabled").
* Surfaced license `updated_at` as the source for "Last Checked" instead of mistakenly using the update-check timestamp.
* Dropped JS double-masking of the license key (server already masks via `get_public_data()`).
* Removed the unused `currentVersion` field from the localized config payload; removed `auto_update_window` from the `/updates/status` response (server endpoint + storage retained for future scheduled-update feature).

= 1.4.0 - 20-04-2026 =
* Fixed normalize sdk assets path for windows compatibility.
* Improved purchase URL handling on the client with UTM parameters.
* Added `blocking` always `true` in request args ensuring client requests are always blocking (required for server-side data saving).
* Improved updater cache handling.
* Added support for localizing SDK frontend data via `script_handler`, `script_object`, `get_js_param_name()`, and `get_js_params()` for custom JavaScript or React-based license management screens.
* Added package update metadata to localized SDK data, including current version, available update details, and update availability status.
* Added a REST API package info endpoint for SDK integrations that need update data in custom management UIs.
* Added `redirect_on_activation` option to allow disabling the automatic redirect to the manage license page after plugin activation.
* Made `package_file`, `package_name`, `basename`, `package_type`, and `package_version` optional in `se_license_init()` by auto-detecting package metadata from the current plugin or theme when possible.
* Improved package file detection to resolve a plugin's main bootstrap file when the SDK is initialized from an included file.
* Normalized the configured license server URL before using it in client requests.
* Updated SDK helper documentation to cover the new script localization and activation redirect options.
* Improved updater filter signatures and forced-refresh handling for package information requests.
* Refined internal SDK component initialization to keep shared client state in sync across license, updater, REST API, promotions, and insights modules.
* Fixed license signature validation after activation and Freemius migration by aligning signature generation with the scheduled license check timestamp.
* Refreshed Composer package metadata and modernized development dependency constraints.

= 1.3.0 - 30-03-2026 =
* Fixed float to int implicit conversion warning in `PHP 8.1`
* Improved IntelliSense & type hints for sdk-init helper function.
* Added `SensitiveParameter` attribute to hide license key from error log & crash reports.
* Added isolated per-client REST API for license and insights management.
* Added `init_restapi` option to `se_license_init` to conditionally enable/disable the REST API.
* Unified license activation and deactivation logic across the REST API and the built-in management page.
* Added support for license key masking and public data formatting in the license class.
* Introduced `get_js_params()` in the client class for easier frontend integration.
* Improved SDK initialization and path resolution, including PHP 8.1+ compatibility fixes.
* Refactored REST API callbacks to provide enriched error and success responses.
* Comprehensive documentation for `se_license_init` arguments.
* General code style improvements and standard compliance fixes.

= 1.2.0 - 02-12-2025 =
* Refactored user agent string generation in client class for more detailed environment info and standardized header casing.
* Enhanced promotions class to handle non-JSON and error responses from the server, ensuring only valid promotions are processed.
* Added comments explaining logic for hiding promotions for users and admins.
* Unified version information, passing version to SDK client form initializer.

= 1.1.0 - 06-10-2025 =
* Updated documentation and fixed typo in readme file.
* Added doc-comment
* Added GitHub Actions workflow for release on tag.
* Fixed promo transient data type before storing.
* Refactored insights opt-in logic to support delayed and conditional display.
* Added new methods for install time and notice delay, and improved admin notice rendering.
* Introduced new views for deactivation reasons and support ticket submission/email.
* Updated license form and related styles for better UI consistency.
* Redirects to license form anchor after license check for better user experience.
* Added UTM parameters to the purchase link for tracking.
* Updated license-check redirect URL (with hash fragment) for better navigation UX.
* Updated insights and promotions modules to use a unified CSS variable for primary color, improving consistency across notices, modals, and forms.
* Refactored support ticket email and form templates for insights, renamed and replaced related view files, and enhanced promo hiding logic to combine transient and user options.
* Improved code style and documentation for better developer experience.
* Fixed opt-in notice (insight) template loading.
* Updated deactivation modal script loader hook priority.
* Removed error_log statements & clean up unnecessary debug output.

= 1.0.0 - 21-08-2025 =
* Initial Commit.
