# PHASE 06: CATEGORY, LISTING, FILTERS AND SEARCH

Read `docs/03-UX-SPECIFICATION.md` section 2 and decision D-14.

## Scope

The listing experience, its filters and its indexation control.

## Build

1. `woocommerce/archive-product.php` and `content-product.php` overrides, following the mobile section order in the UX spec.
2. Two-column grid on mobile, three at 1024, four at 1200. The card component from phase 5, unchanged.
3. Load More at 24 products, not infinite scroll. The button uses the REST API, appends results and updates the URL with `history.pushState` so the position is shareable. The footer must stay reachable.
   **Real `/page/2/` URLs must still exist and work**, rendering the next 24 server side, because Load More produces nothing for a crawler to follow and nothing for QA test A4 to check. Load More is progressive enhancement over real pagination, not a replacement for it. The Load More route is one of the three deliberately public endpoints in `CLAUDE.md` section 6: named `permission_callback`, read-only, rate limited.
4. Sort: Popular, Newest, Price low to high, Price high to low. Preserves active filters.
5. Filters, MVP set only: Price (range), Category, Fabric, Size, Colour. **Pattern and Availability are deferred**, do not build them.
6. Filter UI: a drawer on mobile opened from the sticky bar, a persistent 240px left rail on desktop. Active filters render as chips above the grid, each individually clearable, plus Clear all.
7. Filters are server-rendered from `pa_` attribute terms with product counts, filtered by the current category. Build this ourselves, no filter plugin: WooCommerce product filter plugins are on the rejected list in technical architecture section 6, because our own filter over `pa_` attributes is cheaper and adds no joins.
8. `noindex, follow` plus a canonical to the clean category on any URL carrying a filter or sort parameter. **Do not also disallow these in `robots.txt`** (decision D-25): a disallowed URL is never fetched, so the crawler never sees the noindex, and anything already indexed stays indexed. Paginated pages self-canonicalise with a `| Page N` title suffix.
9. Zero-result state: a message plus Clear all, never a blank grid.
10. Category intro block above the grid and an SEO body block below it, both from the category description fields. Sibling category links at the bottom.
11. Search results template, using the same grid, with an empty state offering category links.
12. `posts_per_page` capped at 12 for the initial render.

## Acceptance criteria

- [ ] Filtering by fabric, size and colour together narrows correctly, chips appear, count updates
- [ ] The filtered URL is shareable and reproduces the identical result set
- [ ] Filter counts are accurate and scoped to the current category
- [ ] A zero-result combination shows the empty state with Clear all
- [ ] Load More appends without a full page reload and the URL updates
- [ ] `/page/2/` loads server side with the next 24 products, self-canonicalises, and carries the page title suffix
- [ ] Sorting preserves active filters, and filtering preserves the sort
- [ ] `curl -I` on a filtered URL shows `noindex, follow`, and view-source shows the canonical pointing at the clean category
- [ ] Page 2 self-canonicalises and its title carries the page suffix
- [ ] Out of stock products appear in the grid with the badge and remain indexable
- [ ] Search for a misspelling shows the empty state with category links, never a dead end
- [ ] Correct at all six widths, no horizontal scroll at 360
- [ ] JS budget still met

## Stop

No product detail page. Next phase.
