# Changelog All notable changes to XShop follow [Keep a Changelog](https://keepachangelog.com/) and [SemVer](https://semver.org/). ## [1.0.0] – Unreleased ### Added - Phase 0: product spec, architecture, feature matrix, roadmap, UI spec, database model, security, performance, testing, compatibility docs + ADRs. - **M1 — Theme Foundation**: - Templates: `header.php`, `footer.php`, `index.php`, `front-page.php` (section-based hero/category/product/promo/latest), `single.php`, `page.php`, `archive.php`, `search.php`, `404.php`, `sidebar.php`, `comments.php` (threaded, paginated, accessible). - Components: `template-parts/components/site-header.php`, `site-footer.php`, `breadcrumbs.php` (Woo-aware, filterable), `pagination.php` (RTL-safe), `search-form.php`, `post-card.php`, `empty-state.php`, `loading.php`, `section-heading.php`, `hero.php`, `promo-banner.php`. - Design system: finalized `assets/css/tokens.css` (full palette/typography/spacing/radius/shadow/z/transition + dark mode semantic swap), `base.css`, `layout.css`, `components.css`, `utilities.css` — logical properties, zero `left`/`right`. - JS: `assets/js/theme.js` extended with accessible mobile nav (aria-expanded, focus trap, Esc, click-outside) + dark-mode toggle. - PHP: `inc/helpers/template.php` (xshop_has_woocommerce, xshop_is_woocommerce_page, branding fallback), `inc/setup/body-classes.php`, `inc/setup/assets.php` (layered enqueue + Woo style dequeue), `inc/woocommerce/setup.php` (wrappers). - Docs: updated `docs/ARCHITECTURE.md`, `docs/UI-SPEC.md`, `docs/TESTING.md` for M1. - **M1.1 — Runtime QA**: - Local XAMPP WP 7.1 + WC 11.1.0 + PHP 8.2.12 — 29 tests all PASS, 2 bugs fixed (`.htaccess` + ZIP wrapper), `docs/RUNTIME-QA.md` + `.gitignore` update, `tools/build-release.ps1` fix. - **M2 — WooCommerce Storefront Foundation**: - `assets/css/woo.css` (shop toolbar, product grid 4/3/2/1, card badges/price/rating, single gallery/thumbs/tabs/related, cart responsive table, mini-cart, account 220|1fr, checkout col2-set — logical props, token-based, dark-mode via semantic tokens). - Components: `product-card.php` (WC_Product sale%/outofstock/featured/no-image/long titles, hook `xshop_product_card_after_actions`), `product-grid.php`, `product-gallery.php` (keyboard Arrow/Home/End, `is-active`), `mini-cart.php` (count/items/subtotal/remove/empty). - Woo integration: `inc/woocommerce/setup.php` (loop_start/end `xshop-products`, toolbar, remove gallery, related 4/4, `product.js` conditional on `is_product()`), `inc/setup/assets.php` (woo.css global intentional, documented). - Templates: `woocommerce/content-product.php:3.6.0`, `archive-product.php:3.4.0`, `content-single-product.php:3.6.0`, `single-product.php:1.6.4` (all minimal, versioned, hooks preserved). - JS: `assets/js/product.js` (gallery thumbs, 40 lines, vanilla). - Data: variable product 24 with 3 real variations (S 120k instock 10 / M 150→130k outofstock / L 180k) — attribute `Size` S/M/L, `data-product_variations` JSON, `reset_variations`. - Runtime: shop/category/empty, simple/sale/no-image/outofstock/variable, gallery, cart quantity/coupon/totals/empty, account, checkout, RTL/LTR, dark, mobile — 18 tests PASS, 2 bugs fixed (coming-soon + `global $product: Type` parse error), `debug.log` clean, ZIP rebuild + manual install PASS. - Docs: `ARCHITECTURE.md`, `UI-SPEC.md`, `TESTING.md`, `COMPATIBILITY.md`, `RUNTIME-QA.md` updated. - **M3 — WooCommerce AJAX Product Search (REST-first)**: - Endpoint `GET /wp-json/xshop/v1/search` (`xshop-core/includes/REST/Search.php:1`, `Core/Plugin.php`) — public `__return_true`, sanitized `search` (strip_tags/sanitize_text_field/max 100) + `limit` 1..20, min 2 → empty, `WP_Query s` + `_sku` LIKE, `publish` only, `no_found_rows` `fields ids` deduped to limit default 6, response `{items:[id,title,url,image,price_html,type,in_stock],total}` + `Cache-Control: public, max-age=60`, no private/draft/customer/order, SKU + title/content search, HPOS-safe. - UI: `template-parts/components/ajax-search.php:1` reusable combobox (`role=combobox` `aria-expanded/controls/autocomplete` `listbox` `aria-live` `data-*`, view-all `?s=&post_type=product`, hidden `post_type` when Woo active, RTL via logical, dark via tokens). - JS: `assets/js/search.js:1` vanilla debounce 250ms, `AbortController`, stale `seq`, dedupe `lastQuery`, `fetch`, loading/empty/error/view-all, keyboard `ArrowUp/Down/Enter/Escape`, `click outside`, `aria-activedescendant`, no `console.log`. - Style: `assets/css/woo.css:1` dropdown `absolute→fixed@640` `max 60vh` `token` search item grid `48px 1fr` + price/stock. - Asset: `inc/setup/assets.php:1` enqueue `search.js` only where `data-xshop-search` (header front-end, init guard), versioned. - Security: strict sanitization/validation, bounded limit/length, no private data, escaped output, abuse note in `SECURITY.md` (rate limit via proxy). - Performance: debounce, bounded, stale cancel, dedupe, `Cache-Control`, minimal payload, documented in `PERFORMANCE.md`. - Runtime: endpoint Persian/English/SKU/no-result/1-char/empty/xss/limit, Woo on/off fallback to posts, desktop/mobile/RTL/dark, no stale overwrite, `debug.log` clean. - Docs: `ARCHITECTURE.md:7` `UI-SPEC.md:3,8` `SECURITY.md:11` `PERFORMANCE.md:8` `TESTING.md:M3` `RUNTIME-QA.md:M3`. - **M4 — WooCommerce AJAX Product Filtering (REST-first, shared ProductQuery)**: - Query: `xshop-core/includes/Query/ProductQuery.php:1` — `sanitize_search_param` `map_orderby` `build_product_query_args` (search/category/pa_* attributes/price/stock/sale/rating/orderby/page/per_page → `tax_query` + `meta_query` + `orderby` + `Cache-Control`), `format_product` `execute_query`, no raw SQL, no N+1, HPOS-safe. - Endpoint `GET /wp-json/xshop/v1/products` (`REST/Products.php:1`, `Core/Plugin.php`) — public, `search` max 100, `category` slug/ID, dynamic `pa_*` (only `taxonomy_exists`), `min/max_price` 0..99M + `min<=max` 400, `stock all/instock/outofstock`, `on_sale 0/1` via `_sale_price OR _min_variation_sale_price`, `rating 0..5`, `orderby` allow-list, `page 1..100` `per_page 1..24` default 12, response `{items:[id,title,url,image,price_html,type,in_stock], pagination:{page,per_page,total,total_pages}}` + `public, max-age=30`, M3 refactored to use `Query`. - UI: `template-parts/components/product-filters.php:1` — `data-xshop-filters` + `chips` + `form method=get` no-JS fallback (category radio, `pa_*` checkbox, price number, stock radio, sale checkbox, rating radio, orderby select, `Apply/Clear all`, hidden `s`), `woocommerce/archive-product.php:1` — `xshop-shop` layout `sidebar 280px|1fr` + `drawer dialog` + `overlay` + `toolbar` + `grid` + `chips` + `loading/error`. - JS: `assets/js/filter.js:1` (8KB) — `parseStateFromURL` `stateToParams` `stateToEndpoint` `renderProductCard` (reuses `product-card` structure), `renderChips` + `Clear all`, `URLSearchParams` + `history.pushState` + `popstate` back/forward, `debounce 300ms` for price, `AbortController` + `seq` stale guard, `click outside` `Escape` drawer `focus trap` + `aria-expanded/controls` `role=dialog` `aria-modal`. - Style: `assets/css/woo.css:1` — `.xshop-shop__layout` responsive, `.xshop-chip`, `.xshop-filter` card, `.xshop-drawer` fixed, RTL via `inset-inline`, dark via tokens. - Asset: `inc/setup/assets.php:1` — `filter.js` front-end with DOM guard (`data-xshop-filters`), versioned, `woo.css` global intentional. - Server fallback: `inc/woocommerce/setup.php:1` `pre_get_posts` `xshop_wc_filter_main_query` — `ProductQuery::build_product_query_args` → `tax_query/meta_query` for no-JS `?stock=instock` etc. - Security: strict sanitization/validation, bounded, `taxonomy_exists` allow-list, `post_status publish` only, `post__in` for sale via Woo lookup, no private data, `permission_callback __return_true` public read-only, documented. - Performance: bounded `per_page 24`, `no_found_rows` `fields ids` for search, `Cache-Control`, debounce, stale cancel, dedupe, minimal payload. - Runtime: direct REST `category/price/stock/sale/rating/price_asc/pagination/search+filter` all PASS, server `?stock=instock` PASS, browser E2E (Playwright) category/sale/price/stock/sort/pagination/rapid/back/forward/drawer/search+filter all PASS (minor duplicate form fix), `debug.log` clean.