diff --git a/shop/CHANGELOG.md b/shop/CHANGELOG.md index 563445a..e0dc3a1 100644 --- a/shop/CHANGELOG.md +++ b/shop/CHANGELOG.md @@ -24,4 +24,14 @@ All notable changes to XShop follow [Keep a Changelog](https://keepachangelog.co - 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`. diff --git a/shop/docs/ARCHITECTURE.md b/shop/docs/ARCHITECTURE.md index 67f944b..a5945b4 100644 --- a/shop/docs/ARCHITECTURE.md +++ b/shop/docs/ARCHITECTURE.md @@ -15,33 +15,24 @@ Two installable artifacts: theme (`xshop`) + plugin (`xshop-core`). Theme never ## 2. Theme – `xshop-theme` -### 2.1 File map (M2) +### 2.1 File map (M3) ``` xshop-theme/ - style.css - functions.php # thin loader - screenshot.png / rtl.css - header.php / footer.php / index.php / front-page.php / single.php / page.php / archive.php / search.php / 404.php / sidebar.php / comments.php + style.css / functions.php / rtl.css / header.php / footer.php / index.php etc inc/ - setup/ # theme-support, menus, sidebars, body-classes, assets (woo.css global, documented) - helpers/ # sanitize, template (xshop_has_woocommerce etc) - customization/ # options - compatibility/ # gutenberg, elementor - woocommerce/ # wrappers, loop_start/end, toolbar, remove gallery, related args, product.js enqueue, cart quantity filter - performance/ # defer + setup/assets.php — tokens→base→layout→components→woo→utilities→style.css + theme.js + search.js (front-end only where data-xshop-search exists, documented) + woocommerce/setup.php — loop_start/end, toolbar, gallery remove, related 4/4, product.js conditional assets/ - css/ # tokens, base, layout, components, woo (shop/product/cart/account/checkout), utilities - js/ # theme.js + product.js (gallery thumbs, keyboard) + css/woo.css — + search dropdown (.xshop-search, _dropdown, _results, _item, responsive fixed @640) + js/search.js — debounce 250ms, AbortController, stale seq, keyboard, a11y template-parts/components/ - site-header/-footer, breadcrumbs, pagination, search-form, post-card, - product-card.php (WC_Product, badges, price, rating, hooks), product-grid.php, product-gallery.php (featured+thumbs, keyboard), mini-cart.php, empty-state, loading, section-heading, hero, promo-banner - woocommerce/ - content-product.php (3.6.0) — delegates to product-card - archive-product.php (3.4.0) — shop/category header + toolbar + grid + empty-state CTA - content-single-product.php (3.6.0) — grid gallery+summary, hooks preserved - single-product.php (1.6.4) — delegator to content-single-product - languages/ + ajax-search.php — reusable combobox (role=combobox, aria-expanded/controls, listbox, live status) + + previous M2 components + woocommerce/ — 4 overrides as M2 +xshop-core/ + includes/REST/Search.php — xshop/v1/search endpoint + includes/Core/Plugin.php — loads REST/Search.php ``` ### 2.2 Bootstrap @@ -116,9 +107,10 @@ Tokens: `tokens.css` unchanged — adds WC tokens `--xshop-wc-badge-sale/--xshop - Localized `xshopData` on `xshop` handle. No jQuery for theme code; Woo's jQuery remains for variations/add-to-cart. - Build: `tools/build-release.ps1` now includes top-level folder `xshop/` wrapper (fixed in M1.1). -## 7. AJAX / REST +## 7. AJAX / REST (M3: Search) -Prefer **WP REST API** for search/filter/Q&A (cacheable, nonce via `X-WP-Nonce`), fallback to `admin-ajax` for cart/mini-cart where WC expects it. All endpoints: sanitize → validate → capability check → nonce → escaped output. +- **Search**: `GET /wp-json/xshop/v1/search?search=&limit=` via `xshop-core/includes/REST/Search.php:1` (`register_rest_route xshop/v1/search`). Public read-only (`permission_callback __return_true`), sanitized `search` (trim/strip_tags/sanitize_text_field/max 100) + `limit` 1..20, min length 2 returns empty without DB, `WP_Query` s + `_sku` LIKE, `post_status publish`, `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 data, no N+1, no draft, no customer/order, `wc_get_product` only for returned IDs. +- Prefer REST for search/filter/Q&A, fallback `admin-ajax` for cart where WC expects. ## 8. Security Model diff --git a/shop/docs/PERFORMANCE.md b/shop/docs/PERFORMANCE.md index bfe0fcf..cbbfcf7 100644 --- a/shop/docs/PERFORMANCE.md +++ b/shop/docs/PERFORMANCE.md @@ -41,7 +41,19 @@ - `System Status` reports: PHP/WP/WC versions, active plugins, asset sizes, transient hit rate. - Lighthouse CI optional (not required for v1). -## 8. Anti-Patterns Forbidden +## 8. Search Performance (M3) + +- **Debounce**: 250ms in `assets/js/search.js:debounce` (200–300ms target). Prevents request on every keystroke. +- **Min length**: 2 chars (handler returns empty without query). Input `data-min-length`. +- **Bounded**: `limit` default 6, max 20, validated; handler over-fetches `limit*2` for dedup then slices to `limit`; max 40 IDs scanned. +- **Stale protection**: `AbortController` cancels previous fetch + monotonic `seq` counter ignores stale JSON; rapid `iph → iphone` cannot overwrite newer. +- **Dedupe**: identical query (`lastQuery`) reopens dropdown without fetch. +- **Query**: `WP_Query` `s` + `_sku` LIKE, `post_status publish`, `no_found_rows`, `fields ids`, no N+1, `wc_get_product` only for returned IDs (≤6). +- **Asset**: `search.js` enqueued only on front-end where `data-xshop-search` exists (header on all front-end pages, guarded via `initAll`); `woo.css` global intentional. +- **Caching**: server `Cache-Control: public, max-age=60`; no client cache beyond in-memory `lastQuery` (no stale invalidation issues). +- **Payload**: minimal 6 items × 6 fields (~2KB JSON). + +## 9. Anti-Patterns Forbidden - Global `wp_enqueue_script` without condition. - Querying all products to compute filters. diff --git a/shop/docs/RUNTIME-QA.md b/shop/docs/RUNTIME-QA.md index 16f2e25..75cf45a 100644 --- a/shop/docs/RUNTIME-QA.md +++ b/shop/docs/RUNTIME-QA.md @@ -165,3 +165,45 @@ Same XAMPP WP 7.1 / WC 11.1.0 / PHP 8.2.12, with additional products: ## Conclusion **M2 ACCEPTED** — Shop/category/grid/card/sorting/pagination/empty, product gallery + variable with real variations (price/stock reset/add-to-cart), cart quantity/coupon/totals/empty, mini-cart, account, checkout, RTL/LTR/dark/mobile, no XShop PHP/JS errors, ZIP installs via Expand-Archive. + +--- + +# M3 — AJAX Search (2026-09-13) + +## Endpoint + +- `GET /wp-json/xshop/v1/search?search=Test&limit=6` → `200 {"items":[{"id":23,"title":"Out of Stock Test...","url":"...","image":"...","price_html":"...","type":"simple","in_stock":false}],"total":1}` — verified via `Invoke-WebRequest`. +- **Persian**: `search=لپ` → product 22 sale `-20%`; `search=Restore` → 21; `SKU XSHOP-SKU-123` → 21. +- **Validation**: `search` >100 → 400, `limit` >20 → 400, `search` 1-char / empty / whitespace → `{"items":[],"total":0}` 200, `limit` default 6 enforced. +- **Security**: `sanitize_text_field` + `wp_strip_all_tags` + `mb_substr 100`, `absint` 1..20; only `publish` products; no `post_password`/customer/order; `price_html` from Woo trusted; no `TODO`/`console.log`. +- **Woo inactive**: deactivated Woo → `search=Hello` → 2 posts `type post` with `price_html ""` (fallback), re-activated Woo → product search restored. + +## UI + +- Header `data-xshop-search` markup present on `GET /` → `xshop-search__dropdown` + `search.js` enqueued (`