# Shop Sidebar Builder Todo

Status: Implemented through Phase 5. IDR/Rupiah migration remains deferred to `docs/shop-idr-currency-migration-todo.md`.

Purpose: make the `/shop` sidebar/filter area configurable from the Payload admin through the existing `ShopArchive` block, while keeping ecommerce plugin logic, product query behavior, URL parameters, SEO policy, cart, orders, and checkout untouched.

## Locked Decisions

- `/shop` remains the public storefront route.
- `Shop` Global remains the admin page-builder shell for `/shop`.
- `ShopArchive` remains the single controlled bridge between page builder content and storefront product listing.
- Sidebar configuration belongs inside `ShopArchive`, not as a free sibling block in the Shop Global layout.
- Product listing data source remains `queryShopProducts`.
- URL query contract must be preserved: `q`, `segment`, `category`, `attribute`, `usecase`, `size`, `sort`, `page`.
- Existing taxonomy data sources remain authoritative: `segments`, `categories`, `productAttributes`, `productUseCases`, `productSizes`.
- Stitch design is a structure and UX reference only, not a static data source.
- Search, filter, sort, active chips, pagination, and product grid must stay synchronized through the same `/shop` URL state.
- Current SEO policy for `/shop?...` query URLs must remain unchanged unless a separate SEO task explicitly changes it.
- Current `showSearch`, `showFilters`, and `showSort` fields must remain backward-compatible.
- `sidebarLayout` remains optional for backward compatibility, but it is the recommended primary control for new or intentionally migrated ShopArchive data.
- Styling polish is excluded from the first implementation slices; the first target is structural control and behavior stability.
- Search should become a configurable sidebar control so the final structure can move toward the Stitch sidebar composition.

## Reference Points
- Payload block schema source: `node_modules/payload/dist/fields/config/types.d.ts`
- Current Shop Global: `src/globals/Shop.ts`
- Current ShopArchive config: `src/blocks/ShopArchive/config.ts`
- Current ShopArchive component: `src/blocks/ShopArchive/Component.tsx`
- Current shop shell: `src/components/shop/ShopArchiveShell.tsx`
- Current hardcoded taxonomy filters: `src/components/layout/search/ShopFilters.tsx`
- Current taxonomy filter client behavior: `src/components/layout/search/TaxonomyFilterSection.client.tsx`
- Current search UI: `src/components/Search/index.tsx`
- Current sort UI: `src/components/layout/search/filter`
- Current mobile drawer: `src/components/shop/ShopMobileFilterDrawer.tsx`
- Current product results and active chips: `src/components/shop/ShopProductResults.tsx`
- Current shop params: `src/utilities/shopParams.ts`
- Current product query: `src/lib/shop/queryProducts.ts`
- Current shop route adapter: `src/app/(app)/shop/page.tsx`
- Current shop loading shell: `src/app/(app)/shop/loading.tsx`
- Current ShopArchive duplicate guard: `src/globals/shopLayoutValidation.ts`

## Current Project Facts

- `ShopArchive` exposes presentational controls: intro content, limit, container width, desktop sidebar position, spacing, show search, show filters, and show sort.
- `ShopArchive` now has an optional `sidebarLayout` field for structural sidebar controls.
- When `sidebarLayout` is configured, search renders as a sidebar control; when it is missing, the legacy top search fallback remains available.
- Filter groups can now render from configured sidebar controls while preserving the old all-groups `ShopFilters` fallback.
- Sort can render from configured `SortControl` or legacy `FilterList` fallback.
- Mobile filtering uses `ShopMobileFilterDrawer` and receives the same configured controls as desktop.
- Active filter chips currently live inside `ShopProductResults`, not inside the sidebar.
- `/shop` has a fallback virtual `ShopArchive` if the Shop Global layout omits one.
- The saved Shop Global layout is validated to prevent duplicate `ShopArchive` blocks.
- Nested blocks inside a block are valid by Payload field types because `Block.fields` accepts `Field[]`, and `Field` includes `BlocksField`.
- Existing and added tests cover shop taxonomy filtering, active chips, mobile drawer, query product behavior, seed defaults, layout validation, configured sidebar rendering, admin Shop Global loading, and shop SEO.

## No-Touch Zones

- Do not fork or patch `@payloadcms/plugin-ecommerce`.
- Do not replace `queryShopProducts`.
- Do not change cart, checkout, orders, transactions, payment methods, or customer auth.
- Do not change product detail routes.
- Do not rename taxonomy collection slugs.
- Do not remove or reinterpret existing query params.
- Do not make arbitrary `/shop?...` filter URLs indexable.
- Do not use fake static filter data from Stitch.
- Do not add price range filtering in the first sidebar-builder slice.
- Do not add filter result counts in the first sidebar-builder slice.
- Do not add staged "Tampilkan Hasil" behavior in the first sidebar-builder slice.

## Candidate Sidebar Controls

- `SearchControl`: optional shop search form.
- `ActiveFiltersControl`: deferred; active chips stay in the results area for the first sidebar-builder slice.
- `FilterGroupControl`: one configurable taxonomy source per group.
- `SortControl`: sort options from the existing `sorting` list.
- `DividerControl`: presentational separation only.
- `NoteControl`: small admin-authored helper text only.

## Safe Filter Source Registry

- `segment`: collection `segments`, query key `segment`, single-select behavior.
- `category`: collection `categories`, query key `category`, single-select behavior, segment-aware visibility.
- `attribute`: collection `productAttributes`, query key `attribute`, multi-select behavior.
- `usecase`: collection `productUseCases`, query key `usecase`, multi-select behavior.
- `size`: collection `productSizes`, query key `size`, multi-select behavior through variants.

Do not allow admin to pick arbitrary collections as filter sources in this phase. Every source must map to an existing query contract and known product or variant relationship path.

## Phase 1 Contract Decisions

- `sidebarLayout` should be optional so existing ShopArchive data remains valid.
- If `sidebarLayout` is missing, runtime should preserve current behavior from `showSearch`, `showFilters`, and `showSort`.
- If `sidebarLayout` exists, sidebar controls should come from `sidebarLayout`.
- `sidebarLayout` should not become required in schema/runtime; old data and fallback archive behavior must remain safe without migration pressure.
- Do not render both legacy top search and sidebar search at the same time.
- Search is a first-class sidebar control in the target structure.
- Active filter chips remain in `ShopProductResults` for the first implementation slice.
- `ActiveFiltersControl` is deferred until the structural sidebar is stable.
- Styling polish inspired by Stitch is deferred; first implementation should only create structural control.
- Sidebar validation should be minimal and structural: disallow duplicate search controls, duplicate sort controls, and duplicate filter group sources; allow repeated divider and note controls.

Default first-version sidebar structure:

- `SearchControl`
- `FilterGroupControl` with source `segment`
- `FilterGroupControl` with source `category`
- `FilterGroupControl` with source `attribute`
- `FilterGroupControl` with source `usecase`
- `FilterGroupControl` with source `size`
- `SortControl`

## Phase 0 - Preflight Audit

Goal: confirm the working state before any schema, renderer, or test edits.

Tasks:

- [x] Re-read the historical shop page-builder audit before cleanup. The implemented `/shop`
      page-builder state is now captured in this document and the source files listed below.
- [x] Re-check current `ShopArchive` config.
- [x] Re-check current `ShopArchiveShell`.
- [x] Re-check current `ShopFilters`.
- [x] Re-check current search, sort, mobile drawer, and active chip behavior.
- [x] Re-check current shop route adapter and fallback archive behavior.
- [x] Re-check existing tests that cover shop filtering.
- [x] Re-check `git status --short` immediately before implementation.
- [x] Re-read relevant Next local docs before route/page/layout work. No route/page/layout code was changed in the final admin-test continuation.
- [x] Confirm no-touch zones immediately before implementation.

Acceptance criteria:

- [x] TODO matches current project structure.
- [x] No implementation files were changed while preparing this TODO.
- [x] Known backward-compatibility constraints are documented.

## Phase 1 - Sidebar Contract Planning

Goal: define the sidebar configuration contract before coding.

Tasks:

- [x] Decide whether default sidebar layout should preserve current visual order exactly or move search into the aside. Resolved: target structure moves search into the sidebar; old top search is fallback only.
- [x] Decide whether `SearchControl` in sidebar should coexist with legacy top search. Resolved: search should be added to the sidebar structure; legacy top search must remain backward-compatible only as fallback while migration is in progress.
- [x] Decide whether active filter chips stay in `ShopProductResults` or get an optional sidebar control. Resolved: keep chips in results for the first slice.
- [x] Define `sidebarLayout` block names and field shapes. Resolved: use `searchControl`, `filterGroupControl`, `sortControl`, `dividerControl`, and `noteControl`.
- [x] Define default sidebar layout for new seed data. Resolved: search, segment, category, attribute, usecase, size, sort.
- [x] Define fallback behavior when old `ShopArchive` data has no `sidebarLayout`. Resolved: preserve current behavior from `showSearch`, `showFilters`, and `showSort`.
- [x] Define validation for invalid or duplicate sidebar controls if needed. Resolved: use block allowlist and source whitelist; reject duplicate search, duplicate sort, and duplicate filter source controls.
- [x] Define mobile drawer rendering behavior for each control type. Resolved: mobile drawer uses the same configured sidebar renderer.
- [x] Decide styling scope. Resolved: exclude Tailwind/Stitch polish from the first implementation; build the structural skeleton first.

Acceptance criteria:

- [x] No ambiguity remains between legacy `showSearch/showFilters/showSort` and new `sidebarLayout`.
- [x] Fallback archive in `/shop/page.tsx` can render safely without new fields.
- [x] Admin cannot create a sidebar that desynchronizes controls from product results because sidebar sources are restricted to the known query contract and duplicate search/sort/filter sources are rejected.
- [x] Sidebar structure supports search as a first-class control before visual polishing starts.

## Phase 2 - Schema Only

Goal: add sidebar configuration to the Payload admin without changing public `/shop` behavior.

Candidate files:

- `src/blocks/ShopArchive/config.ts`
- `src/payload-types.ts` after type generation
- `src/endpoints/seed/shop-page.ts` only if defaults are intentionally added in this phase
- `tests/int/shopSeedData.int.spec.ts` only if seed defaults are changed

Tasks:

- [x] Add optional `sidebarLayout` blocks field inside `ShopArchive`.
- [x] Keep existing fields for backward compatibility.
- [x] Use controlled block allowlist only.
- [x] Avoid arbitrary source selection.
- [x] Add default values only after Phase 1 decisions are locked. Resolved: default control field values were added, but no default `sidebarLayout` array was injected yet so public runtime fallback remains unchanged.
- [x] Generate Payload types.
- [x] Verify admin schema loads.
- [x] Do not change public renderer yet.

Acceptance criteria:

- [x] `ShopArchive` with sidebar configuration can be persisted through the Payload API and rendered publicly.
- [x] Existing Shop Global data without `sidebarLayout` remains valid.
- [x] Public `/shop` renderer was not changed in this phase.
- [x] Type generation passes.

Verification notes:

- Added nested `sidebarLayout` blocks to `ShopArchive`: `searchControl`, `filterGroupControl`, `sortControl`, `dividerControl`, and `noteControl`.
- Added minimal structural validation for duplicate search controls, duplicate sort controls, invalid filter sources, and duplicate filter group sources.
- Kept `showSearch`, `showFilters`, and `showSort` for backward-compatible fallback behavior.
- `pnpm run generate:types` passed.
- `pnpm exec tsc --noEmit --pretty false` passed.
- Runtime schema probe passed: `sidebarLayout` is a blocks field, block allowlist is correct, duplicate search control validation rejects, and default sidebar pattern validates.
- First `pnpm build` attempt timed out while still writing `.next` artifacts; a later retry initially saw Next's "Another next build process is already running" guard.
- Final `pnpm build` retry passed after the timed-out build stopped.
- Admin route verification passed: `pnpm exec playwright test tests/e2e/admin.e2e.spec.ts -g "loads globals and top-level admin edit routes" --config=playwright.config.ts --timeout=120000`.
- `/admin/globals/shop` renders the Shop Global editor, exposes the Content tab, and shows `Shop Archive` plus `Sidebar Layout`.

Rollback note:

- Remove `sidebarLayout` from `ShopArchive` if schema/admin behavior fails before renderer work starts.

## Phase 3 - Renderer Refactor

Goal: render sidebar controls from configuration while preserving current query behavior.

Candidate files:

- `src/blocks/ShopArchive/Component.tsx`
- `src/components/shop/ShopArchiveShell.tsx`
- `src/components/layout/search/ShopFilters.tsx`
- New local sidebar renderer component under `src/components/shop`
- `src/components/shop/ShopMobileFilterDrawer.tsx` if drawer labels/counts need control-aware updates

Tasks:

- [x] Add a sidebar renderer that receives configured controls.
- [x] Split `ShopFilters` into reusable taxonomy group pieces.
- [x] Preserve category segment-aware visibility.
- [x] Preserve multi-select behavior for attributes, use cases, and sizes.
- [x] Preserve single-select behavior for segment and category.
- [x] Preserve current sort URL behavior.
- [x] Preserve current search URL behavior.
- [x] Keep desktop and mobile controls synchronized.
- [x] Ensure missing `sidebarLayout` falls back to current behavior.
- [x] Keep `queryShopProducts` unchanged.
- [x] Keep `shopParams` unchanged.

Acceptance criteria:

- [x] `/shop` default behavior remains functionally equivalent.
- [x] Admin-configured control order affects sidebar order only.
- [x] Hidden controls do not break active URL states.
- [x] Mobile drawer still closes after URL changes.
- [x] No hydration regression.

Verification notes:

- Added `ShopSidebarControls` renderer for configured sidebar controls.
- `ShopArchiveShell` now uses configured `sidebarLayout` when present and legacy search/filter/sort fallback when absent.
- `Search` now accepts optional label and placeholder while preserving the previous default placeholder.
- `ShopFilters` can render a configured subset/order of taxonomy groups while preserving the previous all-groups default.
- `queryShopProducts` and `shopParams` were not changed.
- `pnpm exec tsc --noEmit --pretty false` passed.
- `pnpm exec vitest run tests/int/queryShopProducts.int.spec.ts tests/int/shopLayoutValidation.int.spec.ts --config ./vitest.config.mts --pool=threads` passed.
- `pnpm exec vitest run tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed.
- `pnpm build` passed.
- Targeted E2E passed: `pnpm exec playwright test tests/e2e/taxonomy.e2e.spec.ts -g "shop (filters respect segment visibility and preserve query params|shows result count and removable active filter chips|exposes mobile filter drawer without changing URL filter behavior)" --config=playwright.config.ts --timeout=120000`.
- In-app browser smoke on `/shop` passed for legacy fallback data: search input, filter drawer, Segment, Category, result count, and product links were present.
- Added configured-sidebar E2E coverage in `tests/e2e/shop-sidebar.e2e.spec.ts`.
- Configured-sidebar E2E temporarily snapshots the active `Shop` Global, writes a configured `sidebarLayout`, verifies desktop and mobile behavior, then restores the original global data.
- First configured-sidebar E2E run passed with one retry after an initial stale fallback read; test was stabilized with a unique `/shop?sidebarSmoke=...` URL.
- Stabilized configured-sidebar E2E passed without retry: `pnpm exec playwright test tests/e2e/shop-sidebar.e2e.spec.ts --config=playwright.config.ts --timeout=120000`.
- Restore probe confirmed no smoke placeholder remained in active Shop Global data after the E2E.
- Read-only restore probe found active Shop Global currently has one existing sidebar control: `filterGroupControl` source `segment` with title `hhhhh`; this was preserved and not modified by the test cleanup.
- Full Shop SEO E2E passed: `pnpm exec playwright test tests/e2e/shop-seo.e2e.spec.ts --config=playwright.config.ts --timeout=120000`.
- Updated the SEO navigation regression test so it no longer depends on a visible sort control, because sort is now admin-configurable and can be hidden.

Rollback note:

- Restore `ShopArchiveShell` hardcoded control rendering if configured renderer changes behavior unexpectedly.

## Phase 4 - Test Coverage

Goal: prove the configured sidebar did not regress shop behavior.

Candidate files:

- `tests/e2e/taxonomy.e2e.spec.ts`
- `tests/int/queryShopProducts.int.spec.ts`
- `tests/int/shopSeedData.int.spec.ts`
- New sidebar layout validation test if validation is added

Tasks:

- [x] Run type generation.
- [x] Run TypeScript check.
- [x] Run build.
- [x] Run `queryShopProducts` integration tests.
- [x] Run taxonomy e2e tests for segment/category visibility.
- [x] Run taxonomy e2e tests for active chips.
- [x] Run taxonomy e2e tests for mobile drawer.
- [x] Add test for configured sidebar order if practical.
- [x] Add test for fallback old archive data if practical.
- [x] Verify `/shop?...` SEO metadata remains unchanged.

Acceptance criteria:

- [x] Existing shop tests pass.
- [x] New sidebar behavior is covered where it can regress query state.
- [x] Public route, mobile drawer, and admin schema/API persistence are verified.

## Data Readiness Gate - 2026-06-07

Goal: confirm Stitch-inspired shop sidebar values are available as dynamic CMS data, not hardcoded UI strings.

Confirmed ready:

- `productAttributes` contains all expected base sidebar values: `berbunga`, `indoor`, `outdoor`, `tahan-panas`, `gantung`, `low-maintenance`, `air-purifier`, and `pet-friendly`.
- `productSizes.notes` is populated for all current size records:
- `bibit-biji`: `Fase awal, harga rendah`
- `ukuran-kecil`: `< 50 cm, cocok untuk pot meja`
- `ukuran-sedang`: `50-150 cm, cocok untuk sudut ruangan`
- `ukuran-besar`: `> 150 cm, statement plant`
- `borongan-proyek`: `Pembelian massal lansekap`
- Active `Shop` Global `ShopArchive.sidebarLayout` is configured with `searchControl`, `segment`, `category`, `attribute`, `usecase`, `size`, and `sortControl`.
- Core taxonomy fields are present on active products; the audit found no active products missing `segment` or `category`.
- Fresh seed source was updated so new seed runs keep the same taxonomy values, size notes, and Shop sidebar defaults.

Important nuance:

- Attribute records with zero assigned products are still valid prepared vocabulary, but selecting them can return empty results until real products are tagged by admin.
- Current zero-assignment attributes include `berbunga`, `outdoor`, `tahan-panas`, `gantung`, `air-purifier`, and `pet-friendly` in the active local database.
- Do not auto-assign these attributes to products without product-domain confirmation, because incorrect tagging would be worse than an empty filter.

Still not data-ready because model/query work is missing:

- IDR/Rupiah storefront currency: ecommerce config still uses the plugin's USD currency setup.

Now implemented in follow-up commerce readiness slices:

- Price range filtering: `minPrice` / `maxPrice` URL contract, effective price filtering, active chips, and `priceRangeControl`.
- Facet counts: count mode A from current final result set for segment/category/attribute/usecase/size.
- Product common name: optional `products.commonName`.
- Rating/review stars first slice: admin-managed `products.ratingSummary`.
- `Terlaris`/campaign badge first slice: manual `products.badge`.
- Popular sort first slice: manual `products.popularityScore` and `sort=popular`.

Follow-up TODO:

- Continue these seven commerce data/model gaps in `docs/shop-commerce-data-readiness-todo.md`.

Verification:

- Local Payload audit passed via `.codex-tmp/audit-shop-data-readiness.ts`.
- `/shop?seedSmoke=1` smoke confirmed `Cari tanaman impian Anda...`, `Atribut`, `Air Purifier`, `Pet Friendly`, `Use Case`, `Ukuran`, and `Urutkan` render in HTML.
- `pnpm exec tsc --noEmit --pretty false` passed after seed/data updates.
- `pnpm exec vitest run tests/int/shopSeedData.int.spec.ts tests/int/shopSidebarLayout.int.spec.ts --config ./vitest.config.mts --pool=threads` passed.

## Phase 5 - Optional Facet Expansion

Goal: support future admin-created filter group categories without opening arbitrary unsafe query sources.

Current findings:

- `productAttributes` currently has `title`, `description`, `sortOrder`, and `slug` only.
- `products.attributes[]` is the canonical many-to-many relationship to `productAttributes`.
- `queryShopProducts` resolves each selected `attribute` separately and intersects product IDs, so `attribute` uses AND logic.
- `ShopFilters` currently fetches all `productAttributes` and renders one Attribute filter group.
- Active chips and filter labels already read selected attribute IDs through the existing `attribute` query key.
- Therefore grouping must be presentation/admin metadata only. It must not change `products.attributes[]`, `shopParams`, active chips, label lookup, or `queryShopProducts` semantics.

Possible future approach:

- [x] Add `ProductAttributeGroups` collection.
- [x] Add optional `group` relationship to `ProductAttributes`.
- [x] Let `FilterGroupControl` select an attribute group.
- [x] Keep URL query key as `attribute`.
- [x] Keep product query behavior unchanged for grouped attributes.

Recommended Phase 5 slice order:

1. Schema-only metadata slice:

- [x] Add collection `productAttributeGroups` under the existing `Taxonomy` admin group.
- [x] Fields should mirror local taxonomy conventions: `title`, `description`, `sortOrder`, and `slug`.
- [x] Access should mirror `ProductAttributes`: public read, admin-only create/update/delete.
- [x] Add optional `group` relationship on `productAttributes`.
- [x] Do not update product query behavior in this slice.
- [x] Run type generation, TypeScript check, and targeted coverage.

2. Sidebar contract slice:

- [x] Extend `FilterGroupControl` only for `source = 'attribute'` with optional `attributeGroup`.
- [x] Keep `attributeGroup` ignored for every other source.
- [x] Update sidebar validation:
- [x] Non-attribute sources still cannot repeat.
- [x] Attribute can repeat only when each repeated Attribute control targets a distinct `attributeGroup`.
- [x] An ungrouped Attribute control should not coexist with grouped Attribute controls, because it would duplicate options and confuse admin intent.
- [x] Do not change URL query keys.

3. Renderer slice:

- [x] `ShopFilters` filters attribute options by `attributeGroup` when the control has one.
- [x] Ungrouped Attribute control continues to render all attributes.
- [x] Segment, category, use case, size, sort, search, mobile drawer, active chips, and product query remain unchanged.
- [x] Empty grouped attribute filters use the existing `emptyLabel`.

4. Seed/migration slice:

- [x] Safe default is no automatic migration and no required seed group.
- [x] Existing attributes remain valid with `group = null`.
- [ ] Optional seed can create a few groups only after the actual BMJ attribute taxonomy is confirmed.
- [ ] If groups are seeded, assign attributes by slug, never by display title.

Phase 5 verification notes:

- Added `productAttributeGroups` collection under the Taxonomy admin group.
- Added optional `productAttributes.group` relationship.
- Added optional `attributeGroup` to `ShopArchive.sidebarLayout[].filterGroupControl` only for Attribute controls.
- Sidebar validation now permits repeated Attribute controls only when each targets a distinct group, and rejects grouped + ungrouped Attribute mixes.
- `ShopFilters` now filters Attribute options by selected group while preserving the existing `attribute` URL query key and `queryShopProducts` semantics.
- `queryShopProducts`, `shopParams`, active chips, SEO policy, cart, orders, transactions, and ecommerce plugin internals were not changed.
- `pnpm run generate:types` passed.
- `pnpm exec tsc --noEmit --pretty false` passed.
- `pnpm exec vitest run tests/int/shopSidebarLayout.int.spec.ts tests/int/shopSeedData.int.spec.ts --config ./vitest.config.mts --pool=threads` passed.
- `pnpm exec playwright test tests/e2e/shop-sidebar.e2e.spec.ts --project=chromium --reporter=line --timeout=120000` passed after starting a fresh dev server.

Candidate files if Phase 5 is approved:

- `src/collections/ProductAttributeGroups.ts`
- `src/collections/ProductAttributes.ts`
- `src/payload.config.ts`
- `src/blocks/ShopArchive/config.ts`
- `src/components/layout/search/ShopFilters.tsx`
- `src/components/shop/ShopSidebarControls.tsx`
- `src/payload-types.ts` after type generation
- `src/endpoints/seed/index.ts` only if seed groups are explicitly approved
- `tests/int/shopSidebarLayout.int.spec.ts`
- `tests/e2e/shop-sidebar.e2e.spec.ts`
- `tests/e2e/admin.e2e.spec.ts`

Phase 5 no-touch constraints:

- Do not modify `queryShopProducts`.
- Do not modify `shopParams`.
- Do not modify active chip URL behavior.
- Do not modify `products.attributes[]` semantics.
- Do not create a new public query param such as `attributeGroup`.
- Do not make grouped facet URLs indexable.
- Do not seed arbitrary groups without confirming BMJ taxonomy vocabulary.

Do not start this phase until:

- [x] Sidebar layout renderer is stable for the structural first slice.
- [x] Current taxonomy filters pass verification.
- [x] User confirms the need for admin-created facet groups.
- [x] Migration or seed strategy is planned. Resolved: no automatic seed/migration for groups in first implementation; existing attributes remain ungrouped until admin assigns them.

## Open Decisions

- [x] Should sidebar search become the default, or should current top search remain default? Resolved: sidebar search is the target structure; current top search is fallback/backward compatibility only.
- [x] Should admin be allowed to show both top search and sidebar search during the transition? Resolved: no; configured sidebar search replaces top search, old top search is fallback only.
- [x] Should active filter chips remain in results, move to sidebar, or be configurable in both places? Resolved: keep active chips in results for the first slice.
- [x] Should sidebar controls have visual variants inspired by Stitch, or should visual styling wait for a later design phase? Resolved: visual styling waits; first implementation is structural skeleton only.
- [x] Should `sidebarLayout` be optional forever, or become the primary control after old data is migrated? Resolved: keep it optional indefinitely for backward compatibility, while treating it as the recommended primary control for new ShopArchive configurations.
- [x] Should attribute grouping be implemented now or deferred? Resolved: implemented as Phase 5 metadata + sidebar grouping without changing product query semantics.

## Execution Rule

Each phase must be executed separately. Before starting any implementation phase:

- [ ] Re-read this document.
- [ ] Re-check `git status --short`.
- [ ] Identify exact files to touch.
- [ ] Confirm no-touch zones.
- [ ] Define acceptance criteria for that slice.
- [ ] Run verification before moving to the next phase.
