# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.11.1-beta1]

### Fixed
- `msProducts` / `msCart` / `msGetOrder` with `includeThumbs` returned an SQL syntax error in 1.11.0-beta1 (#293). The new `ProductThumbnailJoin` helper wrapped `$modx->getTableName()` result in extra backticks; xPDO already escapes the name itself, so runtime SQL had triple backticks around the table and MySQL rejected the statement. Storefront catalog listings with thumbnails were empty until reload. Fixed by stripping the outer backticks in the sprintf template.
- Package upgrade to 1.11.0-beta1 failed on MODX installs where `modx_transport_packages.metadata` is `TEXT` (#296). The full project changelog (~33 KB of history since 1.0.0-alpha) plus license + readme + requires, serialized into package attributes, overflowed the 65 535-byte limit. Transport-package metadata now contains only the current release block from the changelog (~7 KB); the full file remains in `core/components/minishop3/docs/changelog.txt` inside the installed package.

## [1.11.0-beta1]

### Added
- New `msOptionGroup` model replaces `modCategory` for grouping product options (#10). Migration converts existing `msOption.modcategory_id` → `option_group_id` automatically (each distinct `modcategory_id` becomes a row in `ms3_option_groups` using `modCategory.category` as name). Admin: separate "Option Groups" tab next to "Options" with CRUD, drag-and-drop ordering, options-per-group counter.
- Public `window.ms3.refresh()` storefront JS API and `ms3:refresh` DOM event (#274) for integration with third-party AJAX components that replace catalog DOM (mFilter / mSearch2 / custom AJAX / infinite scroll). The component dispatches `ms3:refresh` after its DOM swap — MS3 re-binds `ProductCardUI` / `QuantityUI` state. MS3 does NOT listen for vendor-specific events; dependency direction is component-→-MS3.
- Manager API endpoint `POST /api/mgr/orders/{id}/recalculate-cost` and "Recalculate cost" button in the order admin (#212). Three modes: `auto` (DefaultDelivery/DefaultPayment by fields, no external provider calls), `manual` (operator-provided `manual_delivery_cost`), `force_provider` (explicit provider call in try/catch). Honours discounts via `PriceAdjustment` and total clamp via `OrderService::clampComputedTotal()`.
- Negative additional cost for delivery and payment methods (#211): the `price` field now accepts fixed discounts (`-10`) and percent discounts (`-10%`). Common normalizer `MiniShop3\Utils\PriceAdjustment` shared between manager API and legacy processors.
- Negative total guard `OrderService::clampComputedTotal(?msOrder, float, float, float = 0.0)` (#265): when sum of cart + delivery + payment costs goes below zero (e.g. misconfigured discounts), total is clamped to 0 and a `LOG_LEVEL_WARN` line with the breakdown is written. Wired into all calculation paths.
- Vue admin: live "Discount" / "Markup" badges on the `price` field of delivery/payment edit forms (#265).
- Validation rules editor for deliveries now lists model fields and Object Extension fields for `msOrder` / `msOrderAddress` (#215) in addition to the static set.
- Order fields tabs: "Configure fields" empty-state hint and conditional action bar visibility on Info / Address tabs (#182).
- Import: product `stock` field is now available in the field mapping (#283), with auto-mapping of typical CSV headers (`stock`, `remains`, `quantity`, `qty`).
- Web API for customer profile: `POST /api/v1/customer/add` (quick profile field update incl. extra fields, #241/#261), `POST /api/v1/customer/changeAddress`, `GET /api/v1/customer/email/verify` (with HTML redirect support), `POST /api/v1/customer/email/resend-verification` (#226).
- System settings: `ms3_email_verification_url`, `ms3_email_verification_success_url` for customizing the email-verification flow.

### Changed (breaking)
- **Storefront snippets — numeric placeholders contract** (#242, breaking). Plain placeholders (`cost`, `cart_cost`, `delivery_cost`, `price`, `weight`, …) are now plain `float` for arithmetic and Fenom `|number`. Display strings with locale / currency / unit live in `*_formatted`. Removed `formatPrices` no-op in `msProducts` and `msOrderTotal`. Migration: change `{$order.cost}` (which used to render `5 300 ₽`) to `{$order.cost_formatted}`, or keep `{$order.cost}` only for arithmetic / `|number`. Affects `msOrder`, `msGetOrder`, `msCart`, `msProducts`, `msOrderTotal`, default `ms3_order.tpl`.
- **Schema — `msOption.modcategory_id` → `msOption.option_group_id`** (#10, breaking for direct DB readers). Data migration is automatic. Custom code reading `modcategory_id` directly must switch to `option_group_id`; the join alias `category_name` in `OptionLoaderService` / `CategoryOptionService` is renamed to `group_name`. Snippet `msProductOptions` filters/sorts by `group_name`.
- **Manager API — `GET /api/mgr/options/modcategories` removed**, replaced by `/api/mgr/option-groups` (CRUD + reorder + bulk delete + per-group options count).
- `msCart` snippet no longer suppresses output on `?msorder=...` by default (#249) — the cart loads and renders the chunk as usual, so mini-cart blocks in the global layout keep working on the "thank you" page. New `hideOnThanks=1` parameter brings back the previous "blank on thanks page" behavior for a specific call.
- `Processors\Product\Update`: option deletion now applies on save (#199 follow-up). Form options are captured in `beforeSet` into `$ms3ProductFormOptions` and passed to `ProductDataService::saveOptions(..., removeOther: true)` after the parent `afterSave` — avoids MODX 3 quirk where the processor's `options` property is often empty post-save.
- `ProductDataService::saveCategories()`: missing `categories` key in the form payload no longer wipes extra category links (#238). Behavior is now symmetric with `saveLinks()` — explicit empty array still clears, but absence of the key (tab not rendered yet) is a no-op. Integrations relying on "no `categories` in POST → clear all" must pass `categories: []` explicitly.
- Web API `POST /api/v1/customer/add` allows all columns present in the `msCustomer` xPDO map (including Object Extension after `loadMap()`), with an explicit denylist of sensitive/system fields (#261). Rakit-validation preserved for `first_name` / `last_name` / `email` / `phone`; other allowed columns are normalized by `phptype`.

### Fixed
- Product duplicate via MODX context menu: option values now copy over to the new product (#257). After `modResource::duplicate()`, `OptionService` re-syncs `ms3_product_options` rows from the source.
- `Processors\Product\Create`: empty option fields no longer wipe options on save. Aligned with `Update` — sync only when the request contained `options-*` keys.
- Plugin events that mutate data via `returnedValues` (#219, #221): `msOnBeforeSendNotification`, `msOnBeforeImport`, `msOnImportRow`, `msOnProductsLoad`, `msOnProductPrepare` apply mutations from the MODX `returnedValues` channel; `Cart::changeOption`, `Customer::validate`, `Customer::getOrCreate`, `OrderStatusService::change`, `OrderUserResolver::getUserId` consume merged `$response['data']` after `invokeEvent`.
- Option category tree for nested catalogs and multi-store setups (#237): tree returns `msCategory` (selectable) plus `modResource`/`modDocument`/`modWebLink` with `isfolder=1` (navigation-only) — no system setting required.
- Product list duplicates on `includeThumbs` (#281): one thumbnail per product via shared `ProductThumbnailJoin` helper in `msProducts`, `msCart`, `msGetOrder`. Fixed `parent` → `parent_id` typo in `msGetOrder`.
- Delivery / payment numeric fields can finally be cleared from the admin form — the controller no longer silently drops `null` values (`isset` → `array_key_exists`).
- Email verification flow: link in the email points to Web API by default (`api.php?route=…/email/verify&token=…&html=1`), with optional custom URL via `ms3_email_verification_url` (#226).
- Phinx defensive checks added in 1.10.x-era seed migrations no longer skip every install due to double `table_prefix` (#276): API methods (`hasTable`, `$this->table()`, `hasColumn`) now receive unprefixed names; raw SQL keeps `{$prefix}name`. Initial schema's foreign-key step also fixed (FK constraints were never being created on fresh installs).
- Many smaller fixes — see project CHANGELOG.md for details.

## [1.10.1-beta1]

### Fixed
- Manager API now invokes `msOnBefore/Create/Update/Remove OrderProduct` events when adding, updating, or removing order line items via the Vue admin (#208, closes #207). Before-hooks may abort with HTTP 400; after-hooks are observe-only and their failures are logged at WARN level (`persistence already done`) without returning a 4xx — the row is already in DB. Previously third-party plugins (ms3PromoCode etc.) silently missed these events.
- Product Options tab: switching between option groups no longer wipes user-entered values (#230). All groups are kept mounted via `v-show` instead of `v-if`-style remount, so per-field local state and hidden inputs survive tab switches and reach the form on submit.
- Cleared dangling `msOption.modcategory_id` references when a `modCategory` is removed (#228). Plugin subscribes to `OnCategoryRemove` and resets the link to 0, mirroring how MODX core handles `modChunk` / `modPlugin` / `modSnippet` / `modTemplate` / `modTemplateVar` on category removal. Prevents the duplicate "no group" tab in the product options UI when a third-party component is uninstalled.
- `OrderFinalizeService` now invokes registered `msOnBeforeMgrCreateOrder` / `msOnMgrCreateOrder` instead of non-existent `msOnBeforeFinalizeOrder` / `msOnFinalizeOrder` (#217). The old names were never registered as `modEvent`, so plugins could not subscribe via the UI.
- Import → TV fields panel uses the correct setting key `ms3_template_product_default` (was: non-existent `ms3_product_default_template`), so the panel lists only TVs bound to the configured default product template instead of every TV in the system (#210).
- Added missing lexicon `ms3_cart_get_success` in `lexicon/{ru,en}/cart.inc.php` — the key was used by `Cart::get()` but missing from translations, producing `Language string not found` warnings in MODX logs (#223, closes #222).
- `ServiceRegistry` no longer logs DEBUG noise for missing default optional override paths (`core/config/ms3.services.php`, `core/config/ms3.services.d/`) on stock installations (#225, closes #224). The DEBUG log is preserved when the operator explicitly sets `ms3_services_config` / `ms3_services_addons_dir` system settings to a missing path.

## [1.10.0-beta1]

### Added
- Full Vue migration of product options management (Settings → Options, Category → Options tab, Product → Options tab) replacing the legacy ExtJS UI (#205).
- REST API for options: `/api/mgr/options` (CRUD + tree, types, modcategories, suggestions, bulk assign/delete) and `/api/mgr/categories/{id}/options` (CRUD + sort + bulk + duplicate from another category).
- Settings → Options Vue screen: DataTable grid, MODX category tree (PrimeVue Tree) with independent checkboxes and context menu (refresh, expand/collapse subtree, select/clear all), create/edit dialog with category assignment tree and values editor for combobox/comboMultiple/comboColors (drag-drop sort, PrimeVue ColorPicker).
- Category → Options Vue tab: link grid with drag-drop reorder, inline default-value editor, bulk activate/deactivate/require/unrequire/remove, `Add option` and `Copy from category` dialogs.
- Product → Options Vue tab: universal ProductOptionField that renders all 10 option types; comboOptions uses PrimeVue InputChips (Enter / comma / blur commit) with a suggestions strip below the field powered by `/api/mgr/options/suggestions`.
- Per-category caption/description override on `ms3_category_options` (#200): non-empty override wins over the global `msOption.caption/description` both in the storefront overlay and the admin. Inline-editable column in the category options grid + fields in the Add Option dialog. Batch prefetch avoids N+1 for `loadForProducts`.
- `msOptionType::getSchema()` — declarative schema API replacing ExtJS JS-string output; option_fields ship both legacy `ext_field` and new `schema`.
- Self-heal migration `20260420120000_repair_order_model_fields_if_missing` (#201): if `SeedModelFields` / `SeedModelFieldSections` did not fully complete (admin showed `ms3_model_fields_empty`), the migration re-seeds only the missing `msOrder` / `msOrderAddress` records. User customizations are preserved — the update is guarded by `WHERE section_id IS NULL`.

### Refactored
- Deleted 7 ExtJS UI files (settings/option/* + category/option.*) and 22 PHP processors (Settings/Option, Category/Option), ~2600 lines of legacy code.
- Order edit screen: provide/inject instead of props chain (#196, #204). `ORDER_CONTEXT_KEY` symbol provides the shared ref/action/helpers set to all tabs; composables `useOrderFormatters`, `useOrderFieldHelpers`, `useOrderLogFormatters` extract what used to be inline helpers. Tabs receive only tab-specific data via props.
- Product form multi-value option POST: single hidden JSON field instead of `name[]` siblings (ExtJS BasicForm.getValues() only kept the last DOM match, silently dropping extras). Server-side decoding via `Utils::decodeOptionValue()` in `Processors\\Product\\Update` and `Processors\\Product\\Create`.
- `OptionLoaderService::convertPreloadedValue()` match is now case-insensitive and covers all three multi-value types (comboMultiple, comboColors, comboOptions) — previously only strict `ComboMultiple` matched, truncating reloaded arrays to their first value.

### Fixed
- Removed product option reappeared on save (#199, #202): `removeOther=true` is now used for the explicit options array from the form POST so keys absent after deletion/copy are removed from `msProductOption`. JSON-only auto-sync still forces `removeOther=false` to preserve category-only options (regression guard for #153/#158).
- Checkbox-type options on the product form no longer render as a 25%-wide sliver on top-aligned labels (root cause `anchor: '25%'` in legacy ms3.utils.js is gone along with the ExtJS UI).
- Category tree in the options dialog no longer lists non-category resources; filters by `class_key = MiniShop3\\Model\\msCategory` (exact) and uses an accurate COUNT-based leaf flag instead of `isfolder`.
- Independent checkboxes in category tree: selecting/deselecting a parent no longer cascades to children (PrimeVue Tree's default propagation replaced by a custom checkbox render).
- Optional `modcategory_id` on option create/update (null/empty payload is coerced to 0 for the NOT NULL column).
- Dialog styles now apply in teleported PrimeVue Dialog markup (class='ms3-option-dialog vueApp' on the dialog root + .ms3-option-dialog scoped selectors).
- DatePicker for `datefield` no longer sends a day-early value — `toISOString()` (UTC) replaced by local `getFullYear/getMonth/getDate` when serializing into the hidden POST field.

## [1.9.0-beta1]

### Added
- `ms3_cart` snippet: sync all status aggregates (`total_cost`, `total_count`, `total_weight`, `total_discount`, `total_positions`) with chunk totals after `msOnGetStatusCart`, including early exits on empty cart; `status` key available in chunk and `return=data` (#197)
- Vendor extra fields support: `DynamicField` component replaces hardcoded `v-if` chain (3 types → 13+ field types including checkbox); `VendorsController` loads xPDO map, dynamic `allowedFields`; `ExtraFieldsService` creates `msProductField` only for `msProductData` (#198)
- `DynamicField`: FileBrowser support for file/image fields, `w-full` on all inputs, `fluid` on InputNumber, `idPrefix` prop for label/id accessibility (#198)

### Fixed
- Manager API overwrote `msOrder.properties` with address data: `mergeAddressIntoOrderData()` excludes conflicting fields (#191)
- Raw lexicon keys in manager order form dropdowns: additional lexicon topics loaded for `comboOptions` translation (#193)
- Customer delete from grid not sending network request after confirmation (#179)
- `AuthUI.initTabSupport()` breaking all Bootstrap tabs on page: scoped to auth block only (#180)
- Options modCategory filter duplicated categories: dedupe + increased combo page size (#186)
- Bulk assign options to categories: fixed processor and select-all tree logic (#187)
- Product option tabs: grouped by `modcategory_id`, normalized id, guarded empty `option_fields` (#188)
- `msPayment` type hints in Payment Sort processor (#177)

### Refactored
- `OrderView.vue` split into `OrderInfoTab`, `OrderProductsTab`, `OrderAddressTab`, `OrderHistoryTab` + `orderFieldsLayout.css` (#176)
- Product options: Map keyed by `modcategory_id` for tabs, named combo page size under `ms3.grid`, documented GROUP BY
- PHPStan: resolved 109 errors, baseline reduced from 277 to 168 (#174)
- `OptionsChips`: styles moved from scoped to non-scoped with `.vueApp` prefix (Vite hash mismatch fix)
- Prettier formatting for 25 Vue components

### Dependencies
- `lodash` 4.17.23 → 4.18.1
- `vite` 6.4.1 → 6.4.2

## [1.8.0-beta1]

### Added
- `MS3OrderTabsRegistry`: plugin API for custom order tabs (Vue and ExtJS) without core modification. Pre-mount queue via `msOnManagerCustomCssJs`, shared props (`orderId`, `order`, `config`, `isCreateMode`), `hideOnCreate` flag, reserved keys protection, lazy ExtJS mount (#166, #167)
- Modular addon routes via `core/config/ms3.routes.d/`: each addon places own route file, no conflicts. `Router::loadRoutesFromDirectory()`, separate `web/` and `manager/` directories, alphabetical loading order (#169, #171)
- Duplicate and Publish action buttons in category products grid: migration updates `ms3_grid_fields` actions column config (#143, #163)

### Fixed
- Empty string in decimal/int Extra Fields broke product save: MySQL error `Incorrect decimal value` caused `save()` to return false, silently skipping categories/options/links. Hardcoded cast of `price`/`old_price`/`weight` replaced with universal loop over `_fieldMeta` for all `float`, `integer` and `boolean` fields (#170)
- `hide_children_in_tree` checkbox not saved in categories: `handleCheckBoxes()` added to `Category/Update` and `Category/Create` processors (#161, #160)
- `publish_document` passed as bool instead of int in all 4 controllers (product/create, product/update, category/create, category/update) — MODX JS uses strict `=== 1` comparison (#160)

## [1.7.0-beta1]

### Added
- Inline editing in category products grid: double-click to edit text, number, boolean fields directly in table (#116, #134)
- Editable field configuration in Utilities → Grid Fields for category-products grid (#134)
- Formatted currency and weight placeholders: `price_formatted`, `cost_formatted`, `old_price_formatted`, `old_cost_formatted`, `weight_formatted`, `discount_price_formatted`, `discount_cost_formatted` (#144, #147)
- New `ms3_weight_unit` system setting, `Format::weightWithUnit()` and `Format::getWeightUnit()` methods (#147)
- Custom fields support in order validation: fields from delivery validation rules persist in draft properties between `order/add` and `order/submit` (#135)

### Refactored
- Removed unused `ms3_category_grid_fields` setting and `Processors\Category\GetList` processor (#145, #146)
- Simplified `Product\GetList` processor: removed non-combo logic, added `combo: true` validation (#151, #152)
- Migrated product gallery from ExtJS to Vue: 6 ExtJS files (~900 lines) replaced with 5 Vue components + composable (~1300 lines) (#112, #150)
- New `Product\UpdateSource` processor for media source change without full form save (#150)

### Fixed
- Product options not deletable in admin: `options-color[]` parsed as `color[]` instead of `color` — extracted to `Utils::extractOptionKey()` (#148, #149)
- Visual editor (TinyMCE) broken in product category: unconditional value reset and missing `modx-resource-content` block restored (#156)
- Custom category options deleted on product save: `saveOptions(null, true)` removed options not in auto-collected JSON fields — now forces `removeOther=false` when `options=null` (#153, #158)
- "Create Document" missing from category context menu: fallback to `pnew` class when `pnew_modDocument` not set by Form Customization (#153, #158)
- Duplicate/Delete buttons missing for products and categories: permission flags compared with `=== 1` but `json_encode(true)` produces `true` — added `(int)` cast (#128, #133)
- `empty('0')` in `OrderFieldManager::add()`: value `'0'` no longer treated as empty (#135)
- `OrderAddressManager::saveToCustomerAddresses()` return type fixed from `?msCustomerAddress` to `bool` (#135)
- `save_address` checkbox uncheck now correctly removes key from properties (#135)
- Table prefix missing for relation fields in grid config — aggregate values returned zeros (#136)
- Customers grid sorting and server-side pagination: `getIterator()` received sort/pagination params as `$cacheFlag` (#137)
- Order status not displayed in customer order history: ad-hoc JOIN fields not hydrated — replaced with status map preloading (#139)
- Incorrect URLs in customer orders: reset button and pagination links pointed to site root — URLs built server-side via `makeUrl()` (#141)
- Incorrect URLs and hardcoded English messages in customer addresses — URLs built server-side, messages replaced with lexicon keys (#142)
- Missing `desc` key in snippet and source properties during package build (#127)

### Breaking Changes
- `cost_formatted` in customer order list now includes currency symbol — custom chunks adding currency manually will get double symbol (#147)

## [1.6.0-beta1]

### Added
- Order cancel by customer in personal account: API endpoint, cancel button in order list/details, `ms3_customer_cancel_allowed_statuses` setting (#119, Issue #117)
- Remember active Vue tab across product page reloads via `localStorage`, `ms3_product_remember_tabs` setting (#120, Issue #111)

### Refactored
- Standalone LK modules (order-cancel, customer-addresses, auth-forms) integrated into ms3 architecture: API → UI class → hooks → message (#126)
- New `AuthUI` class for login/register forms with `beforeLogin`/`afterLogin`, `beforeRegister`/`afterRegister` hooks (#126)
- `CustomerUI` extended with order cancel, address management (set default, delete) with hooks (#126)
- `CustomerAPI` extended with `login`, `register`, `setDefaultAddress`, `cancelOrder` methods (#126)
- New `confirm.js` module: Promise-based Bootstrap Modal confirm dialog with native fallback, i18n by `<html lang>`, declarative binding via `data-ms3-confirm` attribute (#126)
- Order detail URLs now use UUID instead of integer ID for security (#126)
- UUID format validation for `$_GET['order']` parameter (#126)
- 6 new selectors in `Selectors.js` for LK components (#126)

### Fixed
- httpOnly cookie token architecture: single `ms3_token` httpOnly cookie as source of truth instead of 4 unsynchronized storages. Middleware injection for backward compatibility. Cart preserved on login/register (#124)
- Save button display for products and categories in MODX 3.2 — explicit `canSave`/`locked` calculation (#118)
- Access policy `data` format for MODX upgrade compatibility — fixed double JSON encoding (#107, Issue #100)

### Breaking Changes
- Register.php response: `token` field changed from object `{token, expires_at}` to plain string. `expires_at` moved to top level. Custom themes accessing `result.object.token.token` need update (#124)

## [1.5.0-beta1]

### Added
- Centralized selectors module (`Selectors.js`) with override via `ms3Config.selectors` (Issue #18)
- `ServiceCheckMiddleware` for graceful 503 on missing ms3 service (Issue #68)
- `has('ms3')` checks in all entry points, plugin events, and snippets
- `data-ms3-*` attributes as primary selectors, CSS classes as fallback (Issue #17)
- Rows per page selector, first/last pagination buttons, chunk hashes (Issue #78)
- `ms3:cart:updated` event on successful order submission (#96)
- `formatPrices` parameter for `msOrderTotal` snippet
- Sorting in orders list grid (Vue)

### Fixed
- Lexicon corrections (Issue #21)
- Removed `action` from miniShop3 menu configuration (#94)
- EAV options clearing from product form
- Empty orders list caused by unnecessary `GROUP BY`
- Idempotent grid seed migrations
- `Response::error` now includes correct HTTP status code in JSON response
- `CartController::change()`/`remove()` return type (array instead of Response object)
- Correct default order status IDs with fallback for zero values
- `getIterator` for msProduct/msCategory — added `class_key` to criteria

### Changed
- Removed redundant permission checks from processor `initialize()` methods (#95)
- Simplified `CustomerAddressController::getAuthorizedCustomer()` (middleware guarantees service)
- Removed fallback service registration in `api.php` — returns 503 on bootstrap failure

## [1.4.1-beta1] - 2026-02-14

### Added
- Tooltip with placeholder key on product fields
- HTML array format support for cart options
- QuantityUI module extracted from cart logic
- Gallery interface localization
- GitHub Actions workflow for automatic release creation

### Changed
- Minimum PHP version raised to 8.2
- All px units converted to rem in Vue components
- Dropdown components replaced with Select (PrimeVue 4)
- Unified placeholder/value display in form controls
- Text color variables updated for UI consistency

### Fixed
- Product copy no longer duplicates gallery images
- JSON fields (color, size) display in product template
- Vendor placeholder prefix (`vendor.` → `vendor_`)
- Snippet descriptions loading
- Calendar icon display unified across components

## [1.4.0-beta1] - 2026-02-07

### Added
- Notification Center: Email notifications on order status change (manager + customer recipients)
- Notification Center: Telegram notifications for managers on order status change
- Telegram settings: `ms3_telegram_bot_token`, `ms3_telegram_manager_chat_id`
- msOrder snippet: `customerFields` parameter for msCustomer field mapping
- msOrder snippet: Data source selection based on `ms3_customer_sync_enabled` setting
- msProducts snippet: `msOnProductsLoad` and `msOnProductPrepare` events for external packages
- msProducts snippet: `usePackages` parameter for bulk data loading (ms3Variants, msBrands)
- Help page: Migrated to Vue 3 + PrimeVue
- Resource to product conversion: Automatic msProductData creation on class_key change

### Changed
- All 41 services centralized in ServiceRegistry
- bootstrap.php now contains only core `ms3` service

### Fixed
- msOrder snippet: Removed dead POST validation code (validation in OrderSubmitHandler)
- msOrder template: Removed unused error CSS classes
- Vue components: Fixed `ms3.config` access (added `getMs3Config()` utility)
- XML schema and PHP models synchronization (stock, default_value, msGridField)
- Options tab styling fixes

### Removed
- Plupload library (gallery is Vue-based)
- Unused ExtJS files (product.grid.js, gallery/panel.js, model-fields.wrapper.js)
- Plupload lexicons
- Unused notification processors

## [1.3.0-beta1] - 2026-01-30

### Added
- Nested product tabs: Vue TabView inside ExtJS (Properties, Gallery, Categories, Links, Options)
- Plugin Registry API for third-party tabs: `window.MS3ProductTabsRegistry.register()`
- New extra field type "Dropdown" (xtype: `ms3-combo-select`) with static options
- Dynamic msOrderTotal widget update when cart changes
- New chunk `tpl.msOrderTotal` with default markup
- msProducts snippet: msCategoryMember support for additional categories
- msProducts snippet: own parents processing as workaround for pdoTools
- Tooltips for delivery/payment name fields (lexicon key support)
- VueTools added as automatic dependency

### Fixed
- Unified authentication in CustomerAddressController (API token + session customer_id)
- Migration for `select_options` column in `ms3_extra_fields` table

## [1.2.3-beta1] - 2026-01-24

### Added
- Automatic order cost recalculation when changing delivery or payment method
- Automatic delivery cost recalculation when cart changes (free delivery threshold support)
- Pagination for Vendors settings tab (previously limited to 20 records)
- Pagination for Product Links settings tab
- "Show nested" checkbox enabled by default in category products grid

### Fixed
- Order total cost now correctly includes delivery cost (`cost = cart_cost + delivery_cost`)
- Free delivery threshold calculation now uses cart cost passed to method
- Session token preserved during customer auto-registration (cart no longer disappears after page reload)
- Automatic token refresh on `ms3_err_token_invalid` error
- Order validation (delivery, payment) now runs before customer creation
- Payment method name translation in delivery edit dialog
- Validation rules dropdown z-index styling
- Form spacing in delivery and payment edit dialogs

## [1.2.2-beta1] - 2026-01-20

### Fixed
- Gallery image upload: fixed processor namespace escaping in JavaScript
- Clean installation: migrations now check table existence before creation
- Clean installation: all seed migrations use `sort_order` field (xPDO model compatibility)

## [1.2.1-beta1] - 2026-01-20

### Fixed
- MySQL 8.0 compatibility: renamed `rank` field to `sort_order` in `ms3_model_fields` and `ms3_model_field_sections` tables
- MySQL 8.0 compatibility: added backtick escaping for `rank` field in `modContext` system table
- Email verification letters not being sent (missing `modMail` import)
- Placeholder format in email lexicons (`{name}` → `[[+name]]`)
- Missing `minishop3:cart` lexicon loading in snippets

### Changed
- Orders grid: status now uses relation fields instead of direct JOIN
- Orders grid: added `order_status` (badge), `status_color`, `status_name` (relation) fields
- Orders grid: `delivery_name` and `payment_name` fields now use `relation` type

## [1.2.0-beta1] - 2026-01-16

### Added
- Order validation for delivery required fields (from `validation_rules`)
- Customer data validation (email/phone) when creating from order
- Customer creation during order finalization from address data
- Duplicate customer handling during order finalization
- Validation error lexicons for order fields (ru/en)
- OrderFinalizeService for order finalization logic

### Changed
- "Create Order" button renamed to "Create Draft Order"
- Custom routes can now override system routes
- Modernized thumbnail generation API
- Improved product selection UI in order management
- Moved inline styles to centralized CSS file

### Fixed
- Automatic token renewal instead of error on expiration
- Delivery required fields validation during order finalization
- Customer creation flow in admin order management
- Gallery upload/delete 500 error (incorrect getObject/getIterator usage, rank → position field)

## [1.1.0-beta1] - 2026-01-11

### Added
- ProductCardUI module for adaptive cart buttons in product cards
- Cart state display: "Add to cart" button or quantity controls (+/-)
- Automatic UI update when cart changes via `ms3:cart:updated` event
- New lexicons: `ms3_cart_add`, `ms3_cart_in_cart`
- Migration for adding ProductCardUI.js to frontend assets
- Tab descriptions for Utilities page tabs explaining each section purpose
- CSS class `.tab-description` for consistent tab description styling

### Changed
- Product card template (`ms3_products_row.tpl`) with dual-state cart forms
- Product page template (`product.tpl`) with cart state awareness
- Base template with `icon-plus` and `icon-minus` SVG icons
- Renamed "Расширение объектов" tab to "Свои поля" (Custom Fields)
- Renamed "Управление свойствами товара" to "Поля товара (админка)"
- Renamed "Конфигурация полей моделей" to "Поля форм (админка)"
- Improved layout: dropdown and button on same row in "Свои поля" and "Поля таблиц" tabs
- Removed duplicate titles from tab content (already shown in tab headers)

### Fixed
- CSS isolation for Dialog and ConfirmDialog components (appendTo="self")
- Product options (color, size) saving in msProductOption table
- Duplicate Delivery/Payment filters on Orders page
- Field visibility saving in product properties management
- Migration errors on clean installation
- Default value error for `created_at` field

## [1.0.0-beta1] - 2026-01-05

First beta release of MiniShop3 for MODX 3.

### Added
- Modern admin interface with Vue 3 + PrimeVue components
- Service-based architecture with dependency injection
- Flexible field configuration system for products, orders, customers
- Grid configuration system with customizable columns and actions
- API router with FastRoute for REST endpoints
- Customer management with addresses and order history
- Order management with status workflow and notifications
- Delivery and payment methods configuration
- Product gallery with drag-and-drop upload
- Extra fields system for extending entities
- Import functionality for products
- Notification system for order events
- Full lexicon support (Russian, English)

### Changed
- Complete rewrite for MODX 3.0+ compatibility
- PHP 8.1+ required
- Namespace changed to MiniShop3
- Database tables prefixed with ms3_
- ExtJS panels integrated with Vue components

### Dependencies
- Requires VueTools package for Vue infrastructure
- Uses PdoTools for template rendering

## [1.0.0-alpha] - 2025

- Initial alpha releases for testing and development.
