# Shop Section Roadmap TODO

Date: 2026-06-20

Status: Active planning document. Phase 1 visual-only sidebar slice, configurable sidebar heading, and Stitch-aligned sidebar refinement completed on 2026-06-20. Use this as the execution checklist for future `/shop` section work.

Scope: evolve the public `/shop` section toward the current Stitch concept while preserving the Payload ecommerce plugin, product query contract, cart, checkout, orders, transactions, SEO safety, and current admin page-builder model.

## Locked Architecture

- `/shop` remains the public storefront route.
- `Shop` Global remains the admin configuration surface for the storefront page.
- `ShopArchive` remains the controlled bridge between page-builder content and ecommerce product listing.
- Sidebar controls must stay nested inside `ShopArchive`, not as free sibling blocks that can desync from product results.
- Product data must continue to flow through `queryShopProducts`.
- URL state must continue to flow through `shopParams` and the existing `/shop` query contract.
- Existing taxonomy sources remain authoritative: `segments`, `categories`, `productAttributes`, `productAttributeGroups`, `productUseCases`, and `productSizes`.
- Stitch design is a structural and visual reference only. It must not become a static data source.
- Styling polish can be implemented with Tailwind after skeleton and behavior are stable.

## Current Baseline

- [x] `/shop` is configurable through the `Shop` Global.
- [x] `ShopArchive` exists as the page-builder block for product listing.
- [x] `ShopArchive.sidebarLayout` exists and supports search, taxonomy filter groups, price range, sort, divider, and note controls.
- [x] Sidebar search can render inside the aside.
- [x] Segment, category, attribute, use case, size, price range, and sort use the existing URL/query contract.
- [x] Attribute grouping is metadata-only and does not change `products.attributes[]` semantics.
- [x] Active filter chips remain in `ShopProductResults`.
- [x] Mobile filter drawer uses the same configured controls as desktop.
- [x] Current sidebar render was smoke-tested after the latest aside structure work.
- [x] Visual-only sidebar polish has been applied without changing ecommerce query logic.
- [x] Sidebar heading is configurable from the `ShopArchive` block.

## No-Touch Zones

- Do not fork or patch `@payloadcms/plugin-ecommerce`.
- Do not rewrite `queryShopProducts` unless a phase explicitly requires query behavior work.
- Do not rewrite `shopParams` unless a phase explicitly requires URL contract work.
- Do not change cart, checkout, orders, transactions, payment methods, or customer auth.
- Do not rename existing taxonomy collection slugs.
- Do not use fake static values from Stitch.
- Do not make faceted `/shop?...` URLs indexable without a dedicated SEO plan.
- Do not combine `noindex` and canonical policy changes casually. SEO policy changes require a separate research and verification slice.
- Do not move active filters into the sidebar until the optional control contract is planned.

## Phase 0 - Preflight Before Every Slice

Goal: prevent accidental broad changes.

Tasks:

- [ ] Re-read this document.
- [ ] Re-read `docs/shop-sidebar-builder-todo.md` for historical sidebar constraints.
- [ ] Re-check `git status --short`.
- [ ] Identify exact files to touch before editing.
- [ ] Confirm the slice does not touch ecommerce plugin internals.
- [ ] Confirm whether Next.js route/page/layout docs are needed before implementation.
- [ ] Define verification commands before editing.

Acceptance criteria:

- [ ] Scope is clear.
- [ ] Files to touch are listed.
- [ ] No-touch zones are acknowledged.

## Phase 1 - Sidebar Aside Structure Hardening

Goal: make the sidebar structure stable and close to the Stitch composition without changing filter logic.

Current status:

- [x] Desktop sidebar renders as an `aside`.
- [x] Sidebar card has a compact `Katalog` eyebrow and `Filter Produk` heading inside the control stack.
- [x] Search is inside the sidebar.
- [x] Filter groups render as grouped controls.
- [x] Sort renders as chip-style options.
- [x] Size notes can render as item descriptions.
- [x] Mobile drawer keeps the same controls and active count behavior.
- [x] Sidebar shell, search, taxonomy options, price range, sort chips, note/divider, and mobile drawer have one visual language using the botanical/emerald design direction.
- [x] Visual slice did not change `queryShopProducts`, `shopParams`, ecommerce plugin internals, cart, checkout, orders, or transactions.
- [x] Desktop sidebar width regression was fixed by removing the `w-full flex-none` desktop conflict and using fixed responsive sidebar width plus `min-w-0` on the results area.
- [x] Desktop sidebar width was reduced after Stitch comparison so the product result area has a more balanced share of the row.
- [x] Sidebar eyebrow and title are now optional presentational fields on `ShopArchive`.
- [x] Existing data remains safe because renderer fallback still uses `Katalog` and `Filter Produk`.
- [x] Sidebar visual CSS was hardened with explicit botanical color utilities after runtime output showed the earlier `emerald-*`/opacity classes were not visually close enough to the Stitch aside.
- [x] Taxonomy filter controls were compacted toward the Stitch sidebar composition: category as a lean list, segment/attribute/use case as chips, and size as a denser detail list.

Remaining tasks:

- [x] Decide whether sidebar eyebrow and title should be hardcoded or admin-configurable. Resolved: configurable from `ShopArchive`.
- [x] If configurable, add fields in `ShopArchive` only, not in a separate global.
- [x] Add admin labels/descriptions explaining that sidebar heading fields are presentational only.
- [x] Add or update tests for configurable sidebar heading if implemented.

Acceptance criteria:

- [x] Sidebar structure stays synchronized with product results.
- [x] Legacy fallback remains valid when `sidebarLayout` is empty.
- [x] Search, filters, sort, and price controls still update the same URL params.

Verification notes:

- `pnpm exec prettier --write src\components\shop\ShopArchiveShell.tsx src\components\shop\ShopSidebarControls.tsx src\components\shop\ShopMobileFilterDrawer.tsx src\components\Search\index.tsx src\components\layout\search\TaxonomyFilterSection.client.tsx src\components\layout\search\PriceRangeControl.client.tsx src\components\layout\search\filter\FilterItem.tsx src\components\layout\search\filter\index.tsx` passed.
- `pnpm exec tsc --noEmit --pretty false` passed.
- `pnpm exec vitest run tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed.
- Browser smoke on `/shop` confirmed the sidebar aside renders, search/filter groups are present, console errors are empty, and sidebar search updates `q` after client hydration.
- Browser layout measurement after the CSS fix confirmed the sidebar no longer takes full desktop width and product results keep a healthy content width.
- `pnpm run generate:types` passed after adding `sidebarEyebrow` and `sidebarTitle`.
- `pnpm exec tsc --noEmit --pretty false` passed after heading fields and type generation.
- `pnpm exec vitest run tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 4 tests.
- Browser smoke after heading fields confirmed fallback `Katalog` and `Filter Produk` still render and console errors remain empty.
- `pnpm exec tsc --noEmit --pretty false` passed after sidebar CSS hardening.
- `pnpm exec vitest run tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 5 tests after sidebar CSS hardening.
- HTTP smoke on `/shop` returned 200 and confirmed the updated sidebar markup still renders dynamic taxonomy data.
- Runtime CSS bundle smoke confirmed the explicit sidebar palette values are compiled into the served stylesheet.
- Browser layout measurement after sidebar width tuning confirmed desktop sidebar width is now about 272px instead of 320px, while product results gain width and still render 4 cards per row at `xl`.
- Browser layout measurement after compact filter styling confirmed the sidebar visual height dropped substantially while preserving dynamic taxonomy rendering and 4 cards per row at `xl`.
- [x] Stitch-aligned sidebar refinement moved the heavy decorative header out of the shell, reduced desktop aside width to about 264px, and kept search-first ordering before the filter heading.
- [x] Active `Shop` Global data was updated idempotently so the configured sidebar includes `Rentang Harga` between `Ukuran` and `Urutkan`; no full seed was run.
- [x] `pnpm exec tsc --noEmit --pretty false` passed after the Stitch-aligned sidebar refinement.
- [x] `pnpm exec vitest run tests/int/shopSidebarLayout.int.spec.ts tests/int/shopSeedData.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 6 tests.
- [x] `pnpm exec playwright test tests/e2e/shop-sidebar.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 2 tests.
- [x] Browser smoke on `/shop` confirmed sidebar labels `Segmen`, `Kategori`, `Atribut`, `Use Case`, `Ukuran`, `Rentang Harga`, and `Urutkan`, aside width about 264px, and no console errors.

Candidate files:

- `src/blocks/ShopArchive/config.ts`
- `src/components/shop/ShopArchiveShell.tsx`
- `tests/int/shopSidebarLayout.int.spec.ts`
- `tests/e2e/shop-sidebar.e2e.spec.ts`

## Phase 2 - Admin Flexibility For Sidebar Controls

Goal: let administrators adjust the sidebar more flexibly without creating invalid ecommerce states.

Current status:

- [x] `FilterGroupControl.showCounts` can hide facet count badges without changing facet calculation or filtering.
- [x] `FilterGroupControl.displayStyle` can control only the presentation of a known filter source.
- [x] `displayStyle = auto` preserves the source-specific default UI for old and existing ShopArchive data.
- [x] `FilterGroupControl.maxVisibleItems` can collapse long filter groups without changing URL/filter semantics.
- [x] `FilterGroupControl.zeroCountBehavior` can keep zero-count options visible, disabled, or hidden as a presentational choice.
- [x] No arbitrary collection/source selection was introduced.

Tasks:

- [x] Add optional `showCounts` to `FilterGroupControl`.
- [x] Keep `showCounts` presentational only. It hides count badges in the sidebar but does not change facet calculation or product filtering.
- [x] Add optional display settings per filter group only if needed.
- [x] Candidate setting: `displayStyle` with safe values such as `list`, `chips`, or `compact`. Resolved: controlled allowlist `auto`, `list`, `chips`, `softChips`, `segmentGrid`, and `detailList`.
- [x] Candidate setting: `showCounts` if count visibility should be admin-controlled.
- [x] Candidate setting: `hideZeroCountOptions` only if product discovery behavior is agreed. Resolved as safer `zeroCountBehavior = visible | disabled | hidden`, default `visible`.
- [x] Candidate setting: `initiallyCollapsed` only after accessibility behavior is planned. Resolved as `maxVisibleItems`; active options remain visible and the toggle uses `aria-expanded`.
- [x] Keep all settings presentational unless a phase explicitly changes query behavior.
- [x] Validate that repeated attribute controls remain safe when using `attributeGroup`.

Acceptance criteria:

- [x] Admin can customize presentation without choosing arbitrary collections.
- [x] Every filter source still maps to a known query key.
- [x] Invalid combinations are rejected by `validateSidebarLayout`.

Verification notes:

- `pnpm run generate:types` passed after adding `FilterGroupControl.displayStyle`.
- `pnpm exec tsc --noEmit --pretty false` passed after wiring `displayStyle` through schema and renderer.
- `pnpm exec vitest run tests/int/shopSidebarLayout.int.spec.ts tests/int/shopSeedData.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 7 tests.
- `pnpm exec playwright test tests/e2e/shop-sidebar.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 2 tests using configured `displayStyle` values in the Shop Global fixture.
- `pnpm run generate:types` passed after adding `ActiveFiltersControl`, `maxVisibleItems`, show more/less labels, and `zeroCountBehavior`.
- `pnpm exec tsc --noEmit --pretty false` passed after the active filters, show-more, and zero-count slices.
- `pnpm exec vitest run tests/int/shopSidebarLayout.int.spec.ts tests/int/shopSeedData.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 9 tests.
- `pnpm exec playwright test tests/e2e/shop-sidebar.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 2 tests covering active filters, show-more, and disabled zero-count category behavior.

Candidate files:

- `src/blocks/ShopArchive/config.ts`
- `src/components/layout/search/ShopFilters.tsx`
- `src/components/layout/search/TaxonomyFilterSection.client.tsx`
- `src/components/shop/ShopSidebarControls.tsx`
- `src/payload-types.ts`

Verification notes:

- `pnpm exec prettier --write src\blocks\ShopArchive\config.ts src\components\layout\search\ShopFilters.tsx src\components\shop\ShopSidebarControls.tsx tests\int\shopSidebarLayout.int.spec.ts` passed.
- `pnpm run generate:types` passed after adding `showCounts`.
- `pnpm exec tsc --noEmit --pretty false` passed.
- `pnpm exec vitest run tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 5 tests.

## Phase 3 - Active Filters And Reset UX

Goal: improve current filter state visibility without desyncing URL state.

Current decision:

- Active chips stay in results by default. Sidebar active chips are available as an optional `ActiveFiltersControl` inside `ShopArchive.sidebarLayout`.

Tasks:

- [x] Evaluate whether `ActiveFiltersControl` is needed inside sidebar. Resolved: implemented as optional sidebar control.
- [x] If added, keep it optional and nested inside `ShopArchive.sidebarLayout`.
- [x] Reuse existing active-chip label resolution from `ShopProductResults` or extract a shared helper. Resolved: `shopActiveFilters.ts`.
- [x] Add a safe `Reset Semua` control that links to `/shop` with allowed persistent params only if needed. Resolved: optional control uses `getShopClearFiltersHref`.
- [ ] Do not add staged "Tampilkan Hasil" behavior yet because current logic is immediate URL update.

Acceptance criteria:

- [x] Active chips and product results always describe the same URL state.
- [x] Reset behavior does not preserve stale filter params.
- [x] Mobile and desktop behavior remain equivalent.

Verification notes:

- `ShopProductResults` and optional sidebar `ShopActiveFiltersControl` now use the same helper for chip labels, remove links, and clear URL.
- `pnpm run generate:types` passed after adding `activeFiltersControl`.
- `pnpm exec tsc --noEmit --pretty false` passed after active filters wiring.
- `pnpm exec vitest run tests/int/shopSidebarLayout.int.spec.ts tests/int/shopSeedData.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 7 tests immediately after active filters wiring.
- `pnpm exec playwright test tests/e2e/shop-sidebar.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 2 tests using the optional active filters sidebar control in desktop and mobile.

Candidate files:

- `src/components/shop/ShopProductResults.tsx`
- `src/components/shop/ShopSidebarControls.tsx`
- `src/lib/shop/filterLabels.ts`
- `src/utilities/shopParams.ts` only if a shared helper is truly required.

## Phase 4 - Shop Results Visual Skeleton

Goal: align the product results area structurally with the Stitch shop design without changing product data semantics.

Current status:

- [x] Product result grid density uses the shared shop grid class so rendered results and loading skeleton stay synchronized.
- [x] Four-column density starts at `xl`, not `lg`, because desktop `lg` still shares horizontal space with the sidebar.
- [x] Product query, page size, URL params, currency display, cart/order/checkout, and ecommerce plugin internals were not changed.
- [x] Product cards keep the existing generic `ProductGridItem` path but now use a denser Stitch-aligned card rhythm.
- [x] Product card CTA is no longer a nested `button` inside a `Link`; the whole card remains the detail link.
- [x] Result count, active chips, empty state, and server-side load more use the same botanical shell styling as the sidebar/card visual system.

Tasks:

- [x] Audit current `ProductGridItem`, `ProductItem`, `ProductPrice`, and `ShopProductResults`.
- [x] Decide whether shop listing should keep generic product cards or introduce a shop-specific card wrapper. Resolved: keep `ProductGridItem` for this slice.
- [x] Preserve product links, price formatting, currency selector plumbing through `ProductPrice`, and image rendering.
- [x] Align first-pass grid card rhythm with Stitch density by using `xl:grid-cols-4` while keeping `lg:grid-cols-3`.
- [x] Compact product card rhythm by reducing typography weight, padding, title height, badge scale, and CTA height.
- [x] Align result count, active chip placement, empty state, and load more controls.
- [x] Do not add new product fields during this phase unless the field already exists. Resolved: this visual/result-area phase reused existing product fields only.

Acceptance criteria:

- [x] Product grid still renders the same product result set.
- [x] Cards keep the same detail links and do not introduce fake add-to-cart behavior.
- [x] Multi-currency public selector still affects displayed prices correctly.
- [x] No hydration or console errors in the `/shop` browser smoke for this slice.

Candidate files:

- `src/components/shop/ShopProductResults.tsx`
- `src/components/ProductGridItem/index.tsx`
- `src/components/ProductItem/index.tsx`
- `src/components/ProductPrice.tsx`
- `src/components/Grid/tile.tsx`
- `tests/e2e/shop-sidebar.e2e.spec.ts`

Verification notes:

- `pnpm exec prettier --write src\components\shop\shopProductGridClasses.ts src\components\shop\ShopProductResults.tsx src\app\(app)\shop\loading.tsx tests\int\shopProductGridLayout.int.spec.ts docs\shop-section-roadmap-todo.md` passed.
- `pnpm exec tsc --noEmit --pretty false` passed.
- `pnpm exec vitest run tests/int/shopProductGridLayout.int.spec.ts tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed.
- HTTP smoke on `/shop` confirmed the served HTML now contains `xl:grid-cols-4` and no longer contains the old `lg:grid-cols-3 gap-6` hardcoded result-grid string.
- Stitch MCP screen `projects/1612038081325697724/screens/c34933e4d28241c2b6a0a2408b471a7e` was re-read before compacting cards.
- `pnpm exec prettier --write src\components\ProductGridItem\index.tsx tests\int\shopProductGridLayout.int.spec.ts` passed.
- `pnpm exec tsc --noEmit --pretty false` passed after compact card markup.
- `pnpm exec vitest run tests/int/shopProductGridLayout.int.spec.ts tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 7 tests.
- Browser runtime measurement on `/shop` at 1365px confirmed 4 cards per row, first card height reduced from about 469px to about 406px, no nested card button, and console errors were empty.
- Result header, active-chip shell, empty state, and the old pagination shell were styled in `ShopProductResults` without changing existing copy used by E2E selectors (`Clear all filters`, `Previous`, `Next`).
- `pnpm exec tsc --noEmit --pretty false` passed after result-area styling.
- `pnpm exec vitest run tests/int/shopProductGridLayout.int.spec.ts tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 8 tests after result-area styling.
- 2026-07-20: Temporary Playwright currency smoke passed: `/shop` defaults to IDR, selecting USD updates the URL to `currency=USD` and shows USD pricing, selecting IDR returns to clean `/shop`, and console errors stayed empty.
- 2026-07-20: Corrected the result navigation from classic `Previous`/`Next` pagination to cumulative server-side `Load More`. `/shop` renders the first batch, `/shop?page=2` renders the first two batches, and the URL state remains deterministic for SEO/filter/sort/currency behavior.
- 2026-07-20: `pnpm run generate:types` passed after changing the Shop Archive admin label from `Products Per Page` to `Products Per Batch`.
- 2026-07-20: `pnpm exec tsc --noEmit --pretty false` passed after the Load More change.
- 2026-07-20: `pnpm exec vitest run tests/int/queryShopProducts.int.spec.ts tests/int/shopProductGridLayout.int.spec.ts tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 30 tests.
- 2026-07-20: `pnpm exec playwright test tests/e2e/shop-sidebar.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 2 tests.
- 2026-07-20: `pnpm exec playwright test tests/e2e/shop-seo.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 4 tests.
- 2026-07-20: Temporary Playwright Load More smoke passed: `/shop` shows `Load More`, clicking it navigates to `page=2`, increases rendered product cards cumulatively, and records no runtime errors.
- 2026-07-21: Reduced shop product over-fetch by lowering the main product query to `depth: 1` and removing duplicate `variants` selection because variants are hydrated separately for price/size behavior.
- 2026-07-21: After cleaning hung local measurement processes, raw HTTP checks returned `/shop` in about 2.0-2.7s and `/shop?page=2` in about 2.1-2.2s on the local dev server.
- 2026-07-21: `pnpm exec tsc --noEmit --pretty false` passed after the query over-fetch reduction.
- 2026-07-21: `pnpm exec vitest run tests/int/queryShopProducts.int.spec.ts tests/int/shopProductGridLayout.int.spec.ts tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 30 tests.
- 2026-07-21: `pnpm exec playwright test tests/e2e/shop-sidebar.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 2 tests.
- 2026-07-21: `pnpm exec playwright test tests/e2e/shop-seo.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 4 tests.

## Phase 5 - Shop Hero And Surrounding Page Builder Content

Goal: decide which Stitch shop sections belong outside `ShopArchive` as normal page-builder content.

Current status:

- [x] `/shop` does not need a dedicated hero block in this phase; it reuses the existing shared `hero` field from the Shop Global.
- [x] Surrounding page-builder content uses the existing reusable blocks: CTA, Content, Media Block, and Banner.
- [x] Product listing remains isolated inside one controlled `ShopArchive` block.

Tasks:

- [x] Confirm whether `/shop` needs a dedicated hero block or can reuse existing hero/layout blocks. Resolved: reuse existing shared hero/layout blocks.
- [x] Keep header and footer out of this scope because they are global.
- [x] Keep product listing inside exactly one `ShopArchive` block.
- [ ] Add presentational blocks above or below `ShopArchive` only if they are not tied to filter state.
- [x] Validate Shop Global duplicate guard still prevents multiple `ShopArchive` blocks.

Acceptance criteria:

- [x] Admin can compose intro/support content around the listing.
- [x] Product listing remains a single controlled bridge.
- [x] No block can desync sidebar from results.

Candidate files:

- `src/globals/Shop.ts`
- `src/globals/shopLayoutValidation.ts`
- `src/blocks/RenderBlocks.tsx`
- Existing reusable layout blocks, only if approved.

Verification notes:

- `tests/int/shopLayoutValidation.int.spec.ts` now asserts the Shop Global keeps `Hero`, `Content`, and `SEO` tabs, uses only the approved layout block allowlist, and attaches `validateShopLayoutArchiveCount` to the layout field.

## Phase 6 - SEO And Faceted Navigation Guardrails

Goal: keep search engine behavior safe while shop filters evolve.

Current status:

- [x] Malformed taxonomy query params are ignored before they reach Payload relationship filters, preventing `NaN` relationship queries on public `/shop` URLs.
- [x] Clean `/shop` is indexable and self-canonical.
- [x] `/shop` query URLs are `noindex, follow` without a canonical tag, avoiding the `noindex + rel=canonical` conflict.

Tasks:

- [x] Re-audit `/shop` metadata behavior for clean URL and query URLs.
- [x] Confirm current policy for `/shop` without params.
- [x] Confirm current policy for `/shop?q=...`, `/shop?category=...`, `/shop?page=...`, and combined filters.
- [x] Do not mix `noindex` and canonical changes without explicit reasoning and verification.
- [x] If policy changes are required, write a separate SEO TODO before coding. Resolved: no policy change required in this slice.
- [x] Harden malformed taxonomy params such as `segment=hias-retail` so crawler/manual URLs do not throw DB cast errors.

Acceptance criteria:

- [x] Clean `/shop` remains indexable if intended.
- [x] Faceted URLs do not create crawl/index bloat.
- [x] Policy is verified from rendered HTML, not assumed.
- [x] Malformed taxonomy params do not produce `Failed query` or console errors.

Candidate files:

- `src/app/(app)/shop/page.tsx`
- `src/app/(app)/shop/layout.tsx`
- `tests/e2e/shop-seo.e2e.spec.ts`

Verification notes:

- `pnpm exec prettier --write src\utilities\shopParams.ts tests\int\queryShopProducts.int.spec.ts src\components\shop\ShopProductResults.tsx tests\int\shopProductGridLayout.int.spec.ts` passed.
- `pnpm exec tsc --noEmit --pretty false` passed after taxonomy param hardening.
- `pnpm exec vitest run tests/int/queryShopProducts.int.spec.ts tests/int/shopProductGridLayout.int.spec.ts tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 25 tests.
- Browser smoke on `/shop?segment=hias-retail&page=2` confirmed no `Failed query`, no application error, 4 cards per row, and console errors were empty.
- Browser metadata audit confirmed `/shop` has one canonical `/shop` plus `index, follow`, while `/shop?q=hat&sort=price_asc&page=2`, `/shop?page=2`, `/shop?category=999`, and malformed query URLs have `noindex, follow` and no canonical tag.
- `pnpm exec playwright test tests/e2e/shop-seo.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 4 tests.

## Phase 7 - Data Readiness And Product Model Gaps

Goal: keep data/model work separate from sidebar layout work.

Current status:

- [x] Stitch-inspired sidebar taxonomy labels are dynamic CMS data, not static UI strings.
- [x] Product card metadata uses prepared product fields: `commonName`, `badge`, `ratingSummary`, and `ProductPrice`.
- [x] Price, currency, popularity, facet count, and product metadata readiness are tracked in dedicated commerce/currency TODO documents.
- [x] FX-rate automation remains deferred until provider, rate cadence, rounding, and approval workflow are decided.

Use these existing documents:

- `docs/shop-commerce-data-readiness-todo.md`
- `docs/shop-idr-currency-migration-todo.md`
- `docs/shop-multicurrency-public-selector-todo.md`
- `docs/shop-fx-rate-automation-debt-todo.md`

Tasks:

- [x] Do not add new visual dependency on data that is not populated.
- [x] For every Stitch-inspired label, confirm whether it is taxonomy data, product field data, derived data, or presentational copy.
- [x] Keep FX-rate automation as debt until provider and update cadence are decided.

Acceptance criteria:

- [x] No hardcoded fake product/taxonomy values.
- [x] Missing data produces graceful empty or hidden states.
- [x] Seed changes are deliberate and documented.

Verification notes:

- Data readiness re-audit confirmed product card display reads `badge`, `commonName`, `ratingSummary`, and `ProductPrice`, while optional fields stay hidden when absent.
- `tests/int/shopProductGridLayout.int.spec.ts` now asserts product card labels remain data-driven and do not hardcode Stitch sample values such as `Terlaris`, `Swiss Cheese`, or `Rp 125`.

## Phase 8 - Verification Matrix

Run the smallest relevant set for each slice.

Baseline commands:

- [x] `pnpm exec tsc --noEmit --pretty false`
- [x] `pnpm exec vitest run tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads`
- [x] `pnpm exec vitest run tests/int/queryShopProducts.int.spec.ts --config ./vitest.config.mts --pool=threads` if query-adjacent code changes.
- [x] `pnpm exec playwright test tests/e2e/shop-sidebar.e2e.spec.ts --project=chromium --reporter=line --timeout=120000` when UI/admin behavior changes.
- [x] `pnpm exec playwright test tests/e2e/shop-seo.e2e.spec.ts --project=chromium --reporter=line --timeout=120000` when metadata or URL policy changes.

Manual smoke checklist:

- [x] `/admin/globals/shop` loads.
- [x] Shop Archive block can be edited and saved.
- [x] `/shop` renders without console errors.
- [x] Sidebar appears on desktop.
- [x] Mobile drawer opens and shows the same configured controls.
- [x] Search updates `q`.
- [x] Segment/category/attribute/usecase/size filters update URL and results.
- [x] Price range updates `minPrice` and `maxPrice`.
- [x] Sort updates `sort`.
- [x] Active chips and clear behavior match URL state.
- [x] Product cards still link to product detail.

Verification notes:

- 2026-07-20: `pnpm exec tsc --noEmit --pretty false` passed.
- 2026-07-20: `pnpm exec vitest run tests/int/queryShopProducts.int.spec.ts tests/int/shopSidebarLayout.int.spec.ts tests/int/shopProductGridLayout.int.spec.ts tests/int/shopLayoutValidation.int.spec.ts tests/int/shopSeedData.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 34 tests.
- 2026-07-20: `pnpm exec playwright test tests/e2e/shop-sidebar.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 2 tests.
- 2026-07-20: `pnpm exec playwright test tests/e2e/shop-seo.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 4 tests.
- `pnpm exec vitest run tests/int/queryShopProducts.int.spec.ts tests/int/shopSidebarLayout.int.spec.ts tests/int/shopProductGridLayout.int.spec.ts tests/int/shopLayoutValidation.int.spec.ts tests/int/shopSeedData.int.spec.ts --config ./vitest.config.mts --pool=threads` passed with 31 tests.
- The first `shop-sidebar.e2e` run exposed a stale test selector: the mobile drawer trigger now uses the Indonesian label `Filter Produk`, not the old `Filters & sort` label.
- `tests/e2e/shop-sidebar.e2e.spec.ts` was updated to use the current accessible button name.
- `pnpm exec playwright test tests/e2e/shop-sidebar.e2e.spec.ts --config=playwright.config.ts --timeout=120000 --reporter=line` passed with 2 tests.

## Deferred Decisions

- [x] Whether sidebar title/eyebrow should be admin-configurable. Resolved: configurable on `ShopArchive`.
- [x] Whether active chips should become an optional sidebar control. Resolved: optional `activeFiltersControl`.
- [x] Whether filter groups should support collapse/show-more behavior. Resolved: `maxVisibleItems` with show more/less labels.
- [x] Whether zero-count options should be hidden, disabled, or visible. Resolved: per-group `zeroCountBehavior`.
- [x] Whether product result cards need a shop-specific component. Resolved for current slice: keep `ProductGridItem`.
- [x] Whether `/shop` needs a dedicated hero block or should use existing blocks. Resolved: reuse existing Shop Global hero/layout blocks.
- [ ] Whether FX-rate automation should use BI, a paid provider, or another source.

## Execution Rule

Do one phase at a time. Before implementation, list exact files, expected behavior, rollback path, and verification. Do not start the next phase until the current phase is verified.
