Problem
Filters only exist on /shop. Collection routes (/shop/<collectionHandle>) have none.
/shop (src/app/shop/page.tsx) is a hand-written theme-owned page: it parses category, activity and sort from searchParams, builds filterGroups, and renders FilterSidebar + ProductResults off storefront.listProducts(filter, sort).
/shop/[collectionHandle]/page.tsx renders nothing of its own — it hands everything to WeaversePage. It receives searchParams but forwards them untouched to loadWeaversePage() (used only for Weaverse preview/version resolution); no filter or sort is parsed. storefront.getCollectionProducts(handle) takes only a handle, so there is no seam to pass a filter through in the first place. collection-grid (src/sections/collection-grid/index.tsx) reads collectionProducts from context and maps the whole array — no toolbar, no facets, no pagination.
Root cause: the architecture treats collection grid behavior as theme-owned and not a section, but the collection route delegates 100% of its rendering to Weaverse. Nothing is left holding the filter state.
Reference: how Pilot does it
pilot/app/routes/collections/collection.tsx
- The loader owns filter state. It reads
sort via getSortValuesFromParam() and collects every searchParams entry prefixed with FILTER_URL_PREFIX into a ProductFilter[], then passes filters, sortKey and reverse straight into COLLECTION_QUERY as GraphQL variables. Filtering happens server-side in Shopify, not in app code.
- Facets come from Shopify:
collection.products.filters returns the available Filter[] per collection (including PRICE_RANGE). The loader resolves appliedFilters labels against those values.
- The page is a composable section tree:
main-collection -> mc--header, mc--toolbar, mc--content
mc--content -> mc--filters + mc--product-grid
- Filter interaction is client-side over the URL:
useSearchParams() + navigate(link, { preventScrollReset: true }) (filters/filter-item.tsx), so the URL is the source of truth and the loader re-runs.
- Pagination uses Hydrogen
<Pagination> with getPaginationVariables(request, { pageBy: 12 }), with infinite-scroll or load-more as a merchant setting.
Scope
Architecture decision needed
AGENTS.md currently states:
Functional, stateful, and security-owned surfaces are not sections and stay theme-owned: the collection and Shop grid behavior, Cart, and /account/**.
Pilot's model does the opposite — filters and the product grid are sections with merchant-facing settings (sidebar width, expand, swatches, load-more behavior). Adopting it requires amending that constraint in AGENTS.md, keeping the filter state in the URL and owned by the route while the sections stay presentational.
Files touched
src/app/shop/[collectionHandle]/page.tsx
src/lib/storefront/data-source.ts, src/lib/storefront/types.ts, src/lib/storefront/shopify/*
src/sections/collection-grid/ (replace or refactor)
- new
src/sections/main-collection/**
src/lib/weaverse/components.ts, src/lib/weaverse/data-context.ts
AGENTS.md
Problem
Filters only exist on
/shop. Collection routes (/shop/<collectionHandle>) have none./shop(src/app/shop/page.tsx) is a hand-written theme-owned page: it parsescategory,activityandsortfromsearchParams, buildsfilterGroups, and rendersFilterSidebar+ProductResultsoffstorefront.listProducts(filter, sort)./shop/[collectionHandle]/page.tsxrenders nothing of its own — it hands everything toWeaversePage. It receivessearchParamsbut forwards them untouched toloadWeaversePage()(used only for Weaverse preview/version resolution); no filter or sort is parsed.storefront.getCollectionProducts(handle)takes only a handle, so there is no seam to pass a filter through in the first place.collection-grid(src/sections/collection-grid/index.tsx) readscollectionProductsfrom context and maps the whole array — no toolbar, no facets, no pagination.Root cause: the architecture treats collection grid behavior as theme-owned and not a section, but the collection route delegates 100% of its rendering to Weaverse. Nothing is left holding the filter state.
Reference: how Pilot does it
pilot/app/routes/collections/collection.tsxsortviagetSortValuesFromParam()and collects everysearchParamsentry prefixed withFILTER_URL_PREFIXinto aProductFilter[], then passesfilters,sortKeyandreversestraight intoCOLLECTION_QUERYas GraphQL variables. Filtering happens server-side in Shopify, not in app code.collection.products.filtersreturns the availableFilter[]per collection (includingPRICE_RANGE). The loader resolvesappliedFilterslabels against those values.main-collection->mc--header,mc--toolbar,mc--contentmc--content->mc--filters+mc--product-griduseSearchParams()+navigate(link, { preventScrollReset: true })(filters/filter-item.tsx), so the URL is the source of truth and the loader re-runs.<Pagination>withgetPaginationVariables(request, { pageBy: 12 }), with infinite-scroll or load-more as a merchant setting.Scope
getCollectionProducts(handle, filter?, sort?)returning products plus the Shopify facets (filters) and applied-filter labels. Static adapter must return an equivalent shape.sortand the filter-prefixed params insrc/app/shop/[collectionHandle]/page.tsxand pass the resolved products, facets and applied filters throughdataContext.main-collectionshell withmc--header/mc--toolbar/mc--content, andmc--filters+mc--product-gridunder content. Register them insrc/lib/weaverse/components.ts.getCollectionProductscurrently returns the full array).collection-grid— replaced bymc--product-grid, or kept for non-filtered use.bun run check:graphqlafter touching anygql()document.Architecture decision needed
AGENTS.mdcurrently states:Pilot's model does the opposite — filters and the product grid are sections with merchant-facing settings (sidebar width, expand, swatches, load-more behavior). Adopting it requires amending that constraint in
AGENTS.md, keeping the filter state in the URL and owned by the route while the sections stay presentational.Files touched
src/app/shop/[collectionHandle]/page.tsxsrc/lib/storefront/data-source.ts,src/lib/storefront/types.ts,src/lib/storefront/shopify/*src/sections/collection-grid/(replace or refactor)src/sections/main-collection/**src/lib/weaverse/components.ts,src/lib/weaverse/data-context.tsAGENTS.md