Full Code of bigcommerce/catalyst for AI

canary 24cc310e16f8 cached
702 files
2.8 MB
771.8k tokens
1273 symbols
1 requests
Download .txt
Showing preview only (3,102K chars total). Download the full file or copy to clipboard to get everything.
Repository: bigcommerce/catalyst
Branch: canary
Commit: 24cc310e16f8
Files: 702
Total size: 2.8 MB

Directory structure:
gitextract_0h9khkyg/

├── .changeset/
│   ├── cold-foxes-lie.md
│   ├── config.json
│   ├── correlation-id-header.md
│   ├── fix-hidden-fields-d35665be.md
│   ├── fix-html-lang-locale.md
│   ├── translations-patch-d3abeec7.md
│   └── translations-patch-e3d3b994.md
├── .claude/
│   └── skills/
│       ├── release-catalyst/
│       │   └── SKILL.md
│       ├── release-catalyst-patch/
│       │   └── SKILL.md
│       └── sync-makeswift/
│           └── SKILL.md
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── config.yml
│   │   ├── 🐞📝-bug-report-makeswift.md
│   │   └── 🐞📝-bug-report.md
│   ├── dependabot.yml
│   ├── pull_request_template.md
│   ├── scripts/
│   │   ├── __tests__/
│   │   │   ├── audit-unlighthouse.test.mts
│   │   │   ├── bundle-size.test.mts
│   │   │   ├── compare-unlighthouse.test.mts
│   │   │   ├── post-bundle-comment.test.mts
│   │   │   ├── post-unlighthouse-commit-comment.test.mts
│   │   │   └── post-unlighthouse-pr-comment.test.mts
│   │   ├── audit-unlighthouse.mts
│   │   ├── bundle-size.mts
│   │   ├── compare-unlighthouse.mts
│   │   ├── post-bundle-comment.js
│   │   ├── post-unlighthouse-commit-comment.js
│   │   ├── post-unlighthouse-pr-comment.js
│   │   └── prevent-invalid-changesets.js
│   └── workflows/
│       ├── basic.yml
│       ├── bundle-size.yml
│       ├── changesets-release.yml
│       ├── deploy.yml
│       ├── e2e.yml
│       ├── native-hosting.yml
│       ├── prevent-invalid-changesets.yml
│       ├── regression-tests.yml
│       └── translations-changeset.yml
├── .gitignore
├── .nvmrc
├── .vscode/
│   ├── launch.example.json
│   └── settings.example.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── core/
│   ├── .eslintignore
│   ├── .eslintrc.cjs
│   ├── .gitignore
│   ├── AGENTS.md
│   ├── CHANGELOG.md
│   ├── README.md
│   ├── app/
│   │   ├── [locale]/
│   │   │   ├── (default)/
│   │   │   │   ├── (auth)/
│   │   │   │   │   ├── change-password/
│   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   └── change-password.ts
│   │   │   │   │   │   ├── page-data.ts
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   ├── layout.tsx
│   │   │   │   │   ├── login/
│   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   └── login.ts
│   │   │   │   │   │   ├── forgot-password/
│   │   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   │   └── reset-password.ts
│   │   │   │   │   │   │   └── page.tsx
│   │   │   │   │   │   ├── page.tsx
│   │   │   │   │   │   └── token/
│   │   │   │   │   │       └── [token]/
│   │   │   │   │   │           └── route.ts
│   │   │   │   │   ├── logout/
│   │   │   │   │   │   └── route.ts
│   │   │   │   │   └── register/
│   │   │   │   │       ├── _actions/
│   │   │   │   │       │   ├── prefixes.ts
│   │   │   │   │       │   └── register-customer.ts
│   │   │   │   │       ├── page-data.ts
│   │   │   │   │       └── page.tsx
│   │   │   │   ├── (faceted)/
│   │   │   │   │   ├── brand/
│   │   │   │   │   │   └── [slug]/
│   │   │   │   │   │       ├── page-data.ts
│   │   │   │   │   │       └── page.tsx
│   │   │   │   │   ├── category/
│   │   │   │   │   │   └── [slug]/
│   │   │   │   │   │       ├── _components/
│   │   │   │   │   │       │   └── category-viewed.tsx
│   │   │   │   │   │       ├── page-data.ts
│   │   │   │   │   │       └── page.tsx
│   │   │   │   │   ├── fetch-compare-products.ts
│   │   │   │   │   ├── fetch-faceted-search.ts
│   │   │   │   │   └── search/
│   │   │   │   │       ├── page-data.ts
│   │   │   │   │       └── page.tsx
│   │   │   │   ├── [...rest]/
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── _components/
│   │   │   │   │   └── slideshow/
│   │   │   │   │       └── index.tsx
│   │   │   │   ├── account/
│   │   │   │   │   ├── addresses/
│   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   ├── address-action.ts
│   │   │   │   │   │   │   ├── create-address.ts
│   │   │   │   │   │   │   ├── delete-address.ts
│   │   │   │   │   │   │   └── update-address.ts
│   │   │   │   │   │   ├── page-data.ts
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   ├── layout.tsx
│   │   │   │   │   ├── orders/
│   │   │   │   │   │   ├── [id]/
│   │   │   │   │   │   │   ├── page-data.tsx
│   │   │   │   │   │   │   └── page.tsx
│   │   │   │   │   │   ├── fragment.ts
│   │   │   │   │   │   ├── page-data.ts
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   ├── settings/
│   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   ├── change-password.ts
│   │   │   │   │   │   │   ├── update-customer.ts
│   │   │   │   │   │   │   └── update-newsletter-subscription.ts
│   │   │   │   │   │   ├── page-data.tsx
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   └── wishlists/
│   │   │   │   │       ├── [id]/
│   │   │   │   │       │   ├── _actions/
│   │   │   │   │       │   │   └── add-to-cart.tsx
│   │   │   │   │       │   ├── _components/
│   │   │   │   │       │   │   ├── visibility-switch.tsx
│   │   │   │   │       │   │   ├── wishlist-actions.tsx
│   │   │   │   │       │   │   └── wishlist-analytics-provider.tsx
│   │   │   │   │       │   ├── page-data.ts
│   │   │   │   │       │   └── page.tsx
│   │   │   │   │       ├── _actions/
│   │   │   │   │       │   ├── change-wishlist-visibility.ts
│   │   │   │   │       │   ├── delete-wishlist.ts
│   │   │   │   │       │   ├── mutation.ts
│   │   │   │   │       │   ├── new-wishlist.ts
│   │   │   │   │       │   ├── remove-wishlist-item.ts
│   │   │   │   │       │   ├── rename-wishlist.ts
│   │   │   │   │       │   └── schema.ts
│   │   │   │   │       ├── _components/
│   │   │   │   │       │   ├── new-wishlist-button.tsx
│   │   │   │   │       │   └── wishlist-actions-menu.tsx
│   │   │   │   │       ├── modals.tsx
│   │   │   │   │       ├── page-data.ts
│   │   │   │   │       └── page.tsx
│   │   │   │   ├── blog/
│   │   │   │   │   ├── [blogId]/
│   │   │   │   │   │   ├── page-data.ts
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   ├── page-data.ts
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── cart/
│   │   │   │   │   ├── _actions/
│   │   │   │   │   │   ├── add-shipping-cost.ts
│   │   │   │   │   │   ├── add-shipping-info.ts
│   │   │   │   │   │   ├── apply-coupon-code.ts
│   │   │   │   │   │   ├── apply-gift-certificate.ts
│   │   │   │   │   │   ├── remove-coupon-code.ts
│   │   │   │   │   │   ├── remove-gift-certificate.ts
│   │   │   │   │   │   ├── remove-item.ts
│   │   │   │   │   │   ├── update-coupon-code.ts
│   │   │   │   │   │   ├── update-gift-certificate.ts
│   │   │   │   │   │   ├── update-line-item.ts
│   │   │   │   │   │   ├── update-quantity.ts
│   │   │   │   │   │   └── update-shipping-info.ts
│   │   │   │   │   ├── _components/
│   │   │   │   │   │   ├── cart-analytics-provider.tsx
│   │   │   │   │   │   ├── cart-viewed.tsx
│   │   │   │   │   │   └── checkout-preconnect.tsx
│   │   │   │   │   ├── loading.tsx
│   │   │   │   │   ├── page-data.ts
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── checkout/
│   │   │   │   │   └── route.ts
│   │   │   │   ├── compare/
│   │   │   │   │   ├── _actions/
│   │   │   │   │   │   └── add-to-cart.tsx
│   │   │   │   │   ├── _components/
│   │   │   │   │   │   └── compare-analytics-provider.tsx
│   │   │   │   │   ├── page-data.ts
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── error.tsx
│   │   │   │   ├── gift-certificates/
│   │   │   │   │   ├── balance/
│   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   └── get-gift-certificate-by-code.ts
│   │   │   │   │   │   ├── fragment.ts
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   ├── page-data.ts
│   │   │   │   │   ├── page.tsx
│   │   │   │   │   └── purchase/
│   │   │   │   │       ├── _actions/
│   │   │   │   │       │   └── add-to-cart.tsx
│   │   │   │   │       ├── fragment.ts
│   │   │   │   │       ├── page-data.ts
│   │   │   │   │       └── page.tsx
│   │   │   │   ├── layout.tsx
│   │   │   │   ├── page-data.ts
│   │   │   │   ├── page.tsx
│   │   │   │   ├── product/
│   │   │   │   │   └── [slug]/
│   │   │   │   │       ├── _actions/
│   │   │   │   │       │   ├── add-to-cart.tsx
│   │   │   │   │       │   ├── get-more-images.ts
│   │   │   │   │       │   ├── submit-review.ts
│   │   │   │   │       │   └── wishlist-action.ts
│   │   │   │   │       ├── _components/
│   │   │   │   │       │   ├── product-analytics-provider.tsx
│   │   │   │   │       │   ├── product-review-schema/
│   │   │   │   │       │   │   ├── fragment.ts
│   │   │   │   │       │   │   └── product-review-schema.tsx
│   │   │   │   │       │   ├── product-schema/
│   │   │   │   │       │   │   ├── fragment.ts
│   │   │   │   │       │   │   └── index.tsx
│   │   │   │   │       │   ├── product-viewed/
│   │   │   │   │       │   │   ├── fragment.ts
│   │   │   │   │       │   │   └── index.tsx
│   │   │   │   │       │   ├── reviews.tsx
│   │   │   │   │       │   ├── search-params-router-refresh.tsx
│   │   │   │   │       │   └── wishlist-button/
│   │   │   │   │       │       ├── add-to-new-wishlist-modal.tsx
│   │   │   │   │       │       ├── dropdown.tsx
│   │   │   │   │       │       ├── form.tsx
│   │   │   │   │       │       └── index.tsx
│   │   │   │   │       ├── page-data.ts
│   │   │   │   │       └── page.tsx
│   │   │   │   ├── webpages/
│   │   │   │   │   └── [id]/
│   │   │   │   │       ├── _components/
│   │   │   │   │       │   └── web-page.tsx
│   │   │   │   │       ├── contact/
│   │   │   │   │       │   ├── _actions/
│   │   │   │   │       │   │   └── submit-contact-form.ts
│   │   │   │   │       │   ├── page-data.ts
│   │   │   │   │       │   └── page.tsx
│   │   │   │   │       ├── layout.tsx
│   │   │   │   │       └── normal/
│   │   │   │   │           ├── page-data.ts
│   │   │   │   │           └── page.tsx
│   │   │   │   └── wishlist/
│   │   │   │       └── [token]/
│   │   │   │           ├── page-data.ts
│   │   │   │           └── page.tsx
│   │   │   ├── error.tsx
│   │   │   ├── layout.tsx
│   │   │   ├── maintenance/
│   │   │   │   └── page.tsx
│   │   │   └── not-found.tsx
│   │   ├── admin/
│   │   │   └── route.ts
│   │   ├── api/
│   │   │   └── auth/
│   │   │       └── [...nextauth]/
│   │   │           └── route.ts
│   │   ├── fonts.ts
│   │   ├── layout.tsx
│   │   ├── not-found.tsx
│   │   ├── notifications.tsx
│   │   ├── providers.tsx
│   │   ├── robots.txt/
│   │   │   └── route.ts
│   │   ├── sitemap.xml/
│   │   │   └── route.ts
│   │   └── xmlsitemap.php/
│   │       └── route.ts
│   ├── auth/
│   │   ├── anonymous-session.ts
│   │   ├── customer-login-api.ts
│   │   ├── index.ts
│   │   └── types.ts
│   ├── build-config/
│   │   ├── reader.ts
│   │   ├── schema.ts
│   │   └── writer.ts
│   ├── channels.config.ts
│   ├── client/
│   │   ├── correlation-id.ts
│   │   ├── fragments/
│   │   │   ├── pagination.ts
│   │   │   └── pricing.ts
│   │   ├── graphql.ts
│   │   ├── index.ts
│   │   ├── revalidate-target.ts
│   │   ├── tags.ts
│   │   └── util/
│   │       └── index.ts
│   ├── components/
│   │   ├── analytics/
│   │   │   ├── events.tsx
│   │   │   ├── fragment.ts
│   │   │   └── provider.tsx
│   │   ├── breadcrumbs/
│   │   │   └── fragment.ts
│   │   ├── consent-manager/
│   │   │   ├── consent-manager-dialog.tsx
│   │   │   ├── consent-providers.tsx
│   │   │   ├── cookie-banner.tsx
│   │   │   ├── index.tsx
│   │   │   └── scripts-fragment.ts
│   │   ├── featured-products-carousel/
│   │   │   └── fragment.ts
│   │   ├── featured-products-list/
│   │   │   └── fragment.ts
│   │   ├── footer/
│   │   │   ├── fragment.ts
│   │   │   ├── index.tsx
│   │   │   └── payment-icons/
│   │   │       ├── amazon.tsx
│   │   │       ├── american-express.tsx
│   │   │       ├── apple-pay.tsx
│   │   │       ├── mastercard.tsx
│   │   │       ├── paypal.tsx
│   │   │       └── visa.tsx
│   │   ├── force-refresh/
│   │   │   └── index.tsx
│   │   ├── header/
│   │   │   ├── _actions/
│   │   │   │   ├── fragment.ts
│   │   │   │   ├── search.ts
│   │   │   │   └── switch-currency.ts
│   │   │   ├── fragment.ts
│   │   │   ├── index.tsx
│   │   │   └── schema.ts
│   │   ├── image/
│   │   │   └── index.tsx
│   │   ├── link/
│   │   │   └── index.tsx
│   │   ├── modal/
│   │   │   ├── index.tsx
│   │   │   └── modal-form-provider.tsx
│   │   ├── polyfills/
│   │   │   └── container-query/
│   │   │       └── index.tsx
│   │   ├── product-card/
│   │   │   └── fragment.ts
│   │   ├── product-variants-inventory/
│   │   │   └── fragment.ts
│   │   ├── store-logo/
│   │   │   └── fragment.ts
│   │   ├── subscribe/
│   │   │   ├── _actions/
│   │   │   │   └── subscribe.ts
│   │   │   └── index.tsx
│   │   └── wishlist/
│   │       ├── error.ts
│   │       ├── fragment.ts
│   │       ├── modals/
│   │       │   ├── change-visibility.tsx
│   │       │   ├── delete.tsx
│   │       │   ├── new.tsx
│   │       │   ├── rename.tsx
│   │       │   └── share.tsx
│   │       └── share-button.tsx
│   ├── data-transformers/
│   │   ├── breadcrumbs-transformer.ts
│   │   ├── facets-transformer.ts
│   │   ├── form-field-transformer/
│   │   │   ├── fragment.ts
│   │   │   ├── index.ts
│   │   │   └── utils.ts
│   │   ├── logo-transformer.ts
│   │   ├── order-details-transformer.ts
│   │   ├── orders-transformer.ts
│   │   ├── page-info-transformer.ts
│   │   ├── prices-transformer.ts
│   │   ├── product-card-transformer.ts
│   │   ├── product-options-transformer.ts
│   │   ├── scripts-transformer.ts
│   │   ├── search-results-transformer.ts
│   │   └── wishlists-transformer.ts
│   ├── global.ts
│   ├── globals.css
│   ├── i18n/
│   │   ├── locales.ts
│   │   ├── request.ts
│   │   ├── routing.ts
│   │   └── utils.ts
│   ├── instrumentation.ts
│   ├── lib/
│   │   ├── analytics/
│   │   │   ├── analytics.d.ts
│   │   │   ├── bigcommerce/
│   │   │   │   ├── data-events.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── providers/
│   │   │   │   └── google-analytics/
│   │   │   │       └── index.ts
│   │   │   ├── react/
│   │   │   │   └── index.tsx
│   │   │   └── types.ts
│   │   ├── cart/
│   │   │   ├── add-cart-line-item.ts
│   │   │   ├── create-cart.ts
│   │   │   ├── error.ts
│   │   │   ├── index.ts
│   │   │   └── validate-cart.ts
│   │   ├── cdn-image-loader.ts
│   │   ├── client-cookies.ts
│   │   ├── consent-manager/
│   │   │   ├── cookies/
│   │   │   │   ├── client.ts
│   │   │   │   ├── constants.ts
│   │   │   │   ├── parse-compact-format.ts
│   │   │   │   └── server.ts
│   │   │   └── schema.ts
│   │   ├── content-security-policy.ts
│   │   ├── currency.ts
│   │   ├── force-refresh.ts
│   │   ├── kv/
│   │   │   ├── adapters/
│   │   │   │   ├── memory.ts
│   │   │   │   ├── upstash.ts
│   │   │   │   └── vercel-runtime-cache.ts
│   │   │   ├── index.ts
│   │   │   ├── keys.ts
│   │   │   └── types.ts
│   │   ├── otel/
│   │   │   └── tracer.ts
│   │   ├── recaptcha/
│   │   │   └── constants.ts
│   │   ├── recaptcha.ts
│   │   ├── search.tsx
│   │   ├── seo/
│   │   │   └── canonical.ts
│   │   ├── server-toast.ts
│   │   ├── store-assets.ts
│   │   ├── user-agent.ts
│   │   └── utils.ts
│   ├── messages/
│   │   ├── da.json
│   │   ├── de.json
│   │   ├── en.json
│   │   ├── es-419.json
│   │   ├── es-AR.json
│   │   ├── es-CL.json
│   │   ├── es-CO.json
│   │   ├── es-LA.json
│   │   ├── es-MX.json
│   │   ├── es-PE.json
│   │   ├── es.json
│   │   ├── fr.json
│   │   ├── it.json
│   │   ├── ja.json
│   │   ├── nl.json
│   │   ├── no.json
│   │   ├── pl.json
│   │   ├── pt-BR.json
│   │   ├── pt.json
│   │   └── sv.json
│   ├── next.config.ts
│   ├── package.json
│   ├── playwright.config.ts
│   ├── postcss.config.js
│   ├── prettier.config.js
│   ├── proxies/
│   │   ├── compose-proxies.ts
│   │   ├── with-analytics-cookies.ts
│   │   ├── with-auth.ts
│   │   ├── with-channel-id.ts
│   │   ├── with-intl.ts
│   │   └── with-routes.ts
│   ├── proxy.ts
│   ├── scripts/
│   │   └── generate.cjs
│   ├── tailwind.config.js
│   ├── tests/
│   │   ├── README.md
│   │   ├── environment.ts
│   │   ├── fixtures/
│   │   │   ├── blog/
│   │   │   │   └── index.ts
│   │   │   ├── browser.ts
│   │   │   ├── catalog/
│   │   │   │   └── index.ts
│   │   │   ├── currency/
│   │   │   │   └── index.ts
│   │   │   ├── customer/
│   │   │   │   ├── index.ts
│   │   │   │   └── session.ts
│   │   │   ├── fixture.ts
│   │   │   ├── index.ts
│   │   │   ├── order/
│   │   │   │   └── index.ts
│   │   │   ├── page.ts
│   │   │   ├── promotion/
│   │   │   │   └── index.ts
│   │   │   ├── redirects/
│   │   │   │   └── index.ts
│   │   │   ├── settings/
│   │   │   │   └── index.ts
│   │   │   ├── subscribe/
│   │   │   │   └── index.ts
│   │   │   ├── utils/
│   │   │   │   └── api/
│   │   │   │       ├── blog/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── catalog/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── client.ts
│   │   │   │       ├── currencies/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── customers/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── errors/
│   │   │   │       │   ├── index.ts
│   │   │   │       │   └── response-error.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── orders/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── promotions/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── redirects/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── schema.ts
│   │   │   │       ├── settings/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── subscribe/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       └── webpages/
│   │   │   │           ├── http.ts
│   │   │   │           └── index.ts
│   │   │   └── webpage/
│   │   │       └── index.ts
│   │   ├── lib/
│   │   │   ├── formatter/
│   │   │   │   └── index.ts
│   │   │   └── i18n/
│   │   │       └── index.ts
│   │   ├── routes.ts
│   │   ├── tags.ts
│   │   ├── ui/
│   │   │   ├── components/
│   │   │   │   ├── accordion.spec.ts
│   │   │   │   ├── breadcrumbs.spec.ts
│   │   │   │   ├── carousel.spec.ts
│   │   │   │   ├── checkbox.spec.ts
│   │   │   │   ├── counter.spec.ts
│   │   │   │   ├── radio-group.spec.ts
│   │   │   │   ├── search.spec.ts
│   │   │   │   ├── select.spec.ts
│   │   │   │   ├── slideshow.spec.ts
│   │   │   │   └── swatch.spec.ts
│   │   │   └── e2e/
│   │   │       ├── account/
│   │   │       │   ├── account-settings.spec.ts
│   │   │       │   ├── account.spec.ts
│   │   │       │   ├── addresses.spec.ts
│   │   │       │   ├── order-details.spec.ts
│   │   │       │   ├── orders.spec.ts
│   │   │       │   ├── wishlist-details.spec.ts
│   │   │       │   ├── wishlists.mobile.spec.ts
│   │   │       │   └── wishlists.spec.ts
│   │   │       ├── analytics-session.spec.ts
│   │   │       ├── auth/
│   │   │       │   ├── anonymous-session.spec.ts
│   │   │       │   ├── forgot-password.spec.ts
│   │   │       │   ├── login.spec.ts
│   │   │       │   ├── logout.spec.ts
│   │   │       │   └── register.spec.ts
│   │   │       ├── blog.spec.ts
│   │   │       ├── cart.spec.ts
│   │   │       ├── checkout.spec.ts
│   │   │       ├── compare.spec.ts
│   │   │       ├── coupon.spec.ts
│   │   │       ├── facets.spec.ts
│   │   │       ├── home.spec.ts
│   │   │       ├── not-found.spec.ts
│   │   │       ├── product.spec.ts
│   │   │       ├── proxy/
│   │   │       │   └── redirects.spec.ts
│   │   │       ├── reviews.spec.ts
│   │   │       ├── search.spec.ts
│   │   │       ├── shipping.spec.ts
│   │   │       ├── subscribe.spec.ts
│   │   │       └── webpages.spec.ts
│   │   └── visual-regression/
│   │       └── components/
│   │           ├── accordion.spec.ts
│   │           ├── badge.spec.ts
│   │           ├── blog-post-card.spec.ts
│   │           ├── breadcrumbs.spec.ts
│   │           ├── button.spec.ts
│   │           ├── carousel.spec.ts
│   │           ├── checkbox.spec.ts
│   │           ├── counter.spec.ts
│   │           ├── datepicker.spec.ts
│   │           ├── footer.spec.ts
│   │           ├── form.spec.ts
│   │           ├── gallery.spec.ts
│   │           ├── header.spec.ts
│   │           ├── input.spec.ts
│   │           ├── label.spec.ts
│   │           ├── picklist.spec.ts
│   │           ├── radio-group.spec.ts
│   │           ├── rating.spec.ts
│   │           ├── rectangle-list.spec.ts
│   │           ├── select.spec.ts
│   │           ├── slideshow.spec.ts
│   │           ├── swatch.spec.ts
│   │           ├── tags.spec.ts
│   │           └── textarea.spec.ts
│   ├── tsconfig.json
│   ├── user-agent.ts
│   └── vibes/
│       └── soul/
│           ├── form/
│           │   ├── button-radio-group/
│           │   │   └── index.tsx
│           │   ├── card-radio-group/
│           │   │   └── index.tsx
│           │   ├── checkbox/
│           │   │   └── index.tsx
│           │   ├── checkbox-group/
│           │   │   └── index.tsx
│           │   ├── date-picker/
│           │   │   └── index.tsx
│           │   ├── dynamic-form/
│           │   │   ├── index.tsx
│           │   │   ├── schema.ts
│           │   │   └── utils.ts
│           │   ├── field-error/
│           │   │   └── index.tsx
│           │   ├── form-status/
│           │   │   └── index.tsx
│           │   ├── input/
│           │   │   └── index.tsx
│           │   ├── label/
│           │   │   └── index.tsx
│           │   ├── number-input/
│           │   │   └── index.tsx
│           │   ├── radio-group/
│           │   │   └── index.tsx
│           │   ├── range-input/
│           │   │   └── index.tsx
│           │   ├── rating-radio-group/
│           │   │   └── index.tsx
│           │   ├── select/
│           │   │   └── index.tsx
│           │   ├── swatch-radio-group/
│           │   │   └── index.tsx
│           │   ├── switch/
│           │   │   └── index.tsx
│           │   ├── textarea/
│           │   │   └── index.tsx
│           │   └── toggle-group/
│           │       └── index.tsx
│           ├── lib/
│           │   └── streamable.tsx
│           ├── primitives/
│           │   ├── accordion/
│           │   │   └── index.tsx
│           │   ├── alert/
│           │   │   └── index.tsx
│           │   ├── animated-underline/
│           │   │   └── index.tsx
│           │   ├── badge/
│           │   │   └── index.tsx
│           │   ├── banner/
│           │   │   └── index.tsx
│           │   ├── blog-post-card/
│           │   │   └── index.tsx
│           │   ├── button/
│           │   │   └── index.tsx
│           │   ├── button-link/
│           │   │   └── index.tsx
│           │   ├── calendar/
│           │   │   └── index.tsx
│           │   ├── carousel/
│           │   │   └── index.tsx
│           │   ├── chip/
│           │   │   └── index.tsx
│           │   ├── compare-card/
│           │   │   ├── add-to-cart-form.tsx
│           │   │   ├── index.tsx
│           │   │   └── schema.ts
│           │   ├── compare-drawer/
│           │   │   ├── index.tsx
│           │   │   └── loader.tsx
│           │   ├── cursor-pagination/
│           │   │   └── index.tsx
│           │   ├── dropdown-menu/
│           │   │   └── index.tsx
│           │   ├── favorite/
│           │   │   ├── heart.tsx
│           │   │   ├── index.tsx
│           │   │   └── styles.css
│           │   ├── gift-certificate-card/
│           │   │   ├── gift-certificate-card-logo.tsx
│           │   │   └── index.tsx
│           │   ├── inline-email-form/
│           │   │   ├── index.tsx
│           │   │   └── schema.ts
│           │   ├── logo/
│           │   │   └── index.tsx
│           │   ├── modal/
│           │   │   └── index.tsx
│           │   ├── navigation/
│           │   │   └── index.tsx
│           │   ├── price-label/
│           │   │   └── index.tsx
│           │   ├── product-card/
│           │   │   ├── compare.tsx
│           │   │   └── index.tsx
│           │   ├── rating/
│           │   │   └── index.tsx
│           │   ├── reveal/
│           │   │   └── index.tsx
│           │   ├── side-panel/
│           │   │   └── index.tsx
│           │   ├── skeleton/
│           │   │   └── index.tsx
│           │   ├── spinner/
│           │   │   └── index.tsx
│           │   ├── toaster/
│           │   │   └── index.tsx
│           │   ├── tooltip/
│           │   │   └── index.tsx
│           │   └── wishlist-item-card/
│           │       ├── index.tsx
│           │       ├── remove-wishlist-item.tsx
│           │       └── wishlist-item-add-to-cart.tsx
│           └── sections/
│               ├── account-settings/
│               │   ├── change-password-form.tsx
│               │   ├── index.tsx
│               │   ├── newsletter-subscription-form.tsx
│               │   ├── schema.ts
│               │   └── update-account-form.tsx
│               ├── address-list-section/
│               │   ├── index.tsx
│               │   └── schema.ts
│               ├── blog-post-content/
│               │   └── index.tsx
│               ├── blog-post-list/
│               │   └── index.tsx
│               ├── breadcrumbs/
│               │   └── index.tsx
│               ├── cart/
│               │   ├── client.tsx
│               │   ├── coupon-code-form/
│               │   │   ├── coupon-chip.tsx
│               │   │   └── index.tsx
│               │   ├── gift-certificate-code-form/
│               │   │   ├── gift-certificate-chip.tsx
│               │   │   └── index.tsx
│               │   ├── index.tsx
│               │   ├── schema.ts
│               │   └── shipping-form/
│               │       └── index.tsx
│               ├── compare-section/
│               │   └── index.tsx
│               ├── dynamic-form-section/
│               │   └── index.tsx
│               ├── error/
│               │   └── index.tsx
│               ├── featured-blog-post-list/
│               │   └── index.tsx
│               ├── featured-product-carousel/
│               │   └── index.tsx
│               ├── featured-product-list/
│               │   └── index.tsx
│               ├── footer/
│               │   └── index.tsx
│               ├── forgot-password-section/
│               │   ├── forgot-password-form.tsx
│               │   ├── index.tsx
│               │   └── schema.ts
│               ├── gift-certificate-balance-section/
│               │   ├── index.tsx
│               │   └── schema.ts
│               ├── gift-certificate-purchase-section/
│               │   └── index.tsx
│               ├── gift-certificates-section/
│               │   └── index.tsx
│               ├── header-section/
│               │   └── index.tsx
│               ├── maintenance/
│               │   └── index.tsx
│               ├── not-found/
│               │   └── index.tsx
│               ├── order-details-section/
│               │   └── index.tsx
│               ├── order-list/
│               │   └── index.tsx
│               ├── product-carousel/
│               │   └── index.tsx
│               ├── product-detail/
│               │   ├── actions/
│               │   │   └── revalidate-cart.ts
│               │   ├── index.tsx
│               │   ├── product-detail-form.tsx
│               │   ├── product-gallery.tsx
│               │   ├── rating-link.tsx
│               │   └── schema.ts
│               ├── product-list/
│               │   └── index.tsx
│               ├── products-list-section/
│               │   ├── filter-parsers.ts
│               │   ├── filters-panel.tsx
│               │   ├── index.tsx
│               │   └── sorting.tsx
│               ├── reset-password-section/
│               │   ├── index.tsx
│               │   ├── reset-password-form.tsx
│               │   └── schema.ts
│               ├── reviews/
│               │   ├── index.tsx
│               │   ├── review-form.tsx
│               │   └── schema.ts
│               ├── section-layout/
│               │   └── index.tsx
│               ├── sidebar-menu/
│               │   ├── index.tsx
│               │   ├── sidebar-menu-link.tsx
│               │   └── sidebar-menu-select.tsx
│               ├── sign-in-section/
│               │   ├── index.tsx
│               │   ├── schema.ts
│               │   └── sign-in-form.tsx
│               ├── slideshow/
│               │   └── index.tsx
│               ├── sticky-sidebar-layout/
│               │   └── index.tsx
│               ├── subscribe/
│               │   └── index.tsx
│               ├── wishlist-details/
│               │   └── index.tsx
│               ├── wishlist-list/
│               │   └── index.tsx
│               ├── wishlist-list-item/
│               │   └── index.tsx
│               └── wishlists-section/
│                   └── index.tsx
├── graphql.config.json
├── package.json
├── packages/
│   ├── catalyst/
│   │   ├── .eslintrc.cjs
│   │   ├── README.md
│   │   ├── commander.d.ts
│   │   ├── package.json
│   │   ├── prettier.config.cjs
│   │   ├── src/
│   │   │   └── cli/
│   │   │       ├── commands/
│   │   │       │   ├── build.spec.ts
│   │   │       │   ├── build.ts
│   │   │       │   ├── deploy.spec.ts
│   │   │       │   ├── deploy.ts
│   │   │       │   ├── dev.spec.ts
│   │   │       │   ├── dev.ts
│   │   │       │   ├── project.spec.ts
│   │   │       │   ├── project.ts
│   │   │       │   ├── start.spec.ts
│   │   │       │   ├── start.ts
│   │   │       │   ├── telemetry.ts
│   │   │       │   ├── version.spec.ts
│   │   │       │   └── version.ts
│   │   │       ├── hooks/
│   │   │       │   └── telemetry.ts
│   │   │       ├── index.spec.ts
│   │   │       ├── index.ts
│   │   │       ├── lib/
│   │   │       │   ├── get-module-cli-path.ts
│   │   │       │   ├── logger.ts
│   │   │       │   ├── mk-temp-dir.spec.ts
│   │   │       │   ├── mk-temp-dir.ts
│   │   │       │   ├── project-config.spec.ts
│   │   │       │   ├── project-config.ts
│   │   │       │   ├── project.ts
│   │   │       │   ├── telemetry.ts
│   │   │       │   ├── wrangler-config.spec.ts
│   │   │       │   └── wrangler-config.ts
│   │   │       └── program.ts
│   │   ├── templates/
│   │   │   └── open-next.config.ts
│   │   ├── tests/
│   │   │   └── mocks/
│   │   │       ├── handlers.ts
│   │   │       ├── node.ts
│   │   │       └── spinner.ts
│   │   ├── tsconfig.json
│   │   ├── tsup.config.ts
│   │   ├── vitest.config.ts
│   │   └── vitest.setup.ts
│   ├── client/
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── prettier.config.js
│   │   ├── src/
│   │   │   ├── api-error.ts
│   │   │   ├── client.ts
│   │   │   ├── gql-auth-error.ts
│   │   │   ├── gql-error.ts
│   │   │   ├── index.ts
│   │   │   ├── invalid-cat-error.ts
│   │   │   ├── lib/
│   │   │   │   └── error.ts
│   │   │   ├── missing-cat-error.ts
│   │   │   ├── types.ts
│   │   │   └── utils/
│   │   │       ├── getOperationName.ts
│   │   │       ├── normalizeQuery.ts
│   │   │       ├── removeEdgesAndNodes.ts
│   │   │       └── userAgent.ts
│   │   ├── tsconfig.json
│   │   └── tsup.config.ts
│   ├── create-catalyst/
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── bin/
│   │   │   ├── index.cjs
│   │   │   └── supported-node-versions.cjs
│   │   ├── jest.config.cjs
│   │   ├── package.json
│   │   ├── prettier.config.cjs
│   │   ├── src/
│   │   │   ├── commands/
│   │   │   │   ├── create.ts
│   │   │   │   ├── init.ts
│   │   │   │   ├── integration.ts
│   │   │   │   └── telemetry.ts
│   │   │   ├── hooks/
│   │   │   │   └── telemetry.ts
│   │   │   ├── index.ts
│   │   │   ├── prompts/
│   │   │   │   └── multi-select/
│   │   │   │       ├── helpers.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── multi-select.ts
│   │   │   │       └── types.ts
│   │   │   └── utils/
│   │   │       ├── auth.ts
│   │   │       ├── checkout-ref.ts
│   │   │       ├── cli-api.ts
│   │   │       ├── clone-catalyst.ts
│   │   │       ├── config.ts
│   │   │       ├── has-github-ssh.ts
│   │   │       ├── https.ts
│   │   │       ├── install-dependencies.ts
│   │   │       ├── is-exec-exception.ts
│   │   │       ├── localization.ts
│   │   │       ├── login.ts
│   │   │       ├── node-version.spec.ts
│   │   │       ├── parse.ts
│   │   │       ├── reset-branch-to-ref.ts
│   │   │       ├── spinner.spec.ts
│   │   │       ├── spinner.ts
│   │   │       ├── telemetry/
│   │   │       │   ├── index.ts
│   │   │       │   └── telemetry.ts
│   │   │       ├── user-agent.ts
│   │   │       └── write-env.ts
│   │   ├── tsconfig.json
│   │   └── tsup.config.ts
│   └── eslint-config-catalyst/
│       ├── CHANGELOG.md
│       ├── base.js
│       ├── next.js
│       ├── package.json
│       ├── prettier.js
│       └── react.js
├── pnpm-workspace.yaml
├── turbo.json
└── unlighthouse.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .changeset/cold-foxes-lie.md
================================================
---
"@bigcommerce/catalyst-core": patch
---

Fix cart summary Discounts row not showing manual discounts applied via the Management Checkout API


================================================
FILE: .changeset/config.json
================================================
{
  "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
  "changelog": [
    "@changesets/changelog-github",
    { "repo": "bigcommerce/catalyst" }
  ],
  "commit": false,
  "linked": [],
  "access": "public",
  "privatePackages": {
    "version": true,
    "tag": true
  },
  "baseBranch": "canary",
  "updateInternalDependencies": "patch",
  "ignore": ["@bigcommerce/catalyst"]
}


================================================
FILE: .changeset/correlation-id-header.md
================================================
---
"@bigcommerce/catalyst-core": patch
---

Add X-Correlation-ID header to all GraphQL requests. Each page render gets a stable UUID that persists across all fetches within the same render, enabling easier request tracing in server logs.


================================================
FILE: .changeset/fix-hidden-fields-d35665be.md
================================================
---
"@bigcommerce/catalyst-core": patch
---

Fix DynamicForm not rendering hidden field types, which caused `pageEntityId` to be `NaN` on contact form submission.


================================================
FILE: .changeset/fix-html-lang-locale.md
================================================
---
"@bigcommerce/catalyst-core": minor
---

Restore locale-aware `lang` attribute on the root `<html>` tag. The previous root layout hardcoded `lang="en"` for all locales; ownership of `<html>`/`<body>` now lives in `app/[locale]/layout.tsx` so `lang={locale}` reflects the active locale. The root `app/layout.tsx` is now a passthrough, and `app/not-found.tsx` is self-sufficient (renders its own `<html>`/`<body>`) to preserve the branded 404 for non-localized requests.


================================================
FILE: .changeset/translations-patch-d3abeec7.md
================================================
---
"@bigcommerce/catalyst-core": patch
---

Update translations.


================================================
FILE: .changeset/translations-patch-e3d3b994.md
================================================
---
"@bigcommerce/catalyst-core": patch
---

Update translations.


================================================
FILE: .claude/skills/release-catalyst/SKILL.md
================================================
---
name: release-catalyst
description: >
  Cut a new release of Catalyst (`@bigcommerce/catalyst-core` and `@bigcommerce/catalyst-makeswift`).
  Use when the user says "/release-catalyst", "cut a release", "release catalyst", or asks to
  publish new versions of the Catalyst packages. This skill orchestrates the full two-stage release
  process: merging the Version Packages PR on canary, syncing integrations/makeswift, and pushing
  @latest tags.
---

# Release Catalyst

Execute stages in order. Pause for user input where indicated.

## Stage 1: Cut release from `canary`

### 1a. Find and merge the Version Packages PR

```bash
gh pr list --search "Version Packages (canary)" --state open --json number,title,reviews,mergeable
```

- If **no open PR** exists, inform the user that there are no pending changesets on `canary` and stop.
- If the PR is **approved and checks are passing**, merge it: `gh pr merge <number> --squash`
- If the PR is **not approved or checks are not passing**, tell the user and wait.
  - Bot-opened PRs often don't trigger CI. If checks aren't running, push an empty commit to trigger them:
    ```bash
    git checkout --track origin/changeset-release/canary
    git commit --allow-empty -m "chore: trigger CI"
    git push origin changeset-release/canary
    git checkout canary && git branch -D changeset-release/canary
    ```
  - **Stop here.** Wait for the user to confirm checks pass and the PR is approved before merging.

### 1b. Verify the release

After the PR merges:

```bash
git fetch origin --tags
```

Determine the new `@bigcommerce/catalyst-core` version from the PR body (look for `## @bigcommerce/catalyst-core@X.Y.Z`). Then verify:

```bash
gh release view @bigcommerce/catalyst-core@<version> --json tagName,name,isDraft,isPrerelease
```

If the release and tag don't exist yet, wait briefly and retry — the Changesets action may still be running.

Record the **version number** and **bump type** (patch/minor/major) for use in Stage 2.

## Stage 2: Sync and release `integrations/makeswift`

### 2a. Sync branches

Invoke the `/sync-makeswift` skill, with one addition: during the sync (after merge, before pushing), also add a changeset for `@bigcommerce/catalyst-makeswift`:

**Determine bump type**: Match the bump type from Stage 1 (e.g., if core went `1.4.2` → `1.5.0`, that's a `minor`).

**Create changeset file** (`.changeset/sync-canary-<version>.md`, where `<version>` uses hyphens instead of dots — e.g., `1.6.0` → `sync-canary-1-6-0.md`). Changeset filenames only allow lowercase letters and hyphens; dots are invalid.

```markdown
---
"@bigcommerce/catalyst-makeswift": <patch|minor|major>
---

Pulls in changes from the `@bigcommerce/catalyst-core@<version>` release. For more information about what was included in the `@bigcommerce/catalyst-core@<version>` release, see the [changelog entry](https://github.com/bigcommerce/catalyst/blob/<canary-sha>/core/CHANGELOG.md#<version-anchor>).
```

Where:
- `<canary-sha>` is the merge commit SHA on canary (from the Version Packages merge)
- `<version-anchor>` is the version with dots removed (e.g., `1.5.0` → `150`)

Include this changeset in the merge commit (amend if needed) alongside the normal sync work.

### 2b. Merge the Version Packages (`integrations/makeswift`) PR

After the sync lands, the Changesets action will open a "Version Packages (`integrations/makeswift`)" PR.

```bash
gh pr list --search "Version Packages (integrations/makeswift)" --state open --json number,title
```

Same flow as Stage 1a:
- If checks aren't running (bot PR), push an empty commit to trigger CI, then **drop it before merging** by resetting to the parent and force-pushing.
- Once approved and green, merge with `gh pr merge <number> --squash`.
  - Note: squash merging is normally disallowed on `integrations/makeswift` to preserve merge bases for sync PRs. The user may need to temporarily enable squash merging in the branch protection rules for this step, then re-disable it after.

### 2c. Verify the makeswift release

```bash
git fetch origin --tags
gh release view @bigcommerce/catalyst-makeswift@<version> --json tagName,name,isDraft,isPrerelease
```

## Stage 3: Push `@latest` tags

Update both `@latest` tags to point to the new releases:

```bash
git fetch origin --tags
git tag @bigcommerce/catalyst-core@latest @bigcommerce/catalyst-core@<version> -f
git tag @bigcommerce/catalyst-makeswift@latest @bigcommerce/catalyst-makeswift@<version> -f
git push origin @bigcommerce/catalyst-core@latest -f
git push origin @bigcommerce/catalyst-makeswift@latest -f
```

Confirm both tags were pushed successfully.

## Stage 4: Cleanup

```bash
git checkout canary
git pull
```

Delete any leftover local branches (`changeset-release/*`, `sync-integrations-makeswift`, `integrations/makeswift`).

Report the final state: both package versions released, tags updated, branches cleaned up.


================================================
FILE: .claude/skills/release-catalyst-patch/SKILL.md
================================================
---
name: release-catalyst-patch
description: >
  Release a single Catalyst package patch in isolation, without bundling it with
  other queued changesets in the open Version Packages PR. Use when the user says
  "/release-catalyst-patch", "isolate a patch release", "publish only one package",
  or wants to ship a single package's changeset ahead of the normal release cadence.
  Performs the full flow locally (`changeset version`, build, `changeset publish`,
  push, GitHub release) so the remaining queued changesets stay untouched in the
  Version Packages PR.
---

# Release Catalyst Patch (single-package isolation)

The Changesets GitHub Action picks one mode per push to `canary`: if any unconsumed
changesets exist, it opens/refreshes the Version Packages PR and **does not** publish.
That's why we publish locally — we keep the other changesets in `.changeset/` so the
Version Packages PR still tracks them, but we ship just the one we want now.

Execute stages in order. Pause for user input where indicated. **Never execute the
`changeset publish` command yourself** — provide it to the user to run.

## Stage 0: Confirm scope

Identify the changeset to release and the target package + version.

```bash
ls .changeset/
git log --oneline -10                       # find the PR/commit that added the changeset
cat .changeset/<changeset-file>.md          # confirm the package + bump type
npm view <package> version dist-tags        # confirm current published state
```

Confirm with the user:
- Which `.changeset/*.md` file is being released
- The target package and the resulting version (e.g., `1.0.2` → `1.0.3`)
- That **all other** changesets in `.changeset/` should remain queued for the next regular release

If the package has notes in the [Package-specific notes](#package-specific-notes) section
below, review them before continuing.

## Stage 1: Branch + quarantine

Work on a release branch so the workflow doesn't trigger mid-flow.

```bash
git switch canary && git pull
git switch -c release/<package>-<new-version>
```

Move the **other** changesets **outside** the repo. A subdirectory inside `.changeset/`
gets parsed as a malformed changeset by the CLI and crashes `changeset version`:

```bash
mkdir -p /tmp/changeset-hold
mv .changeset/<other-1>.md .changeset/<other-2>.md ... /tmp/changeset-hold/
ls .changeset/   # should leave only config.json + the one changeset to release
```

## Stage 2: Run `changeset version`

Requires `GITHUB_TOKEN` because the repo uses `@changesets/changelog-github`. The `gh`
CLI token works.

```bash
pnpm install --frozen-lockfile
GITHUB_TOKEN=$(gh auth token) pnpm changeset version
```

Restore the held changesets immediately:

```bash
mv /tmp/changeset-hold/*.md .changeset/
rmdir /tmp/changeset-hold
```

Verify the diff:

```bash
git status
git diff packages/<package>/package.json packages/<package>/CHANGELOG.md
```

Expect: bumped `package.json`, new CHANGELOG entry, deleted only the one changeset.
The other changesets should be back in `.changeset/` and untouched.

## Stage 3: Build

Run the root build. Turborepo handles env passthrough from `.env.local` via the
pipeline config, so package builds get the variables they need without manual sourcing.

```bash
pnpm build
```

If a package has build-time secrets that must be present, see the
[Package-specific notes](#package-specific-notes) section for verification steps.

## Stage 4: Commit

```bash
git add -A
git commit -m "Version Packages (\`canary\`)"
```

Match the message format the changesets bot uses, since this is a manual stand-in for it.

## Stage 5: Hand the publish command to the user

**Do not run `changeset publish` from the agent.** Publishing to npm is a destructive,
externally-visible action that should be performed by the user.

Have the user confirm they're logged in, then run the publish themselves. The CLI will
prompt them interactively for the npm OTP.

```bash
npm whoami                       # expect their npm username
pnpm exec changeset publish      # prompts for OTP interactively
```

`changeset publish` is per-package version-aware: it only publishes packages whose local
`package.json` is ahead of npm. Since only one package was bumped, only it ships. It
also creates a local git tag like `@bigcommerce/<package>@<version>`.

Wait for the user to confirm the publish completed before continuing.

## Stage 6: Verify npm state

```bash
npm view <package> version
npm view <package> dist-tags
```

`latest` should point to the new version. If it doesn't (rare — only happens if
`publishConfig.tag` is set), advise the user to fix with:

```bash
npm dist-tag add <package>@<version> latest
```

## Stage 7: Fast-forward canary and push

This requires a direct push to the protected default branch. **Pause and ask for explicit
user authorization** before pushing — the user's "never push directly to main/master/production"
rule guards against this even though the changesets bot does the same thing during normal
releases.

```bash
git switch canary
git fetch origin canary
git log --oneline origin/canary..canary    # should be 1 ahead
git log --oneline canary..origin/canary    # should be 0 behind
git merge --ff-only release/<package>-<new-version>
```

If you're 1 ahead and 0 behind, default `git push` rejects non-fast-forward updates,
which gives the same safety as `--ff-only` on the push side. Apple's git build (≤2.50.x)
does not accept `--ff-only` as a push flag — `git push origin canary` is correct here.

If a hook blocks the agent push, hand these commands to the user:

```bash
git push origin canary
git push origin "@bigcommerce/<package>@<new-version>"
```

## Stage 8: Create the GitHub release manually

CI runs `changeset publish` after the canary push, finds the version already on npm,
and no-ops. Because the action only creates GitHub releases for packages it actually
publishes, **no release is created automatically** in this flow. Make it manually.

Extract the new CHANGELOG section (everything after the `## <version>` heading up to
the next `## ` heading) into a notes file. Then:

```bash
gh release create "@bigcommerce/<package>@<new-version>" \
  --repo bigcommerce/catalyst \
  --title "@bigcommerce/<package>@<new-version>" \
  --notes-file /tmp/release-notes.md
```

Match the body format of prior releases — just the `### Patch Changes` /
`### Minor Changes` heading and the bullets, no version heading at the top. Compare
against an existing release:

```bash
gh release view "@bigcommerce/catalyst-core@<some-prior-version>" --repo bigcommerce/catalyst --json body
```

## Stage 9: Final validation

```bash
git fetch origin canary
git log --oneline origin/canary -3                                              # version commit on canary
git ls-remote --tags origin "@bigcommerce/<package>@<new-version>"              # tag on origin
gh release view "@bigcommerce/<package>@<new-version>" --repo bigcommerce/catalyst --json tagName,isDraft,isPrerelease
gh run list --workflow=changesets-release.yml --limit 1                         # CI run succeeded
gh pr list --search "Version Packages (canary)" --state open --json number,headRefName,updatedAt   # Version Packages PR refreshed
git fetch origin changeset-release/canary
git show --stat origin/changeset-release/canary | head -20                      # confirm only the other changesets remain
npm view <package> version dist-tags
```

Report:
- Published version + dist-tag
- Canary commit SHA
- Tag pushed
- GitHub release URL
- Confirmation that the Version Packages PR now contains **only** the other changesets — the released one has been dropped from its scope.

## Stage 10: Cleanup

```bash
git branch -d release/<package>-<new-version>
```

## Package-specific notes

### `@bigcommerce/create-catalyst`

The CLI build (`tsup` via the package's `tsup.config.ts`) inlines `CLI_SEGMENT_WRITE_KEY`
at build time, falling back to the placeholder `'not-a-valid-segment-write-key'` if the
env var is missing. After Stage 3, verify the real key was embedded:

```bash
grep -c "not-a-valid-segment-write-key" packages/create-catalyst/dist/index.js
# expect: 0
```

If `1`, the env var wasn't loaded. Confirm `CLI_SEGMENT_WRITE_KEY` exists in `.env.local`,
and that the turbo pipeline for `build` declares it under `env` or `passThroughEnv` in
`turbo.json`. Re-run `pnpm build` after fixing.


================================================
FILE: .claude/skills/sync-makeswift/SKILL.md
================================================
---
name: sync-makeswift
description: >
  Sync the `integrations/makeswift` branch with `canary` in the Catalyst monorepo.
  Use when the user says "/sync-makeswift", "sync makeswift", "sync integrations/makeswift",
  or asks to bring `integrations/makeswift` up to date with `canary`.
---

# Sync `integrations/makeswift` with `canary`

Execute the following phases in order. Pause for user input where indicated.

## Phase 1: Prepare and merge

```bash
git fetch origin
git checkout -B sync-integrations-makeswift origin/integrations/makeswift
git merge origin/canary
```

If the merge completes cleanly, skip to changeset cleanup. Otherwise, resolve conflicts.

### Conflict resolution rules

- `core/package.json`: the `name` field MUST stay `@bigcommerce/catalyst-makeswift`. The `version` field MUST stay at the latest published `@bigcommerce/catalyst-makeswift` version (check what's on `origin/integrations/makeswift`, not `canary`).
- `core/CHANGELOG.md`: the latest release entry MUST match the latest published `@bigcommerce/catalyst-makeswift` version.
- `pnpm-lock.yaml`: accept canary's version (`git checkout --theirs pnpm-lock.yaml`), then regenerate with `pnpm install --no-frozen-lockfile`.
- For all other conflicts, prefer canary's structure/patterns while preserving makeswift-specific additions (imports, components, config).

After resolving all conflicts, stage everything and verify no unresolved conflicts remain:

```bash
git add <resolved files>
git diff --name-only --diff-filter=U  # should return empty
```

### Changeset cleanup

Remove any `.changeset/*.md` files that do NOT target `@bigcommerce/catalyst-makeswift`. Read each changeset file and delete any that reference `@bigcommerce/catalyst-core` or other packages. Amend the removals into the merge commit.

### Commit the merge

```bash
git commit --no-edit
```

If changesets were removed after the initial commit, amend them in (`git commit --amend --no-edit`) rather than creating a separate commit.

## Phase 2: Push and open PR

```bash
git push origin sync-integrations-makeswift
```

Open a PR into `integrations/makeswift` (not `canary`):

- Title: `sync \`integrations/makeswift\` with \`canary\``
- Body: summarize what came from canary, list conflict resolutions, and include this notice:

> **Do not squash or rebase-and-merge this PR.** Use a true merge commit or rebase locally to preserve the merge base between `canary` and `integrations/makeswift`.

**Stop here.** Tell the user the PR is ready for review and wait for them to confirm approval before continuing.

## Phase 3: Rebase and push (after PR approval)

```bash
git fetch origin
git checkout -B integrations/makeswift origin/integrations/makeswift
git rebase sync-integrations-makeswift
git push origin integrations/makeswift --force-with-lease
```

This closes the PR automatically. Confirm with the user that the push succeeded and the PR closed.

## Phase 4: Cleanup

Switch back to `canary` and delete the local branches that are no longer needed:

```bash
git checkout canary
git pull
git branch -D sync-integrations-makeswift integrations/makeswift
```


================================================
FILE: .github/CODEOWNERS
================================================
* @bigcommerce/team-trac


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Have questions?
    url: https://github.com/bigcommerce/catalyst/blob/canary/README.md
    about: Explore the Catalyst Docs.
  - name: Need help with Catalyst?
    url: https://github.com/bigcommerce/catalyst/discussions/new?category=q-a
    about: If you can't get something to work the way you expect, join us in Discussions to browse existing topics or to share a new post.
  - name: Feature Request
    url: https://github.com/bigcommerce/catalyst/discussions/categories/feature-requests
    about: Join us in Discussions to share your idea on improving Catalyst. Thanks for your contribution!
  - name: Official BigCommerce Support
    url: https://support.bigcommerce.com/contact
    about: To report a platform bug, outage or greater platform issue, contact our Technical Support team. If you're a partner, please do so via your Partner Portal.
  - name: 💙 Join the BigCommerceDevs Community
    url: https://developer.bigcommerce.com/community
    about: Connect with other devs building on BigCommerce!


================================================
FILE: .github/ISSUE_TEMPLATE/🐞📝-bug-report-makeswift.md
================================================
---
name: "\U0001F41E\U0001F4DD Makeswift Bug report"
about: You're running into a reproducible error while developing with Catalyst and Makeswift.
title: '[x] is not working when I [y]'
labels: ''
assignees: ''
---

We really appreciate the help making Catalyst and Makeswift better. Every issue helps!

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:

1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

Please link to a repo that can be used to reproduce this issue, if possible. It'll help fix the bug faster.

**Previously working?**
Was this functionality previously working? If so, please link to a commit or PR that caused it to stop working.

**Any Errors?**
Were there any errors that surfaced when merging the above PR?

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/🐞📝-bug-report.md
================================================
---
name: "\U0001F41E\U0001F4DD Bug report"
about: You're running into a reproducible error while developing with Catalyst.
title: "[x] is not working when I [y]"
labels: ''
assignees: ''

---

We really appreciate the help making Catalyst better. Every issue helps!

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

Please link to a repo that can be used to reproduce this issue, if possible. It'll help fix the bug faster.

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: 'npm'
    directory: '/'
    schedule:
      interval: 'weekly'
      day: 'monday'
    open-pull-requests-limit: 1
    groups:
      npm-dependencies:
        patterns:
          - "*"
    ignore:
      - dependency-name: '@types/node'
        update-types: ['version-update:semver-major']
      - dependency-name: 'eslint'
        update-types: ['version-update:semver-major']
      # Disabling tailwind due to browser compatibility constraints.
      - dependency-name: 'tailwindcss'
        update-types: ['version-update:semver-major']


================================================
FILE: .github/pull_request_template.md
================================================
## What/Why?
<!--- 
  A description about what this pull request implements and its purpose.
  Try to be detailed and describe any technical details to simplify the job
  of the reviewer and the individual on production support.
--->

## Testing
<!---
  Provide as much information as you can about how you tested and
  how another developer can test.
--->

## Migration
<!---
  If you have moved any files around, or made any breaking changes,
  please provide a migration guide for the developers to make rebases easier.
--->


================================================
FILE: .github/scripts/__tests__/audit-unlighthouse.test.mts
================================================
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';

import { buildReport } from '../audit-unlighthouse.mts';
import type { CiResult } from '../audit-unlighthouse.mts';

// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------

const DEFAULT_METRICS: CiResult['summary']['metrics'] = {
  'largest-contentful-paint': { displayValue: '2.5 s' },
  'cumulative-layout-shift': { displayValue: '0.01' },
  'first-contentful-paint': { displayValue: '1.2 s' },
  'total-blocking-time': { displayValue: '100 ms' },
  'max-potential-fid': { displayValue: '200 ms' },
  interactive: { displayValue: '3.5 s' },
};

function makeCiResult(overrides: {
  score?: number;
  performance?: number;
  accessibility?: number;
  'best-practices'?: number;
  seo?: number;
  metrics?: CiResult['summary']['metrics'];
} = {}): CiResult {
  return {
    summary: {
      score: overrides.score ?? 0.85,
      categories: {
        performance: { score: overrides.performance ?? 0.80 },
        accessibility: { score: overrides.accessibility ?? 0.92 },
        'best-practices': { score: overrides['best-practices'] ?? 1.0 },
        seo: { score: overrides.seo ?? 0.90 },
      },
      metrics: overrides.metrics ?? { ...DEFAULT_METRICS },
    },
  };
}

const BASE = makeCiResult();

// ---------------------------------------------------------------------------
// Report heading
// ---------------------------------------------------------------------------

describe('report heading', () => {
  it('contains the audit heading', () => {
    const markdown = buildReport(BASE, BASE);

    assert.ok(markdown.includes('## Unlighthouse Audit'), 'Missing main heading');
  });

  it('appends branch label when branch is given', () => {
    const markdown = buildReport(BASE, BASE, 'canary');

    assert.ok(
      markdown.includes('## Unlighthouse Audit — `canary`'),
      'Missing branch label in heading',
    );
  });

  it('handles branch names with slashes', () => {
    const markdown = buildReport(BASE, BASE, 'integrations/makeswift');

    assert.ok(markdown.includes('`integrations/makeswift`'));
  });

  it('omits branch label when none provided', () => {
    const markdown = buildReport(BASE, BASE);

    assert.ok(!markdown.includes(' — '), 'Should not contain a branch label separator');
  });

  it('contains the description text', () => {
    const markdown = buildReport(BASE, BASE);

    assert.ok(markdown.includes('Unlighthouse scores for the latest commit on this branch.'));
  });
});

// ---------------------------------------------------------------------------
// Summary Score section
// ---------------------------------------------------------------------------

describe('Summary Score section', () => {
  it('contains the Summary Score heading', () => {
    const markdown = buildReport(BASE, BASE);

    assert.ok(markdown.includes('### Summary Score'));
  });

  it('contains the aggregate score note', () => {
    const markdown = buildReport(BASE, BASE);

    assert.ok(markdown.includes('Aggregate score across all categories as reported by Unlighthouse.'));
  });

  it('renders scores as integers on a 1-100 scale', () => {
    const desktop = makeCiResult({ score: 0.85 });
    const mobile = makeCiResult({ score: 0.72 });
    const markdown = buildReport(desktop, mobile);

    assert.ok(markdown.includes('| Score | 85 | 72 |'));
  });

  it('rounds fractional scores correctly', () => {
    const desktop = makeCiResult({ score: 0.856 }); // rounds to 86
    const markdown = buildReport(desktop, BASE);

    assert.ok(markdown.includes('86'), 'Score 0.856 should round to 86');
  });

  it('contains the two-column header', () => {
    const markdown = buildReport(BASE, BASE);

    assert.ok(markdown.includes('| | Desktop | Mobile |'));
  });
});

// ---------------------------------------------------------------------------
// Category Scores section
// ---------------------------------------------------------------------------

describe('Category Scores section', () => {
  it('contains the Category Scores heading', () => {
    const markdown = buildReport(BASE, BASE);

    assert.ok(markdown.includes('### Category Scores'));
  });

  it('renders all four categories', () => {
    const markdown = buildReport(BASE, BASE);

    assert.ok(markdown.includes('Performance'));
    assert.ok(markdown.includes('Accessibility'));
    assert.ok(markdown.includes('Best Practices'));
    assert.ok(markdown.includes('SEO'));
  });

  it('renders desktop and mobile scores independently', () => {
    const desktop = makeCiResult({ performance: 0.80 });
    const mobile = makeCiResult({ performance: 0.93 });
    const markdown = buildReport(desktop, mobile);

    assert.ok(
      markdown.includes('| Performance | 80 | 93 |'),
      'Performance row should show desktop then mobile score',
    );
  });

  it('shows all four categories independently', () => {
    const desktop = makeCiResult({ seo: 0.88, accessibility: 0.75 });
    const mobile = makeCiResult({ seo: 0.91, accessibility: 0.82 });
    const markdown = buildReport(desktop, mobile);

    assert.ok(markdown.includes('| SEO | 88 | 91 |'));
    assert.ok(markdown.includes('| Accessibility | 75 | 82 |'));
  });
});

// ---------------------------------------------------------------------------
// Core Web Vitals section
// ---------------------------------------------------------------------------

describe('Core Web Vitals section', () => {
  it('contains the Core Web Vitals heading', () => {
    const markdown = buildReport(BASE, BASE);

    assert.ok(markdown.includes('### Core Web Vitals'));
  });

  it('renders all six metrics', () => {
    const markdown = buildReport(BASE, BASE);

    assert.ok(markdown.includes('LCP'));
    assert.ok(markdown.includes('CLS'));
    assert.ok(markdown.includes('FCP'));
    assert.ok(markdown.includes('TBT'));
    assert.ok(markdown.includes('Max Potential FID'));
    assert.ok(markdown.includes('Time to Interactive'));
  });

  it('passes displayValue through unchanged', () => {
    const desktop = makeCiResult({
      metrics: { ...DEFAULT_METRICS, 'largest-contentful-paint': { displayValue: '4.8 s' } },
    });
    const markdown = buildReport(desktop, BASE);

    assert.ok(markdown.includes('4.8 s'));
  });

  it('shows — for a metric missing from a result', () => {
    const desktopMissingMetric = makeCiResult({ metrics: {} });
    const markdown = buildReport(desktopMissingMetric, BASE);

    assert.ok(markdown.includes('—'), 'Missing metric should show —');
  });

  it('shows desktop and mobile displayValues per metric row', () => {
    const desktop = makeCiResult({ metrics: { ...DEFAULT_METRICS, 'total-blocking-time': { displayValue: '80 ms' } } });
    const mobile = makeCiResult({ metrics: { ...DEFAULT_METRICS, 'total-blocking-time': { displayValue: '320 ms' } } });
    const markdown = buildReport(desktop, mobile);

    assert.ok(markdown.includes('| TBT | 80 ms | 320 ms |'));
  });
});


================================================
FILE: .github/scripts/__tests__/bundle-size.test.mts
================================================
import { describe, it, after, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { writeFileSync, mkdirSync, rmSync, unlinkSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';

import {
  round1,
  getGzipSize,
  parseManifestEntries,
  computeRootLayout,
  computeRouteMetrics,
  compareReport,
  clearSizeCache,
  readTurbopackEntries,
} from '../bundle-size.mts';

// ---------------------------------------------------------------------------
// Shared temp directory with fixture chunk files.
// Initialized at module load time so testDir is set before any test runs.
// Files are large enough (varied content) to produce measurable gzip sizes.
// ---------------------------------------------------------------------------

const testDir = join(tmpdir(), `bundle-size-test-${Date.now()}`);

mkdirSync(testDir, { recursive: true });

// Each file gets unique, varied content so gzip produces a non-trivial size.
const makeJs = (prefix: string) =>
  Array.from(
    { length: 30 },
    (_, i) => `export const ${prefix}_${i} = ${JSON.stringify(`${prefix}_v${i}_pad${i * 37 + 13}`)};`,
  ).join('\n') + '\n';

const makeCss = (prefix: string) =>
  Array.from(
    { length: 20 },
    (_, i) => `.${prefix}-class-${i} { color: hsl(${i * 17}, 50%, 50%); margin: ${i}px; }`,
  ).join('\n') + '\n';

writeFileSync(join(testDir, 'route.js'), makeJs('route'));
writeFileSync(join(testDir, 'shared.js'), makeJs('shared'));
writeFileSync(join(testDir, 'root-layout.js'), makeJs('root_layout'));
writeFileSync(join(testDir, 'product-layout.js'), makeJs('product_layout'));
writeFileSync(join(testDir, 'route.css'), makeCss('route'));

after(() => {
  rmSync(testDir, { recursive: true, force: true });
});

// ---------------------------------------------------------------------------
// round1
// ---------------------------------------------------------------------------

describe('round1', () => {
  it('rounds up at .05', () => {
    assert.equal(round1(1.25), 1.3);
  });

  it('rounds down below .05', () => {
    assert.equal(round1(1.24), 1.2);
  });

  it('returns 0 unchanged', () => {
    assert.equal(round1(0), 0);
  });

  it('handles negative values', () => {
    assert.equal(round1(-1.25), -1.2);
  });
});

// ---------------------------------------------------------------------------
// parseManifestEntries
// ---------------------------------------------------------------------------

describe('parseManifestEntries', () => {
  it('routes /layout entries to layouts', () => {
    const { layouts, pages } = parseManifestEntries({
      '/app/layout': ['a.js'],
      '/app/about/page': ['b.js'],
    });

    assert.deepEqual(Object.keys(layouts), ['/app/layout']);
    assert.deepEqual(Object.keys(pages), ['/app/about/page']);
  });

  it('routes /page entries to pages', () => {
    const { pages } = parseManifestEntries({ '/app/contact/page': ['c.js'] });

    assert.deepEqual(Object.keys(pages), ['/app/contact/page']);
  });

  it('ignores entries ending in neither /layout nor /page', () => {
    const { layouts, pages } = parseManifestEntries({
      '/app/route': ['d.js'],
      '/api/handler': [],
      '/app/loading': ['e.js'],
    });

    assert.deepEqual(Object.keys(layouts), []);
    assert.deepEqual(Object.keys(pages), []);
  });

  it('returns empty objects for empty input', () => {
    const { layouts, pages } = parseManifestEntries({});

    assert.deepEqual(layouts, {});
    assert.deepEqual(pages, {});
  });

  it('handles multiple layouts and pages together', () => {
    const { layouts, pages } = parseManifestEntries({
      '/app/layout': ['a.js'],
      '/app/products/layout': ['b.js'],
      '/app/page': ['c.js'],
      '/app/products/page': ['d.js'],
    });

    assert.deepEqual(Object.keys(layouts).sort(), ['/app/layout', '/app/products/layout']);
    assert.deepEqual(Object.keys(pages).sort(), ['/app/page', '/app/products/page']);
  });
});

// ---------------------------------------------------------------------------
// computeRootLayout
// ---------------------------------------------------------------------------

describe('computeRootLayout', () => {
  beforeEach(() => clearSizeCache());

  it('selects shortest path as root when multiple layouts exist', () => {
    const layouts = {
      '/[locale]/products/layout': [],
      '/[locale]/layout': [],
      '/[locale]/about/deep/layout': [],
    };
    const { rootLayoutPath } = computeRootLayout(
      Object.keys(layouts),
      layouts,
      new Set(),
      testDir,
    );

    assert.equal(rootLayoutPath, '/[locale]/layout');
  });

  it('returns null rootLayoutPath when layoutPaths is empty', () => {
    const { rootLayoutPath, rootLayoutChunks, rootLayoutJs, rootLayoutCss } = computeRootLayout(
      [],
      {},
      new Set(),
      testDir,
    );

    assert.equal(rootLayoutPath, null);
    assert.equal(rootLayoutChunks.size, 0);
    assert.equal(rootLayoutJs, 0);
    assert.equal(rootLayoutCss, 0);
  });

  it('excludes sharedChunks from rootLayoutChunks', () => {
    const layouts = { '/layout': ['shared.js', 'root-layout.js'] };
    const sharedChunks = new Set(['shared.js']);
    const { rootLayoutChunks } = computeRootLayout(
      ['/layout'],
      layouts,
      sharedChunks,
      testDir,
    );

    assert.ok(!rootLayoutChunks.has('shared.js'), 'shared.js should be excluded');
    assert.ok(rootLayoutChunks.has('root-layout.js'), 'root-layout.js should be included');
  });

  it('rootLayoutChunks contains all non-shared layout chunks', () => {
    const layouts = { '/layout': ['root-layout.js', 'route.js'] };
    const { rootLayoutChunks } = computeRootLayout(
      ['/layout'],
      layouts,
      new Set(),
      testDir,
    );

    assert.ok(rootLayoutChunks.has('root-layout.js'));
    assert.ok(rootLayoutChunks.has('route.js'));
    assert.equal(rootLayoutChunks.size, 2);
  });

  it('computes non-zero sizes when real files exist', () => {
    const layouts = { '/layout': ['root-layout.js'] };
    const { rootLayoutJs } = computeRootLayout(
      ['/layout'],
      layouts,
      new Set(),
      testDir,
    );

    assert.ok(rootLayoutJs > 0, `Expected rootLayoutJs > 0, got ${rootLayoutJs}`);
  });
});

// ---------------------------------------------------------------------------
// computeRouteMetrics
// ---------------------------------------------------------------------------

describe('computeRouteMetrics', () => {
  beforeEach(() => clearSizeCache());

  it('firstLoadJs equals firstLoadJs arg when all chunks are non-existent', () => {
    const pages = { '/app/page': [] };
    const routes = computeRouteMetrics(
      pages,
      {},
      new Set(),
      null,
      new Set(),
      100,
      testDir,
    );

    assert.equal(routes['/app/page'].firstLoadJs, 100);
  });

  it('firstLoadJs is greater than firstLoadJs arg when real chunk files exist', () => {
    const pages = { '/app/page': ['route.js', 'route.css'] };
    const routes = computeRouteMetrics(
      pages,
      {},
      new Set(),
      null,
      new Set(),
      0,
      testDir,
    );

    const { js, css, firstLoadJs } = routes['/app/page'];

    assert.ok(js > 0, `js should be > 0 (real file exists), got ${js}`);
    assert.ok(css > 0, `css should be > 0 (real file exists), got ${css}`);
    assert.ok(firstLoadJs > 0, `firstLoadJs should be > 0, got ${firstLoadJs}`);
  });

  it('excludes sharedChunks from route chunk set', () => {
    const pages = { '/app/page': ['shared.js', 'route.js'] };

    // With both chunks in sharedChunks, routeChunks is empty -> js = 0
    const routesAllExcluded = computeRouteMetrics(
      pages,
      {},
      new Set(['shared.js', 'route.js']),
      null,
      new Set(),
      0,
      testDir,
    );

    assert.equal(routesAllExcluded['/app/page'].js, 0, 'All shared chunks excluded -> js = 0');

    clearSizeCache();

    // With no exclusions, real files contribute -> js > 0
    const routesNoneExcluded = computeRouteMetrics(
      pages,
      {},
      new Set(),
      null,
      new Set(),
      0,
      testDir,
    );

    assert.ok(routesNoneExcluded['/app/page'].js > 0, 'No exclusions -> js > 0');
  });

  it('excludes rootLayoutChunks from route chunk set', () => {
    const pages = { '/app/page': ['root-layout.js', 'route.js'] };

    // With both chunks in rootLayoutChunks, routeChunks is empty -> js = 0
    const routesAllExcluded = computeRouteMetrics(
      pages,
      {},
      new Set(),
      null,
      new Set(['root-layout.js', 'route.js']),
      0,
      testDir,
    );

    assert.equal(routesAllExcluded['/app/page'].js, 0, 'All rootLayout chunks excluded -> js = 0');

    clearSizeCache();

    // With no rootLayoutChunks excluded, real files contribute -> js > 0
    const routesNoneExcluded = computeRouteMetrics(
      pages,
      {},
      new Set(),
      null,
      new Set(),
      0,
      testDir,
    );

    assert.ok(routesNoneExcluded['/app/page'].js > 0, 'No exclusions -> js > 0');
  });

  it('includes non-root ancestor layout chunks in route size', () => {
    // Page has no own chunks; non-root ancestor layout contributes product-layout.js
    const pages = { '/[locale]/products/page': [] };
    const layouts = {
      '/[locale]/layout': ['root-layout.js'],
      '/[locale]/products/layout': ['product-layout.js'],
    };
    const rootLayoutChunks = new Set(['root-layout.js']);

    const routes = computeRouteMetrics(
      pages,
      layouts,
      new Set(),
      '/[locale]/layout',
      rootLayoutChunks,
      0,
      testDir,
    );

    assert.ok(
      routes['/[locale]/products/page'].js > 0,
      'Non-root ancestor layout chunk should contribute to route js',
    );
  });

  it('does not include root ancestor layout chunks in route size', () => {
    // Page has no own chunks; root layout has root-layout.js (should be excluded)
    const pages = { '/[locale]/page': [] };
    const layouts = {
      '/[locale]/layout': ['root-layout.js'],
    };
    const rootLayoutChunks = new Set(['root-layout.js']);

    const routes = computeRouteMetrics(
      pages,
      layouts,
      new Set(),
      '/[locale]/layout',
      rootLayoutChunks,
      0,
      testDir,
    );

    assert.equal(
      routes['/[locale]/page'].js,
      0,
      'Root ancestor layout chunks should NOT contribute to route js',
    );
  });

  it('applies round1 to all output values', () => {
    const pages = { '/app/page': [] };
    const routes = computeRouteMetrics(
      pages,
      {},
      new Set(),
      null,
      new Set(),
      1.25,
      testDir,
    );

    // firstLoadJs = round1(1.25 + 0 + 0) = 1.3
    assert.equal(routes['/app/page'].firstLoadJs, 1.3);
    assert.equal(routes['/app/page'].js, 0);
    assert.equal(routes['/app/page'].css, 0);
  });
});

// ---------------------------------------------------------------------------
// compareReport
// The warning sign in the report output is U+26A0 U+FE0F (warning emoji).
// Warning table rows end with "| warning-emoji |" while the footer contains
// the same emoji in a sentence. Use "warning-emoji |" to match only table cells.
// ---------------------------------------------------------------------------

const WARN_EMOJI = '\u26a0\ufe0f'; // ⚠️
const WARN_IN_ROW = `${WARN_EMOJI} |`; // appears only in warning table cells

describe('compareReport', () => {
  function makeReport(overrides = {}) {
    return {
      commitSha: 'abc123',
      updatedAt: '2024-01-01',
      firstLoadJs: 100,
      totalJs: 200,
      totalCss: 10,
      routes: {},
      ...overrides,
    };
  }

  it('shows "No bundle size changes detected." when nothing changed', () => {
    const baseline = makeReport();
    const current = makeReport();
    const report = compareReport(baseline, current);

    assert.ok(report.includes('No bundle size changes detected.'));
    assert.ok(!report.includes('_No route changes detected._'));
    assert.ok(!report.includes('### Per-Route First Load JS'));
  });

  it('shows "No route changes detected." when only global metrics changed', () => {
    // Global metric differs (Case 2) but routes are identical → section shown, no threshold
    const baseline = makeReport({ firstLoadJs: 100, routes: { '/app/page': { firstLoadJs: 100, js: 50, css: 5 } } });
    const current = makeReport({ firstLoadJs: 110, routes: { '/app/page': { firstLoadJs: 100, js: 50, css: 5 } } });
    const report = compareReport(baseline, current);

    assert.ok(report.includes('_No route changes detected._'));
    assert.ok(!report.includes(`Threshold:`));
  });

  it('does not show global metrics table when global metrics are unchanged', () => {
    const baseline = makeReport();
    const current = makeReport();
    const report = compareReport(baseline, current);

    assert.ok(!report.includes('| Metric |'));
  });

  it('shows global metrics table only when metrics changed', () => {
    const baseline = makeReport({ firstLoadJs: 100 });
    const current = makeReport({ firstLoadJs: 115 });
    const report = compareReport(baseline, current);

    assert.ok(report.includes('| Metric |'));
    assert.ok(report.includes('First Load JS'));
  });

  it('shows only the changed global metrics', () => {
    const baseline = makeReport({ firstLoadJs: 100, totalJs: 200, totalCss: 10 });
    const current = makeReport({ firstLoadJs: 100, totalJs: 210, totalCss: 10 });
    const report = compareReport(baseline, current);

    // Use pipe-delimited patterns to match table rows only (not the section header)
    assert.ok(report.includes('| Total JS |'));
    assert.ok(!report.includes('| First Load JS |'));
    assert.ok(!report.includes('| Total CSS |'));
  });

  it('shows NEW row for added route', () => {
    const baseline = makeReport({ routes: {} });
    const current = makeReport({
      routes: { '/app/new/page': { firstLoadJs: 120, js: 60, css: 5 } },
    });
    const report = compareReport(baseline, current);

    assert.ok(report.includes('NEW'));
    assert.ok(report.includes('120 kB'));
  });

  it('shows REMOVED row for deleted route', () => {
    const baseline = makeReport({
      routes: { '/app/old/page': { firstLoadJs: 120, js: 60, css: 5 } },
    });
    const current = makeReport({ routes: {} });
    const report = compareReport(baseline, current);

    assert.ok(report.includes('REMOVED'));
    assert.ok(report.includes('120 kB'));
  });

  it('does not show warning for increase under threshold', () => {
    // delta=3kB, pct=3% < 5% threshold: no warning row
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 100, js: 50, css: 5 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 103, js: 53, css: 5 } } });
    const report = compareReport(baseline, current);

    assert.ok(!report.includes(WARN_IN_ROW), 'Should not have a warning table cell');
  });

  it('shows warning for increase over threshold (over 1kB AND over threshold percent)', () => {
    // delta=10kB, pct=10% > 5% threshold: warning row present
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 100, js: 50, css: 5 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 110, js: 60, css: 5 } } });
    const report = compareReport(baseline, current);

    assert.ok(report.includes(WARN_IN_ROW), 'Should have a warning table cell');
  });

  it('does not warn when delta is over threshold percent but 1kB or less', () => {
    // delta=0.5kB = 50% but <=1kB: no warning
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 1, js: 1, css: 0 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 1.5, js: 1.5, css: 0 } } });
    const report = compareReport(baseline, current);

    assert.ok(!report.includes(WARN_IN_ROW));
  });

  it('does not warn when delta is over 1kB but at or under threshold percent', () => {
    // delta=2kB = 1% < 5% threshold: no warning
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 200, js: 200, css: 0 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 202, js: 202, css: 0 } } });
    const report = compareReport(baseline, current);

    assert.ok(!report.includes(WARN_IN_ROW));
  });

  it('respects custom threshold: no warning when under', () => {
    // delta=8kB = 8%, threshold=10: no warning
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 100, js: 50, css: 5 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 108, js: 58, css: 5 } } });
    const report = compareReport(baseline, current, { threshold: 10 });

    assert.ok(!report.includes(WARN_IN_ROW));
  });

  it('respects custom threshold: warning when over', () => {
    // delta=8kB = 8%, threshold=3: warning
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 100, js: 50, css: 5 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 108, js: 58, css: 5 } } });
    const report = compareReport(baseline, current, { threshold: 3 });

    assert.ok(report.includes(WARN_IN_ROW));
  });

  it('uses default threshold of 5 percent when not specified', () => {
    // delta=6kB = 6% > 5%: warning with default
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 100, js: 100, css: 0 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 106, js: 106, css: 0 } } });
    const report = compareReport(baseline, current);

    assert.ok(report.includes(WARN_IN_ROW));
    assert.ok(report.includes('Threshold: 5%'));
  });

  it('shows threshold in footer only when route changes are present', () => {
    // Route changed: threshold callout shown
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 100, js: 50, css: 5 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 110, js: 60, css: 5 } } });
    const report = compareReport(baseline, current, { threshold: 7 });

    assert.ok(report.includes('Threshold: 7%'));
  });

  it('omits threshold footer when there are no route changes', () => {
    // Global metrics differ but routes are identical — no threshold callout
    const baseline = makeReport({ firstLoadJs: 100 });
    const current = makeReport({ firstLoadJs: 115 });
    const report = compareReport(baseline, current);

    assert.ok(!report.includes('Threshold:'));
  });

  it('formats positive delta with + sign and percent', () => {
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 100, js: 50, css: 5 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 110, js: 60, css: 5 } } });
    const report = compareReport(baseline, current);

    assert.ok(report.includes('+10 kB'));
    assert.ok(report.includes('+10%'));
  });

  it('formats negative delta with minus sign and percent', () => {
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 100, js: 50, css: 5 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 90, js: 40, css: 5 } } });
    const report = compareReport(baseline, current);

    assert.ok(report.includes('-10 kB'));
    assert.ok(report.includes('-10%'));
  });

  it('sorts routes alphabetically', () => {
    const makeRoute = (v: number) => ({ firstLoadJs: v, js: v, css: 0 });
    const baseline = makeReport({
      routes: {
        '/z/page': makeRoute(100),
        '/a/page': makeRoute(100),
        '/m/page': makeRoute(100),
      },
    });
    const current = makeReport({
      routes: {
        '/z/page': makeRoute(110),
        '/a/page': makeRoute(110),
        '/m/page': makeRoute(110),
      },
    });
    const report = compareReport(baseline, current);

    const aIdx = report.indexOf('/a/page');
    const mIdx = report.indexOf('/m/page');
    const zIdx = report.indexOf('/z/page');

    assert.ok(aIdx < mIdx, '/a should appear before /m');
    assert.ok(mIdx < zIdx, '/m should appear before /z');
  });

  it('strips the /[locale] prefix from display names', () => {
    const baseline = makeReport({ routes: {} });
    const current = makeReport({
      routes: { '/[locale]/products/page': { firstLoadJs: 120, js: 60, css: 5 } },
    });
    const report = compareReport(baseline, current);

    assert.ok(report.includes('/products/page'), 'Should show /products/page (locale stripped)');
    assert.ok(
      !report.includes('/[locale]/products/page'),
      'Should not show /[locale] prefix',
    );
  });

  it('omits near-zero deltas that round to 0.0', () => {
    // 0.04kB delta rounds to 0.0: treated as no change
    const baseline = makeReport({
      routes: { '/app/page': { firstLoadJs: 100.04, js: 50, css: 5 } },
    });
    const current = makeReport({
      routes: { '/app/page': { firstLoadJs: 100.04, js: 50, css: 5 } },
    });
    const report = compareReport(baseline, current);

    assert.ok(report.includes('No bundle size changes detected.'));
  });

  it('shows Per-Route First Load JS section when there are route changes', () => {
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 100, js: 50, css: 0 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 110, js: 60, css: 0 } } });
    const report = compareReport(baseline, current);

    assert.ok(report.includes('### Per-Route First Load JS'));
  });

  it('omits Per-Route First Load JS section when nothing changed', () => {
    const baseline = makeReport();
    const current = makeReport();
    const report = compareReport(baseline, current);

    assert.ok(!report.includes('### Per-Route First Load JS'));
  });

  it('shows header with baseline commitSha and updatedAt', () => {
    const baseline = makeReport({ commitSha: 'deadbeef', updatedAt: '2024-06-15' });
    const current = makeReport();
    const report = compareReport(baseline, current);

    assert.ok(report.includes('`deadbeef`'));
    assert.ok(report.includes('2024-06-15'));
  });

  it('shows "No bundle size changes detected." for empty routes in both reports', () => {
    const baseline = makeReport({ routes: {} });
    const current = makeReport({ routes: {} });
    const report = compareReport(baseline, current);

    assert.ok(report.includes('No bundle size changes detected.'));
  });

  it('shows table header when routes have changes', () => {
    const baseline = makeReport({ routes: { '/app/page': { firstLoadJs: 100, js: 50, css: 0 } } });
    const current = makeReport({ routes: { '/app/page': { firstLoadJs: 110, js: 60, css: 0 } } });
    const report = compareReport(baseline, current);

    assert.ok(report.includes('| Route |'));
    assert.ok(report.includes('| Baseline |'));
    assert.ok(report.includes('| Current |'));
  });
});

// ---------------------------------------------------------------------------
// readTurbopackEntries
// ---------------------------------------------------------------------------

describe('readTurbopackEntries', () => {
  // Helper: create a minimal _client-reference-manifest.js fixture
  function makeManifestContent(
    routes: Record<string, Record<string, { chunks: string[] }>>,
  ): string {
    const manifest: Record<string, { clientModules: Record<string, { chunks: string[] }> }> = {};

    for (const [routeKey, modules] of Object.entries(routes)) {
      manifest[routeKey] = { clientModules: modules };
    }

    return `globalThis.__RSC_MANIFEST = ${JSON.stringify(manifest)};`;
  }

  it('reads chunk paths from a single manifest and normalizes /_next/ prefix', () => {
    const dir = join(testDir, `turbopack-basic-${Date.now()}`);

    mkdirSync(dir, { recursive: true });
    writeFileSync(
      join(dir, 'page_client-reference-manifest.js'),
      makeManifestContent({
        '/products/page': {
          'mod-a': { chunks: ['/_next/static/chunks/a.js'] },
          'mod-b': { chunks: ['/_next/static/chunks/b.js'] },
        },
      }),
    );

    const entries = readTurbopackEntries(dir);

    assert.ok(entries['/products/page'], 'should have /products/page entry');
    assert.ok(entries['/products/page'].includes('static/chunks/a.js'), 'should normalize /_next/ prefix');
    assert.ok(entries['/products/page'].includes('static/chunks/b.js'));
    assert.ok(!entries['/products/page'].some((c) => c.startsWith('/_next/')), 'no chunk should start with /_next/');
  });

  it('filters out non-/page routes (layouts, route handlers)', () => {
    const dir = join(testDir, `turbopack-filter-${Date.now()}`);

    mkdirSync(dir, { recursive: true });
    writeFileSync(
      join(dir, 'page_client-reference-manifest.js'),
      makeManifestContent({
        '/app/layout': { 'mod-a': { chunks: ['/_next/static/chunks/layout.js'] } },
        '/app/route': { 'mod-b': { chunks: ['/_next/static/chunks/route.js'] } },
        '/app/page': { 'mod-c': { chunks: ['/_next/static/chunks/page.js'] } },
      }),
    );

    const entries = readTurbopackEntries(dir);

    assert.ok(entries['/app/page'], 'should include /page route');
    assert.ok(!entries['/app/layout'], 'should exclude /layout route');
    assert.ok(!entries['/app/route'], 'should exclude /route handler');
  });

  it('deduplicates chunks appearing in multiple modules', () => {
    const dir = join(testDir, `turbopack-dedup-${Date.now()}`);

    mkdirSync(dir, { recursive: true });
    writeFileSync(
      join(dir, 'page_client-reference-manifest.js'),
      makeManifestContent({
        '/shop/page': {
          'mod-a': { chunks: ['/_next/static/chunks/shared.js', '/_next/static/chunks/a.js'] },
          'mod-b': { chunks: ['/_next/static/chunks/shared.js', '/_next/static/chunks/b.js'] },
        },
      }),
    );

    const entries = readTurbopackEntries(dir);
    const chunks = entries['/shop/page'];

    assert.ok(chunks, 'should have /shop/page entry');

    const sharedCount = chunks.filter((c) => c === 'static/chunks/shared.js').length;

    assert.equal(sharedCount, 1, 'shared chunk should appear exactly once');
    assert.equal(chunks.length, 3, 'should have 3 unique chunks');
  });

  it('scans subdirectories recursively', () => {
    const dir = join(testDir, `turbopack-recursive-${Date.now()}`);

    mkdirSync(join(dir, 'nested', 'deep'), { recursive: true });
    writeFileSync(
      join(dir, 'nested', 'deep', 'page_client-reference-manifest.js'),
      makeManifestContent({
        '/nested/deep/page': { 'mod-a': { chunks: ['/_next/static/chunks/deep.js'] } },
      }),
    );

    const entries = readTurbopackEntries(dir);

    assert.ok(entries['/nested/deep/page'], 'should find manifest in nested directory');
  });

  it('skips malformed manifest files gracefully', () => {
    const dir = join(testDir, `turbopack-malformed-${Date.now()}`);

    mkdirSync(dir, { recursive: true });
    writeFileSync(join(dir, 'bad_client-reference-manifest.js'), 'this is not valid JS {{{');
    writeFileSync(
      join(dir, 'good_client-reference-manifest.js'),
      makeManifestContent({
        '/valid/page': { 'mod-a': { chunks: ['/_next/static/chunks/valid.js'] } },
      }),
    );

    // Should not throw, and should still return valid entries
    assert.doesNotThrow(() => readTurbopackEntries(dir));

    const entries = readTurbopackEntries(dir);

    assert.ok(entries['/valid/page'], 'should return valid entries even when another file is malformed');
  });

  it('returns empty object when no manifest files exist', () => {
    const dir = join(testDir, `turbopack-empty-${Date.now()}`);

    mkdirSync(dir, { recursive: true });

    const entries = readTurbopackEntries(dir);

    assert.deepEqual(entries, {});
  });

  it('returns empty object when manifests have no __RSC_MANIFEST', () => {
    const dir = join(testDir, `turbopack-no-rsc-${Date.now()}`);

    mkdirSync(dir, { recursive: true });
    writeFileSync(
      join(dir, 'page_client-reference-manifest.js'),
      'globalThis.somethingElse = {};',
    );

    const entries = readTurbopackEntries(dir);

    assert.deepEqual(entries, {});
  });
});

// ---------------------------------------------------------------------------
// getGzipSize
// ---------------------------------------------------------------------------

describe('getGzipSize', () => {
  beforeEach(() => clearSizeCache());

  it('returns 0 when file does not exist', () => {
    const result = getGzipSize(join(testDir, 'nonexistent-file-xyz.js'));

    assert.equal(result, 0);
  });

  it('returns a positive number for an existing file', () => {
    const result = getGzipSize(join(testDir, 'route.js'));

    assert.ok(result > 0, `Expected positive size, got ${result}`);
  });

  it('caches results and returns same value on second call', () => {
    const filePath = join(testDir, `cache-test-${Date.now()}.js`);

    writeFileSync(filePath, makeJs('cached'));

    const firstResult = getGzipSize(filePath);

    assert.ok(firstResult > 0);

    // Delete the file — the cached value should still be returned
    unlinkSync(filePath);

    const secondResult = getGzipSize(filePath);

    assert.equal(secondResult, firstResult, 'Should return cached value after file deletion');
  });

  it('clearSizeCache resets the cache', () => {
    const filePath = join(testDir, `clear-test-${Date.now()}.js`);

    writeFileSync(filePath, makeJs('cleared'));

    const sizeBeforeDelete = getGzipSize(filePath);

    assert.ok(sizeBeforeDelete > 0);

    unlinkSync(filePath);
    clearSizeCache();

    // After clearing cache, file is gone so size should be 0
    const sizeAfterClear = getGzipSize(filePath);

    assert.equal(sizeAfterClear, 0, 'Should return 0 after cache cleared and file deleted');
  });
});


================================================
FILE: .github/scripts/__tests__/compare-unlighthouse.test.mts
================================================
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';

import { compareResults } from '../compare-unlighthouse.mts';
import type { CiResult } from '../compare-unlighthouse.mts';

// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------

const DEFAULT_METRICS: CiResult['summary']['metrics'] = {
  'largest-contentful-paint': { displayValue: '2.5 s' },
  'cumulative-layout-shift': { displayValue: '0.01' },
  'first-contentful-paint': { displayValue: '1.2 s' },
  'total-blocking-time': { displayValue: '100 ms' },
  'max-potential-fid': { displayValue: '200 ms' },
  interactive: { displayValue: '3.5 s' },
};

function makeCiResult(overrides: {
  score?: number;
  performance?: number;
  accessibility?: number;
  'best-practices'?: number;
  seo?: number;
  metrics?: CiResult['summary']['metrics'];
} = {}): CiResult {
  return {
    summary: {
      score: overrides.score ?? 0.85,
      categories: {
        performance: { score: overrides.performance ?? 0.80 },
        accessibility: { score: overrides.accessibility ?? 0.92 },
        'best-practices': { score: overrides['best-practices'] ?? 1.0 },
        seo: { score: overrides.seo ?? 0.90 },
      },
      metrics: overrides.metrics ?? { ...DEFAULT_METRICS },
    },
  };
}

const BASE = makeCiResult();

// ---------------------------------------------------------------------------
// hasChanges
// ---------------------------------------------------------------------------

describe('hasChanges', () => {
  it('is false when all four results are identical', () => {
    const { hasChanges } = compareResults(BASE, BASE, BASE, BASE, 1);

    assert.equal(hasChanges, false);
  });

  it('is true when preview desktop summary score differs by exactly 1pp', () => {
    const preview = makeCiResult({ score: 0.84 }); // 1pp below
    const { hasChanges } = compareResults(BASE, BASE, preview, BASE, 1);

    assert.equal(hasChanges, true);
  });

  it('is false when summary score differs by less than 1pp', () => {
    const preview = makeCiResult({ score: 0.855 }); // 0.5pp above
    const { hasChanges } = compareResults(BASE, BASE, preview, BASE, 1);

    assert.equal(hasChanges, false);
  });

  it('is true when preview mobile summary score differs by >= 1pp', () => {
    const previewMobile = makeCiResult({ score: 0.74 });
    const { hasChanges } = compareResults(BASE, BASE, BASE, previewMobile, 1);

    assert.equal(hasChanges, true);
  });

  it('is true when a category score differs by >= 1pp', () => {
    const preview = makeCiResult({ performance: 0.79 }); // 1pp below 0.80
    const { hasChanges } = compareResults(BASE, BASE, preview, BASE, 1);

    assert.equal(hasChanges, true);
  });

  it('is false when category score differs by less than 1pp', () => {
    const preview = makeCiResult({ performance: 0.805 }); // 0.5pp above
    const { hasChanges } = compareResults(BASE, BASE, preview, BASE, 1);

    assert.equal(hasChanges, false);
  });

  it('respects a custom threshold', () => {
    // 2pp delta — true at threshold=1, false at threshold=3
    const preview = makeCiResult({ score: 0.83 });

    assert.equal(compareResults(BASE, BASE, preview, BASE, 1).hasChanges, true);
    assert.equal(compareResults(BASE, BASE, preview, BASE, 3).hasChanges, false);
  });
});

// ---------------------------------------------------------------------------
// Report heading
// ---------------------------------------------------------------------------

describe('report heading', () => {
  it('contains the comparison heading', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1);

    assert.ok(
      markdown.includes('## Unlighthouse Performance Comparison'),
      'Missing main heading',
    );
  });

  it('appends provider label when provider is given', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1, 'vercel');

    assert.ok(
      markdown.includes('## Unlighthouse Performance Comparison — Vercel'),
      'Missing provider label in heading',
    );
  });

  it('capitalises the provider label', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1, 'cloudflare');

    assert.ok(markdown.includes('— Cloudflare'), 'Provider should be capitalised');
  });

  it('omits provider label when none provided', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1);

    assert.ok(
      !markdown.includes(' — '),
      'Should not contain a provider label separator',
    );
  });

  it('contains the description text', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1);

    assert.ok(
      markdown.includes(
        'Comparing PR preview deployment Unlighthouse scores vs production Unlighthouse scores.',
      ),
    );
  });
});

// ---------------------------------------------------------------------------
// Summary Score section
// ---------------------------------------------------------------------------

describe('Summary Score section', () => {
  it('contains the Summary Score heading', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1);

    assert.ok(markdown.includes('### Summary Score'));
  });

  it('contains the aggregate score note', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1);

    assert.ok(
      markdown.includes(
        'Aggregate score across all categories as reported by Unlighthouse.',
      ),
    );
  });

  it('renders scores as integers on a 1-100 scale', () => {
    const prod = makeCiResult({ score: 0.85 });
    const prev = makeCiResult({ score: 0.72 });
    const { markdown } = compareResults(prod, prod, prev, prev, 1);

    assert.ok(markdown.includes('| Score | 85 | 85 | 72 | 72 |'));
  });

  it('rounds fractional scores correctly', () => {
    const prod = makeCiResult({ score: 0.856 }); // rounds to 86
    const { markdown } = compareResults(prod, BASE, prod, BASE, 1);

    assert.ok(markdown.includes('86'), 'Score 0.856 should round to 86');
  });

  it('contains the four-column header', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1);

    assert.ok(
      markdown.includes('| | Prod Desktop | Prod Mobile | Preview Desktop | Preview Mobile |'),
    );
  });
});

// ---------------------------------------------------------------------------
// Category Scores section
// ---------------------------------------------------------------------------

describe('Category Scores section', () => {
  it('contains the Category Scores heading', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1);

    assert.ok(markdown.includes('### Category Scores'));
  });

  it('renders all four categories', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1);

    assert.ok(markdown.includes('Performance'));
    assert.ok(markdown.includes('Accessibility'));
    assert.ok(markdown.includes('Best Practices'));
    assert.ok(markdown.includes('SEO'));
  });

  it('renders category scores as integers on a 1-100 scale', () => {
    const prod = makeCiResult({ performance: 0.80 });
    const prev = makeCiResult({ performance: 0.93 });
    const { markdown } = compareResults(prod, prod, prev, prev, 1);

    assert.ok(
      markdown.includes('| Performance | 80 | 80 | 93 | 93 |'),
      'Performance row should contain all four scores as integers',
    );
  });

  it('shows all four column values independently', () => {
    const prodDesktop = makeCiResult({ seo: 0.88 });
    const prodMobile = makeCiResult({ seo: 0.75 });
    const prevDesktop = makeCiResult({ seo: 0.91 });
    const prevMobile = makeCiResult({ seo: 0.82 });
    const { markdown } = compareResults(prodDesktop, prodMobile, prevDesktop, prevMobile, 1);

    assert.ok(markdown.includes('| SEO | 88 | 75 | 91 | 82 |'));
  });
});

// ---------------------------------------------------------------------------
// Core Web Vitals section
// ---------------------------------------------------------------------------

describe('Core Web Vitals section', () => {
  it('contains the Core Web Vitals heading', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1);

    assert.ok(markdown.includes('### Core Web Vitals'));
  });

  it('renders all six metrics', () => {
    const { markdown } = compareResults(BASE, BASE, BASE, BASE, 1);

    assert.ok(markdown.includes('LCP'));
    assert.ok(markdown.includes('CLS'));
    assert.ok(markdown.includes('FCP'));
    assert.ok(markdown.includes('TBT'));
    assert.ok(markdown.includes('Max Potential FID'));
    assert.ok(markdown.includes('Time to Interactive'));
  });

  it('passes displayValue through unchanged', () => {
    const ci = makeCiResult({
      metrics: {
        ...DEFAULT_METRICS,
        'largest-contentful-paint': { displayValue: '4.8 s' },
      },
    });
    const { markdown } = compareResults(ci, ci, ci, ci, 1);

    assert.ok(markdown.includes('4.8 s'), 'displayValue should appear as-is');
  });

  it('shows — for a metric missing from a result', () => {
    const ciMissingMetric = makeCiResult({ metrics: {} });
    const { markdown } = compareResults(BASE, ciMissingMetric, BASE, BASE, 1);

    assert.ok(markdown.includes('—'), 'Missing metric should show —');
  });

  it('shows four displayValues per metric row', () => {
    const prodDesktop = makeCiResult({ metrics: { ...DEFAULT_METRICS, 'total-blocking-time': { displayValue: '80 ms' } } });
    const prodMobile = makeCiResult({ metrics: { ...DEFAULT_METRICS, 'total-blocking-time': { displayValue: '320 ms' } } });
    const prevDesktop = makeCiResult({ metrics: { ...DEFAULT_METRICS, 'total-blocking-time': { displayValue: '75 ms' } } });
    const prevMobile = makeCiResult({ metrics: { ...DEFAULT_METRICS, 'total-blocking-time': { displayValue: '310 ms' } } });
    const { markdown } = compareResults(prodDesktop, prodMobile, prevDesktop, prevMobile, 1);

    assert.ok(markdown.includes('| TBT | 80 ms | 320 ms | 75 ms | 310 ms |'));
  });
});


================================================
FILE: .github/scripts/__tests__/post-bundle-comment.test.mts
================================================
import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const postBundleComment = require('../post-bundle-comment.js') as (args: {
  github: ReturnType<typeof makeGithub>['github'];
  context: ReturnType<typeof makeContext>;
  reportPath?: string;
}) => Promise<void>;

const marker = '<!-- bundle-size-report -->';

let tmpDir: string;
let reportPath: string;

beforeEach(() => {
  tmpDir = join(tmpdir(), `post-bundle-test-${Date.now()}`);
  mkdirSync(tmpDir, { recursive: true });
  reportPath = join(tmpDir, 'report.md');
  writeFileSync(reportPath, '## Bundle Size Report\n\nSome content here.');
});

interface Comment {
  id: number;
  body: string;
}

interface GithubCalls {
  create: object[];
  update: object[];
  list: object[];
}

// Helper to create a mock github object and record calls
function makeGithub(existingComments: Comment[] = []) {
  const calls: GithubCalls = { create: [], update: [], list: [] };
  const github = {
    rest: {
      issues: {
        listComments: async (args: object) => {
          calls.list.push(args);
          return { data: existingComments };
        },
        createComment: async (args: object) => {
          calls.create.push(args);
        },
        updateComment: async (args: object) => {
          calls.update.push(args);
        },
      },
    },
  };
  return { github, calls };
}

function makeContext({ owner = 'test-owner', repo = 'test-repo', number = 42 } = {}) {
  return {
    repo: { owner, repo },
    issue: { number },
  };
}

describe('post-bundle-comment', () => {
  it('creates a new comment when no existing comment contains the marker', async () => {
    const { github, calls } = makeGithub([]);
    await postBundleComment({ github, context: makeContext(), reportPath });

    assert.equal(calls.create.length, 1, 'Should create exactly one comment');
    assert.equal(calls.update.length, 0, 'Should not update any comment');
  });

  it('updates existing comment when marker found', async () => {
    const existing = { id: 99, body: `${marker}\nOld content` };
    const { github, calls } = makeGithub([existing]);
    await postBundleComment({ github, context: makeContext(), reportPath });

    assert.equal(calls.update.length, 1, 'Should update exactly one comment');
    assert.equal(calls.create.length, 0, 'Should not create a new comment');
    assert.equal((calls.update[0] as { comment_id: number }).comment_id, 99, 'Should update the correct comment by id');
  });

  it('body always starts with marker and newline', async () => {
    const { github, calls } = makeGithub([]);
    await postBundleComment({ github, context: makeContext(), reportPath });

    const body = (calls.create[0] as { body: string }).body;

    assert.ok(body.startsWith(`${marker}\n`), `Body should start with marker, got: ${body.slice(0, 50)}`);
  });

  it('updated comment body also starts with marker and newline', async () => {
    const existing = { id: 7, body: `${marker}\nStale content` };
    const { github, calls } = makeGithub([existing]);
    await postBundleComment({ github, context: makeContext(), reportPath });

    const body = (calls.update[0] as { body: string }).body;

    assert.ok(body.startsWith(`${marker}\n`));
  });

  it('includes report file content in the comment body', async () => {
    const { github, calls } = makeGithub([]);
    await postBundleComment({ github, context: makeContext(), reportPath });

    const body = (calls.create[0] as { body: string }).body;

    assert.ok(body.includes('## Bundle Size Report'), 'Should include report heading');
    assert.ok(body.includes('Some content here.'), 'Should include report body content');
  });

  it('reads report from a custom reportPath', async () => {
    const customPath = join(tmpDir, 'custom.md');

    writeFileSync(customPath, 'Custom report content for testing!');

    const { github, calls } = makeGithub([]);
    await postBundleComment({ github, context: makeContext(), reportPath: customPath });

    assert.ok((calls.create[0] as { body: string }).body.includes('Custom report content for testing!'));
  });

  it('passes correct owner, repo, issue_number from context to listComments', async () => {
    const { github, calls } = makeGithub([]);
    await postBundleComment({
      github,
      context: makeContext({ owner: 'my-org', repo: 'my-repo', number: 123 }),
      reportPath,
    });

    assert.equal((calls.list[0] as { owner: string }).owner, 'my-org');
    assert.equal((calls.list[0] as { repo: string }).repo, 'my-repo');
    assert.equal((calls.list[0] as { issue_number: number }).issue_number, 123);
  });

  it('passes correct owner, repo, issue_number from context to createComment', async () => {
    const { github, calls } = makeGithub([]);
    await postBundleComment({
      github,
      context: makeContext({ owner: 'my-org', repo: 'my-repo', number: 123 }),
      reportPath,
    });

    assert.equal((calls.create[0] as { owner: string }).owner, 'my-org');
    assert.equal((calls.create[0] as { repo: string }).repo, 'my-repo');
    assert.equal((calls.create[0] as { issue_number: number }).issue_number, 123);
  });

  it('passes correct owner and repo to updateComment', async () => {
    const existing = { id: 55, body: `${marker}\nOld` };
    const { github, calls } = makeGithub([existing]);
    await postBundleComment({
      github,
      context: makeContext({ owner: 'org2', repo: 'repo2', number: 7 }),
      reportPath,
    });

    assert.equal((calls.update[0] as { owner: string }).owner, 'org2');
    assert.equal((calls.update[0] as { repo: string }).repo, 'repo2');
  });

  it('uses the first comment that contains the marker (not just exact match)', async () => {
    const comments = [
      { id: 1, body: 'Just a regular comment' },
      { id: 2, body: `${marker}\nFirst bundle report` },
      { id: 3, body: `${marker}\nSecond bundle report` },
    ];
    const { github, calls } = makeGithub(comments);
    await postBundleComment({ github, context: makeContext(), reportPath });

    assert.equal(calls.update.length, 1);
    assert.equal((calls.update[0] as { comment_id: number }).comment_id, 2, 'Should update the first matching comment');
  });

  it('creates comment when existing comments do not contain the marker', async () => {
    const comments = [
      { id: 10, body: 'No marker here' },
      { id: 11, body: 'Also no marker' },
    ];
    const { github, calls } = makeGithub(comments);
    await postBundleComment({ github, context: makeContext(), reportPath });

    assert.equal(calls.create.length, 1);
    assert.equal(calls.update.length, 0);
  });
});


================================================
FILE: .github/scripts/__tests__/post-unlighthouse-commit-comment.test.mts
================================================
import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { writeFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const postReport = require('../post-unlighthouse-commit-comment.js') as (args: {
  github: ReturnType<typeof makeGithub>['github'];
  context: ReturnType<typeof makeContext>;
  reportPath?: string;
}) => Promise<void>;

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

interface Calls {
  createCommitComment: object[];
}

function makeGithub() {
  const calls: Calls = { createCommitComment: [] };
  const github = {
    rest: {
      repos: {
        createCommitComment: async (args: object) => {
          calls.createCommitComment.push(args);
        },
      },
    },
  };
  return { github, calls };
}

function makeContext({
  owner = 'test-owner',
  repo = 'test-repo',
  sha = 'abc123',
  runId = 99,
}: {
  owner?: string;
  repo?: string;
  sha?: string;
  runId?: number;
} = {}) {
  return {
    repo: { owner, repo },
    runId,
    payload: {
      deployment: { sha },
    },
  };
}

let reportPath: string;

beforeEach(() => {
  const tmpDir = join(tmpdir(), `post-unlighthouse-report-test-${Date.now()}`);
  mkdirSync(tmpDir, { recursive: true });
  reportPath = join(tmpDir, 'report.md');
  writeFileSync(reportPath, '## Unlighthouse Audit — `canary`\n\nSome results.');
});

// ---------------------------------------------------------------------------
// Early exits
// ---------------------------------------------------------------------------

describe('early exits', () => {
  it('does nothing when deployment sha is missing', async () => {
    const { github, calls } = makeGithub();
    const context = { ...makeContext(), payload: {} };
    await postReport({ github, context, reportPath });

    assert.equal(calls.createCommitComment.length, 0);
  });
});

// ---------------------------------------------------------------------------
// Commit comment creation
// ---------------------------------------------------------------------------

describe('commit comment creation', () => {
  it('creates a commit comment with the deployment sha', async () => {
    const { github, calls } = makeGithub();
    await postReport({ github, context: makeContext({ sha: 'deadbeef' }), reportPath });

    assert.equal(calls.createCommitComment.length, 1);
    assert.equal(
      (calls.createCommitComment[0] as { commit_sha: string }).commit_sha,
      'deadbeef',
    );
  });

  it('passes the correct owner and repo', async () => {
    const { github, calls } = makeGithub();
    await postReport({
      github,
      context: makeContext({ owner: 'my-org', repo: 'my-repo' }),
      reportPath,
    });

    assert.equal((calls.createCommitComment[0] as { owner: string }).owner, 'my-org');
    assert.equal((calls.createCommitComment[0] as { repo: string }).repo, 'my-repo');
  });
});

// ---------------------------------------------------------------------------
// Comment body
// ---------------------------------------------------------------------------

describe('comment body', () => {
  it('starts with the canary marker', async () => {
    const { github, calls } = makeGithub();
    await postReport({ github, context: makeContext(), reportPath });

    const body = (calls.createCommitComment[0] as { body: string }).body;

    assert.ok(
      body.startsWith('<!-- canary-lighthouse-report -->\n'),
      `Body should start with marker, got: ${body.slice(0, 60)}`,
    );
  });

  it('includes the report file content', async () => {
    const { github, calls } = makeGithub();
    await postReport({ github, context: makeContext(), reportPath });

    const body = (calls.createCommitComment[0] as { body: string }).body;

    assert.ok(body.includes('## Unlighthouse Audit'));
    assert.ok(body.includes('Some results.'));
  });

  it('includes the workflow run link', async () => {
    const { github, calls } = makeGithub();
    await postReport({
      github,
      context: makeContext({ owner: 'my-org', repo: 'my-repo', runId: 12345 }),
      reportPath,
    });

    const body = (calls.createCommitComment[0] as { body: string }).body;

    assert.ok(
      body.includes('https://github.com/my-org/my-repo/actions/runs/12345'),
      'Body should contain the workflow run URL',
    );
  });

  it('run link is preceded by a newline', async () => {
    const { github, calls } = makeGithub();
    await postReport({ github, context: makeContext(), reportPath });

    const body = (calls.createCommitComment[0] as { body: string }).body;
    const linkIndex = body.indexOf('[Full Unlighthouse report');

    assert.ok(linkIndex > 0, 'Run link should be present');
    assert.equal(body[linkIndex - 1], '\n', 'Run link should be preceded by a newline');
  });
});


================================================
FILE: .github/scripts/__tests__/post-unlighthouse-pr-comment.test.mts
================================================
import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { writeFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const postComment = require('../post-unlighthouse-pr-comment.js') as (args: {
  github: ReturnType<typeof makeGithub>['github'];
  context: ReturnType<typeof makeContext>;
  provider?: string;
  reportPath?: string;
  metaPath?: string;
}) => Promise<void>;

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

interface Comment {
  id: number;
  body: string;
}

interface Calls {
  listPrs: object[];
  listComments: object[];
  create: object[];
  update: object[];
}

function makeGithub(opts: { prs?: { number: number }[]; comments?: Comment[] } = {}) {
  const calls: Calls = { listPrs: [], listComments: [], create: [], update: [] };
  const github = {
    rest: {
      repos: {
        listPullRequestsAssociatedWithCommit: async (args: object) => {
          calls.listPrs.push(args);
          return { data: opts.prs ?? [{ number: 42 }] };
        },
      },
      issues: {
        listComments: async (args: object) => {
          calls.listComments.push(args);
          return { data: opts.comments ?? [] };
        },
        createComment: async (args: object) => {
          calls.create.push(args);
        },
        updateComment: async (args: object) => {
          calls.update.push(args);
        },
      },
    },
  };
  return { github, calls };
}

function makeContext({
  owner = 'test-owner',
  repo = 'test-repo',
  sha = 'abc123',
  runId = 99,
}: {
  owner?: string;
  repo?: string;
  sha?: string;
  runId?: number;
} = {}) {
  return {
    repo: { owner, repo },
    runId,
    payload: {
      deployment: { sha },
    },
  };
}

let tmpDir: string;
let reportPath: string;
let metaPath: string;

beforeEach(() => {
  tmpDir = join(tmpdir(), `post-unlighthouse-test-${Date.now()}`);
  mkdirSync(tmpDir, { recursive: true });
  reportPath = join(tmpDir, 'report.md');
  metaPath = join(tmpDir, 'meta.json');
  writeFileSync(reportPath, '## Unlighthouse Performance Comparison\n\nSome results.');
  writeFileSync(metaPath, JSON.stringify({ hasChanges: true }));
});

// ---------------------------------------------------------------------------
// Early exits
// ---------------------------------------------------------------------------

describe('early exits', () => {
  it('does nothing when hasChanges is false', async () => {
    writeFileSync(metaPath, JSON.stringify({ hasChanges: false }));
    const { github, calls } = makeGithub();
    await postComment({ github, context: makeContext(), reportPath, metaPath });

    assert.equal(calls.create.length, 0);
    assert.equal(calls.update.length, 0);
  });

  it('does nothing when deployment sha is missing', async () => {
    const { github, calls } = makeGithub();
    const context = { ...makeContext(), payload: {} };
    await postComment({ github, context, reportPath, metaPath });

    assert.equal(calls.create.length, 0);
    assert.equal(calls.update.length, 0);
  });

  it('does nothing when no PR is associated with the sha', async () => {
    const { github, calls } = makeGithub({ prs: [] });
    await postComment({ github, context: makeContext(), reportPath, metaPath });

    assert.equal(calls.create.length, 0);
    assert.equal(calls.update.length, 0);
  });
});

// ---------------------------------------------------------------------------
// Comment creation
// ---------------------------------------------------------------------------

describe('comment creation', () => {
  it('creates a comment when no existing comment has the marker', async () => {
    const { github, calls } = makeGithub({ comments: [] });
    await postComment({ github, context: makeContext(), reportPath, metaPath });

    assert.equal(calls.create.length, 1);
    assert.equal(calls.update.length, 0);
  });

  it('uses the PR number found from the commit sha', async () => {
    const { github, calls } = makeGithub({ prs: [{ number: 77 }] });
    await postComment({ github, context: makeContext(), reportPath, metaPath });

    assert.equal((calls.create[0] as { issue_number: number }).issue_number, 77);
  });

  it('passes the correct owner and repo', async () => {
    const { github, calls } = makeGithub();
    await postComment({
      github,
      context: makeContext({ owner: 'my-org', repo: 'my-repo' }),
      reportPath,
      metaPath,
    });

    assert.equal((calls.create[0] as { owner: string }).owner, 'my-org');
    assert.equal((calls.create[0] as { repo: string }).repo, 'my-repo');
  });
});

// ---------------------------------------------------------------------------
// Comment update
// ---------------------------------------------------------------------------

describe('comment update', () => {
  it('updates an existing comment that contains the marker', async () => {
    const marker = '<!-- unlighthouse-vercel-report -->';
    const existing = { id: 55, body: `${marker}\nOld content` };
    const { github, calls } = makeGithub({ comments: [existing] });
    await postComment({ github, context: makeContext(), provider: 'vercel', reportPath, metaPath });

    assert.equal(calls.update.length, 1);
    assert.equal(calls.create.length, 0);
    assert.equal((calls.update[0] as { comment_id: number }).comment_id, 55);
  });

  it('creates a new comment when existing comments do not contain the marker', async () => {
    const comments = [
      { id: 1, body: 'unrelated comment' },
      { id: 2, body: '<!-- some-other-marker -->\nOther report' },
    ];
    const { github, calls } = makeGithub({ comments });
    await postComment({ github, context: makeContext(), provider: 'vercel', reportPath, metaPath });

    assert.equal(calls.create.length, 1);
    assert.equal(calls.update.length, 0);
  });
});

// ---------------------------------------------------------------------------
// Comment body
// ---------------------------------------------------------------------------

describe('comment body', () => {
  it('starts with the provider-specific marker', async () => {
    const { github, calls } = makeGithub();
    await postComment({ github, context: makeContext(), provider: 'vercel', reportPath, metaPath });

    const body = (calls.create[0] as { body: string }).body;

    assert.ok(
      body.startsWith('<!-- unlighthouse-vercel-report -->\n'),
      `Body should start with vercel marker, got: ${body.slice(0, 60)}`,
    );
  });

  it('uses provider name in the marker', async () => {
    const { github, calls } = makeGithub();
    await postComment({ github, context: makeContext(), provider: 'cloudflare', reportPath, metaPath });

    const body = (calls.create[0] as { body: string }).body;

    assert.ok(body.includes('<!-- unlighthouse-cloudflare-report -->'));
  });

  it('includes the report file content', async () => {
    const { github, calls } = makeGithub();
    await postComment({ github, context: makeContext(), reportPath, metaPath });

    const body = (calls.create[0] as { body: string }).body;

    assert.ok(body.includes('## Unlighthouse Performance Comparison'));
    assert.ok(body.includes('Some results.'));
  });

  it('includes the workflow run link', async () => {
    const { github, calls } = makeGithub();
    await postComment({
      github,
      context: makeContext({ owner: 'my-org', repo: 'my-repo', runId: 12345 }),
      reportPath,
      metaPath,
    });

    const body = (calls.create[0] as { body: string }).body;

    assert.ok(
      body.includes('https://github.com/my-org/my-repo/actions/runs/12345'),
      'Body should contain the workflow run URL',
    );
  });

  it('run link is not inside a table (preceded by a blank line)', async () => {
    const { github, calls } = makeGithub();
    await postComment({ github, context: makeContext(), reportPath, metaPath });

    const body = (calls.create[0] as { body: string }).body;
    const linkIndex = body.indexOf('[Full Unlighthouse report');

    assert.ok(linkIndex > 0, 'Run link should be present');
    // The character before the link text should be a newline (blank line separator)
    assert.equal(body[linkIndex - 1], '\n', 'Run link should be preceded by a blank line');
  });
});

// ---------------------------------------------------------------------------
// Sha lookup
// ---------------------------------------------------------------------------

describe('sha lookup', () => {
  it('passes the deployment sha to listPullRequestsAssociatedWithCommit', async () => {
    const { github, calls } = makeGithub();
    await postComment({
      github,
      context: makeContext({ sha: 'deadbeef' }),
      reportPath,
      metaPath,
    });

    assert.equal(
      (calls.listPrs[0] as { commit_sha: string }).commit_sha,
      'deadbeef',
    );
  });
});


================================================
FILE: .github/scripts/audit-unlighthouse.mts
================================================
#!/usr/bin/env node
/* eslint-disable no-console, no-restricted-syntax, no-plusplus */

import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";
import { resolve } from "node:path";

interface CiResult {
  summary: {
    score: number;
    categories: Record<string, { score: number }>;
    metrics: Record<string, { displayValue: string }>;
  };
}

function loadCiResult(filePath: string): CiResult {
  if (!existsSync(filePath)) {
    console.error(`Error: file not found: ${filePath}`);
    process.exit(1);
  }

  return JSON.parse(readFileSync(filePath, "utf-8")) as CiResult;
}

function score(value: number): string {
  return String(Math.round(value * 100));
}

function row(label: string, desktop: string, mobile: string): string {
  return `| ${label} | ${desktop} | ${mobile} |`;
}

const CATEGORY_ORDER = ["performance", "accessibility", "best-practices", "seo"];

const CATEGORY_LABELS: Record<string, string> = {
  performance: "Performance",
  accessibility: "Accessibility",
  "best-practices": "Best Practices",
  seo: "SEO",
};

const METRIC_ORDER = [
  "largest-contentful-paint",
  "cumulative-layout-shift",
  "first-contentful-paint",
  "total-blocking-time",
  "max-potential-fid",
  "interactive",
];

const METRIC_LABELS: Record<string, string> = {
  "largest-contentful-paint": "LCP",
  "cumulative-layout-shift": "CLS",
  "first-contentful-paint": "FCP",
  "total-blocking-time": "TBT",
  "max-potential-fid": "Max Potential FID",
  interactive: "Time to Interactive",
};

function buildReport(
  desktop: CiResult,
  mobile: CiResult,
  branch?: string,
): string {
  const branchLabel = branch ? ` — \`${branch}\`` : "";

  const lines: string[] = [];

  lines.push(`## Unlighthouse Audit${branchLabel}`);
  lines.push("Unlighthouse scores for the latest commit on this branch.");
  lines.push("");

  lines.push("### Summary Score");
  lines.push(
    "_Aggregate score across all categories as reported by Unlighthouse._",
  );
  lines.push("");
  lines.push("| | Desktop | Mobile |");
  lines.push("|:-|:--------|:-------|");
  lines.push(row("Score", score(desktop.summary.score), score(mobile.summary.score)));
  lines.push("");

  lines.push("### Category Scores");
  lines.push("");
  lines.push("| Category | Desktop | Mobile |");
  lines.push("|:---------|:--------|:-------|");

  for (const id of CATEGORY_ORDER) {
    lines.push(
      row(
        CATEGORY_LABELS[id] ?? id,
        score(desktop.summary.categories[id]?.score ?? 0),
        score(mobile.summary.categories[id]?.score ?? 0),
      ),
    );
  }

  lines.push("");

  lines.push("### Core Web Vitals");
  lines.push("");
  lines.push("| Metric | Desktop | Mobile |");
  lines.push("|:-------|:--------|:-------|");

  for (const id of METRIC_ORDER) {
    lines.push(
      row(
        METRIC_LABELS[id] ?? id,
        desktop.summary.metrics[id]?.displayValue ?? "—",
        mobile.summary.metrics[id]?.displayValue ?? "—",
      ),
    );
  }

  lines.push("");

  return lines.join("\n");
}

export { buildReport };
export type { CiResult };

const isMain = process.argv[1] === fileURLToPath(import.meta.url);

if (isMain) {
  const { values } = parseArgs({
    options: {
      desktop: { type: "string" },
      mobile: { type: "string" },
      branch: { type: "string" },
      output: { type: "string" },
    },
  });

  if (!values.desktop || !values.mobile) {
    console.error(
      "Usage: report-unlighthouse.mts --desktop <path> --mobile <path> [--branch <name>] [--output <path>]",
    );
    process.exit(1);
  }

  const desktop = loadCiResult(resolve(values.desktop));
  const mobile = loadCiResult(resolve(values.mobile));
  const markdown = buildReport(desktop, mobile, values.branch);
  const outputPath = values.output ? resolve(values.output) : null;

  if (outputPath) {
    writeFileSync(outputPath, markdown);
    console.error(`Unlighthouse report written to ${outputPath}`);
  } else {
    process.stdout.write(markdown);
  }
}


================================================
FILE: .github/scripts/bundle-size.mts
================================================
#!/usr/bin/env node
/* eslint-disable no-console, no-restricted-syntax, no-plusplus, no-continue */

import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";
import { gzipSync } from "node:zlib";

// eslint-disable-next-line no-underscore-dangle
const __dirname = dirname(fileURLToPath(import.meta.url));
const CORE_DIR = resolve(__dirname, "..", "..", "core");

interface ChunkSizes {
  js: number;
  css: number;
}

interface RouteMetric {
  js: number;
  css: number;
  firstLoadJs: number;
}

interface BundleReport {
  commitSha: string;
  updatedAt: string;
  firstLoadJs: number;
  totalJs: number;
  totalCss: number;
  shared?: { js: number; css: number };
  routes?: Record<string, RouteMetric>;
}

interface CompareOptions {
  threshold?: number;
}

function round1(n: number): number {
  return Math.round(n * 10) / 10;
}

const sizeCache = new Map<string, number>();

function clearSizeCache(): void {
  sizeCache.clear();
}

function getGzipSize(filePath: string): number {
  if (sizeCache.has(filePath)) return sizeCache.get(filePath)!;

  if (!existsSync(filePath)) {
    sizeCache.set(filePath, 0);

    return 0;
  }

  const data = readFileSync(filePath);
  const gzipped = gzipSync(data, { level: 6 });
  const sizeKb = gzipped.length / 1024;

  sizeCache.set(filePath, sizeKb);

  return sizeKb;
}

function sumChunkSizes(chunks: Iterable<string>, dir: string): ChunkSizes {
  let js = 0;
  let css = 0;

  for (const chunk of chunks) {
    const size = getGzipSize(join(dir, chunk));

    if (chunk.endsWith(".css")) {
      css += size;
    } else {
      js += size;
    }
  }

  return { js, css };
}

function parseManifestEntries(entries: Record<string, string[]>): {
  layouts: Record<string, string[]>;
  pages: Record<string, string[]>;
} {
  const layouts: Record<string, string[]> = {};
  const pages: Record<string, string[]> = {};

  for (const [route, chunks] of Object.entries(entries)) {
    if (route.endsWith("/layout")) {
      layouts[route] = chunks;
    } else if (route.endsWith("/page")) {
      pages[route] = chunks;
    }
  }

  return { layouts, pages };
}

function computeRootLayout(
  layoutPaths: string[],
  layouts: Record<string, string[]>,
  sharedChunks: Set<string>,
  nextDir: string,
): {
  rootLayoutPath: string | null;
  rootLayoutChunks: Set<string>;
  rootLayoutJs: number;
  rootLayoutCss: number;
} {
  const sorted = [...layoutPaths].sort(
    (a, b) => a.split("/").length - b.split("/").length,
  );
  const rootLayoutPath = sorted[0] ?? null;
  const rootLayoutChunks = new Set<string>();
  let rootLayoutJs = 0;
  let rootLayoutCss = 0;

  if (rootLayoutPath) {
    const uniqueChunks = layouts[rootLayoutPath].filter(
      (c) => !sharedChunks.has(c),
    );
    const sizes = sumChunkSizes(uniqueChunks, nextDir);

    rootLayoutJs = sizes.js;
    rootLayoutCss = sizes.css;
    uniqueChunks.forEach((c) => rootLayoutChunks.add(c));
  }

  return { rootLayoutPath, rootLayoutChunks, rootLayoutJs, rootLayoutCss };
}

function computeRouteMetrics(
  pages: Record<string, string[]>,
  layouts: Record<string, string[]>,
  sharedChunks: Set<string>,
  rootLayoutPath: string | null,
  rootLayoutChunks: Set<string>,
  firstLoadJs: number,
  nextDir: string,
): Record<string, RouteMetric> {
  const routes: Record<string, RouteMetric> = {};

  for (const [route, chunks] of Object.entries(pages)) {
    const segments = route.split("/");

    segments.pop(); // remove 'page'

    const ancestorLayouts: string[] = [];

    for (let i = segments.length; i >= 1; i--) {
      const parentPath = `${segments.slice(0, i).join("/")}/layout`;

      if (layouts[parentPath]) {
        ancestorLayouts.push(parentPath);
      }
    }

    const routeChunks = new Set<string>();

    for (const chunk of chunks.filter((c) => !sharedChunks.has(c))) {
      if (!rootLayoutChunks.has(chunk)) {
        routeChunks.add(chunk);
      }
    }

    for (const layoutPath of ancestorLayouts) {
      if (layoutPath === rootLayoutPath) continue;

      for (const chunk of layouts[layoutPath].filter(
        (c) => !sharedChunks.has(c),
      )) {
        if (!rootLayoutChunks.has(chunk)) {
          routeChunks.add(chunk);
        }
      }
    }

    const sizes = sumChunkSizes(routeChunks, nextDir);

    routes[route] = {
      js: round1(sizes.js),
      css: round1(sizes.css),
      firstLoadJs: round1(firstLoadJs + sizes.js + sizes.css),
    };
  }

  return routes;
}

function readTurbopackEntries(serverAppDir: string): Record<string, string[]> {
  const entries: Record<string, string[]> = {};

  function scanDir(dir: string): void {
    const items = readdirSync(dir, { withFileTypes: true });

    for (const item of items) {
      const fullPath = join(dir, item.name);

      if (item.isDirectory()) {
        scanDir(fullPath);
      } else if (item.name.endsWith("_client-reference-manifest.js")) {
        try {
          const content = readFileSync(fullPath, "utf-8");
          const g: Record<string, unknown> = {};
          // eslint-disable-next-line no-new-func
          const fn = new Function("globalThis", "self", `${content}\nreturn globalThis;`);
          const result = fn(g, g) as {
            __RSC_MANIFEST?: Record<
              string,
              { clientModules?: Record<string, { chunks?: string[] }> }
            >;
          };
          const manifest = result.__RSC_MANIFEST;

          if (!manifest) continue;

          for (const [routeKey, entry] of Object.entries(manifest)) {
            if (!routeKey.endsWith("/page")) continue;

            const chunks = new Set<string>();

            for (const mod of Object.values(entry.clientModules ?? {})) {
              for (const chunk of mod.chunks ?? []) {
                // Normalize: "/_next/static/chunks/xxx.js" → "static/chunks/xxx.js"
                chunks.add(chunk.replace(/^\/_next\//, ""));
              }
            }

            entries[routeKey] = [...chunks];
          }
        } catch {
          // Skip malformed manifest files
        }
      }
    }
  }

  scanDir(serverAppDir);

  return entries;
}

function compareReport(
  baseline: BundleReport,
  current: BundleReport,
  { threshold = 5 }: CompareOptions = {},
): string {
  function hasChanged(base: number, curr: number): boolean {
    if (round1(curr - base) === 0) return false;
    const pct = base > 0 ? ((curr - base) / base) * 100 : null;
    if (pct !== null && round1(pct) === 0) return false;
    return true;
  }

  function formatDelta(base: number, curr: number): string {
    const delta = curr - base;
    const rounded = round1(delta);
    const sign = delta >= 0 ? "+" : "";
    const pct = base > 0 ? (delta / base) * 100 : 0;
    const pctStr = base > 0 ? ` (${sign}${round1(pct)}%)` : "";
    return `${sign}${rounded} kB${pctStr}`;
  }

  function isWarning(base: number, curr: number): boolean {
    const delta = curr - base;
    const pct = base > 0 ? (delta / base) * 100 : 0;

    return delta > 1 && pct > threshold;
  }

  function displayRoute(route: string): string {
    return route.replace(/^\/\[locale\]/, "");
  }

  const lines: string[] = [];

  lines.push("## Bundle Size Report");
  lines.push("");
  lines.push(
    `Comparing against baseline from \`${baseline.commitSha}\` (${baseline.updatedAt}).`,
  );
  lines.push("");

  const changedMetrics = [
    {
      name: "First Load JS",
      base: baseline.firstLoadJs,
      curr: current.firstLoadJs,
    },
    { name: "Total JS", base: baseline.totalJs, curr: current.totalJs },
    { name: "Total CSS", base: baseline.totalCss, curr: current.totalCss },
  ].filter((m) => hasChanged(m.base, m.curr));

  const allRoutes = new Set([
    ...Object.keys(baseline.routes ?? {}),
    ...Object.keys(current.routes ?? {}),
  ]);

  const sortedRoutes = [...allRoutes].sort();
  const routeLines: string[] = [];

  for (const route of sortedRoutes) {
    const display = displayRoute(route);
    const base = baseline.routes?.[route];
    const curr = current.routes?.[route];

    if (!base && curr) {
      routeLines.push(
        `| ${display} | -- | ${round1(curr.firstLoadJs)} kB | ✨ NEW | |`,
      );
    } else if (base && !curr) {
      routeLines.push(
        `| ${display} | ${round1(base.firstLoadJs)} kB | -- | REMOVED | |`,
      );
    } else if (base && curr && hasChanged(base.firstLoadJs, curr.firstLoadJs)) {
      const d = formatDelta(base.firstLoadJs, curr.firstLoadJs);
      const warn = isWarning(base.firstLoadJs, curr.firstLoadJs) ? " ⚠️" : "";

      routeLines.push(
        `| ${display} | ${round1(base.firstLoadJs)} kB | ${round1(curr.firstLoadJs)} kB | ${d} |${warn} |`,
      );
    }
  }

  if (changedMetrics.length === 0 && routeLines.length === 0) {
    lines.push("No bundle size changes detected.");
    lines.push("");
    return lines.join("\n");
  }

  if (changedMetrics.length > 0) {
    lines.push("| Metric | Baseline | Current | Delta | |");
    lines.push("|:-------|:---------|:--------|:------|:-|");

    for (const m of changedMetrics) {
      const d = formatDelta(m.base, m.curr);
      const warn = isWarning(m.base, m.curr) ? " ⚠️" : "";

      lines.push(
        `| ${m.name} | ${round1(m.base)} kB | ${round1(m.curr)} kB | ${d} |${warn} |`,
      );
    }

    lines.push("");
  }

  lines.push("### Per-Route First Load JS");
  lines.push("");

  if (routeLines.length > 0) {
    lines.push("| Route | Baseline | Current | Delta | |");
    lines.push("|:------|:---------|:--------|:------|:-|");
    lines.push(...routeLines);
    lines.push("");
    lines.push(
      `> Threshold: ${threshold}% increase. Routes with ⚠️ exceed the threshold.`,
    );
  } else {
    lines.push("_No route changes detected._");
  }

  lines.push("");

  return lines.join("\n");
}

function generate(
  nextDir: string,
  values: Record<string, string | undefined>,
): void {
  const appManifestPath = join(nextDir, "app-build-manifest.json");
  const buildManifestPath = join(nextDir, "build-manifest.json");
  const serverAppDir = join(nextDir, "server", "app");

  const isWebpack = existsSync(appManifestPath);
  const isTurbopack = !isWebpack && existsSync(serverAppDir);

  if (!isWebpack && !isTurbopack) {
    console.error(
      "Error: No build output found (.next/app-build-manifest.json or .next/server/app/). Run `next build` first.",
    );
    process.exit(1);
  }

  const buildManifest = JSON.parse(
    readFileSync(buildManifestPath, "utf-8"),
  ) as {
    rootMainFiles?: string[];
    polyfillFiles?: string[];
  };

  const rootMainFiles = new Set(buildManifest.rootMainFiles ?? []);
  const polyfillFiles = new Set(buildManifest.polyfillFiles ?? []);
  const sharedChunks = new Set([...rootMainFiles, ...polyfillFiles]);

  let entries: Record<string, string[]>;

  if (isWebpack) {
    const appManifest = JSON.parse(readFileSync(appManifestPath, "utf-8")) as {
      pages?: Record<string, string[]>;
    };

    entries = appManifest.pages ?? {};
  } else {
    entries = readTurbopackEntries(serverAppDir);
  }
  const { layouts, pages } = parseManifestEntries(entries);

  // Shared JS = sum of rootMainFiles gzipped sizes
  const sharedSizes = sumChunkSizes(rootMainFiles, nextDir);
  const sharedJs = round1(sharedSizes.js);

  // Root layout
  const { rootLayoutPath, rootLayoutChunks, rootLayoutJs, rootLayoutCss } =
    computeRootLayout(Object.keys(layouts), layouts, sharedChunks, nextDir);

  const sharedCss = round1(rootLayoutCss);
  const firstLoadJs = round1(sharedJs + rootLayoutJs + rootLayoutCss);

  // Total JS and CSS across all unique chunks
  const allChunksSet = new Set<string>();

  for (const chunks of Object.values(entries)) {
    for (const chunk of chunks) {
      allChunksSet.add(chunk);
    }
  }

  const totals = sumChunkSizes(allChunksSet, nextDir);
  const totalJs = round1(totals.js);
  const totalCss = round1(totals.css);

  // Per-route metrics
  const routes = computeRouteMetrics(
    pages,
    layouts,
    sharedChunks,
    rootLayoutPath,
    rootLayoutChunks,
    firstLoadJs,
    nextDir,
  );

  const result: BundleReport = {
    commitSha: values.sha ?? "unknown",
    updatedAt: new Date().toISOString().split("T")[0],
    firstLoadJs,
    shared: { js: sharedJs, css: sharedCss },
    routes,
    totalJs,
    totalCss,
  };

  const output = values.output ?? null;
  const json = `${JSON.stringify(result, null, 2)}\n`;

  if (output) {
    writeFileSync(resolve(output), json);
    console.error(`Bundle size report written to ${output}`);
  } else {
    process.stdout.write(json);
  }
}

function compare(
  nextDir: string,
  values: Record<string, string | undefined>,
): void {
  const baselinePath = resolve(
    values.baseline ?? join(CORE_DIR, "bundle-baseline.json"),
  );
  const currentPath = resolve(values.current ?? "");
  const threshold = Number(values.threshold ?? "5");

  if (!currentPath || !existsSync(currentPath)) {
    console.error("Error: --current <path> is required and must exist");
    process.exit(1);
  }

  if (!existsSync(baselinePath)) {
    console.error(`Error: baseline not found at ${baselinePath}`);
    process.exit(1);
  }

  const baseline = JSON.parse(
    readFileSync(baselinePath, "utf-8"),
  ) as BundleReport;
  const current = JSON.parse(
    readFileSync(currentPath, "utf-8"),
  ) as BundleReport;

  process.stdout.write(compareReport(baseline, current, { threshold }));
}

export {
  round1,
  getGzipSize,
  sumChunkSizes,
  parseManifestEntries,
  computeRootLayout,
  computeRouteMetrics,
  compareReport,
  clearSizeCache,
  readTurbopackEntries,
};

export type { BundleReport, RouteMetric, ChunkSizes, CompareOptions };

const isMain = process.argv[1] === fileURLToPath(import.meta.url);

if (isMain) {
  const { values, positionals } = parseArgs({
    allowPositionals: true,
    options: {
      output: { type: "string" },
      baseline: { type: "string" },
      current: { type: "string" },
      threshold: { type: "string" },
      sha: { type: "string" },
      dir: { type: "string" },
    },
  });

  const NEXT_DIR = values.dir ? resolve(values.dir) : join(CORE_DIR, ".next");
  const command = positionals.at(0);

  if (command === "generate") {
    generate(NEXT_DIR, values);
  } else if (command === "compare") {
    compare(NEXT_DIR, values);
  } else {
    console.error("Usage: bundle-size.mts <generate|compare> [options]");
    console.error("");
    console.error("Commands:");
    console.error(
      "  generate  Analyze .next/ build output and produce bundle size JSON",
    );
    console.error("    --output <path>  Write JSON to file instead of stdout");
    console.error("");
    console.error("  compare   Compare current bundle against a baseline");
    console.error(
      "    --baseline <path>  Path to baseline JSON (default: ./bundle-baseline.json)",
    );
    console.error(
      "    --current <path>   Path to current bundle JSON (required)",
    );
    console.error(
      "    --threshold <n>    Warning threshold percentage (default: 5)",
    );
    process.exit(1);
  }
}


================================================
FILE: .github/scripts/compare-unlighthouse.mts
================================================
#!/usr/bin/env node
/* eslint-disable no-console, no-restricted-syntax, no-plusplus, no-continue */

import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";
import { resolve } from "node:path";

interface CiResult {
  summary: {
    score: number;
    categories: Record<string, { score: number }>;
    metrics: Record<string, { displayValue: string }>;
  };
}

function loadCiResult(filePath: string): CiResult {
  if (!existsSync(filePath)) {
    console.error(`Error: file not found: ${filePath}`);
    process.exit(1);
  }

  return JSON.parse(readFileSync(filePath, "utf-8")) as CiResult;
}

function score(value: number): string {
  return String(Math.round(value * 100));
}

function row(
  label: string,
  prodDesktop: string,
  prodMobile: string,
  prevDesktop: string,
  prevMobile: string,
): string {
  return `| ${label} | ${prodDesktop} | ${prodMobile} | ${prevDesktop} | ${prevMobile} |`;
}

const CATEGORY_ORDER = ["performance", "accessibility", "best-practices", "seo"];

const CATEGORY_LABELS: Record<string, string> = {
  performance: "Performance",
  accessibility: "Accessibility",
  "best-practices": "Best Practices",
  seo: "SEO",
};

const METRIC_ORDER = [
  "largest-contentful-paint",
  "cumulative-layout-shift",
  "first-contentful-paint",
  "total-blocking-time",
  "max-potential-fid",
  "interactive",
];

const METRIC_LABELS: Record<string, string> = {
  "largest-contentful-paint": "LCP",
  "cumulative-layout-shift": "CLS",
  "first-contentful-paint": "FCP",
  "total-blocking-time": "TBT",
  "max-potential-fid": "Max Potential FID",
  interactive: "Time to Interactive",
};

const COL_HEADER =
  "| | Prod Desktop | Prod Mobile | Preview Desktop | Preview Mobile |";
const COL_SEP =
  "|:-|:------------|:------------|:----------------|:---------------|";

function compareResults(
  productionDesktop: CiResult,
  productionMobile: CiResult,
  previewDesktop: CiResult,
  previewMobile: CiResult,
  threshold: number,
  provider?: string,
): { markdown: string; hasChanges: boolean } {
  const thresholdDecimal = threshold / 100;

  // hasChanges: any summary or category score pair differs by >= threshold
  let hasChanges =
    Math.abs(previewDesktop.summary.score - productionDesktop.summary.score) >=
      thresholdDecimal ||
    Math.abs(previewMobile.summary.score - productionMobile.summary.score) >=
      thresholdDecimal;

  if (!hasChanges) {
    for (const id of CATEGORY_ORDER) {
      const deltaDesktop = Math.abs(
        (previewDesktop.summary.categories[id]?.score ?? 0) -
          (productionDesktop.summary.categories[id]?.score ?? 0),
      );
      const deltaMobile = Math.abs(
        (previewMobile.summary.categories[id]?.score ?? 0) -
          (productionMobile.summary.categories[id]?.score ?? 0),
      );

      if (deltaDesktop >= thresholdDecimal || deltaMobile >= thresholdDecimal) {
        hasChanges = true;
        break;
      }
    }
  }

  const lines: string[] = [];

  const providerLabel = provider
    ? ` — ${provider.charAt(0).toUpperCase()}${provider.slice(1)}`
    : "";

  lines.push(`## Unlighthouse Performance Comparison${providerLabel}`);
  lines.push(
    "Comparing PR preview deployment Unlighthouse scores vs production Unlighthouse scores.",
  );
  lines.push("");

  lines.push("### Summary Score");
  lines.push(
    "_Aggregate score across all categories as reported by Unlighthouse._",
  );
  lines.push("");
  lines.push(COL_HEADER);
  lines.push(COL_SEP);
  lines.push(
    row(
      "Score",
      score(productionDesktop.summary.score),
      score(productionMobile.summary.score),
      score(previewDesktop.summary.score),
      score(previewMobile.summary.score),
    ),
  );
  lines.push("");

  lines.push("### Category Scores");
  lines.push("");
  lines.push(
    "| Category | Prod Desktop | Prod Mobile | Preview Desktop | Preview Mobile |",
  );
  lines.push(
    "|:---------|:------------|:------------|:----------------|:---------------|",
  );

  for (const id of CATEGORY_ORDER) {
    lines.push(
      row(
        CATEGORY_LABELS[id] ?? id,
        score(productionDesktop.summary.categories[id]?.score ?? 0),
        score(productionMobile.summary.categories[id]?.score ?? 0),
        score(previewDesktop.summary.categories[id]?.score ?? 0),
        score(previewMobile.summary.categories[id]?.score ?? 0),
      ),
    );
  }

  lines.push("");

  lines.push("### Core Web Vitals");
  lines.push("");
  lines.push(
    "| Metric | Prod Desktop | Prod Mobile | Preview Desktop | Preview Mobile |",
  );
  lines.push(
    "|:-------|:------------|:------------|:----------------|:---------------|",
  );

  for (const id of METRIC_ORDER) {
    lines.push(
      row(
        METRIC_LABELS[id] ?? id,
        productionDesktop.summary.metrics[id]?.displayValue ?? "—",
        productionMobile.summary.metrics[id]?.displayValue ?? "—",
        previewDesktop.summary.metrics[id]?.displayValue ?? "—",
        previewMobile.summary.metrics[id]?.displayValue ?? "—",
      ),
    );
  }

  lines.push("");

  return { markdown: lines.join("\n"), hasChanges };
}

export { compareResults };
export type { CiResult };

const isMain = process.argv[1] === fileURLToPath(import.meta.url);

if (isMain) {
  const { values } = parseArgs({
    options: {
      "preview-desktop": { type: "string" },
      "preview-mobile": { type: "string" },
      "production-desktop": { type: "string" },
      "production-mobile": { type: "string" },
      output: { type: "string" },
      "meta-output": { type: "string" },
      threshold: { type: "string" },
      provider: { type: "string" },
    },
  });

  const previewDesktopPath = values["preview-desktop"] ?? "";
  const previewMobilePath = values["preview-mobile"] ?? "";
  const productionDesktopPath = values["production-desktop"] ?? "";
  const productionMobilePath = values["production-mobile"] ?? "";

  if (
    !previewDesktopPath ||
    !previewMobilePath ||
    !productionDesktopPath ||
    !productionMobilePath
  ) {
    console.error(
      "Usage: compare-unlighthouse.mts --preview-desktop <path> --preview-mobile <path> --production-desktop <path> --production-mobile <path> [--output <path>] [--meta-output <path>] [--threshold <n>] [--provider <name>]",
    );
    process.exit(1);
  }

  const threshold = Number(values.threshold ?? "1");

  const previewDesktop = loadCiResult(resolve(previewDesktopPath));
  const previewMobile = loadCiResult(resolve(previewMobilePath));
  const productionDesktop = loadCiResult(resolve(productionDesktopPath));
  const productionMobile = loadCiResult(resolve(productionMobilePath));

  const { markdown, hasChanges } = compareResults(
    productionDesktop,
    productionMobile,
    previewDesktop,
    previewMobile,
    threshold,
    values.provider,
  );

  const outputPath = values.output ? resolve(values.output) : null;
  const metaOutputPath = values["meta-output"]
    ? resolve(values["meta-output"])
    : null;

  if (outputPath) {
    writeFileSync(outputPath, markdown);
    console.error(`Unlighthouse comparison report written to ${outputPath}`);
  } else {
    process.stdout.write(markdown);
  }

  if (metaOutputPath) {
    writeFileSync(
      metaOutputPath,
      `${JSON.stringify({ hasChanges }, null, 2)}\n`,
    );
    console.error(`Meta output written to ${metaOutputPath}`);
  }
}


================================================
FILE: .github/scripts/post-bundle-comment.js
================================================
const fs = require('fs');

module.exports = async ({ github, context, reportPath = '/tmp/bundle-report.md' }) => {
  const marker = '<!-- bundle-size-report -->';
  const body = marker + '\n' + fs.readFileSync(reportPath, 'utf-8');

  const { data: comments } = await github.rest.issues.listComments({
    owner: context.repo.owner,
    repo: context.repo.repo,
    issue_number: context.issue.number,
  });

  const existing = comments.find(c => c.body.includes(marker));

  if (existing) {
    await github.rest.issues.updateComment({
      owner: context.repo.owner,
      repo: context.repo.repo,
      comment_id: existing.id,
      body,
    });
  } else {
    await github.rest.issues.createComment({
      owner: context.repo.owner,
      repo: context.repo.repo,
      issue_number: context.issue.number,
      body,
    });
  }
};


================================================
FILE: .github/scripts/post-unlighthouse-commit-comment.js
================================================
const fs = require('fs');

module.exports = async ({ github, context, reportPath = '/tmp/unlighthouse-report.md' }) => {
  const sha = context.payload.deployment?.sha;

  if (!sha) return;

  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
  const marker = `<!-- canary-lighthouse-report -->`;
  const body = marker + '\n' + fs.readFileSync(reportPath, 'utf-8') + `\n[Full Unlighthouse report →](${runUrl})\n`;

  await github.rest.repos.createCommitComment({
    owner: context.repo.owner,
    repo: context.repo.repo,
    commit_sha: sha,
    body,
  });
};


================================================
FILE: .github/scripts/post-unlighthouse-pr-comment.js
================================================
const fs = require('fs');

module.exports = async ({ github, context, provider = 'unknown', reportPath = '/tmp/unlighthouse-report.md', metaPath = '/tmp/unlighthouse-meta.json' }) => {
  // Exit early if no changes
  const { hasChanges } = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));

  if (!hasChanges) return;

  // Find PR from commit SHA (context.issue.number is 0 in deployment_status events;
  // deployment.ref is also the SHA in Vercel deployments, not the branch name)
  const sha = context.payload.deployment?.sha;

  if (!sha) return;

  const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
    owner: context.repo.owner,
    repo: context.repo.repo,
    commit_sha: sha,
  });

  const prNumber = prs[0]?.number;

  if (!prNumber) return;

  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
  const marker = `<!-- unlighthouse-${provider}-report -->`;
  const body = marker + '\n' + fs.readFileSync(reportPath, 'utf-8') + `\n[Full Unlighthouse report →](${runUrl})\n`;

  const { data: comments } = await github.rest.issues.listComments({
    owner: context.repo.owner,
    repo: context.repo.repo,
    issue_number: prNumber,
  });

  const existing = comments.find(c => c.body.includes(marker));

  if (existing) {
    await github.rest.issues.updateComment({
      owner: context.repo.owner,
      repo: context.repo.repo,
      comment_id: existing.id,
      body,
    });
  } else {
    await github.rest.issues.createComment({
      owner: context.repo.owner,
      repo: context.repo.repo,
      issue_number: prNumber,
      body,
    });
  }
};


================================================
FILE: .github/scripts/prevent-invalid-changesets.js
================================================
const fs = require("fs");

module.exports = async ({ core, exec }) => {
  try {
    await exec.exec("git", [
      "fetch",
      "https://github.com/bigcommerce/catalyst.git",
      "integrations/makeswift",
    ]);

    const { stdout } = await exec.getExecOutput("git", [
      "diff",
      "--name-only",
      `origin/integrations/makeswift...HEAD`,
    ]);

    const allFilenames = stdout.split("\n").filter((line) => line.trim());
    const changesetFilenames = allFilenames.filter(
      (file) => file.startsWith(".changeset/") && file.endsWith(".md"),
    );

    if (changesetFilenames.length === 0) {
      core.info("No changeset files found to validate");
      return;
    }

    core.info(`Found ${changesetFilenames.length} changeset files to validate`);

    for (const filename of changesetFilenames) {
      core.info(`Checking ${filename}...`);

      // .changeset/*.md filenames should only contain alphanumeric characters, hyphens, and underscores
      if (!/^\.changeset\/[a-zA-Z0-9_-]+\.md$/.test(filename)) {
        core.setFailed(`Invalid filename pattern: ${filename}`);
        return;
      }

      // extra defense against path traversal attacks
      if (
        filename.includes("..") ||
        (filename.includes("/") && !filename.startsWith(".changeset/"))
      ) {
        core.setFailed(`Suspicious file path: ${filename}`);
        return;
      }

      if (!fs.existsSync(filename)) {
        core.warning(
          `File not found: ${filename}. This is likely a version PR where the changeset was already consumed. Skipping validation for this file.`,
        );
        continue;
      }

      // check file size (limit to 100KB)
      const stats = fs.statSync(filename);
      if (stats.size > 102400) {
        core.error(`File too large`, { file: filename });
        core.setFailed(`File ${filename} is too large`);
        return;
      }

      if (stats.isSymbolicLink()) {
        core.error(`Symlinks are not allowed`, { file: filename });
        core.setFailed(`File ${filename} is a symlink`);
        return;
      }

      const content = fs.readFileSync(filename, "utf8");

      // starts with "---", captures everything until the next "---"
      const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);

      if (!frontmatterMatch) {
        core.error(`Failed to extract frontmatter or file has no frontmatter`, {
          file: filename,
        });
        core.setFailed(`File ${filename} has invalid or missing frontmatter`);
        return;
      }

      const frontmatter = frontmatterMatch[1];

      // extract all packages starting with "@bigcommerce/
      const packageMatches = frontmatter.match(/"@bigcommerce\/[^"]+"/g);

      if (packageMatches) {
        const invalidPackages = packageMatches.filter(
          (pkg) => pkg !== '"@bigcommerce/catalyst-makeswift"',
        );

        if (invalidPackages.length > 0) {
          core.error(
            `Invalid package found in changeset file. Only @bigcommerce/catalyst-makeswift is allowed.`,
            { file: filename },
          );
          core.setFailed(
            `File ${filename} contains invalid packages: ${invalidPackages.join(
              ", ",
            )}`,
          );
          return;
        }
      }
    }

    core.info("All changeset files validated successfully");
  } catch (error) {
    core.setFailed(`Validation failed: ${error.message}`);
  }
};


================================================
FILE: .github/workflows/basic.yml
================================================
name: Basic

on:
  push:
    branches: [canary, integrations/makeswift, integrations/b2b-makeswift]
  pull_request:
    types: [opened, synchronize]
  merge_group:
    types: [checks_requested]

env:
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: ${{ vars.TURBO_TEAM }}
  TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
  BIGCOMMERCE_STORE_HASH: ${{ vars.BIGCOMMERCE_STORE_HASH }}
  BIGCOMMERCE_CHANNEL_ID: ${{ vars.BIGCOMMERCE_CHANNEL_ID }}
  BIGCOMMERCE_CLIENT_ID: ${{ secrets.BIGCOMMERCE_CLIENT_ID }}
  BIGCOMMERCE_CLIENT_SECRET: ${{ secrets.BIGCOMMERCE_CLIENT_SECRET }}
  BIGCOMMERCE_STOREFRONT_TOKEN: ${{ secrets.BIGCOMMERCE_STOREFRONT_TOKEN }}
  BIGCOMMERCE_ACCESS_TOKEN: ${{ secrets.BIGCOMMERCE_ACCESS_TOKEN }}
  TEST_CUSTOMER_ID: ${{ vars.TEST_CUSTOMER_ID }}
  TEST_CUSTOMER_EMAIL: ${{ vars.TEST_CUSTOMER_EMAIL }}
  TEST_CUSTOMER_PASSWORD: ${{ secrets.TEST_CUSTOMER_PASSWORD }}
  TESTS_FALLBACK_LOCALE: ${{ vars.TESTS_FALLBACK_LOCALE }}
  TESTS_READ_ONLY: ${{ vars.TESTS_READ_ONLY }}
  DEFAULT_PRODUCT_ID: ${{ vars.DEFAULT_PRODUCT_ID }}
  DEFAULT_COMPLEX_PRODUCT_ID: ${{ vars.DEFAULT_COMPLEX_PRODUCT_ID }}

jobs:
  lint-typecheck:
    name: Lint, Typecheck, and gql.tada

    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - uses: pnpm/action-setup@v3

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"
          cache: "pnpm"

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Gql Tada
        run: pnpm run -r generate

      - name: Lint
        run: pnpm run lint -- --max-warnings=0

      - name: Typecheck
        run: pnpm run typecheck

  cli-tests:
    name: CLI Tests

    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    runs-on: ${{ matrix.os }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - uses: pnpm/action-setup@v3

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"
          cache: "pnpm"

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Run Tests
        run: pnpm run test


================================================
FILE: .github/workflows/bundle-size.yml
================================================
name: Bundle Size
# Reports the bundle size impact of a PR by comparing the current build against
# a live build of the base branch (canary or integrations/makeswift).
#
# build-pr and build-baseline run in parallel, each uploading a JSON artifact.
# compare downloads both artifacts, runs the comparison, and posts the PR comment.

on:
  pull_request:
    types: [opened, synchronize]

env:
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: ${{ vars.TURBO_TEAM }}
  TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
  BIGCOMMERCE_STORE_HASH: ${{ vars.BIGCOMMERCE_STORE_HASH }}
  BIGCOMMERCE_CHANNEL_ID: ${{ vars.BIGCOMMERCE_CHANNEL_ID }}
  BIGCOMMERCE_CLIENT_ID: ${{ secrets.BIGCOMMERCE_CLIENT_ID }}
  BIGCOMMERCE_CLIENT_SECRET: ${{ secrets.BIGCOMMERCE_CLIENT_SECRET }}
  BIGCOMMERCE_STOREFRONT_TOKEN: ${{ secrets.BIGCOMMERCE_STOREFRONT_TOKEN }}
  BIGCOMMERCE_ACCESS_TOKEN: ${{ secrets.BIGCOMMERCE_ACCESS_TOKEN }}

jobs:
  build-pr:
    name: Build & Measure PR Bundle
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          path: pr

      - uses: pnpm/action-setup@v4
        with:
          package_json_file: pr/package.json

      - uses: actions/setup-node@v4
        with:
          node-version-file: pr/.nvmrc
          cache: pnpm
          cache-dependency-path: pr/pnpm-lock.yaml

      - run: pnpm install --frozen-lockfile
        working-directory: pr

      - run: pnpm build
        working-directory: pr

      - run: node .github/scripts/bundle-size.mts generate --output /tmp/bundle-current.json --sha ${{ github.sha }}
        working-directory: pr

      - uses: actions/upload-artifact@v4
        with:
          name: bundle-current
          path: /tmp/bundle-current.json

  build-baseline:
    name: Build & Measure Baseline Bundle
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          path: pr

      - name: Detect baseline branch
        id: baseline
        run: |
          PKG_NAME=$(node -p "require('./pr/core/package.json').name")
          if [ "$PKG_NAME" = "@bigcommerce/catalyst-makeswift" ]; then
            echo "branch=integrations/makeswift" >> $GITHUB_OUTPUT
          else
            echo "branch=canary" >> $GITHUB_OUTPUT
          fi

      - uses: actions/checkout@v4
        with:
          ref: ${{ steps.baseline.outputs.branch }}
          path: baseline

      - uses: pnpm/action-setup@v4
        with:
          package_json_file: pr/package.json

      - uses: actions/setup-node@v4
        with:
          node-version-file: pr/.nvmrc
          cache: pnpm
          cache-dependency-path: baseline/pnpm-lock.yaml

      - run: pnpm install --frozen-lockfile
        working-directory: baseline

      - run: pnpm build
        working-directory: baseline

      - name: Generate baseline bundle size
        run: |
          SHA=$(git -C $GITHUB_WORKSPACE/baseline rev-parse --short HEAD)
          node .github/scripts/bundle-size.mts generate --dir $GITHUB_WORKSPACE/baseline/core/.next --output /tmp/bundle-baseline.json --sha $SHA
        working-directory: pr

      - uses: actions/upload-artifact@v4
        with:
          name: bundle-baseline
          path: /tmp/bundle-baseline.json

  compare:
    name: Compare Bundles & Post Report
    needs: [build-pr, build-baseline]
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          path: pr

      - uses: actions/setup-node@v4
        with:
          node-version-file: pr/.nvmrc

      - uses: actions/download-artifact@v4
        with:
          pattern: bundle-*
          path: /tmp
          merge-multiple: true

      - run: node .github/scripts/bundle-size.mts compare --baseline /tmp/bundle-baseline.json --current /tmp/bundle-current.json > /tmp/bundle-report.md
        working-directory: pr

      - run: cat /tmp/bundle-report.md >> "$GITHUB_STEP_SUMMARY"

      - uses: actions/github-script@v7
        with:
          script: |
            const postComment = require('./pr/.github/scripts/post-bundle-comment.js')
            await postComment({ github, context })


================================================
FILE: .github/workflows/changesets-release.yml
================================================
name: Changesets Release

on:
  push:
    branches:
      - canary
      - integrations/makeswift

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
  id-token: write
  contents: write
  packages: write
  pull-requests: write

jobs:
  changesets-release:
    name: Changesets Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v3

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"
          cache: "pnpm"

      - name: Install Dependencies
        run: pnpm install --frozen-lockfile

      - name: Build Packages
        run: pnpm --filter "./packages/**" build
        env:
          CLI_SEGMENT_WRITE_KEY: ${{ secrets.CLI_SEGMENT_WRITE_KEY }}

      - name: Create Release Pull Request or Publish to npm
        id: changesets
        uses: changesets/action@v1
        with:
          publish: pnpm exec changeset publish
          title: "Version Packages (`${{ github.ref_name }}`)"
          commit: "Version Packages (`${{ github.ref_name }}`)"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/deploy.yml
================================================
name: Production Tag Deployment
env:
    # secrets is for dependabot compatibility; prefer vars when available
    VERCEL_ORG_ID: ${{ vars.VERCEL_ORG_ID != '' && vars.VERCEL_ORG_ID || secrets.VERCEL_ORG_ID }}
    VERCEL_PROJECT_ID: ${{ vars.VERCEL_PROJECT_ID != '' && vars.VERCEL_PROJECT_ID || secrets.VERCEL_PROJECT_ID }}
on:
    push:
        tags:
            - "@bigcommerce/catalyst-core@latest"
            - "@bigcommerce/catalyst-makeswift@latest"
            - "@bigcommerce/catalyst-b2b-makeswift@latest"
jobs:
    deploy-tag:
        name: Deploy `${{ github.ref_name }}` tag
        runs-on: ubuntu-latest
        concurrency:
            group: ${{ github.workflow }}-${{ github.ref_name }}
        steps:
            - uses: actions/checkout@v4

            - name: Install Vercel CLI
              run: npm install --global vercel@latest

            - name: Configure catalyst-core deployment
              if: contains(github.ref_name, 'catalyst-core@')
              run: |
                  echo "DOMAIN=catalyst-demo.site" >> $GITHUB_ENV
                  echo "CHANNEL_ID=${{ vars.CORE_BIGCOMMERCE_CHANNEL_ID }}" >> $GITHUB_ENV
                  echo "STOREFRONT_TOKEN=${{ secrets.CORE_BIGCOMMERCE_STOREFRONT_TOKEN }}" >> $GITHUB_ENV

            - name: Configure catalyst-makeswift deployment
              if: contains(github.ref_name, 'catalyst-makeswift@')
              run: |
                  echo "DOMAIN=makeswift.catalyst-demo.site" >> $GITHUB_ENV
                  echo "CHANNEL_ID=${{ vars.MAKESWIFT_BIGCOMMERCE_CHANNEL_ID }}" >> $GITHUB_ENV
                  echo "STOREFRONT_TOKEN=${{ secrets.MAKESWIFT_BIGCOMMERCE_STOREFRONT_TOKEN }}" >> $GITHUB_ENV
                  echo "MAKESWIFT_KEY=${{ secrets.MAKESWIFT_SITE_API_KEY }}" >> $GITHUB_ENV

            - name: Configure catalyst-b2b-makeswift deployment
              if: contains(github.ref_name, 'catalyst-b2b-makeswift@')
              run: |
                  echo "DOMAIN=b2b-makeswift.catalyst-demo.site" >> $GITHUB_ENV
                  echo "CHANNEL_ID=${{ vars.B2B_MAKESWIFT_BIGCOMMERCE_CHANNEL_ID }}" >> $GITHUB_ENV
                  echo "STOREFRONT_TOKEN=${{ secrets.B2B_MAKESWIFT_BIGCOMMERCE_STOREFRONT_TOKEN }}" >> $GITHUB_ENV
                  echo "MAKESWIFT_KEY=${{ secrets.B2B_MAKESWIFT_SITE_API_KEY }}" >> $GITHUB_ENV
                  echo "B2B_API_HOST=${{ vars.B2B_API_HOST }}" >> $GITHUB_ENV
                  echo "BIGCOMMERCE_ACCESS_TOKEN=${{ secrets.B2B_BIGCOMMERCE_ACCESS_TOKEN }}" >> $GITHUB_ENV

            - name: Deploy to Vercel
              id: deploy
              timeout-minutes: 15
              env:
                  VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
              run: |
                  DEPLOY_ARGS=(
                    --token="$VERCEL_TOKEN"
                    --env BIGCOMMERCE_CHANNEL_ID="$CHANNEL_ID"
                    --env BIGCOMMERCE_STOREFRONT_TOKEN="$STOREFRONT_TOKEN"
                  )

                  if [[ -n "$MAKESWIFT_KEY" ]]; then
                    DEPLOY_ARGS+=(--env MAKESWIFT_SITE_API_KEY="$MAKESWIFT_KEY")
                  fi

                  if [[ -n "$B2B_API_HOST" ]]; then
                    DEPLOY_ARGS+=(--env B2B_API_HOST="$B2B_API_HOST")
                  fi

                  if [[ -n "$BIGCOMMERCE_ACCESS_TOKEN" ]]; then
                    DEPLOY_ARGS+=(--env BIGCOMMERCE_ACCESS_TOKEN="$BIGCOMMERCE_ACCESS_TOKEN")
                  fi

                  DEPLOYMENT_URL=$(vercel deploy --scope="${{ vars.VERCEL_TEAM_SLUG }}" "${DEPLOY_ARGS[@]}")
                  echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT

            - name: Set Vercel Domain Alias
              timeout-minutes: 5
              env:
                  VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
              run: |
                  vercel alias ${{ steps.deploy.outputs.deployment_url }} $DOMAIN --scope="${{ vars.VERCEL_TEAM_SLUG }}" --token="$VERCEL_TOKEN"


================================================
FILE: .github/workflows/e2e.yml
================================================
name: E2E Tests

on:
  pull_request:
    types: [opened, synchronize]
    branches: [canary, integrations/makeswift, integrations/b2b-makeswift]

env:
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: ${{ vars.TURBO_TEAM }}
  TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
  BIGCOMMERCE_STORE_HASH: ${{ vars.BIGCOMMERCE_STORE_HASH }}
  BIGCOMMERCE_CHANNEL_ID: ${{ vars.BIGCOMMERCE_CHANNEL_ID }}
  BIGCOMMERCE_CLIENT_ID: ${{ secrets.BIGCOMMERCE_CLIENT_ID }}
  BIGCOMMERCE_CLIENT_SECRET: ${{ secrets.BIGCOMMERCE_CLIENT_SECRET }}
  BIGCOMMERCE_STOREFRONT_TOKEN: ${{ secrets.BIGCOMMERCE_STOREFRONT_TOKEN }}
  BIGCOMMERCE_ACCESS_TOKEN: ${{ secrets.BIGCOMMERCE_ACCESS_TOKEN }}
  TEST_CUSTOMER_ID: ${{ vars.TEST_CUSTOMER_ID }}
  TEST_CUSTOMER_EMAIL: ${{ vars.TEST_CUSTOMER_EMAIL }}
  TEST_CUSTOMER_PASSWORD: ${{ secrets.TEST_CUSTOMER_PASSWORD }}
  TESTS_FALLBACK_LOCALE: ${{ vars.TESTS_FALLBACK_LOCALE }}
  TESTS_READ_ONLY: ${{ vars.TESTS_READ_ONLY }}
  DEFAULT_PRODUCT_ID: ${{ vars.DEFAULT_PRODUCT_ID }}
  DEFAULT_COMPLEX_PRODUCT_ID: ${{ vars.DEFAULT_COMPLEX_PRODUCT_ID }}

jobs:
  e2e-tests:
    name: E2E Functional Tests (${{ matrix.name }})

    runs-on: ubuntu-latest

    strategy:
      matrix:
        include:
          - name: default
            browsers: chromium webkit
            test-filter: tests/ui/e2e
            trailing-slash: true
            locale-var: TESTS_LOCALE
            artifact-name: playwright-report
          - name: TRAILING_SLASH=false
            browsers: chromium
            test-filter: tests/ui/e2e --grep @no-trailing-slash
            trailing-slash: false
            locale-var: TESTS_LOCALE
            artifact-name: playwright-report-no-trailing
          - name: alternate locale
            browsers: chromium
            test-filter: tests/ui/e2e --grep @alternate-locale
            trailing-slash: true
            locale-var: TESTS_ALTERNATE_LOCALE
            artifact-name: playwright-report-alternate-locale

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - uses: pnpm/action-setup@v3

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"
          cache: "pnpm"

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Install Playwright browsers
        run: pnpm exec playwright install --with-deps ${{ matrix.browsers }}
        working-directory: ./core

      - name: Build catalyst
        run: pnpm build

      - name: Start server
        run: |
          mkdir -p ./.tests/reports/
          pnpm start > ./.tests/reports/nextjs.app.log 2>&1 &
          npx wait-on http://localhost:3000 --timeout 60000
        working-directory: ./core
        env:
          PORT: 3000
          AUTH_SECRET: ${{ secrets.TESTS_AUTH_SECRET }}
          AUTH_TRUST_HOST: ${{ vars.TESTS_AUTH_TRUST_HOST }}
          BIGCOMMERCE_TRUSTED_PROXY_SECRET: ${{ secrets.BIGCOMMERCE_TRUSTED_PROXY_SECRET }}
          TESTS_LOCALE: ${{ vars[matrix.locale-var] }}
          TRAILING_SLASH: ${{ matrix.trailing-slash }}
          DEFAULT_REVALIDATE_TARGET: ${{ matrix.name == 'default' && '1' || '' }}

      - name: Run E2E tests
        run: pnpm exec playwright test ${{ matrix.test-filter }}
        working-directory: ./core
        env:
          PLAYWRIGHT_TEST_BASE_URL: http://localhost:3000

      - name: Upload test results
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.artifact-name }}
          path: ./core/.tests/reports/
          retention-days: 3


================================================
FILE: .github/workflows/native-hosting.yml
================================================
name: Native Hosting

on:
  push:
    branches: [canary]

jobs:
  build-and-deploy:
    name: Build and Deploy
    runs-on: ubuntu-latest
    concurrency:
      group: ${{ github.workflow }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - uses: pnpm/action-setup@v3

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Install Catalyst CLI
        run: pnpm add @bigcommerce/catalyst@alpha @opennextjs/cloudflare@1.17.3
        working-directory: core

      - name: Convert proxy.ts to middleware.ts
        working-directory: core
        run: |
          mv proxy.ts middleware.ts
          sed -i 's/export const proxy/export const middleware/' middleware.ts
          sed -i "s/export const config = {/export const config = {\n  runtime: 'experimental-edge',/" middleware.ts

      - name: Build monorepo packages
        run: pnpm --filter "./packages/*" build

      - name: Generate GraphQL types
        run: pnpm run generate
        working-directory: core
        env:
          BIGCOMMERCE_STORE_HASH: ${{ vars.NATIVE_HOSTING_BIGCOMMERCE_STORE_HASH }}
          BIGCOMMERCE_STOREFRONT_TOKEN: ${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_STOREFRONT_TOKEN }}
          BIGCOMMERCE_CHANNEL_ID: ${{ vars.NATIVE_HOSTING_BIGCOMMERCE_CHANNEL_ID }}

      - name: Build
        run: pnpm exec catalyst build
        working-directory: core
        env:
          # CLI env vars
          CATALYST_PROJECT_UUID: ${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_PROJECT_UUID }}
          # App env vars (needed by Next.js build for GraphQL calls in next.config.ts)
          BIGCOMMERCE_STORE_HASH: ${{ vars.NATIVE_HOSTING_BIGCOMMERCE_STORE_HASH }}
          BIGCOMMERCE_STOREFRONT_TOKEN: ${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_STOREFRONT_TOKEN }}
          BIGCOMMERCE_CHANNEL_ID: ${{ vars.NATIVE_HOSTING_BIGCOMMERCE_CHANNEL_ID }}
          AUTH_SECRET: ${{ secrets.NATIVE_HOSTING_AUTH_SECRET }}

      - name: Deploy
        run: |
          pnpm exec catalyst deploy --prebuilt \
            --secret BIGCOMMERCE_STORE_HASH=${{ vars.NATIVE_HOSTING_BIGCOMMERCE_STORE_HASH }} \
            --secret BIGCOMMERCE_STOREFRONT_TOKEN=${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_STOREFRONT_TOKEN }} \
            --secret BIGCOMMERCE_CHANNEL_ID=${{ vars.NATIVE_HOSTING_BIGCOMMERCE_CHANNEL_ID }} \
            --secret AUTH_SECRET=${{ secrets.NATIVE_HOSTING_AUTH_SECRET }}
        working-directory: core
        env:
          CATALYST_STORE_HASH: ${{ vars.NATIVE_HOSTING_BIGCOMMERCE_STORE_HASH }}
          CATALYST_ACCESS_TOKEN: ${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_ACCESS_TOKEN }}
          CATALYST_PROJECT_UUID: ${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_PROJECT_UUID }}


================================================
FILE: .github/workflows/prevent-invalid-changesets.yml
================================================
name: Prevent invalid packages for Changesets

on:
  pull_request:
    branches:
      - integrations/makeswift

permissions:
  contents: read
  pull-requests: read

jobs:
  validate-changesets:
    runs-on: ubuntu-latest
    name: Validate Changeset Packages
    steps:
      - name: Checkout PR code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Validate changesets only target @bigcommerce/catalyst-makeswift
        uses: actions/github-script@v7
        with:
          script: |
            const script = require('./.github/scripts/prevent-invalid-changesets.js')
            await script({ core, exec })


================================================
FILE: .github/workflows/regression-tests.yml
================================================
name: Regression Tests

on:
  deployment_status:
    states: ["success"]

env:
  VERCEL_PROTECTION_BYPASS: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}

jobs:
  detect-provider:
    name: Detect Deployment Provider
    runs-on: ubuntu-latest
    outputs:
      provider: ${{ steps.detect.outputs.provider }}
      production-url: ${{ steps.detect.outputs.production-url }}
      branch-label: ${{ steps.detect.outputs.branch-label }}
      is-preview: ${{ steps.detect.outputs.is-preview }}
    steps:
      - uses: actions/checkout@v4

      - name: Detect provider and production URL
        id: detect
        run: |
          CREATOR="${{ github.event.deployment_status.creator.login }}"
          ENVIRONMENT="${{ github.event.deployment.environment }}"

          if [[ "$ENVIRONMENT" == "Preview" ]]; then
            echo "is-preview=true" >> $GITHUB_OUTPUT
          else
            echo "is-preview=false" >> $GITHUB_OUTPUT
          fi

          if [[ "$CREATOR" == "vercel[bot]" ]]; then
            echo "provider=vercel" >> $GITHUB_OUTPUT
            PKG_NAME=$(node -p "require('./core/package.json').name")

            if [[ "$ENVIRONMENT" == "Preview" ]]; then
              case "$PKG_NAME" in
                "@bigcommerce/catalyst-core")
                  echo "production-url=https://canary.catalyst-demo.site/" >> $GITHUB_OUTPUT ;;
                "@bigcommerce/catalyst-makeswift")
                  echo "production-url=https://canary.makeswift.catalyst-demo.site" >> $GITHUB_OUTPUT ;;
                *)
                  echo "::warning::No production URL configured for package: $PKG_NAME. Skipping comparison."
                  echo "production-url=" >> $GITHUB_OUTPUT ;;
              esac
              echo "branch-label=" >> $GITHUB_OUTPUT
            else
              case "$PKG_NAME" in
                "@bigcommerce/catalyst-core")
                  echo "branch-label=canary" >> $GITHUB_OUTPUT
                  echo "production-url=${{ github.event.deployment_status.target_url }}" >> $GITHUB_OUTPUT ;;
                "@bigcommerce/catalyst-makeswift")
                  echo "branch-label=integrations/makeswift" >> $GITHUB_OUTPUT
                  echo "production-url=${{ github.event.deployment_status.target_url }}" >> $GITHUB_OUTPUT ;;
                *)
                  echo "branch-label=" >> $GITHUB_OUTPUT
                  echo "production-url=" >> $GITHUB_OUTPUT ;;
              esac
            fi

          elif [[ "$CREATOR" == "cloudflare-pages[bot]" ]]; then
            echo "provider=cloudflare" >> $GITHUB_OUTPUT
            echo "::warning::Cloudflare production URL not yet configured. Skipping comparison."
            echo "production-url=" >> $GITHUB_OUTPUT
            echo "branch-label=" >> $GITHUB_OUTPUT

          else
            echo "::warning::Unknown deployment provider: $CREATOR. Skipping audits."
            echo "provider=unknown" >> $GITHUB_OUTPUT
            echo "production-url=" >> $GITHUB_OUTPUT
            echo "branch-label=" >> $GITHUB_OUTPUT
          fi

  unlighthouse-audit-preview:
    name: Unlighthouse Audit Preview (${{ needs.detect-provider.outputs.provider }}) - ${{ matrix.device }}
    needs: [detect-provider]
    if: needs.detect-provider.outputs.is-preview == 'true'
    runs-on: ubuntu-latest
    concurrency:
      group: regression-preview-${{ github.event.deployment.ref }}-${{ matrix.device }}
      cancel-in-progress: true
    strategy:
      matrix:
        device: [desktop, mobile]

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install Dependencies
        run: npm install @unlighthouse/cli puppeteer -g

      - name: Unlighthouse audit on ${{ matrix.device }} (preview)
        env:
          PROVIDER: ${{ needs.detect-provider.outputs.provider }}
          PREVIEW_URL: ${{ github.event.deployment_status.target_url }}
        run: |
          if [[ "$PROVIDER" == "vercel" ]]; then
            unlighthouse-ci --site "$PREVIEW_URL" --${{ matrix.device }} --disable-robots-txt \
              --extra-headers "x-vercel-protection-bypass=$VERCEL_PROTECTION_BYPASS,x-vercel-set-bypass-cookie=true"
          else
            unlighthouse-ci --site "$PREVIEW_URL" --${{ matrix.device }} --disable-robots-txt
          fi

      - name: Upload ${{ matrix.device }} preview audit
        if: failure() || success()
        uses: actions/upload-artifact@v4
        with:
          name: unlighthouse-preview-${{ matrix.device }}-report
          path: "./.unlighthouse/"
          include-hidden-files: "true"

  unlighthouse-audit-production:
    name: Unlighthouse Audit Production (${{ needs.detect-provider.outputs.provider }}) - ${{ matrix.device }}
    needs: [detect-provider]
    if: needs.detect-provider.outputs.production-url != ''
    runs-on: ubuntu-latest
    concurrency:
      group: regression-production-${{ github.event.deployment.environment == 'Preview' && github.event.deployment.ref || github.event.deployment.sha }}-${{ matrix.device }}
      cancel-in-progress: ${{ github.event.deployment.environment == 'Preview' }}
    strategy:
      matrix:
        device: [desktop, mobile]

    steps:
      - uses: actions/checkout@v4

      - name: Install Dependencies
        run: npm install @unlighthouse/cli puppeteer -g

      - name: Unlighthouse audit on ${{ matrix.device }} (production)
        env:
          PRODUCTION_URL: ${{ needs.detect-provider.outputs.production-url }}
        run: unlighthouse-ci --site "$PRODUCTION_URL" --${{ matrix.device }} --disable-robots-txt

      - name: Upload ${{ matrix.device }} production audit
        if: failure() || success()
        uses: actions/upload-artifact@v4
        with:
          name: unlighthouse-production-${{ matrix.device }}-report
          path: "./.unlighthouse/"
          include-hidden-files: "true"

  unlighthouse-compare:
    name: Unlighthouse Compare & Comment (${{ needs.detect-provider.outputs.provider }})
    needs: [detect-provider, unlighthouse-audit-preview, unlighthouse-audit-production]
    if: needs.detect-provider.outputs.is-preview == 'true' && needs.detect-provider.outputs.production-url != ''
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"

      - name: Download all Unlighthouse artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: unlighthouse-*-report
          path: /tmp/unlighthouse-artifacts
          merge-multiple: false

      - name: Compare audits
        env:
          PROVIDER: ${{ needs.detect-provider.outputs.provider }}
        run: |
          node .github/scripts/compare-unlighthouse.mts \
            --preview-desktop    /tmp/unlighthouse-artifacts/unlighthouse-preview-desktop-report/ci-result.json \
            --preview-mobile     /tmp/unlighthouse-artifacts/unlighthouse-preview-mobile-report/ci-result.json \
            --production-desktop /tmp/unlighthouse-artifacts/unlighthouse-production-desktop-report/ci-result.json \
            --production-mobile  /tmp/unlighthouse-artifacts/unlighthouse-production-mobile-report/ci-result.json \
            --output /tmp/unlighthouse-report.md \
            --meta-output /tmp/unlighthouse-meta.json \
            --provider "$PROVIDER"
          cat /tmp/unlighthouse-report.md >> "$GITHUB_STEP_SUMMARY"

      - name: Post PR comment
        uses: actions/github-script@v7
        with:
          script: |
            const postComment = require('./.github/scripts/post-unlighthouse-pr-comment.js')
            await postComment({
              github,
              context,
              provider: '${{ needs.detect-provider.outputs.provider }}',
              reportPath: '/tmp/unlighthouse-report.md',
              metaPath: '/tmp/unlighthouse-meta.json',
            })

  unlighthouse-report:
    name: Unlighthouse Report (${{ needs.detect-provider.outputs.provider }}) — ${{ needs.detect-provider.outputs.branch-label }}
    needs: [detect-provider, unlighthouse-audit-production]
    if: needs.detect-provider.outputs.is-preview == 'false' && needs.detect-provider.outputs.production-url != ''
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"

      - name: Download production Unlighthouse artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: unlighthouse-production-*-report
          path: /tmp/unlighthouse-artifacts
          merge-multiple: false

      - name: Format report
        run: |
          node .github/scripts/audit-unlighthouse.mts \
            --desktop /tmp/unlighthouse-artifacts/unlighthouse-production-desktop-report/ci-result.json \
            --mobile  /tmp/unlighthouse-artifacts/unlighthouse-production-mobile-report/ci-result.json \
            --branch  "${{ needs.detect-provider.outputs.branch-label }}" \
            --output  /tmp/unlighthouse-report.md
          cat /tmp/unlighthouse-report.md >> "$GITHUB_STEP_SUMMARY"

      - name: Post commit comment
        uses: actions/github-script@v7
        with:
          script: |
            const postReport = require('./.github/scripts/post-unlighthouse-commit-comment.js')
            await postReport({ github, context, reportPath: '/tmp/unlighthouse-report.md' })


================================================
FILE: .github/workflows/translations-changeset.yml
================================================
name: Create translations patch

on:
  pull_request:
    types:
      - opened
    branches:
      - canary

jobs:
  create-translations-patch:
    if: github.actor == 'bc-svc-local'
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repo
        uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Use commit SHA for filename
        id: generate-sha
        run: |
          short_sha=$(echo "${GITHUB_SHA}" | cut -c1-8)
          echo "SHORT_SHA=$short_sha" >> $GITHUB_OUTPUT

      - name: Create a translations changeset
        env:
          SHORT_SHA: ${{ steps.generate-sha.outputs.SHORT_SHA }}
        run: |
          mkdir -p .changeset
          echo "---
          \"@bigcommerce/catalyst-core\": patch
          ---

          Update translations." > .changeset/translations-patch-$SHORT_SHA.md

      - name: Commit changeset
        env:
          SHORT_SHA: ${{ steps.generate-sha.outputs.SHORT_SHA }}
        run: |
          git config --global user.name 'bc-svc-local'
          git config --global user.email 'bc-svc-local@users.noreply.github.com'
          git add .changeset/translations-patch-$SHORT_SHA.md
          git commit -m "chore(core): create translations patch"
      
      - name: Push changeset
        env:
          TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} HEAD:$GITHUB_HEAD_REF


================================================
FILE: .gitignore
================================================
node_modules
dist
.turbo
.vscode/**/*
!.vscode/settings.example.json
!.vscode/launch.example.json
.idea
.vercel
.catalyst
.env
.env*.local
.env*.test
test-results/
playwright-report/
playwright/.cache/
.tests
bigcommerce.graphql
bigcommerce-graphql.d.ts
.DS_Store
coverage/
.history
.unlighthouse
.bigcommerce
.mcp.json


================================================
FILE: .nvmrc
================================================
24


================================================
FILE: .vscode/launch.example.json
================================================
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Catalyst: debug server-side",
            "type": "node-terminal",
            "request": "launch",
            "command": "pnpm run dev",
            "cwd": "${workspaceFolder}"
        },
        {
            "name": "Catalyst: debug client-side",
            "type": "chrome", // Use "chrome" "firefox" or "msedge" as needed
            "request": "launch",
            "url": "http://localhost:3000",
            "webRoot": "${workspaceFolder}/core"
        },
        {
            "name": "Catalyst: debug full stack",
            "type": "node",
            "request": "launch",
            "cwd": "${workspaceFolder}/core",
            "program": "${workspaceFolder}/core/node_modules/next/dist/bin/next",
            "args": ["dev"],
            "runtimeArgs": ["--inspect"],
            "skipFiles": ["<node_internals>/**"],
            "env": {
                "NODE_ENV": "development"
            },
            "serverReadyAction": {
                "action": "openExternally",
                "killOnServerStop": true,
                "pattern": "- Local:.+(https?://.+)",
                "uriFormat": "%s"
            }
        }
    ]
}


================================================
FILE: .vscode/settings.example.json
================================================
{
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true,
  "eslint.workingDirectories": [
    { "pattern": "core" },
    { "pattern": "packages/*/" }
  ]
}


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at engineering@bigcommerce.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Catalyst

Thanks for showing interest in contributing!

The following is a set of guidelines for contributing to Catalyst. These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.

## Repository Structure

Catalyst is a monorepo that contains the code for the Catalyst Next.js application inside of `core/`, and supporting packages such as the GraphQL API client and the `create-catalyst` CLI in `packages/`.

The default branch for this repository is called `canary`. This is the primary development branch where active development takes place, including the introduction of new features, bug fixes, and other changes before they are released in stable versions.

To contribute to the `canary` branch, you can create a new branch off of `canary` and submit a PR against that branch.

## API Scope

Catalyst is intended to work with the [BigCommerce Storefront GraphQL API](https://developer.bigcommerce.com/docs/storefront/graphql) and not directly integrate out of the box with the [REST Management API](https://developer.bigcommerce.com/docs/rest-management).

You're welcome to integrate the REST Management API in your own fork, but we will not accept pull requests that incorporate or depend on the REST Management API. If your contribution requires Management API functionality, it is out of scope for this project.

## Makeswift Integration

In addition to `canary`, we also maintain the `integrations/makeswift` branch, which contains additional code required to integrate with [Makeswift](https://www.makeswift.com).

To contribute to the `integrations/makeswift` branch, you can create a new branch off of `integrations/makeswift` and submit a PR against that branch.

### Keeping `integrations/makeswift` in sync with `canary`

Except for the additional code required to integrate with Makeswift, the `integrations/makeswift` branch is a mirror of the `canary` branch. This means that the `integrations/makeswift` branch should be kept in sync with the `canary` branch as much as possible.

#### Prerequisites

In order to complete the following steps, you will need to have met the following prerequisites:

- You have a remote named `origin` pointing to the [`bigcommerce/catalyst` repository on GitHub](https://github.com/bigcommerce/catalyst).
- You have rights to push to the `integrations/makeswift` branch on GitHub.

#### Steps

1. Fetch latest from `origin`

   ```bash
   git fetch origin
   ```

2. Create a branch to perform a merge from `canary`

   ```bash
   git checkout -B sync-integrations-makeswift origin/integrations/makeswift
   ```

> [!TIP]
> The `-B` flag means "create branch or reset existing branch":
>
> - If the local branch doesn't exist, it creates it from `origin/integrations/makeswift`
> - If the local branch exists, it resets it to match `origin/integrations/makeswift`

3. Merge `canary` and resolve merge conflicts, if necessary:

   ```bash
   git merge canary
   ```

> [!WARNING]
> **Gotchas when merging canary into integrations/makeswift:**
>
> - The `name` field in `core/package.json` should remain `@bigcommerce/catalyst-makeswift`
> - The `version` field in `core/package.json` should remain whatever the latest published `@bigcommerce/catalyst-makeswift` version was
> - The latest release in `core/CHANGELOG.md` should remain whatever the latest published `@bigcommerce/catalyst-makeswift` version was

4. After resolving any merge conflicts, open a new PR in GitHub to merge your `sync-integrations-makeswift` into `integrations/makeswift`. This PR should be code reviewed and approved before the next steps.

5. Rebase `integrations/makeswift` to establish new merge base

   ```bash
   git checkout -B integrations/makeswift origin/integrations/makeswift
   git rebase sync-integrations-makeswift
   ```

6. Push the changes up to GitHub:

   ```bash
   git push origin integrations/makeswift
   ```

This should close the PR in GitHub automatically.

> [!IMPORTANT]
> Do not squash or rebase-and-merge PRs into `integrations/makeswift`. Always use a true merge commit or rebase locally (as shown below). This is to preserve the merge commit and establish a new merge base between `canary` and `integrations/makeswift`.

## Cutting New Releases

Catalyst uses [Changesets](https://github.com/changesets/changesets) to manage version bumps, changelogs, and publishing. Releases happen in **two stages**:

1. Cut a release from `canary`
2. Sync that release into `integrations/makeswift` and cut again

This ensures `integrations/makeswift` remains a faithful mirror of `canary` while including its additional integration code.

#### Stage 1: Cut a release from `canary`

1. Begin the release process by merging the **Version Packages (`canary`)** PR. When `.changeset/` files exist on `canary`, a GitHub Action opens a **Version Packages (`canary`)** PR. This PR consolidates pending changesets, bumps versions, and updates changelogs. Merging this PR should publish new tags to GitHub, and optionally publish new package versions to NPM.

#### Stage 2: Sync and Release `integrations/makeswift`

2. Follow steps 1-6 under "[Keeping `integrations/makeswift` in sync with `canary`](#keeping-integrationsmakeswift-in-sync-with-canary)", with one addition: **include a changeset for `@bigcommerce/catalyst-makeswift` in the sync merge commit** rather than opening a separate PR for it afterwards.

   - Match the bump type from Stage 1 (e.g., if `@bigcommerce/catalyst-core` went from `1.4.2` to `1.5.0`, use `minor`)
   - Create a changeset file in `.changeset/` (e.g., `.changeset/sync-canary-1-5-0.md`):

     ```
     ---
     "@bigcommerce/catalyst-makeswift": minor
     ---

     Pulls in changes from the `@bigcommerce/catalyst-core@1.5.0` release. For more information, see the [changelog entry](https://github.com/bigcommerce/catalyst/blob/<canary-sha>/core/CHANGELOG.md#150).
     ```

   - Replace `<canary-sha>` with the merge commit SHA of the Version Packages PR on `canary` so the link remains stable
   - Amend this changeset into the merge commit alongside any other sync changes (changeset cleanup, `core/package.json` and `core/CHANGELOG.md` fixes, etc.)

3. Merge the **Version Packages (`integrations/makeswift`)** PR: After the sync lands, Changesets will open a PR (similar to Stage 1) bumping `@bigcommerce/catalyst-makeswift`. Merge it following the same process. This cuts a new release of the Makeswift variant.

4. **Tags and Releases:** Confirm tags exist for both `@bigcommerce/catalyst-core` and `@bigcommerce/catalyst-makeswift`. Update `latest` tags to point to the new releases:

   ```bash
   git fetch origin --tags
   git tag @bigcommerce/catalyst-core@latest @bigcommerce/catalyst-core@<version> -f
   git tag @bigcommerce/catalyst-makeswift@latest @bigcommerce/catalyst-makeswift@<version> -f
   git push origin @bigcommerce/catalyst-core@latest -f
   git push origin @bigcommerce/catalyst-makeswift@latest -f
   ```

### Additional Notes

- **Release cadence:** Teams typically review on Wednesdays whether to cut a release, but you may cut releases more frequently as needed.

## Other Ways to Contribute

- Consider reporting bugs, contributing to test coverage, or helping spread the word about Catalyst.

## Git Commit Messages

- Use the present tense ("Add feature" not "Added feature")
- Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
- Limit the first line to 72 characters or less
- Reference pull requests and external links liberally

Thank you again for your interest in contributing to Catalyst!


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 BigCommerce

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<a href="https://catalyst.dev" target="_blank" rel="noopener norerrer">
  <img src="https://storage.googleapis.com/bigcommerce-developers/images/catalyst_readme_banner.png" alt="Catalyst for Composable Commerce Image Banner" title="Catalyst">
</a>

<br />
<br />

<div align="center">

[![MIT License](https://img.shields.io/github/license/bigcommerce/catalyst)](LICENSE.md)
[![Lighthouse Report](https://github.com/bigcommerce/catalyst/actions/workflows/lighthouse.yml/badge.svg)](https://github.com/bigcommerce/catalyst/actions/workflows/lighthouse.yml) [![Lint, Typecheck, gql.tada](https://github.com/bigcommerce/catalyst/actions/workflows/basic.yml/badge.svg)](https://github.com/bigcommerce/catalyst/actions/workflows/basic.yml)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bigcommerce/catalyst)

</div>

**Catalyst** is the composable, fully customizable headless commerce framework for
[BigCommerce](https://www.bigcommerce.com/). Catalyst is built with [Next.js](https://nextjs.org/), uses
our [React](https://react.dev/) storefront components, and is backed by the
[GraphQL Storefront API](https://docs.bigcommerce.com/developer/docs/storefront/guides/graphql-storefront-api/overview).

By choosing Catalyst, you'll have a fully-functional storefront within a few seconds, and spend zero time on wiring
up APIs or building SEO, Accessibility, and Performance-optimized ecommerce components you've probably written many
times before. You can instead go straight to work building your brand and making this your own.

## Demo

- [Catalyst Demo](https://catalyst-demo.site)

![-----------------------------------------------------](https://storage.googleapis.com/bigcommerce-developers/images/catalyst_readme_hr.png)

<p align="center">
 <a href="https://www.catalyst.dev">🚀 catalyst.dev</a> •
 <a href="https://docs.bigcommerce.com/developer/community/connect">🤗 BigCommerce Developer Community</a> •
 <a href="https://github.com/bigcommerce/catalyst/discussions">💬 GitHub Discussions</a> •
 <a href="https://docs.bigcommerce.com/developer/docs/storefront/catalyst/overview">💡 Documentation</a>
</p>

![-----------------------------------------------------](https://storage.googleapis.com/bigcommerce-developers/images/catalyst_readme_hr.png)

## Deploy via One-Click Catalyst App

The easiest way to deploy your Catalyst Storefront is to use the [One-Click Catalyst App](http://login.bigcommerce.com/deep-links/app/53284) available in the BigCommerce App Marketplace.

Check out the [Commerce One-Click Catalyst Documentation](https://docs.bigcommerce.com/developer/docs/storefront/catalyst/getting-started/workflows/one-click-catalyst) for more details.

## Getting Started

**Requirements:**

- A [BigCommerce account](https://www.bigcommerce.com/start-your-trial)
- Node.js version 24
- Corepack-enabled `pnpm`

  ```bash
  corepack enable pnpm
  ```

1. Install the latest version of Catalyst:

   ```bash
   pnpm create @bigcommerce/catalyst@latest
   ```

2. Run the local development server:

   ```bash
   pnpm run dev
   ```

Learn more about Catalyst at [catalyst.dev](https://catalyst.dev).

## Resources

- [Catalyst Documentation](https://docs.bigcommerce.com/developer/docs/storefront/catalyst/overview)
- [GraphQL Storefront API Playground](https://docs.bigcommerce.com/developer/docs/storefront/guides/graphql-storefront-api/overview#accessing-the-graphql-storefront-playground)
- [BigCommerce DevDocs](https://docs.bigcommerce.com/developer/docs/overview/quick-start)


================================================
FILE: SECURITY.md
================================================
# Reporting security issues
BigCommerce is dedicated to the responsible disclosure of security vulnerabilities.
If you have found a security vulnerability in an active open-source repository created and owned by BigCommerce, please report it to our [public bug bounty program](https://bugcrowd.com/bigcommerce). If you would prefer to submit via email, please send your report to [security@bigcommerce.com](mailto:security@bigcommerce.com).

We ask that you **do not** open a public GitHub issue to report security concerns.

_Note: Only submissions to our bounty program on BugCrowd will be eligible for bounties. Bounty eligibility and amounts are determined according to the program guidelines._

_Note: Bugs in 3rd-party modules and/or dependencies should be reported to the owners/maintainers or those modules and/or dependencies, BigCommerce has no control or authority over third party content._

Thank you in advance for collaborating with us to help protect us and our customers.


================================================
FILE: core/.eslintignore
================================================
# Dependencies
node_modules/

# Build outputs
.next/
.wrangler/
.open-next/
out/
dist/
build/

# Generated files
.turbo/
messages/*.d.json.ts
next-env.d.ts
*-graphql.d.ts

# Test outputs
playwright-report/
test-results/
.tests/

# Cache
.eslintcache


================================================
FILE: core/.eslintrc.cjs
================================================
// @ts-check

require('@bigcommerce/eslint-config/patch');

/** @type {import('eslint').Linter.LegacyConfig} */
const config = {
  root: true,
  extends: [
    '@bigcommerce/catalyst/base',
    '@bigcommerce/catalyst/react',
    '@bigcommerce/catalyst/next',
    '@bigcommerce/catalyst/prettier',
  ],
  rules: {
    '@typescript-eslint/naming-convention': 'off',
    '@next/next/no-html-link-for-pages': 'off',
    'import/dynamic-import-chunkname': 'off',
    'no-underscore-dangle': ['error', { allow: ['__typename'] }],
    '@typescript-eslint/prefer-nullish-coalescing': 'off',
    '@typescript-eslint/no-unsafe-enum-comparison': 'off',
    '@typescript-eslint/no-restricted-imports': [
      'error',
      {
        paths: [
          {
            name: 'next/link',
            message: "Please import 'Link' from '~/components/Link' instead.",
          },
          {
            name: 'next/image',
            importNames: ['default'],
            message:
              "Please import 'Image' from '~/components/image' instead. This component handles CDN and static image optimization.",
          },
          {
            name: '~/i18n/routing',
            importNames: ['Link'],
            message: "Please import 'Link' from '~/components/Link' instead.",
          },
          {
            name: 'next/router',
            importNames: ['useRouter'],
            message: 'Please import from `~/i18n/routing` instead.',
          },
          {
            name: 'next/navigation',
            importNames: ['redirect', 'permanentRedirect', 'useRouter', 'usePathname'],
            message: 'Please import from `~/i18n/routing` instead.',
          },
          {
            name: '@playwright/test',
            importNames: ['expect', 'test'],
            message: 'Please import from `~/tests/fixtures` instead.',
          },
        ],
      },
    ],
    'check-file/folder-naming-convention': [
      'error',
      {
        '**': 'NEXT_JS_APP_ROUTER_CASE',
      },
    ],
  },
  overrides: [
    {
      files: ['**/*.spec.ts', '**/*.test.ts'],
      rules: {
        '@typescript-eslint/no-restricted-imports': [
          'error',
          {
            paths: [
              {
                name: 'next-intl/server',
                importNames: ['getTranslations', 'getFormatter'],
                message:
                  'Please import `getTranslations` from `~/tests/lib/i18n` and `getFormatter` from `~/tests/lib/formatter` instead.',
              },
            ],
          },
        ],
      },
    },
  ],
};

module.exports = config;


================================================
FILE: core/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage
/test-results/
/playwright-report/
/playwright/.cache/
/.tests

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# generated
client/generated

# next-intl
messages/*.d.json.ts

# secrets
.catalyst

# Build config
build-config.json

# OpenNext
.open-next
.wrangler


================================================
FILE: core/AGENTS.md
================================================
# AGENTS.md

## BigCommerce Catalyst Codebase Overview

This document provides guidance for Large Language Models (LLMs) working with the BigCommerce Catalyst codebase, focusing on the **Next.js App Router application** architecture, data fetching patterns, and key design principles.

**Catalyst is built as a Next.js App Router application** with React Server Components, enabling server-side data fetching, automatic code splitting, and optimal performance for e-commerce workloads.

## Repository Structure

The main Next.js application is located in the `/core` directory, which contains the complete e-commerce storefront implementation. Other packages exist outside of `/core` but are not the primary focus for most development work.

## Proxy Architecture

The application uses the Next.js 16 proxy pattern (`proxy.ts`) with a composed proxy stack that significantly alters the default Next.js routing behavior. The proxy composition (in the `proxies/` directory) includes authentication, internationalization, analytics, channel handling, and most importantly, custom routing.

### Custom Routing with `with-routes`

The `with-routes` proxy is the most critical component that overrides Next.js's default path-based routing. Instead of relying on file-based routing, this proxy:

1. **Queries the BigCommerce GraphQL API** to resolve incoming URL paths to specific entity types (products, categories, brands, blog posts, pages).

2. **Rewrites requests** to internal Next.js routes based on the resolved entity type.

3. **Handles redirects** automatically based on BigCommerce's redirect configuration.

This means that URLs like `/my-product-name` can resolve to `/en/product/123` internally, providing flexible URL structure while maintaining SEO-friendly paths.

## Data Fetching and Partial Prerendering (PPR)

### PPR Configuration

The application uses Next.js Partial Prerendering with incremental adoption. This allows static parts of pages to be prerendered while dynamic content streams in.

### Streamable Pattern

The `Streamable<T>` pattern is a core architectural concept that enables efficient data streaming and React Server Component compatibility.

#### What is Streamable?

```typescript
export type Streamable<T> = T | Promise<T>;
```

A `Streamable<T>` represents data that can be either:
- **Immediate**: Already resolved data of type `T`
- **Deferred**: A Promise that will resolve to type `T`

#### Core Streamable API

Located in `core/vibes/soul/lib/streamable.tsx`, the Streamable system provides:

**`Streamable.from()`** - Creates a streamable from a lazy promise factory:
```typescript
const streamableProducts = Streamable.from(async () => {
  const customerToken = await getSessionCustomerAccessToken();
  const currencyCode = await getPreferredCurrencyCode();
  return getProducts(customerToken, currencyCode);
});
```

**`Streamable.all()`** - Combines multiple streamables with automatic caching:
```typescript
const combined = Streamable.all([
  streamableProducts,
  streamableCategories,
  streamableUser
]);
```

**`useStreamable()`** - Hook for consuming streamables in components:
```typescript
function MyComponent({ data }: { data: Streamable<Product[]> }) {
  const products = useStreamable(data);
  return <div>{products.map(...)}</div>;
}
```

**`<Stream>` Component** - Provides Suspense boundary for streamable data:
```tsx
<Stream value={streamableProducts} fallback={<ProductSkeleton />}>
  {(products) => <ProductList products={products} />}
</Stream>
```

#### Streamable Benefits

- **Performance**: Enables concurrent data fetching and streaming
- **Caching**: Automatic promise deduplication and stability
- **Flexibility**: Works with both sync and async data
- **Suspense Integration**: Built-in React Suspense support
- **Composition**: Easy chaining and combination of data sources

### Data Fetching Best Practices

1. **Use React's `cache()` function** for server-side data fetching to memoize function results and prevent repeated fetches or computations **per request** (React will invalidate the cache for all memoized functions for each server request).

2. **Implement proper cache strategies** based on whether user authentication is present.

3. **Leverage Streamable for progressive enhancement** where static content loads immediately and dynamic content streams in.

## GraphQL API Client

### Centralized Client Configuration

All interactions with the BigCommerce Storefront GraphQL API should use the centralized GraphQL client. This client provides:

- Automatic channel ID resolution based on locale
- Proper authentication token handling
- Request/response logging in development
- Error handling with automatic auth redirects
- IP address forwarding for personalization

### Usage Pattern

Always import and use the configured client rather than making direct API calls. The client handles all the necessary headers, authentication, and channel context automatically.

## UI Design System (Vibes)

### Architecture Overview

The `vibes/` directory contains the **highly customizable and styleable UI layer** that is completely separate from data fetching and business logic. This separation enables:

- **Complete visual customization** without touching data logic
- **Theme-based styling** through CSS variables
- **Reusable components** across different page contexts
- **Clear separation of concerns** between data and presentation

### Vibes vs Pages Architecture

**`vibes/` folder**: Contains presentation components that are meant to be highly customizable and styleable to change the UI:
- Accept `Streamable<T>` data as props
- Handle rendering, styling, and user interactions
- Support theming through CSS variables
- No direct data fetching or business logic

**`page.tsx` files**: Where data fetching patterns should live:
- Handle authentication and authorization
- Create `Streamable` data sources
- Transform API responses for vibes components
- Manage routing and server-side logic

### Component Hierarchy

```
vibes/soul/
├── lib/
│   └── streamable.tsx     # Streamable utilities
├── primitives/           # Basic UI components
│   ├── button/
│   ├── product-card/
│   └── navigation/
└── sections/             # Complex UI sections
    ├── product-list/
    ├── featured-product-carousel/
    └── footer/
```

1. **Primitives** (`vibes/soul/primitives/`) - Basic reusable UI components like buttons, cards, forms.

2. **Sections** (`vibes/soul/sections/`) - Page-level components that compose primitives into complete page sections.

3. **Library** (`vibes/soul/lib/`) - Utility functions and patterns like the Streamable implementation.

### Data Flow Pattern

```
page.tsx → Streamable data → Vibes components → User interaction
```

**Example Pattern:**
```typescript
// app/[locale]/(default)/page.tsx - Data fetching
export default async function HomePage({ params }: Props) {
  const streamableProducts = Streamable.from(async () => {
    const customerToken = await getSessionCustomerAccessToken();
    return getProducts(customerToken);
  });

  return (
    <FeaturedProductList 
      products={streamableProducts} // Pass streamable to vibes
      title="Featured Products"
    />
  );
}

// vibes/soul/sections/featured-product-list/index.tsx - Presentation
export function FeaturedProductList({ 
  products, 
  title 
}: {
  products: Streamable<Product[]>; // Accept streamable
  title: string;
}) {
  return (
    <section>
      <h2>{title}</h2>
      <Stream value={products} fallback={<ProductSkeleton />}>
        {(productList) => (
          <div className="grid">
            {productList.map(product => <ProductCard key={product.id} product={product} />)}
          </div>
        )}
      </Stream>
    </section>
  );
}
```

### Import Patterns

Components should be imported from the vibes design system using the `@/vibes/soul/` alias, maintaining clear separation between business logic in `/components` and design system components in `/vibes`.

## App Router Data Fetching Patterns

### Server Components by Default

All pages are React Server Components, enabling:
- Server-side data fetching with zero client JavaScript
- Automatic code splitting and optimization
- SEO-friendly content rendering
- Direct database/API access

### File-based Routing Structure

```
app/[locale]/(default)/
├── page.tsx              # Homepage with data fetching
├── layout.tsx            # Shared layout components
├── product/[slug]/
│   ├── page.tsx          # Product detail page
│   └── page-data.ts      # Product data fetching logic
├── (faceted)/category/[slug]/
│   └── page.tsx          # Category page
└── cart/
    └── page.tsx          # Cart page
```

### Data Fetching Example

```typescript
// page.tsx - Server Component with async data fetching
export default async function ProductPage({ params, searchParams }: Props) {
  const { slug } = await params;
  const customerAccessToken = await getSessionCustomerAccessToken();
  
  // Create streamables for concurrent data loading
  const streamableProduct = Streamable.from(async () => {
    return getProduct(slug, customerAccessToken);
  });

  const streamableReviews = Streamable.from(async () => {
    const product = await streamableProduct; // Reuses cached promise
    return getProductReviews(product.id);
  });

  return (
    <ProductDetail 
      product={streamableProduct}
      reviews={streamableReviews}
    />
  );
}
```

## Key Architectural Principles

1. **App Router Architecture**: Built on Next.js App Router with React Server Components for optimal performance
2. **Routing Flexibility**: Unlike typical Next.js applications, URLs are resolved dynamically via GraphQL rather than file structure
3. **Progressive Enhancement**: Static content loads immediately with dynamic content streaming via PPR and Streamable
4. **Vibes Separation**: Complete separation between data fetching (`page.tsx`) and presentation (`vibes/`) concerns
5. **Centralized API Access**: All BigCommerce API interactions go through the configured GraphQL client
6. **Proxy-First**: Critical functionality like routing, auth, and internationalization handled at the proxy layer

## Notes

This codebase differs significantly from typical Next.js applications due to the custom routing proxy and e-commerce-specific patterns. The `with-routes` proxy (composed within `proxy.ts`) essentially turns Next.js into a headless CMS router, where content structure is determined by the BigCommerce backend rather than the filesystem. Understanding this fundamental difference is crucial for working effectively with the codebase.

The Streamable pattern and PPR integration provide excellent user experience through progressive loading, but require understanding of React's newer concurrent features like the `use()` hook and Suspense boundaries.


================================================
FILE: core/CHANGELOG.md
================================================
# Changelog

## 1.6.2

### Patch Changes

- [#2947](https://github.com/bigcommerce/catalyst/pull/2947) [`e198d89`](https://github.com/bigcommerce/catalyst/commit/e198d8966d589bd6707cdb1588986c9c092d73be) Thanks [@jorgemoya](https://github.com/jorgemoya)! - Add root-level not-found page so /404 renders a branded page instead of the default Vercel error screen

- [#2945](https://github.com/bigcommerce/catalyst/pull/2945) [`4479964`](https://github.com/bigcommerce/catalyst/commit/447996400e6fcc6388937011e101d802308e6b33) Thanks [@bc-svc-local](https://github.com/bc-svc-local)! - Update translations.

## 1.6.1

### Patch Changes

- [#2934](https://github.com/bigcommerce/catalyst/pull/2934) [`6a5b019`](https://github.com/bigcommerce/catalyst/commit/6a5b019083aa3e000e5989f6f13256b57c22c479) Thanks [@chanceaclark](https://github.com/chanceaclark)! - Fix extra thick border on dropdown menu by changing `ring` (3px) to `ring-1` (1px) to match the Select component styling.

## 1.6.0

### Minor Changes

- [#2896](https://github.com/bigcommerce/catalyst/pull/2896) [`fc84210`](https://github.com/bigcommerce/catalyst/commit/fc84210ab8562ce24320d3d0e3284ed318a4cbce) Thanks [@jamesqquick](https://github.com/jamesqquick)! - Add reCAPTCHA v2 support to storefront forms. The reCAPTCHA widget is rendered on the registration, contact, and product review forms when enabled in the BigCommerce admin. All validation and error handling is performed server-side in the corresponding form actions. The token is read from the native `g-recaptcha-response` field that the widget injects into the form, eliminating the need for manual token extraction on the client.

  ## Migration steps

  ### Step 1: Install dependencies

  Add `react-google-recaptcha` and its type definitions:

  ```bash
  pnpm add react-google-recaptcha
  pnpm add -D @types/react-google-recaptcha
  ```

  ### Step 2: Add the reCAPTCHA server library

  Create `core/lib/recaptcha/constants.ts`:

  ```ts
  export interface ReCaptchaSettings {
    isEnabledOnStorefront: boolean;
    siteKey: string;
  }

  export const RECAPTCHA_TOKEN_FORM_KEY = 'g-recaptcha-response';
  ```

  Create `core/lib/recaptcha.ts` with the server-side helpers for fetching reCAPTCHA settings, extracting the token from form data, and asserting the token is present. See the file in this release for the full implementation.

  ### Step 3: Add reCAPTCHA translation strings

  Update `core/messages/en.json` to add the `recaptchaRequired` message in each form namespace:

  ```diff
    "Auth": {
      "Register": {
  +     "recaptchaRequired": "Please complete the reCAPTCHA verification.",
  ```

  ```diff
    "Product": {
      "Reviews": {
        "Form": {
  +       "recaptchaRequired": "Please complete the reCAPTCHA verification.",
  ```

  ```diff
    "WebPages": {
      "ContactUs": {
        "Form": {
  +       "recaptchaRequired": "Please complete the reCAPTCHA verification.",
  ```

  ```diff
    "Form": {
  +   "recaptchaRequired": "Please complete the reCAPTCHA verification.",
  ```

  ### Step 4: Update GraphQL mutations to accept reCAPTCHA token

  Update `core/app/[locale]/(default)/(auth)/register/_actions/register-customer.ts`:

  ```diff
  + import { assertRecaptchaTokenPresent, getRecaptchaFromForm } from '~/lib/recaptcha';
    ...
    const RegisterCustomerMutation = graphql(`
  -   mutation RegisterCustomerMutation($input: RegisterCustomerInput!) {
  +   mutation RegisterCustomerMutation(
  +     $input: RegisterCustomerInput!
  +     $reCaptchaV2: ReCaptchaV2Input
  +   ) {
        customer {
  -       registerCustomer(input: $input) {
  +       registerCustomer(input: $input, reCaptchaV2: $reCaptchaV2) {
  ```

  Update `core/app/[locale]/(default)/product/[slug]/_actions/submit-review.ts`:

  ```diff
  + import { assertRecaptchaTokenPresent, getRecaptchaFromForm } from '~/lib/recaptcha';
    ...
    const AddProductReviewMutation = graphql(`
  -   mutation AddProductReviewMutation($input: AddProductReviewInput!) {
  +   mutation AddProductReviewMutation(
  +     $input: AddProductReviewInput!
  +     $reCaptchaV2: ReCaptchaV2Input
  +   ) {
        catalog {
  -       addProductReview(input: $input) {
  +       addProductReview(input: $input, reCaptchaV2: $reCaptchaV2) {
  ```

  Update `core/app/[locale]/(default)/webpages/[id]/contact/_actions/submit-contact-form.ts`:

  ```diff
  + import { assertRecaptchaTokenPresent, getRecaptchaFromForm } from '~/lib/recaptcha';
    ...
    const SubmitContactUsMutation = graphql(`
  -   mutation SubmitContactUsMutation($input: SubmitContactUsInput!) {
  -     submitContactUs(input: $input) {
  +   mutation SubmitContactUsMutation($input: SubmitContactUsInput!, $reCaptchaV2: ReCaptchaV2Input) {
  +     submitContactUs(input: $input, reCaptchaV2: $reCaptchaV2) {
  ```

  ### Step 5: Add server-side reCAPTCHA validation to form actions

  In each of the three server actions above, add the validation block after the `parseWithZod` check and pass the token to the GraphQL mutation. For example in `register-customer.ts`:

  ```diff
  +   const { siteKey, token } = await getRecaptchaFromForm(formData);
  +   const recaptchaValidation = assertRecaptchaTokenPresent(siteKey, token, t('recaptchaRequired'));
  +
  +   if (!recaptchaValidation.success) {
  +     return {
  +       lastResult: submission.reply({ formErrors: recaptchaValidation.formErrors }),
  +     };
  +   }
      ...
      const response = await client.fetch({
        document: RegisterCustomerMutation,
        variables: {
          input,
  +       reCaptchaV2:
  +         recaptchaValidation.token != null ? { token: recaptchaValidation.token } : undefined,
        },
  ```

  Apply the same pattern to `submit-review.ts` and `submit-contact-form.ts`.

  ### Step 6: Pass `recaptchaSiteKey` to form components

  Fetch the site key in each page and pass it down through the component tree.

  Update `core/app/[locale]/(default)/(auth)/register/page.tsx`:

  ```diff
  + import { getRecaptchaSiteKey } from '~/lib/recaptcha';
    ...
  + const recaptchaSiteKey = await getRecaptchaSiteKey();
    ...
    <DynamicFormSection
  +   recaptchaSiteKey={recaptchaSiteKey}
  ```

  Update `core/app/[locale]/(default)/product/[slug]/page.tsx`:

  ```diff
  + import { getRecaptchaSiteKey } from '~/lib/recaptcha';
    ...
  - const { product: baseProduct, settings } = await getProduct(productId, customerAccessToken);
  + const [{ product: baseProduct, settings }, recaptchaSiteKey] = await Promise.all([
  +   getProduct(productId, customerAccessToken),
  +   getRecaptchaSiteKey(),
  + ]);
    ...
    <ProductDetail
  +   recaptchaSiteKey={recaptchaSiteKey}
    ...
    <Reviews
  +   recaptchaSiteKey={recaptchaSiteKey}
  ```

  Update `core/app/[locale]/(default)/webpages/[id]/contact/page.tsx`:

  ```diff
  + import { getRecaptchaSiteKey } from '~/lib/recaptcha';
    ...
  + cons
Download .txt
gitextract_0h9khkyg/

├── .changeset/
│   ├── cold-foxes-lie.md
│   ├── config.json
│   ├── correlation-id-header.md
│   ├── fix-hidden-fields-d35665be.md
│   ├── fix-html-lang-locale.md
│   ├── translations-patch-d3abeec7.md
│   └── translations-patch-e3d3b994.md
├── .claude/
│   └── skills/
│       ├── release-catalyst/
│       │   └── SKILL.md
│       ├── release-catalyst-patch/
│       │   └── SKILL.md
│       └── sync-makeswift/
│           └── SKILL.md
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── config.yml
│   │   ├── 🐞📝-bug-report-makeswift.md
│   │   └── 🐞📝-bug-report.md
│   ├── dependabot.yml
│   ├── pull_request_template.md
│   ├── scripts/
│   │   ├── __tests__/
│   │   │   ├── audit-unlighthouse.test.mts
│   │   │   ├── bundle-size.test.mts
│   │   │   ├── compare-unlighthouse.test.mts
│   │   │   ├── post-bundle-comment.test.mts
│   │   │   ├── post-unlighthouse-commit-comment.test.mts
│   │   │   └── post-unlighthouse-pr-comment.test.mts
│   │   ├── audit-unlighthouse.mts
│   │   ├── bundle-size.mts
│   │   ├── compare-unlighthouse.mts
│   │   ├── post-bundle-comment.js
│   │   ├── post-unlighthouse-commit-comment.js
│   │   ├── post-unlighthouse-pr-comment.js
│   │   └── prevent-invalid-changesets.js
│   └── workflows/
│       ├── basic.yml
│       ├── bundle-size.yml
│       ├── changesets-release.yml
│       ├── deploy.yml
│       ├── e2e.yml
│       ├── native-hosting.yml
│       ├── prevent-invalid-changesets.yml
│       ├── regression-tests.yml
│       └── translations-changeset.yml
├── .gitignore
├── .nvmrc
├── .vscode/
│   ├── launch.example.json
│   └── settings.example.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── core/
│   ├── .eslintignore
│   ├── .eslintrc.cjs
│   ├── .gitignore
│   ├── AGENTS.md
│   ├── CHANGELOG.md
│   ├── README.md
│   ├── app/
│   │   ├── [locale]/
│   │   │   ├── (default)/
│   │   │   │   ├── (auth)/
│   │   │   │   │   ├── change-password/
│   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   └── change-password.ts
│   │   │   │   │   │   ├── page-data.ts
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   ├── layout.tsx
│   │   │   │   │   ├── login/
│   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   └── login.ts
│   │   │   │   │   │   ├── forgot-password/
│   │   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   │   └── reset-password.ts
│   │   │   │   │   │   │   └── page.tsx
│   │   │   │   │   │   ├── page.tsx
│   │   │   │   │   │   └── token/
│   │   │   │   │   │       └── [token]/
│   │   │   │   │   │           └── route.ts
│   │   │   │   │   ├── logout/
│   │   │   │   │   │   └── route.ts
│   │   │   │   │   └── register/
│   │   │   │   │       ├── _actions/
│   │   │   │   │       │   ├── prefixes.ts
│   │   │   │   │       │   └── register-customer.ts
│   │   │   │   │       ├── page-data.ts
│   │   │   │   │       └── page.tsx
│   │   │   │   ├── (faceted)/
│   │   │   │   │   ├── brand/
│   │   │   │   │   │   └── [slug]/
│   │   │   │   │   │       ├── page-data.ts
│   │   │   │   │   │       └── page.tsx
│   │   │   │   │   ├── category/
│   │   │   │   │   │   └── [slug]/
│   │   │   │   │   │       ├── _components/
│   │   │   │   │   │       │   └── category-viewed.tsx
│   │   │   │   │   │       ├── page-data.ts
│   │   │   │   │   │       └── page.tsx
│   │   │   │   │   ├── fetch-compare-products.ts
│   │   │   │   │   ├── fetch-faceted-search.ts
│   │   │   │   │   └── search/
│   │   │   │   │       ├── page-data.ts
│   │   │   │   │       └── page.tsx
│   │   │   │   ├── [...rest]/
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── _components/
│   │   │   │   │   └── slideshow/
│   │   │   │   │       └── index.tsx
│   │   │   │   ├── account/
│   │   │   │   │   ├── addresses/
│   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   ├── address-action.ts
│   │   │   │   │   │   │   ├── create-address.ts
│   │   │   │   │   │   │   ├── delete-address.ts
│   │   │   │   │   │   │   └── update-address.ts
│   │   │   │   │   │   ├── page-data.ts
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   ├── layout.tsx
│   │   │   │   │   ├── orders/
│   │   │   │   │   │   ├── [id]/
│   │   │   │   │   │   │   ├── page-data.tsx
│   │   │   │   │   │   │   └── page.tsx
│   │   │   │   │   │   ├── fragment.ts
│   │   │   │   │   │   ├── page-data.ts
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   ├── settings/
│   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   ├── change-password.ts
│   │   │   │   │   │   │   ├── update-customer.ts
│   │   │   │   │   │   │   └── update-newsletter-subscription.ts
│   │   │   │   │   │   ├── page-data.tsx
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   └── wishlists/
│   │   │   │   │       ├── [id]/
│   │   │   │   │       │   ├── _actions/
│   │   │   │   │       │   │   └── add-to-cart.tsx
│   │   │   │   │       │   ├── _components/
│   │   │   │   │       │   │   ├── visibility-switch.tsx
│   │   │   │   │       │   │   ├── wishlist-actions.tsx
│   │   │   │   │       │   │   └── wishlist-analytics-provider.tsx
│   │   │   │   │       │   ├── page-data.ts
│   │   │   │   │       │   └── page.tsx
│   │   │   │   │       ├── _actions/
│   │   │   │   │       │   ├── change-wishlist-visibility.ts
│   │   │   │   │       │   ├── delete-wishlist.ts
│   │   │   │   │       │   ├── mutation.ts
│   │   │   │   │       │   ├── new-wishlist.ts
│   │   │   │   │       │   ├── remove-wishlist-item.ts
│   │   │   │   │       │   ├── rename-wishlist.ts
│   │   │   │   │       │   └── schema.ts
│   │   │   │   │       ├── _components/
│   │   │   │   │       │   ├── new-wishlist-button.tsx
│   │   │   │   │       │   └── wishlist-actions-menu.tsx
│   │   │   │   │       ├── modals.tsx
│   │   │   │   │       ├── page-data.ts
│   │   │   │   │       └── page.tsx
│   │   │   │   ├── blog/
│   │   │   │   │   ├── [blogId]/
│   │   │   │   │   │   ├── page-data.ts
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   ├── page-data.ts
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── cart/
│   │   │   │   │   ├── _actions/
│   │   │   │   │   │   ├── add-shipping-cost.ts
│   │   │   │   │   │   ├── add-shipping-info.ts
│   │   │   │   │   │   ├── apply-coupon-code.ts
│   │   │   │   │   │   ├── apply-gift-certificate.ts
│   │   │   │   │   │   ├── remove-coupon-code.ts
│   │   │   │   │   │   ├── remove-gift-certificate.ts
│   │   │   │   │   │   ├── remove-item.ts
│   │   │   │   │   │   ├── update-coupon-code.ts
│   │   │   │   │   │   ├── update-gift-certificate.ts
│   │   │   │   │   │   ├── update-line-item.ts
│   │   │   │   │   │   ├── update-quantity.ts
│   │   │   │   │   │   └── update-shipping-info.ts
│   │   │   │   │   ├── _components/
│   │   │   │   │   │   ├── cart-analytics-provider.tsx
│   │   │   │   │   │   ├── cart-viewed.tsx
│   │   │   │   │   │   └── checkout-preconnect.tsx
│   │   │   │   │   ├── loading.tsx
│   │   │   │   │   ├── page-data.ts
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── checkout/
│   │   │   │   │   └── route.ts
│   │   │   │   ├── compare/
│   │   │   │   │   ├── _actions/
│   │   │   │   │   │   └── add-to-cart.tsx
│   │   │   │   │   ├── _components/
│   │   │   │   │   │   └── compare-analytics-provider.tsx
│   │   │   │   │   ├── page-data.ts
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── error.tsx
│   │   │   │   ├── gift-certificates/
│   │   │   │   │   ├── balance/
│   │   │   │   │   │   ├── _actions/
│   │   │   │   │   │   │   └── get-gift-certificate-by-code.ts
│   │   │   │   │   │   ├── fragment.ts
│   │   │   │   │   │   └── page.tsx
│   │   │   │   │   ├── page-data.ts
│   │   │   │   │   ├── page.tsx
│   │   │   │   │   └── purchase/
│   │   │   │   │       ├── _actions/
│   │   │   │   │       │   └── add-to-cart.tsx
│   │   │   │   │       ├── fragment.ts
│   │   │   │   │       ├── page-data.ts
│   │   │   │   │       └── page.tsx
│   │   │   │   ├── layout.tsx
│   │   │   │   ├── page-data.ts
│   │   │   │   ├── page.tsx
│   │   │   │   ├── product/
│   │   │   │   │   └── [slug]/
│   │   │   │   │       ├── _actions/
│   │   │   │   │       │   ├── add-to-cart.tsx
│   │   │   │   │       │   ├── get-more-images.ts
│   │   │   │   │       │   ├── submit-review.ts
│   │   │   │   │       │   └── wishlist-action.ts
│   │   │   │   │       ├── _components/
│   │   │   │   │       │   ├── product-analytics-provider.tsx
│   │   │   │   │       │   ├── product-review-schema/
│   │   │   │   │       │   │   ├── fragment.ts
│   │   │   │   │       │   │   └── product-review-schema.tsx
│   │   │   │   │       │   ├── product-schema/
│   │   │   │   │       │   │   ├── fragment.ts
│   │   │   │   │       │   │   └── index.tsx
│   │   │   │   │       │   ├── product-viewed/
│   │   │   │   │       │   │   ├── fragment.ts
│   │   │   │   │       │   │   └── index.tsx
│   │   │   │   │       │   ├── reviews.tsx
│   │   │   │   │       │   ├── search-params-router-refresh.tsx
│   │   │   │   │       │   └── wishlist-button/
│   │   │   │   │       │       ├── add-to-new-wishlist-modal.tsx
│   │   │   │   │       │       ├── dropdown.tsx
│   │   │   │   │       │       ├── form.tsx
│   │   │   │   │       │       └── index.tsx
│   │   │   │   │       ├── page-data.ts
│   │   │   │   │       └── page.tsx
│   │   │   │   ├── webpages/
│   │   │   │   │   └── [id]/
│   │   │   │   │       ├── _components/
│   │   │   │   │       │   └── web-page.tsx
│   │   │   │   │       ├── contact/
│   │   │   │   │       │   ├── _actions/
│   │   │   │   │       │   │   └── submit-contact-form.ts
│   │   │   │   │       │   ├── page-data.ts
│   │   │   │   │       │   └── page.tsx
│   │   │   │   │       ├── layout.tsx
│   │   │   │   │       └── normal/
│   │   │   │   │           ├── page-data.ts
│   │   │   │   │           └── page.tsx
│   │   │   │   └── wishlist/
│   │   │   │       └── [token]/
│   │   │   │           ├── page-data.ts
│   │   │   │           └── page.tsx
│   │   │   ├── error.tsx
│   │   │   ├── layout.tsx
│   │   │   ├── maintenance/
│   │   │   │   └── page.tsx
│   │   │   └── not-found.tsx
│   │   ├── admin/
│   │   │   └── route.ts
│   │   ├── api/
│   │   │   └── auth/
│   │   │       └── [...nextauth]/
│   │   │           └── route.ts
│   │   ├── fonts.ts
│   │   ├── layout.tsx
│   │   ├── not-found.tsx
│   │   ├── notifications.tsx
│   │   ├── providers.tsx
│   │   ├── robots.txt/
│   │   │   └── route.ts
│   │   ├── sitemap.xml/
│   │   │   └── route.ts
│   │   └── xmlsitemap.php/
│   │       └── route.ts
│   ├── auth/
│   │   ├── anonymous-session.ts
│   │   ├── customer-login-api.ts
│   │   ├── index.ts
│   │   └── types.ts
│   ├── build-config/
│   │   ├── reader.ts
│   │   ├── schema.ts
│   │   └── writer.ts
│   ├── channels.config.ts
│   ├── client/
│   │   ├── correlation-id.ts
│   │   ├── fragments/
│   │   │   ├── pagination.ts
│   │   │   └── pricing.ts
│   │   ├── graphql.ts
│   │   ├── index.ts
│   │   ├── revalidate-target.ts
│   │   ├── tags.ts
│   │   └── util/
│   │       └── index.ts
│   ├── components/
│   │   ├── analytics/
│   │   │   ├── events.tsx
│   │   │   ├── fragment.ts
│   │   │   └── provider.tsx
│   │   ├── breadcrumbs/
│   │   │   └── fragment.ts
│   │   ├── consent-manager/
│   │   │   ├── consent-manager-dialog.tsx
│   │   │   ├── consent-providers.tsx
│   │   │   ├── cookie-banner.tsx
│   │   │   ├── index.tsx
│   │   │   └── scripts-fragment.ts
│   │   ├── featured-products-carousel/
│   │   │   └── fragment.ts
│   │   ├── featured-products-list/
│   │   │   └── fragment.ts
│   │   ├── footer/
│   │   │   ├── fragment.ts
│   │   │   ├── index.tsx
│   │   │   └── payment-icons/
│   │   │       ├── amazon.tsx
│   │   │       ├── american-express.tsx
│   │   │       ├── apple-pay.tsx
│   │   │       ├── mastercard.tsx
│   │   │       ├── paypal.tsx
│   │   │       └── visa.tsx
│   │   ├── force-refresh/
│   │   │   └── index.tsx
│   │   ├── header/
│   │   │   ├── _actions/
│   │   │   │   ├── fragment.ts
│   │   │   │   ├── search.ts
│   │   │   │   └── switch-currency.ts
│   │   │   ├── fragment.ts
│   │   │   ├── index.tsx
│   │   │   └── schema.ts
│   │   ├── image/
│   │   │   └── index.tsx
│   │   ├── link/
│   │   │   └── index.tsx
│   │   ├── modal/
│   │   │   ├── index.tsx
│   │   │   └── modal-form-provider.tsx
│   │   ├── polyfills/
│   │   │   └── container-query/
│   │   │       └── index.tsx
│   │   ├── product-card/
│   │   │   └── fragment.ts
│   │   ├── product-variants-inventory/
│   │   │   └── fragment.ts
│   │   ├── store-logo/
│   │   │   └── fragment.ts
│   │   ├── subscribe/
│   │   │   ├── _actions/
│   │   │   │   └── subscribe.ts
│   │   │   └── index.tsx
│   │   └── wishlist/
│   │       ├── error.ts
│   │       ├── fragment.ts
│   │       ├── modals/
│   │       │   ├── change-visibility.tsx
│   │       │   ├── delete.tsx
│   │       │   ├── new.tsx
│   │       │   ├── rename.tsx
│   │       │   └── share.tsx
│   │       └── share-button.tsx
│   ├── data-transformers/
│   │   ├── breadcrumbs-transformer.ts
│   │   ├── facets-transformer.ts
│   │   ├── form-field-transformer/
│   │   │   ├── fragment.ts
│   │   │   ├── index.ts
│   │   │   └── utils.ts
│   │   ├── logo-transformer.ts
│   │   ├── order-details-transformer.ts
│   │   ├── orders-transformer.ts
│   │   ├── page-info-transformer.ts
│   │   ├── prices-transformer.ts
│   │   ├── product-card-transformer.ts
│   │   ├── product-options-transformer.ts
│   │   ├── scripts-transformer.ts
│   │   ├── search-results-transformer.ts
│   │   └── wishlists-transformer.ts
│   ├── global.ts
│   ├── globals.css
│   ├── i18n/
│   │   ├── locales.ts
│   │   ├── request.ts
│   │   ├── routing.ts
│   │   └── utils.ts
│   ├── instrumentation.ts
│   ├── lib/
│   │   ├── analytics/
│   │   │   ├── analytics.d.ts
│   │   │   ├── bigcommerce/
│   │   │   │   ├── data-events.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── providers/
│   │   │   │   └── google-analytics/
│   │   │   │       └── index.ts
│   │   │   ├── react/
│   │   │   │   └── index.tsx
│   │   │   └── types.ts
│   │   ├── cart/
│   │   │   ├── add-cart-line-item.ts
│   │   │   ├── create-cart.ts
│   │   │   ├── error.ts
│   │   │   ├── index.ts
│   │   │   └── validate-cart.ts
│   │   ├── cdn-image-loader.ts
│   │   ├── client-cookies.ts
│   │   ├── consent-manager/
│   │   │   ├── cookies/
│   │   │   │   ├── client.ts
│   │   │   │   ├── constants.ts
│   │   │   │   ├── parse-compact-format.ts
│   │   │   │   └── server.ts
│   │   │   └── schema.ts
│   │   ├── content-security-policy.ts
│   │   ├── currency.ts
│   │   ├── force-refresh.ts
│   │   ├── kv/
│   │   │   ├── adapters/
│   │   │   │   ├── memory.ts
│   │   │   │   ├── upstash.ts
│   │   │   │   └── vercel-runtime-cache.ts
│   │   │   ├── index.ts
│   │   │   ├── keys.ts
│   │   │   └── types.ts
│   │   ├── otel/
│   │   │   └── tracer.ts
│   │   ├── recaptcha/
│   │   │   └── constants.ts
│   │   ├── recaptcha.ts
│   │   ├── search.tsx
│   │   ├── seo/
│   │   │   └── canonical.ts
│   │   ├── server-toast.ts
│   │   ├── store-assets.ts
│   │   ├── user-agent.ts
│   │   └── utils.ts
│   ├── messages/
│   │   ├── da.json
│   │   ├── de.json
│   │   ├── en.json
│   │   ├── es-419.json
│   │   ├── es-AR.json
│   │   ├── es-CL.json
│   │   ├── es-CO.json
│   │   ├── es-LA.json
│   │   ├── es-MX.json
│   │   ├── es-PE.json
│   │   ├── es.json
│   │   ├── fr.json
│   │   ├── it.json
│   │   ├── ja.json
│   │   ├── nl.json
│   │   ├── no.json
│   │   ├── pl.json
│   │   ├── pt-BR.json
│   │   ├── pt.json
│   │   └── sv.json
│   ├── next.config.ts
│   ├── package.json
│   ├── playwright.config.ts
│   ├── postcss.config.js
│   ├── prettier.config.js
│   ├── proxies/
│   │   ├── compose-proxies.ts
│   │   ├── with-analytics-cookies.ts
│   │   ├── with-auth.ts
│   │   ├── with-channel-id.ts
│   │   ├── with-intl.ts
│   │   └── with-routes.ts
│   ├── proxy.ts
│   ├── scripts/
│   │   └── generate.cjs
│   ├── tailwind.config.js
│   ├── tests/
│   │   ├── README.md
│   │   ├── environment.ts
│   │   ├── fixtures/
│   │   │   ├── blog/
│   │   │   │   └── index.ts
│   │   │   ├── browser.ts
│   │   │   ├── catalog/
│   │   │   │   └── index.ts
│   │   │   ├── currency/
│   │   │   │   └── index.ts
│   │   │   ├── customer/
│   │   │   │   ├── index.ts
│   │   │   │   └── session.ts
│   │   │   ├── fixture.ts
│   │   │   ├── index.ts
│   │   │   ├── order/
│   │   │   │   └── index.ts
│   │   │   ├── page.ts
│   │   │   ├── promotion/
│   │   │   │   └── index.ts
│   │   │   ├── redirects/
│   │   │   │   └── index.ts
│   │   │   ├── settings/
│   │   │   │   └── index.ts
│   │   │   ├── subscribe/
│   │   │   │   └── index.ts
│   │   │   ├── utils/
│   │   │   │   └── api/
│   │   │   │       ├── blog/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── catalog/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── client.ts
│   │   │   │       ├── currencies/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── customers/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── errors/
│   │   │   │       │   ├── index.ts
│   │   │   │       │   └── response-error.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── orders/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── promotions/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── redirects/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── schema.ts
│   │   │   │       ├── settings/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── subscribe/
│   │   │   │       │   ├── http.ts
│   │   │   │       │   └── index.ts
│   │   │   │       └── webpages/
│   │   │   │           ├── http.ts
│   │   │   │           └── index.ts
│   │   │   └── webpage/
│   │   │       └── index.ts
│   │   ├── lib/
│   │   │   ├── formatter/
│   │   │   │   └── index.ts
│   │   │   └── i18n/
│   │   │       └── index.ts
│   │   ├── routes.ts
│   │   ├── tags.ts
│   │   ├── ui/
│   │   │   ├── components/
│   │   │   │   ├── accordion.spec.ts
│   │   │   │   ├── breadcrumbs.spec.ts
│   │   │   │   ├── carousel.spec.ts
│   │   │   │   ├── checkbox.spec.ts
│   │   │   │   ├── counter.spec.ts
│   │   │   │   ├── radio-group.spec.ts
│   │   │   │   ├── search.spec.ts
│   │   │   │   ├── select.spec.ts
│   │   │   │   ├── slideshow.spec.ts
│   │   │   │   └── swatch.spec.ts
│   │   │   └── e2e/
│   │   │       ├── account/
│   │   │       │   ├── account-settings.spec.ts
│   │   │       │   ├── account.spec.ts
│   │   │       │   ├── addresses.spec.ts
│   │   │       │   ├── order-details.spec.ts
│   │   │       │   ├── orders.spec.ts
│   │   │       │   ├── wishlist-details.spec.ts
│   │   │       │   ├── wishlists.mobile.spec.ts
│   │   │       │   └── wishlists.spec.ts
│   │   │       ├── analytics-session.spec.ts
│   │   │       ├── auth/
│   │   │       │   ├── anonymous-session.spec.ts
│   │   │       │   ├── forgot-password.spec.ts
│   │   │       │   ├── login.spec.ts
│   │   │       │   ├── logout.spec.ts
│   │   │       │   └── register.spec.ts
│   │   │       ├── blog.spec.ts
│   │   │       ├── cart.spec.ts
│   │   │       ├── checkout.spec.ts
│   │   │       ├── compare.spec.ts
│   │   │       ├── coupon.spec.ts
│   │   │       ├── facets.spec.ts
│   │   │       ├── home.spec.ts
│   │   │       ├── not-found.spec.ts
│   │   │       ├── product.spec.ts
│   │   │       ├── proxy/
│   │   │       │   └── redirects.spec.ts
│   │   │       ├── reviews.spec.ts
│   │   │       ├── search.spec.ts
│   │   │       ├── shipping.spec.ts
│   │   │       ├── subscribe.spec.ts
│   │   │       └── webpages.spec.ts
│   │   └── visual-regression/
│   │       └── components/
│   │           ├── accordion.spec.ts
│   │           ├── badge.spec.ts
│   │           ├── blog-post-card.spec.ts
│   │           ├── breadcrumbs.spec.ts
│   │           ├── button.spec.ts
│   │           ├── carousel.spec.ts
│   │           ├── checkbox.spec.ts
│   │           ├── counter.spec.ts
│   │           ├── datepicker.spec.ts
│   │           ├── footer.spec.ts
│   │           ├── form.spec.ts
│   │           ├── gallery.spec.ts
│   │           ├── header.spec.ts
│   │           ├── input.spec.ts
│   │           ├── label.spec.ts
│   │           ├── picklist.spec.ts
│   │           ├── radio-group.spec.ts
│   │           ├── rating.spec.ts
│   │           ├── rectangle-list.spec.ts
│   │           ├── select.spec.ts
│   │           ├── slideshow.spec.ts
│   │           ├── swatch.spec.ts
│   │           ├── tags.spec.ts
│   │           └── textarea.spec.ts
│   ├── tsconfig.json
│   ├── user-agent.ts
│   └── vibes/
│       └── soul/
│           ├── form/
│           │   ├── button-radio-group/
│           │   │   └── index.tsx
│           │   ├── card-radio-group/
│           │   │   └── index.tsx
│           │   ├── checkbox/
│           │   │   └── index.tsx
│           │   ├── checkbox-group/
│           │   │   └── index.tsx
│           │   ├── date-picker/
│           │   │   └── index.tsx
│           │   ├── dynamic-form/
│           │   │   ├── index.tsx
│           │   │   ├── schema.ts
│           │   │   └── utils.ts
│           │   ├── field-error/
│           │   │   └── index.tsx
│           │   ├── form-status/
│           │   │   └── index.tsx
│           │   ├── input/
│           │   │   └── index.tsx
│           │   ├── label/
│           │   │   └── index.tsx
│           │   ├── number-input/
│           │   │   └── index.tsx
│           │   ├── radio-group/
│           │   │   └── index.tsx
│           │   ├── range-input/
│           │   │   └── index.tsx
│           │   ├── rating-radio-group/
│           │   │   └── index.tsx
│           │   ├── select/
│           │   │   └── index.tsx
│           │   ├── swatch-radio-group/
│           │   │   └── index.tsx
│           │   ├── switch/
│           │   │   └── index.tsx
│           │   ├── textarea/
│           │   │   └── index.tsx
│           │   └── toggle-group/
│           │       └── index.tsx
│           ├── lib/
│           │   └── streamable.tsx
│           ├── primitives/
│           │   ├── accordion/
│           │   │   └── index.tsx
│           │   ├── alert/
│           │   │   └── index.tsx
│           │   ├── animated-underline/
│           │   │   └── index.tsx
│           │   ├── badge/
│           │   │   └── index.tsx
│           │   ├── banner/
│           │   │   └── index.tsx
│           │   ├── blog-post-card/
│           │   │   └── index.tsx
│           │   ├── button/
│           │   │   └── index.tsx
│           │   ├── button-link/
│           │   │   └── index.tsx
│           │   ├── calendar/
│           │   │   └── index.tsx
│           │   ├── carousel/
│           │   │   └── index.tsx
│           │   ├── chip/
│           │   │   └── index.tsx
│           │   ├── compare-card/
│           │   │   ├── add-to-cart-form.tsx
│           │   │   ├── index.tsx
│           │   │   └── schema.ts
│           │   ├── compare-drawer/
│           │   │   ├── index.tsx
│           │   │   └── loader.tsx
│           │   ├── cursor-pagination/
│           │   │   └── index.tsx
│           │   ├── dropdown-menu/
│           │   │   └── index.tsx
│           │   ├── favorite/
│           │   │   ├── heart.tsx
│           │   │   ├── index.tsx
│           │   │   └── styles.css
│           │   ├── gift-certificate-card/
│           │   │   ├── gift-certificate-card-logo.tsx
│           │   │   └── index.tsx
│           │   ├── inline-email-form/
│           │   │   ├── index.tsx
│           │   │   └── schema.ts
│           │   ├── logo/
│           │   │   └── index.tsx
│           │   ├── modal/
│           │   │   └── index.tsx
│           │   ├── navigation/
│           │   │   └── index.tsx
│           │   ├── price-label/
│           │   │   └── index.tsx
│           │   ├── product-card/
│           │   │   ├── compare.tsx
│           │   │   └── index.tsx
│           │   ├── rating/
│           │   │   └── index.tsx
│           │   ├── reveal/
│           │   │   └── index.tsx
│           │   ├── side-panel/
│           │   │   └── index.tsx
│           │   ├── skeleton/
│           │   │   └── index.tsx
│           │   ├── spinner/
│           │   │   └── index.tsx
│           │   ├── toaster/
│           │   │   └── index.tsx
│           │   ├── tooltip/
│           │   │   └── index.tsx
│           │   └── wishlist-item-card/
│           │       ├── index.tsx
│           │       ├── remove-wishlist-item.tsx
│           │       └── wishlist-item-add-to-cart.tsx
│           └── sections/
│               ├── account-settings/
│               │   ├── change-password-form.tsx
│               │   ├── index.tsx
│               │   ├── newsletter-subscription-form.tsx
│               │   ├── schema.ts
│               │   └── update-account-form.tsx
│               ├── address-list-section/
│               │   ├── index.tsx
│               │   └── schema.ts
│               ├── blog-post-content/
│               │   └── index.tsx
│               ├── blog-post-list/
│               │   └── index.tsx
│               ├── breadcrumbs/
│               │   └── index.tsx
│               ├── cart/
│               │   ├── client.tsx
│               │   ├── coupon-code-form/
│               │   │   ├── coupon-chip.tsx
│               │   │   └── index.tsx
│               │   ├── gift-certificate-code-form/
│               │   │   ├── gift-certificate-chip.tsx
│               │   │   └── index.tsx
│               │   ├── index.tsx
│               │   ├── schema.ts
│               │   └── shipping-form/
│               │       └── index.tsx
│               ├── compare-section/
│               │   └── index.tsx
│               ├── dynamic-form-section/
│               │   └── index.tsx
│               ├── error/
│               │   └── index.tsx
│               ├── featured-blog-post-list/
│               │   └── index.tsx
│               ├── featured-product-carousel/
│               │   └── index.tsx
│               ├── featured-product-list/
│               │   └── index.tsx
│               ├── footer/
│               │   └── index.tsx
│               ├── forgot-password-section/
│               │   ├── forgot-password-form.tsx
│               │   ├── index.tsx
│               │   └── schema.ts
│               ├── gift-certificate-balance-section/
│               │   ├── index.tsx
│               │   └── schema.ts
│               ├── gift-certificate-purchase-section/
│               │   └── index.tsx
│               ├── gift-certificates-section/
│               │   └── index.tsx
│               ├── header-section/
│               │   └── index.tsx
│               ├── maintenance/
│               │   └── index.tsx
│               ├── not-found/
│               │   └── index.tsx
│               ├── order-details-section/
│               │   └── index.tsx
│               ├── order-list/
│               │   └── index.tsx
│               ├── product-carousel/
│               │   └── index.tsx
│               ├── product-detail/
│               │   ├── actions/
│               │   │   └── revalidate-cart.ts
│               │   ├── index.tsx
│               │   ├── product-detail-form.tsx
│               │   ├── product-gallery.tsx
│               │   ├── rating-link.tsx
│               │   └── schema.ts
│               ├── product-list/
│               │   └── index.tsx
│               ├── products-list-section/
│               │   ├── filter-parsers.ts
│               │   ├── filters-panel.tsx
│               │   ├── index.tsx
│               │   └── sorting.tsx
│               ├── reset-password-section/
│               │   ├── index.tsx
│               │   ├── reset-password-form.tsx
│               │   └── schema.ts
│               ├── reviews/
│               │   ├── index.tsx
│               │   ├── review-form.tsx
│               │   └── schema.ts
│               ├── section-layout/
│               │   └── index.tsx
│               ├── sidebar-menu/
│               │   ├── index.tsx
│               │   ├── sidebar-menu-link.tsx
│               │   └── sidebar-menu-select.tsx
│               ├── sign-in-section/
│               │   ├── index.tsx
│               │   ├── schema.ts
│               │   └── sign-in-form.tsx
│               ├── slideshow/
│               │   └── index.tsx
│               ├── sticky-sidebar-layout/
│               │   └── index.tsx
│               ├── subscribe/
│               │   └── index.tsx
│               ├── wishlist-details/
│               │   └── index.tsx
│               ├── wishlist-list/
│               │   └── index.tsx
│               ├── wishlist-list-item/
│               │   └── index.tsx
│               └── wishlists-section/
│                   └── index.tsx
├── graphql.config.json
├── package.json
├── packages/
│   ├── catalyst/
│   │   ├── .eslintrc.cjs
│   │   ├── README.md
│   │   ├── commander.d.ts
│   │   ├── package.json
│   │   ├── prettier.config.cjs
│   │   ├── src/
│   │   │   └── cli/
│   │   │       ├── commands/
│   │   │       │   ├── build.spec.ts
│   │   │       │   ├── build.ts
│   │   │       │   ├── deploy.spec.ts
│   │   │       │   ├── deploy.ts
│   │   │       │   ├── dev.spec.ts
│   │   │       │   ├── dev.ts
│   │   │       │   ├── project.spec.ts
│   │   │       │   ├── project.ts
│   │   │       │   ├── start.spec.ts
│   │   │       │   ├── start.ts
│   │   │       │   ├── telemetry.ts
│   │   │       │   ├── version.spec.ts
│   │   │       │   └── version.ts
│   │   │       ├── hooks/
│   │   │       │   └── telemetry.ts
│   │   │       ├── index.spec.ts
│   │   │       ├── index.ts
│   │   │       ├── lib/
│   │   │       │   ├── get-module-cli-path.ts
│   │   │       │   ├── logger.ts
│   │   │       │   ├── mk-temp-dir.spec.ts
│   │   │       │   ├── mk-temp-dir.ts
│   │   │       │   ├── project-config.spec.ts
│   │   │       │   ├── project-config.ts
│   │   │       │   ├── project.ts
│   │   │       │   ├── telemetry.ts
│   │   │       │   ├── wrangler-config.spec.ts
│   │   │       │   └── wrangler-config.ts
│   │   │       └── program.ts
│   │   ├── templates/
│   │   │   └── open-next.config.ts
│   │   ├── tests/
│   │   │   └── mocks/
│   │   │       ├── handlers.ts
│   │   │       ├── node.ts
│   │   │       └── spinner.ts
│   │   ├── tsconfig.json
│   │   ├── tsup.config.ts
│   │   ├── vitest.config.ts
│   │   └── vitest.setup.ts
│   ├── client/
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── prettier.config.js
│   │   ├── src/
│   │   │   ├── api-error.ts
│   │   │   ├── client.ts
│   │   │   ├── gql-auth-error.ts
│   │   │   ├── gql-error.ts
│   │   │   ├── index.ts
│   │   │   ├── invalid-cat-error.ts
│   │   │   ├── lib/
│   │   │   │   └── error.ts
│   │   │   ├── missing-cat-error.ts
│   │   │   ├── types.ts
│   │   │   └── utils/
│   │   │       ├── getOperationName.ts
│   │   │       ├── normalizeQuery.ts
│   │   │       ├── removeEdgesAndNodes.ts
│   │   │       └── userAgent.ts
│   │   ├── tsconfig.json
│   │   └── tsup.config.ts
│   ├── create-catalyst/
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── bin/
│   │   │   ├── index.cjs
│   │   │   └── supported-node-versions.cjs
│   │   ├── jest.config.cjs
│   │   ├── package.json
│   │   ├── prettier.config.cjs
│   │   ├── src/
│   │   │   ├── commands/
│   │   │   │   ├── create.ts
│   │   │   │   ├── init.ts
│   │   │   │   ├── integration.ts
│   │   │   │   └── telemetry.ts
│   │   │   ├── hooks/
│   │   │   │   └── telemetry.ts
│   │   │   ├── index.ts
│   │   │   ├── prompts/
│   │   │   │   └── multi-select/
│   │   │   │       ├── helpers.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── multi-select.ts
│   │   │   │       └── types.ts
│   │   │   └── utils/
│   │   │       ├── auth.ts
│   │   │       ├── checkout-ref.ts
│   │   │       ├── cli-api.ts
│   │   │       ├── clone-catalyst.ts
│   │   │       ├── config.ts
│   │   │       ├── has-github-ssh.ts
│   │   │       ├── https.ts
│   │   │       ├── install-dependencies.ts
│   │   │       ├── is-exec-exception.ts
│   │   │       ├── localization.ts
│   │   │       ├── login.ts
│   │   │       ├── node-version.spec.ts
│   │   │       ├── parse.ts
│   │   │       ├── reset-branch-to-ref.ts
│   │   │       ├── spinner.spec.ts
│   │   │       ├── spinner.ts
│   │   │       ├── telemetry/
│   │   │       │   ├── index.ts
│   │   │       │   └── telemetry.ts
│   │   │       ├── user-agent.ts
│   │   │       └── write-env.ts
│   │   ├── tsconfig.json
│   │   └── tsup.config.ts
│   └── eslint-config-catalyst/
│       ├── CHANGELOG.md
│       ├── base.js
│       ├── next.js
│       ├── package.json
│       ├── prettier.js
│       └── react.js
├── pnpm-workspace.yaml
├── turbo.json
└── unlighthouse.config.ts
Download .txt
SYMBOL INDEX (1273 symbols across 372 files)

FILE: core/app/[locale]/(default)/(auth)/change-password/_actions/change-password.ts
  function changePassword (line 32) | async function changePassword(

FILE: core/app/[locale]/(default)/(auth)/change-password/page.tsx
  type Props (line 11) | interface Props {
  function generateMetadata (line 19) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function ChangePassword (line 29) | async function ChangePassword({ params, searchParams }: Props) {

FILE: core/app/[locale]/(default)/(auth)/layout.tsx
  type Props (line 6) | interface Props extends PropsWithChildren {
  function Layout (line 10) | async function Layout({ children, params }: Props) {

FILE: core/app/[locale]/(default)/(auth)/login/forgot-password/page.tsx
  type Props (line 8) | interface Props {
  function generateMetadata (line 12) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function Reset (line 22) | async function Reset(props: Props) {

FILE: core/app/[locale]/(default)/(auth)/login/page.tsx
  type Props (line 12) | interface Props {
  function generateMetadata (line 20) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function Login (line 30) | async function Login({ params, searchParams }: Props) {

FILE: core/app/[locale]/(default)/(auth)/login/token/[token]/route.ts
  function GET (line 14) | async function GET(_: Request, { params }: { params: Promise<{ token: st...

FILE: core/app/[locale]/(default)/(auth)/register/_actions/prefixes.ts
  constant ADDRESS_FIELDS_NAME_PREFIX (line 1) | const ADDRESS_FIELDS_NAME_PREFIX = 'customAddress_';
  constant CUSTOMER_FIELDS_NAME_PREFIX (line 2) | const CUSTOMER_FIELDS_NAME_PREFIX = 'customCustomer_';

FILE: core/app/[locale]/(default)/(auth)/register/_actions/register-customer.ts
  function parseRegisterCustomerInput (line 164) | function parseRegisterCustomerInput(
  function registerCustomer (line 342) | async function registerCustomer<F extends Field>(

FILE: core/app/[locale]/(default)/(auth)/register/page-data.ts
  type Variables (line 50) | type Variables = VariablesOf<typeof RegisterCustomerQuery>;
  type Props (line 52) | interface Props {

FILE: core/app/[locale]/(default)/(auth)/register/page.tsx
  type Props (line 23) | interface Props {
  function generateMetadata (line 27) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function removeExlusiveOffersField (line 39) | function removeExlusiveOffersField(field: Field | Field[]): boolean {
  function Register (line 48) | async function Register({ params }: Props) {

FILE: core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx
  type Props (line 62) | interface Props {
  function generateMetadata (line 70) | async function generateMetadata(props: Props): Promise<Metadata> {
  function Brand (line 92) | async function Brand(props: Props) {

FILE: core/app/[locale]/(default)/(faceted)/category/[slug]/_components/category-viewed.tsx
  type Category (line 12) | type Category = Awaited<ReturnType<typeof getCategoryPageData>>['categor...
  type productSearchItem (line 13) | type productSearchItem = FragmentOf<typeof ProductCardFragment>;
  type Props (line 15) | interface Props {

FILE: core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx
  type Props (line 64) | interface Props {
  function generateMetadata (line 72) | async function generateMetadata(props: Props): Promise<Metadata> {
  function Category (line 99) | async function Category(props: Props) {

FILE: core/app/[locale]/(default)/(faceted)/fetch-compare-products.ts
  type Variables (line 43) | type Variables = VariablesOf<typeof CompareProductsQuery>;

FILE: core/app/[locale]/(default)/(faceted)/fetch-faceted-search.ts
  type Variables (line 169) | type Variables = VariablesOf<typeof GetProductSearchResultsQuery>;
  type SearchProductsSortInput (line 170) | type SearchProductsSortInput = Variables['sort'];
  type SearchProductsFiltersInput (line 171) | type SearchProductsFiltersInput = Variables['filters'];
  type ProductSearch (line 173) | interface ProductSearch {

FILE: core/app/[locale]/(default)/(faceted)/search/page.tsx
  type Props (line 57) | interface Props {
  function generateMetadata (line 62) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function Search (line 72) | async function Search(props: Props) {

FILE: core/app/[locale]/(default)/[...rest]/page.tsx
  function CatchAllPage (line 3) | function CatchAllPage() {

FILE: core/app/[locale]/(default)/_components/slideshow/index.tsx
  function Slideshow (line 9) | function Slideshow() {

FILE: core/app/[locale]/(default)/account/addresses/_actions/address-action.ts
  type State (line 10) | interface State {
  function addressAction (line 16) | async function addressAction(

FILE: core/app/[locale]/(default)/account/addresses/_actions/create-address.ts
  function parseAddAddressInput (line 100) | function parseAddAddressInput(
  function createAddress (line 199) | async function createAddress(

FILE: core/app/[locale]/(default)/account/addresses/_actions/delete-address.ts
  function parseDeleteAddressInput (line 39) | function parseDeleteAddressInput(
  function deleteAddress (line 47) | async function deleteAddress(prevState: Awaited<State>, formData: FormDa...

FILE: core/app/[locale]/(default)/account/addresses/_actions/update-address.ts
  function parseUpdateAddressInput (line 109) | function parseUpdateAddressInput(
  function updateAddress (line 212) | async function updateAddress(

FILE: core/app/[locale]/(default)/account/addresses/page-data.ts
  type Pagination (line 66) | interface Pagination {

FILE: core/app/[locale]/(default)/account/addresses/page.tsx
  type Props (line 20) | interface Props {
  function generateMetadata (line 29) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function Addresses (line 39) | async function Addresses({ params, searchParams }: Props) {

FILE: core/app/[locale]/(default)/account/layout.tsx
  type Props (line 7) | interface Props extends PropsWithChildren {
  function Layout (line 11) | async function Layout({ children, params }: Props) {

FILE: core/app/[locale]/(default)/account/orders/[id]/page.tsx
  type Props (line 10) | interface Props {
  function OrderDetails (line 17) | async function OrderDetails(props: Props) {

FILE: core/app/[locale]/(default)/account/orders/page-data.ts
  type OrdersFiltersInput (line 79) | type OrdersFiltersInput = VariablesOf<typeof CustomerAllOrders>['filters'];
  type OrderStatus (line 80) | type OrderStatus = NonNullable<OrdersFiltersInput>['status'];
  type OrderDateRange (line 81) | type OrderDateRange = NonNullable<OrdersFiltersInput>['dateRange'];
  type CustomerOrdersArgs (line 83) | interface CustomerOrdersArgs {

FILE: core/app/[locale]/(default)/account/orders/page.tsx
  type Props (line 9) | interface Props {
  function getOrders (line 18) | async function getOrders(after?: string, before?: string): Promise<Order...
  function getPaginationInfo (line 34) | async function getPaginationInfo(after?: string, before?: string) {
  function Orders (line 43) | async function Orders({ params, searchParams }: Props) {

FILE: core/app/[locale]/(default)/account/settings/page-data.tsx
  type Variables (line 56) | type Variables = VariablesOf<typeof AccountSettingsQuery>;
  type Props (line 58) | interface Props {

FILE: core/app/[locale]/(default)/account/settings/page.tsx
  type Props (line 13) | interface Props {
  function generateMetadata (line 17) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function Settings (line 27) | async function Settings({ params }: Props) {

FILE: core/app/[locale]/(default)/account/wishlists/[id]/_actions/add-to-cart.tsx
  type State (line 13) | interface State {
  function addWishlistItemToCart (line 24) | async function addWishlistItemToCart(prevState: State, formData: FormDat...

FILE: core/app/[locale]/(default)/account/wishlists/[id]/_components/wishlist-actions.tsx
  type Props (line 14) | interface Props {
  function WishlistActionsSkeleton (line 72) | function WishlistActionsSkeleton() {

FILE: core/app/[locale]/(default)/account/wishlists/[id]/_components/wishlist-analytics-provider.tsx
  type AddToCartContext (line 10) | interface AddToCartContext {
  function WishlistAnalyticsProvider (line 24) | function WishlistAnalyticsProvider(
  function WishlistAnalyticsProviderResolved (line 34) | function WishlistAnalyticsProviderResolved({

FILE: core/app/[locale]/(default)/account/wishlists/[id]/page-data.ts
  type Pagination (line 34) | interface Pagination {

FILE: core/app/[locale]/(default)/account/wishlists/[id]/page.tsx
  type Props (line 23) | interface Props {
  function getWishlist (line 36) | async function getWishlist(
  function getPaginationInfo (line 79) | async function getPaginationInfo(
  function WishlistPage (line 90) | async function WishlistPage({ params, searchParams }: Props) {

FILE: core/app/[locale]/(default)/account/wishlists/_actions/change-wishlist-visibility.ts
  type State (line 16) | interface State {
  function toggleWishlistVisibility (line 22) | async function toggleWishlistVisibility(

FILE: core/app/[locale]/(default)/account/wishlists/_actions/delete-wishlist.ts
  type State (line 17) | interface State {
  function deleteWishlist (line 22) | async function deleteWishlist(

FILE: core/app/[locale]/(default)/account/wishlists/_actions/new-wishlist.ts
  type State (line 16) | interface State {
  function newWishlist (line 21) | async function newWishlist(prevState: Awaited<State>, formData: FormData...

FILE: core/app/[locale]/(default)/account/wishlists/_actions/remove-wishlist-item.ts
  type State (line 17) | interface State {
  function removeWishlistItem (line 22) | async function removeWishlistItem(

FILE: core/app/[locale]/(default)/account/wishlists/_actions/rename-wishlist.ts
  type State (line 16) | interface State {
  function renameWishlist (line 21) | async function renameWishlist(

FILE: core/app/[locale]/(default)/account/wishlists/_components/new-wishlist-button.tsx
  type Props (line 11) | interface Props {

FILE: core/app/[locale]/(default)/account/wishlists/_components/wishlist-actions-menu.tsx
  type WishlistActionBase (line 13) | interface WishlistActionBase {
  type WishlistModalProps (line 20) | interface WishlistModalProps {
  type WishlistModalAction (line 28) | interface WishlistModalAction extends WishlistActionBase {
  type WishlistMenuAction (line 33) | interface WishlistMenuAction extends WishlistActionBase {
  type WishlistAction (line 37) | type WishlistAction = WishlistModalAction | WishlistMenuAction;
  type Props (line 39) | interface Props {
  function reducer (line 57) | function reducer(state: Record<string, boolean>, action: { modal: string...
  function getShareMenuItemProps (line 64) | function getShareMenuItemProps(

FILE: core/app/[locale]/(default)/account/wishlists/page-data.ts
  type Pagination (line 30) | interface Pagination {

FILE: core/app/[locale]/(default)/account/wishlists/page.tsx
  type Props (line 25) | interface Props {
  function listWishlists (line 38) | async function listWishlists(
  function getPaginationInfo (line 53) | async function getPaginationInfo(
  function Wishlists (line 62) | async function Wishlists({ params, searchParams }: Props) {

FILE: core/app/[locale]/(default)/blog/[blogId]/page-data.ts
  type Variables (line 39) | type Variables = VariablesOf<typeof BlogPageQuery>;

FILE: core/app/[locale]/(default)/blog/[blogId]/page.tsx
  type Props (line 14) | interface Props {
  function generateMetadata (line 21) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function getBlogPost (line 45) | async function getBlogPost(props: Props): Promise<BlogPostContentBlogPos...
  function getBlogPostBreadcrumbs (line 76) | async function getBlogPostBreadcrumbs(props: Props): Promise<Breadcrumb[...
  function Blog (line 106) | async function Blog(props: Props) {

FILE: core/app/[locale]/(default)/blog/page-data.ts
  type BlogPostsFiltersInput (line 65) | interface BlogPostsFiltersInput {
  type Pagination (line 69) | interface Pagination {

FILE: core/app/[locale]/(default)/blog/page.tsx
  type Props (line 14) | interface Props {
  function generateMetadata (line 28) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function listBlogPosts (line 46) | async function listBlogPosts(searchParamsPromise: Promise<SearchParams>) {
  function getEmptyStateTitle (line 54) | async function getEmptyStateTitle(): Promise<string | null> {
  function getEmptyStateSubtitle (line 60) | async function getEmptyStateSubtitle(): Promise<string | null> {
  function getPaginationInfo (line 66) | async function getPaginationInfo(searchParamsPromise: Promise<SearchPara...
  function Blog (line 73) | async function Blog(props: Props) {

FILE: core/app/[locale]/(default)/cart/_actions/add-shipping-cost.ts
  type Props (line 22) | interface Props {

FILE: core/app/[locale]/(default)/cart/_actions/add-shipping-info.ts
  type AddProps (line 31) | interface AddProps {
  type UpdateProps (line 97) | interface UpdateProps {

FILE: core/app/[locale]/(default)/cart/_actions/apply-coupon-code.ts
  type Variables (line 22) | type Variables = VariablesOf<typeof ApplyCheckoutCouponMutation>;
  type Props (line 24) | interface Props {

FILE: core/app/[locale]/(default)/cart/_actions/apply-gift-certificate.ts
  type Variables (line 24) | type Variables = VariablesOf<typeof ApplyCheckoutGiftCertificateMutation>;
  type Props (line 26) | interface Props {

FILE: core/app/[locale]/(default)/cart/_actions/remove-coupon-code.ts
  type Variables (line 22) | type Variables = VariablesOf<typeof UnapplyCheckoutCouponMutation>;
  type Props (line 24) | interface Props {

FILE: core/app/[locale]/(default)/cart/_actions/remove-gift-certificate.ts
  type Variables (line 24) | type Variables = VariablesOf<typeof UnapplyCheckoutGiftCertificateMutati...
  type Props (line 26) | interface Props {

FILE: core/app/[locale]/(default)/cart/_actions/remove-item.ts
  type Variables (line 24) | type Variables = VariablesOf<typeof DeleteCartLineItemMutation>;
  type DeleteCartLineItemInput (line 25) | type DeleteCartLineItemInput = Variables['input'];
  function removeItem (line 27) | async function removeItem({

FILE: core/app/[locale]/(default)/cart/_actions/update-line-item.ts
  type LineItem (line 17) | type LineItem = {

FILE: core/app/[locale]/(default)/cart/_actions/update-quantity.ts
  type CartLineItemInput (line 25) | type CartLineItemInput = ReturnType<typeof graphql.scalar<'CartLineItemI...
  type CartSelectedOptionsInput (line 26) | type CartSelectedOptionsInput = ReturnType<
  type Variables (line 29) | type Variables = VariablesOf<typeof UpdateCartLineItemMutation>;
  type UpdateCartLineItemInput (line 30) | type UpdateCartLineItemInput = Variables['input'];
  type UpdateProductQuantityParams (line 32) | interface UpdateProductQuantityParams extends CartLineItemInput {

FILE: core/app/[locale]/(default)/cart/_components/cart-analytics-provider.tsx
  type AddToCartContext (line 10) | interface AddToCartContext {
  function CartAnalyticsProvider (line 25) | function CartAnalyticsProvider(
  function CartAnalyticsProviderResolved (line 35) | function CartAnalyticsProviderResolved({

FILE: core/app/[locale]/(default)/cart/_components/cart-viewed.tsx
  type PhysicalItem (line 14) | type PhysicalItem = FragmentOf<typeof PhysicalItemFragment>;
  type DigitalItem (line 15) | type DigitalItem = FragmentOf<typeof DigitalItemFragment>;
  type GiftCertificateItem (line 16) | type GiftCertificateItem = FragmentOf<typeof CartGiftCertificateFragment>;
  type LineItem (line 17) | type LineItem = PhysicalItem | DigitalItem | GiftCertificateItem;
  type Props (line 19) | interface Props {

FILE: core/app/[locale]/(default)/cart/_components/checkout-preconnect.tsx
  function CheckoutPreconnect (line 5) | function CheckoutPreconnect({ url }: { url: string }) {

FILE: core/app/[locale]/(default)/cart/loading.tsx
  function Loading (line 5) | function Loading() {

FILE: core/app/[locale]/(default)/cart/page-data.ts
  type Variables (line 304) | type Variables = VariablesOf<typeof CartPageQuery>;

FILE: core/app/[locale]/(default)/cart/page.tsx
  type Props (line 19) | interface Props {
  constant CHECKOUT_URL (line 23) | const CHECKOUT_URL = process.env.TRAILING_SLASH !== 'false' ? '/checkout...
  function generateMetadata (line 25) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function Cart (line 63) | async function Cart({ params }: Props) {

FILE: core/app/[locale]/(default)/checkout/route.ts
  function GET (line 55) | async function GET(req: NextRequest, { params }: { params: Promise<{ loc...

FILE: core/app/[locale]/(default)/compare/_actions/add-to-cart.tsx
  type State (line 14) | interface State {

FILE: core/app/[locale]/(default)/compare/_components/compare-analytics-provider.tsx
  type AddToCartContext (line 10) | interface AddToCartContext {
  function CompareAnalyticsProvider (line 24) | function CompareAnalyticsProvider(
  function CompareAnalyticsProviderResolved (line 34) | function CompareAnalyticsProviderResolved({

FILE: core/app/[locale]/(default)/compare/page-data.ts
  constant MAX_COMPARE_LIMIT (line 10) | const MAX_COMPARE_LIMIT = 10;

FILE: core/app/[locale]/(default)/compare/page.tsx
  type Props (line 34) | interface Props {
  function generateMetadata (line 41) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function Compare (line 52) | async function Compare(props: Props) {

FILE: core/app/[locale]/(default)/error.tsx
  type Props (line 7) | interface Props {
  function Error (line 12) | function Error({ reset }: Props) {

FILE: core/app/[locale]/(default)/gift-certificates/balance/_actions/get-gift-certificate-by-code.ts
  type State (line 15) | interface State {
  function transformGiftCertificate (line 34) | function transformGiftCertificate(
  function getGiftCertificateByCode (line 57) | async function getGiftCertificateByCode(

FILE: core/app/[locale]/(default)/gift-certificates/balance/page.tsx
  type Props (line 13) | interface Props {
  function generateMetadata (line 17) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function GiftCertificates (line 28) | async function GiftCertificates(props: Props) {

FILE: core/app/[locale]/(default)/gift-certificates/page.tsx
  type Props (line 11) | interface Props {
  function generateMetadata (line 15) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function GiftCertificates (line 26) | async function GiftCertificates(props: Props) {

FILE: core/app/[locale]/(default)/gift-certificates/purchase/_actions/add-to-cart.tsx
  type State (line 22) | interface State {
  function addGiftCertificateToCart (line 103) | async function addGiftCertificateToCart<F extends Field>(

FILE: core/app/[locale]/(default)/gift-certificates/purchase/page.tsx
  type Props (line 16) | interface Props {
  function generateMetadata (line 20) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function getFields (line 31) | function getFields(
  function getExpiryDate (line 125) | function getExpiryDate(
  function GiftCertificatePurchasePage (line 150) | async function GiftCertificatePurchasePage({ params }: Props) {

FILE: core/app/[locale]/(default)/layout.tsx
  type Props (line 7) | interface Props extends PropsWithChildren {
  function DefaultLayout (line 11) | async function DefaultLayout({ params, children }: Props) {

FILE: core/app/[locale]/(default)/page.tsx
  type Props (line 17) | interface Props {
  function generateMetadata (line 21) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function Home (line 29) | async function Home({ params }: Props) {

FILE: core/app/[locale]/(default)/product/[slug]/_actions/add-to-cart.tsx
  type CartSelectedOptionsInput (line 15) | type CartSelectedOptionsInput = ReturnType<typeof graphql.scalar<'CartSe...
  type State (line 17) | interface State {

FILE: core/app/[locale]/(default)/product/[slug]/_actions/get-more-images.ts
  function getMoreProductImages (line 31) | async function getMoreProductImages(

FILE: core/app/[locale]/(default)/product/[slug]/_actions/submit-review.ts
  function submitReview (line 33) | async function submitReview(

FILE: core/app/[locale]/(default)/product/[slug]/_actions/wishlist-action.ts
  type WishlistAddMutationVariables (line 93) | interface WishlistAddMutationVariables {
  type WishlistRemoveMutationVariables (line 99) | interface WishlistRemoveMutationVariables {
  function getVariantIdFromSku (line 104) | async function getVariantIdFromSku(productId: number, sku: string, custo...
  function addToDefaultWishlist (line 119) | async function addToDefaultWishlist(
  function addToWishlist (line 143) | async function addToWishlist(customerAccessToken: string, variables: Wis...
  function removeFromWishlist (line 156) | async function removeFromWishlist(
  function getLoginRedirect (line 172) | function getLoginRedirect(redirectTo: string) {
  function wishlistAction (line 181) | async function wishlistAction(payload: FormData): Promise<void> {
  type State (line 261) | interface State {
  function addToNewWishlist (line 273) | async function addToNewWishlist(

FILE: core/app/[locale]/(default)/product/[slug]/_components/product-analytics-provider.tsx
  type AddToCartContext (line 10) | interface AddToCartContext {
  function ProductAnalyticsProvider (line 23) | function ProductAnalyticsProvider(
  function ProductAnalyticsProviderResolved (line 33) | function ProductAnalyticsProviderResolved({

FILE: core/app/[locale]/(default)/product/[slug]/_components/product-review-schema/product-review-schema.tsx
  type Props (line 12) | interface Props {

FILE: core/app/[locale]/(default)/product/[slug]/_components/product-schema/index.tsx
  type Props (line 8) | interface Props {
  type ItemCondition (line 51) | enum ItemCondition {
  type Availability (line 59) | enum Availability {

FILE: core/app/[locale]/(default)/product/[slug]/_components/product-viewed/index.tsx
  type Props (line 11) | interface Props {

FILE: core/app/[locale]/(default)/product/[slug]/_components/reviews.tsx
  type Props (line 77) | interface Props {

FILE: core/app/[locale]/(default)/product/[slug]/_components/wishlist-button/add-to-new-wishlist-modal.tsx
  type Props (line 11) | interface Props {

FILE: core/app/[locale]/(default)/product/[slug]/_components/wishlist-button/dropdown.tsx
  type Props (line 12) | interface Props extends React.PropsWithChildren {

FILE: core/app/[locale]/(default)/product/[slug]/_components/wishlist-button/form.tsx
  type Props (line 11) | interface Props {

FILE: core/app/[locale]/(default)/product/[slug]/_components/wishlist-button/index.tsx
  type WishlistButtonWishlistInfo (line 68) | interface WishlistButtonWishlistInfo {
  type WishlistButton (line 74) | interface WishlistButton {
  function getWishlistButton (line 79) | async function getWishlistButton(
  type Props (line 132) | interface Props {

FILE: core/app/[locale]/(default)/product/[slug]/page-data.ts
  type VariantInventoryVariables (line 250) | type VariantInventoryVariables = VariablesOf<typeof StreamableProductVar...
  type Variables (line 323) | type Variables = VariablesOf<typeof StreamableProductQuery>;
  type ProductInventoryVariables (line 366) | type ProductInventoryVariables = VariablesOf<typeof StreamableProductQue...

FILE: core/app/[locale]/(default)/product/[slug]/page.tsx
  type Props (line 37) | interface Props {
  function generateMetadata (line 42) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function Product (line 68) | async function Product({ params, searchParams }: Props) {

FILE: core/app/[locale]/(default)/webpages/[id]/_components/web-page.tsx
  type WebPage (line 4) | interface WebPage {
  type Props (line 15) | interface Props {
  function WebPageContent (line 21) | function WebPageContent({ webPage: streamableWebPage, breadcrumbs, child...
  function WebPageTitleSkeleton (line 51) | function WebPageTitleSkeleton() {
  function WebPageBodySkeleton (line 59) | function WebPageBodySkeleton() {
  function WebPageContentSkeleton (line 73) | function WebPageContentSkeleton() {

FILE: core/app/[locale]/(default)/webpages/[id]/contact/_actions/submit-contact-form.ts
  function parseContactFormInput (line 43) | function parseContactFormInput(
  function submitContactForm (line 62) | async function submitContactForm<F extends Field>(

FILE: core/app/[locale]/(default)/webpages/[id]/contact/page-data.ts
  type Variables (line 32) | type Variables = VariablesOf<typeof ContactPageQuery>;

FILE: core/app/[locale]/(default)/webpages/[id]/contact/page.tsx
  type Props (line 23) | interface Props {
  type ContactPage (line 28) | interface ContactPage extends WebPage {
  type ContactField (line 42) | type ContactField = keyof typeof fieldMapping;
  function getWebPageBreadcrumbs (line 65) | async function getWebPageBreadcrumbs(id: string): Promise<Breadcrumb[]> {
  function getWebPageWithSuccessContent (line 85) | async function getWebPageWithSuccessContent(id: string, message: string) {
  function getContactFields (line 94) | async function getContactFields(id: string) {
  function generateMetadata (line 157) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function ContactPage (line 172) | async function ContactPage({ params, searchParams }: Props) {

FILE: core/app/[locale]/(default)/webpages/[id]/layout.tsx
  type Props (line 11) | interface Props extends React.PropsWithChildren {
  type PageLink (line 43) | interface PageLink {
  function WebPageLayout (line 78) | async function WebPageLayout({ params, children }: Props) {

FILE: core/app/[locale]/(default)/webpages/[id]/normal/page-data.ts
  type Variables (line 30) | type Variables = VariablesOf<typeof NormalPageQuery>;

FILE: core/app/[locale]/(default)/webpages/[id]/normal/page.tsx
  type Props (line 18) | interface Props {
  function getWebPageBreadcrumbs (line 40) | async function getWebPageBreadcrumbs(id: string): Promise<Breadcrumb[]> {
  function generateMetadata (line 60) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function WebPage (line 76) | async function WebPage({ params }: Props) {

FILE: core/app/[locale]/(default)/wishlist/[token]/page-data.ts
  type Pagination (line 47) | interface Pagination {

FILE: core/app/[locale]/(default)/wishlist/[token]/page.tsx
  type Props (line 27) | interface Props {
  function getWishlist (line 40) | async function getWishlist(
  function getPaginationInfo (line 57) | async function getPaginationInfo(
  function generateMetadata (line 67) | async function generateMetadata({ params, searchParams }: Props): Promis...
  function getBreadcrumbs (line 104) | async function getBreadcrumbs(
  function PublicWishlist (line 118) | async function PublicWishlist({ params, searchParams }: Props) {

FILE: core/app/[locale]/error.tsx
  type Props (line 7) | interface Props {
  function Error (line 12) | function Error({ reset }: Props) {

FILE: core/app/[locale]/layout.tsx
  function generateMetadata (line 67) | async function generateMetadata(): Promise<Metadata> {
  type Props (line 119) | interface Props extends PropsWithChildren {
  function RootLayout (line 123) | async function RootLayout({ params, children }: Props) {
  function generateStaticParams (line 174) | function generateStaticParams() {

FILE: core/app/[locale]/maintenance/page.tsx
  type Props (line 29) | interface Props {
  function generateMetadata (line 33) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function Maintenance (line 49) | async function Maintenance({ params }: Props) {

FILE: core/app/[locale]/not-found.tsx
  function NotFound (line 7) | async function NotFound() {

FILE: core/app/admin/route.ts
  constant BIGCOMMERCE_STORE_HASH (line 5) | const BIGCOMMERCE_STORE_HASH = process.env.BIGCOMMERCE_STORE_HASH;
  constant ENABLE_ADMIN_ROUTE (line 6) | const ENABLE_ADMIN_ROUTE = process.env.ENABLE_ADMIN_ROUTE;

FILE: core/app/layout.tsx
  function RootLayout (line 9) | function RootLayout({ children }: PropsWithChildren) {

FILE: core/app/not-found.tsx
  function RootNotFound (line 10) | function RootNotFound() {

FILE: core/app/providers.tsx
  function Providers (line 8) | function Providers({ children }: PropsWithChildren) {

FILE: core/app/robots.txt/route.ts
  function parseUrl (line 28) | function parseUrl(url?: string): URL {

FILE: core/auth/index.ts
  function handleLoginCart (line 89) | async function handleLoginCart(guestCartId?: string, loginResultCartId?:...
  function loginWithPassword (line 105) | async function loginWithPassword(credentials: unknown): Promise<User | n...
  function loginWithJwt (line 138) | async function loginWithJwt(credentials: unknown): Promise<User | null> {
  method session (line 242) | session({ session, token }) {
  method signOut (line 263) | async signOut(message) {

FILE: core/auth/types.ts
  type Session (line 4) | interface Session {
  type User (line 8) | interface User {
  type AnonymousUser (line 17) | interface AnonymousUser {
  type JWT (line 23) | interface JWT {

FILE: core/build-config/reader.ts
  class BuildConfig (line 4) | class BuildConfig {
    method get (line 7) | get<K extends keyof BuildConfigSchema>(key: K): BuildConfigSchema[K] {

FILE: core/build-config/schema.ts
  type BuildConfigSchema (line 17) | type BuildConfigSchema = z.infer<typeof buildConfigSchema>;

FILE: core/build-config/writer.ts
  constant CONFIG_FILE (line 10) | const CONFIG_FILE = join(destinationPath, 'build-config.json');
  function writeBuildConfig (line 13) | async function writeBuildConfig(data: unknown) {

FILE: core/channels.config.ts
  function getChannelIdFromLocale (line 6) | function getChannelIdFromLocale(locale = '') {

FILE: core/client/tags.ts
  constant TAGS (line 1) | const TAGS = {

FILE: core/client/util/index.ts
  type ExistingResultType (line 2) | type ExistingResultType<T extends (...args: any) => any> = NonNullable<

FILE: core/components/analytics/events.tsx
  type EventsContext (line 3) | interface EventsContext {

FILE: core/components/analytics/provider.tsx
  type Props (line 14) | interface Props {
  function AnalyticsProvider (line 53) | function AnalyticsProvider({

FILE: core/components/consent-manager/consent-manager-dialog.tsx
  function ConsentManagerDialogHeaderTitle (line 16) | function ConsentManagerDialogHeaderTitle() {
  function ConsentManagerDialogHeaderDescription (line 26) | function ConsentManagerDialogHeaderDescription() {
  function ConsentManagerDialog (line 36) | function ConsentManagerDialog(props: ConsentManagerDialogProps) {
  function ConsentManagerWidgetRejectButton (line 52) | function ConsentManagerWidgetRejectButton() {
  function ConsentManagerWidgetAcceptAllButton (line 64) | function ConsentManagerWidgetAcceptAllButton() {
  function ConsentManagerWidgetSaveButton (line 80) | function ConsentManagerWidgetSaveButton() {
  function ConsentManagerAccordionItems (line 92) | function ConsentManagerAccordionItems() {
  function ConsentManagerWidget (line 138) | function ConsentManagerWidget(props: ConsentManagerWidgetProps) {

FILE: core/components/consent-manager/consent-providers.tsx
  type C15tScripts (line 7) | type C15tScripts = NonNullable<ComponentProps<typeof ClientSideOptionsPr...
  type ConsentManagerProviderProps (line 9) | interface ConsentManagerProviderProps extends PropsWithChildren {
  function ConsentManagerProvider (line 15) | function ConsentManagerProvider({

FILE: core/components/consent-manager/cookie-banner.tsx
  function CookieBannerTitle (line 11) | function CookieBannerTitle() {
  function CookieBannerDescription (line 21) | function CookieBannerDescription({ privacyPolicyUrl }: { privacyPolicyUr...
  function CookieBannerFooter (line 45) | function CookieBannerFooter({ children }: PropsWithChildren) {
  function CookieBannerRejectButton (line 53) | function CookieBannerRejectButton() {
  function CookieBannerAcceptButton (line 65) | function CookieBannerAcceptButton() {
  function CookieBannerCustomizeButton (line 77) | function CookieBannerCustomizeButton() {
  function CookieBanner (line 89) | function CookieBanner({

FILE: core/components/consent-manager/index.tsx
  type ConsentManagerProps (line 7) | interface ConsentManagerProps extends PropsWithChildren {
  function ConsentManager (line 13) | function ConsentManager({

FILE: core/components/header/_actions/search.ts
  function search (line 43) | async function search(

FILE: core/components/header/fragment.ts
  type Currency (line 49) | type Currency = NonNullable<
  type CurrencyCode (line 52) | type CurrencyCode = Currency['code'];

FILE: core/components/image/index.tsx
  function shouldUseLoaderProp (line 9) | function shouldUseLoaderProp(props: ImageProps): boolean {

FILE: core/components/link/index.tsx
  type NextLinkProps (line 7) | type NextLinkProps = Omit<ComponentPropsWithRef<typeof NavLink>, 'prefet...
  type PrefetchOptions (line 9) | interface PrefetchOptions {
  type Props (line 14) | type Props = NextLinkProps & PrefetchOptions;
  function computePrefetchProp (line 68) | function computePrefetchProp({

FILE: core/components/modal/index.tsx
  type ModalButton (line 18) | interface ModalButton {
  type Action (line 26) | type Action<S, P> = (state: Awaited<S>, payload: P) => S | Promise<S>;
  type ModalFormState (line 28) | interface ModalFormState {
  type ModalFormAction (line 34) | type ModalFormAction = Action<ModalFormState, FormData>;
  type ModalFormProps (line 36) | interface ModalFormProps {
  type ModalProps (line 42) | interface ModalProps extends ModalPrimitiveProps {
  function ModalButton (line 64) | function ModalButton({ label, className, variant, type, action }: ModalB...
  function ModalSubmitButton (line 94) | function ModalSubmitButton({ label, className, variant }: ModalButton) {
  function ModalFormWrapper (line 110) | function ModalFormWrapper({
  function ModalForm (line 124) | function ModalForm({

FILE: core/components/modal/modal-form-provider.tsx
  type ModalFormContext (line 7) | interface ModalFormContext<T extends z.ZodTypeAny> {
  function useModalForm (line 18) | function useModalForm<T extends z.ZodTypeAny>(

FILE: core/components/polyfills/container-query/index.tsx
  function checkBrowserSupport (line 5) | function checkBrowserSupport(name: string | undefined, version: string |...
  function ContainerQueryPolyfill (line 28) | async function ContainerQueryPolyfill() {

FILE: core/components/wishlist/error.ts
  class WishlistMutationError (line 1) | class WishlistMutationError extends Error {
    method constructor (line 2) | constructor(message: string) {

FILE: core/components/wishlist/share-button.tsx
  type Props (line 17) | interface Props {
  function WishlistShareButtonSkeleton (line 124) | function WishlistShareButtonSkeleton({ size = 'small' }: { size?: Props[...

FILE: core/data-transformers/breadcrumbs-transformer.ts
  type BreadcrumbsResult (line 10) | type BreadcrumbsResult =
  function truncateBreadcrumbs (line 24) | function truncateBreadcrumbs(breadcrumbs: Breadcrumb[], length: number):...

FILE: core/data-transformers/form-field-transformer/fragment.ts
  type FormField (line 3) | type FormField = NonNullable<FragmentOf<typeof FormFieldsFragment>>;
  type FormFieldValue (line 58) | type FormFieldValue = NonNullable<FragmentOf<typeof FormFieldValuesFragm...

FILE: core/data-transformers/form-field-transformer/utils.ts
  type FieldNameToFieldId (line 8) | enum FieldNameToFieldId {
  constant CUSTOMER_FIELDS_TO_EXCLUDE (line 26) | const CUSTOMER_FIELDS_TO_EXCLUDE = [FieldNameToFieldId.currentPassword];
  constant REGISTER_CUSTOMER_FORM_LAYOUT (line 28) | const REGISTER_CUSTOMER_FORM_LAYOUT = [
  constant ADDRESS_FORM_LAYOUT (line 41) | const ADDRESS_FORM_LAYOUT = [

FILE: core/data-transformers/order-details-transformer.ts
  function getStatusColor (line 9) | function getStatusColor(

FILE: core/data-transformers/page-info-transformer.ts
  function pageInfoTransformer (line 13) | function pageInfoTransformer(

FILE: core/data-transformers/scripts-transformer.ts
  type BigCommerceScriptsResult (line 7) | type BigCommerceScriptsResult = ResultOf<typeof ScriptsFragment>;
  type BigCommerceScripts (line 8) | type BigCommerceScripts = BigCommerceScriptsResult['scripts'] | null;
  type C15tScripts (line 9) | type C15tScripts = NonNullable<ConsentManagerProviderProps['options']['s...
  type C15tScript (line 10) | type C15tScript = C15tScripts[number];
  constant BC_TO_C15T_CONSENT_CATEGORY_MAP (line 12) | const BC_TO_C15T_CONSENT_CATEGORY_MAP = {
  type ScriptInfo (line 20) | type ScriptInfo = { textContent: string } | { src: string } | null;
  function extractAttributes (line 28) | function extractAttributes(scriptTag: string): Record<string, string> {
  function extractScriptInfo (line 60) | function extractScriptInfo(scriptTag: string): ScriptInfo {
  function isValidConsentCategory (line 85) | function isValidConsentCategory(key: string): key is keyof typeof BC_TO_...
  function mapConsentCategory (line 89) | function mapConsentCategory(
  function scriptsTransformer (line 99) | function scriptsTransformer(scripts: BigCommerceScripts): C15tScripts {

FILE: core/data-transformers/search-results-transformer.ts
  function searchResultsTransformer (line 9) | async function searchResultsTransformer(

FILE: core/data-transformers/wishlists-transformer.ts
  function wishlistItemsTransformer (line 53) | function wishlistItemsTransformer(
  function wishlistTransformer (line 77) | function wishlistTransformer(

FILE: core/global.ts
  type AppConfig (line 5) | interface AppConfig {

FILE: core/i18n/routing.ts
  type LocalePrefixes (line 6) | enum LocalePrefixes {

FILE: core/i18n/utils.ts
  function createErrorMap (line 6) | function createErrorMap(errorTranslations?: FormErrorTranslationMap) {
  function parseWithZodTranslatedErrors (line 16) | function parseWithZodTranslatedErrors<Schema extends z.ZodType>(

FILE: core/instrumentation.ts
  function register (line 53) | function register() {

FILE: core/lib/analytics/analytics.d.ts
  type Metadata (line 2) | interface Metadata {
  type Product (line 7) | interface Product {
  type ProductViewedPayload (line 30) | interface ProductViewedPayload {
  type CategoryViewedPayload (line 36) | interface CategoryViewedPayload {
  type ProviderEvents (line 43) | interface ProviderEvents {
  type Events (line 48) | interface Events {
  type ProductAddedPayload (line 55) | interface ProductAddedPayload {
  type CartViewedPayload (line 65) | interface CartViewedPayload {
  type ProductRemovedPayload (line 71) | interface ProductRemovedPayload {
  type ProviderEvents (line 77) | interface ProviderEvents {
  type Events (line 83) | interface Events {
  type ConsentNames (line 91) | type ConsentNames = 'necessary' | 'functionality' | 'marketing' | 'measu...
  type ConsentValues (line 92) | type ConsentValues = Record<ConsentNames, boolean>;
  type ProviderEvents (line 94) | interface ProviderEvents {
  type Events (line 98) | interface Events {

FILE: core/lib/analytics/bigcommerce/data-events.ts
  type AnalyticsInitiator (line 24) | interface AnalyticsInitiator {
  type AnalyticsRequest (line 29) | interface AnalyticsRequest {
  type VisitStartedEvent (line 35) | interface VisitStartedEvent {
  type ProductViewedEvent (line 40) | interface ProductViewedEvent {
  function sendVisitStartedEvent (line 46) | async function sendVisitStartedEvent({ initiator, request }: VisitStarte...
  function sendProductViewedEvent (line 56) | async function sendProductViewedEvent({
  function preareCommonInput (line 73) | function preareCommonInput(initiator: AnalyticsInitiator, request: Analy...

FILE: core/lib/analytics/bigcommerce/index.ts
  constant VISITOR_COOKIE_NAME (line 3) | const VISITOR_COOKIE_NAME = 'catalyst.visitorId';
  constant VISIT_COOKIE_NAME (line 4) | const VISIT_COOKIE_NAME = 'catalyst.visitId';
  constant VISITOR_DURATION (line 5) | const VISITOR_DURATION = 400 * 24 * 60 * 60;
  constant VISIT_DURATION (line 6) | const VISIT_DURATION = 30 * 60;
  function getVisitorIdCookie (line 8) | async function getVisitorIdCookie(): Promise<string | undefined> {
  function setVisitorIdCookie (line 14) | async function setVisitorIdCookie(visitorId: string): Promise<void> {
  function getVisitIdCookie (line 25) | async function getVisitIdCookie(): Promise<string | undefined> {
  function setVisitIdCookie (line 31) | async function setVisitIdCookie(visitId: string): Promise<void> {

FILE: core/lib/analytics/index.ts
  class Analytics (line 5) | class Analytics implements IAnalytics {
    method constructor (line 12) | constructor(private readonly config: AnalyticsConfig) {
    method initialize (line 20) | initialize() {
    method bindCartEvents (line 26) | private bindCartEvents() {
    method bindNavigationEvents (line 55) | private bindNavigationEvents() {
    method bindConsentEvents (line 76) | private bindConsentEvents() {

FILE: core/lib/analytics/providers/google-analytics/index.ts
  type GoogleAnalyticsConfig (line 3) | interface GoogleAnalyticsConfig {
  class GoogleAnalyticsProvider (line 13) | class GoogleAnalyticsProvider implements AnalyticsProvider {
    method constructor (line 23) | constructor(private readonly config: GoogleAnalyticsConfig) {
    method initialize (line 33) | initialize() {
    method validateConfig (line 43) | private validateConfig() {
    method initializeDataLayer (line 53) | private initializeDataLayer() {
    method initializeConsent (line 77) | private initializeConsent() {
    method initializeGTM (line 106) | private initializeGTM() {
    method getCartEvents (line 122) | private getCartEvents() {
    method getNavigationEvents (line 199) | private getNavigationEvents() {
    method getConsentEvents (line 252) | private getConsentEvents() {

FILE: core/lib/analytics/react/index.tsx
  type AnalyticsProviderProps (line 9) | interface AnalyticsProviderProps {

FILE: core/lib/analytics/types.ts
  type AnalyticsProvider (line 1) | interface AnalyticsProvider {
  type AnalyticsConfig (line 9) | interface AnalyticsConfig {
  type Analytics (line 14) | interface Analytics {

FILE: core/lib/cart/add-cart-line-item.ts
  type Variables (line 17) | type Variables = VariablesOf<typeof AddCartLineItemMutation>;
  type AddCartLineItemsInput (line 18) | type AddCartLineItemsInput = Variables['input'];

FILE: core/lib/cart/create-cart.ts
  type Variables (line 18) | type Variables = VariablesOf<typeof CreateCartMutation>;
  type CreateCartInput (line 19) | type CreateCartInput = Variables['createCartInput'];

FILE: core/lib/cart/error.ts
  class MissingCartError (line 1) | class MissingCartError extends Error {
    method constructor (line 2) | constructor() {

FILE: core/lib/cart/index.ts
  function getCartId (line 13) | async function getCartId(): Promise<string | undefined> {
  function setCartId (line 25) | async function setCartId(cartId: string): Promise<void> {
  function clearCartId (line 37) | async function clearCartId(): Promise<void> {
  function addToOrCreateCart (line 49) | async function addToOrCreateCart(

FILE: core/lib/cart/validate-cart.ts
  function validateCartId (line 16) | async function validateCartId(cartId?: string) {

FILE: core/lib/cdn-image-loader.ts
  function bcCdnImageLoader (line 5) | function bcCdnImageLoader({ src, width }: ImageLoaderProps): string {

FILE: core/lib/client-cookies.ts
  constant FORCE_REFRESH_COOKIE (line 1) | const FORCE_REFRESH_COOKIE = 'force-refresh';
  type ClientCookieOptions (line 3) | interface ClientCookieOptions {
  function getCookieValue (line 21) | function getCookieValue(name: string): string | null {
  function setCookie (line 34) | function setCookie(

FILE: core/lib/consent-manager/cookies/constants.ts
  constant CONSENT_COOKIE_NAME (line 1) | const CONSENT_COOKIE_NAME = 'c15t-consent';

FILE: core/lib/consent-manager/cookies/parse-compact-format.ts
  function parseCompactFormat (line 7) | function parseCompactFormat(raw: string): Record<string, string | number> {

FILE: core/lib/currency.ts
  function getPreferredCurrencyCode (line 8) | async function getPreferredCurrencyCode(): Promise<CurrencyCode | undefi...
  function setPreferredCurrencyCode (line 21) | async function setPreferredCurrencyCode(currencyCode: CurrencyCode): Pro...

FILE: core/lib/force-refresh.ts
  function setForceRefreshCookie (line 5) | async function setForceRefreshCookie() {

FILE: core/lib/kv/adapters/memory.ts
  type CacheEntry (line 6) | interface CacheEntry {
  class MemoryKvAdapter (line 11) | class MemoryKvAdapter implements KvAdapter {
    method mget (line 16) | async mget<Data>(...keys: string[]) {
    method set (line 23) | async set<Data>(key: string, value: Data, options: { ex?: number } = {...
    method get (line 32) | private async get<Data>(key: string) {

FILE: core/lib/kv/adapters/upstash.ts
  class UpstashKvAdapter (line 5) | class UpstashKvAdapter implements KvAdapter {
    method mget (line 8) | async mget<Data>(...keys: string[]) {
    method set (line 12) | async set<Data>(key: string, value: Data, opts?: SetCommandOptions) {

FILE: core/lib/kv/adapters/vercel-runtime-cache.ts
  class RuntimeCacheAdapter (line 5) | class RuntimeCacheAdapter implements KvAdapter {
    method mget (line 8) | async mget<Data>(...keys: string[]): Promise<Array<Data | null>> {
    method set (line 52) | async set<Data>(key: string, value: Data, _opts?: SetCommandOptions): ...
    method logger (line 67) | private logger(message: string) {

FILE: core/lib/kv/index.ts
  type Config (line 4) | interface Config {
  class KV (line 10) | class KV<Adapter extends KvAdapter> implements KvAdapter {
    method constructor (line 14) | constructor(
    method get (line 19) | async get<Data>(key: string) {
    method mget (line 25) | async mget<Data>(...keys: string[]) {
    method set (line 58) | async set<Data>(key: string, value: Data, opts?: SetCommandOptions) {
    method getKv (line 68) | private async getKv() {
    method logger (line 76) | private logger(message: string) {
  function createKVAdapter (line 84) | async function createKVAdapter() {

FILE: core/lib/kv/keys.ts
  constant VERSION (line 1) | const VERSION = 'v3';
  constant STORE_STATUS_KEY (line 3) | const STORE_STATUS_KEY = 'storeStatus';

FILE: core/lib/kv/types.ts
  type SetCommandOptions (line 1) | type SetCommandOptions = Record<string, unknown>;
  type KvAdapter (line 3) | interface KvAdapter {

FILE: core/lib/recaptcha.ts
  function getRecaptchaFromForm (line 53) | async function getRecaptchaFromForm(
  function assertRecaptchaTokenPresent (line 63) | function assertRecaptchaTokenPresent(

FILE: core/lib/recaptcha/constants.ts
  type ReCaptchaSettings (line 1) | interface ReCaptchaSettings {
  constant RECAPTCHA_TOKEN_FORM_KEY (line 6) | const RECAPTCHA_TOKEN_FORM_KEY = 'g-recaptcha-response';

FILE: core/lib/search.tsx
  type SearchContextProps (line 10) | interface SearchContextProps {

FILE: core/lib/seo/canonical.ts
  type CanonicalUrlOptions (line 8) | interface CanonicalUrlOptions {
  function getMetadataAlternates (line 63) | async function getMetadataAlternates(options: CanonicalUrlOptions) {
  function buildLocalizedUrl (line 88) | function buildLocalizedUrl(baseUrl: string, pathname: string, locale: st...

FILE: core/lib/server-toast.ts
  type ServerToastData (line 15) | type ServerToastData = z.infer<typeof serverToastSchema>;
  type ServerToastOptions (line 17) | type ServerToastOptions = Pick<ServerToastData, 'position' | 'descriptio...
  constant TOAST_COOKIE (line 19) | const TOAST_COOKIE = 'toast-notification';
  function encode (line 21) | function encode(data: ServerToastData): string {
  function decode (line 25) | function decode(data: string): ServerToastData {
  function setToastCookie (line 29) | async function setToastCookie(data: ServerToastData) {

FILE: core/lib/user-agent.ts
  function getUserAgent (line 6) | async function getUserAgent() {
  function isMobileUser (line 10) | async function isMobileUser() {

FILE: core/lib/utils.ts
  function exists (line 1) | function exists<T>(value: T | null | undefined): value is T {

FILE: core/next.config.ts
  function writeSettingsToBuildConfig (line 34) | async function writeSettingsToBuildConfig() {
  method headers (line 74) | async headers() {

FILE: core/proxies/compose-proxies.ts
  type ProxyFactory (line 3) | type ProxyFactory = (proxy: NextProxy) => NextProxy;

FILE: core/proxies/with-analytics-cookies.ts
  function recordNewVisit (line 44) | async function recordNewVisit(request: Request, visitorId: string, visit...

FILE: core/proxies/with-auth.ts
  function redirectToLogin (line 10) | function redirectToLogin(url: string) {

FILE: core/proxies/with-routes.ts
  type Route (line 124) | type Route = Awaited<ReturnType<typeof getRoute>>;
  type StorefrontStatusType (line 125) | type StorefrontStatusType = ReturnType<typeof graphql.scalar<'Storefront...
  type RouteCache (line 127) | interface RouteCache {
  type StorefrontStatusCache (line 132) | interface StorefrontStatusCache {
  function normalizeForCompare (line 228) | function normalizeForCompare(url: URL): string {
  function recordProductVisit (line 427) | async function recordProductVisit(request: Request, productId: number) {

FILE: core/tests/fixtures/blog/index.ts
  class BlogFixture (line 6) | class BlogFixture extends Fixture {
    method getBlog (line 9) | getBlog(): Promise<Blog> {
    method getPosts (line 13) | getPosts(page?: number, limit?: number): Promise<BlogPost[]> {
    method createPost (line 17) | async createPost(data?: Partial<CreateBlogPostData>): Promise<BlogPost> {
    method cleanup (line 34) | async cleanup() {

FILE: core/tests/fixtures/browser.ts
  function extendedBrowser (line 7) | function extendedBrowser(browser: Browser): Browser {
  function extendedBrowserContext (line 21) | function extendedBrowserContext(context: BrowserContext) {

FILE: core/tests/fixtures/catalog/index.ts
  class CatalogFixture (line 13) | class CatalogFixture extends Fixture {
    method getDefaultOrCreateSimpleProduct (line 16) | async getDefaultOrCreateSimpleProduct(): Promise<Product> {
    method getDefaultOrCreateComplexProduct (line 32) | async getDefaultOrCreateComplexProduct(): Promise<Product> {
    method getCategories (line 48) | getCategories(filters?: { nameLike?: string; ids?: number[] }): Promis...
    method getBrands (line 52) | getBrands(filters?: { nameLike?: string; ids?: number[] }): Promise<Br...
    method createSimpleProduct (line 56) | async createSimpleProduct(
    method createComplexProduct (line 68) | async createComplexProduct(overrides?: Partial<CreateProductData>): Pr...
    method cleanup (line 85) | async cleanup() {
    method fakeCreateProductData (line 89) | private fakeCreateProductData(
    method fakeCreateVariantData (line 111) | private fakeCreateVariantData(label?: string): CreateVariantData {

FILE: core/tests/fixtures/currency/index.ts
  class CurrencyFixture (line 4) | class CurrencyFixture extends Fixture {
    method getDefaultCurrency (line 5) | async getDefaultCurrency(): Promise<string> {
    method getEnabledCurrencies (line 11) | async getEnabledCurrencies(): Promise<string[]> {
    method convertWithExchangeRate (line 17) | async convertWithExchangeRate(currencyCode: string, value: number): Pr...
    method selectCurrency (line 28) | async selectCurrency(currency: string): Promise<void> {
    method cleanup (line 36) | async cleanup() {

FILE: core/tests/fixtures/customer/index.ts
  class CustomerFixture (line 19) | class CustomerFixture extends Fixture {
    method constructor (line 24) | constructor(
    method getOrCreateTestCustomer (line 35) | async getOrCreateTestCustomer(): Promise<Customer> {
    method createNewCustomer (line 45) | async createNewCustomer(): Promise<Customer> {
    method getTestCustomer (line 62) | async getTestCustomer(): Promise<Customer | undefined> {
    method getById (line 79) | getById(customerId: number, includeAddresses?: boolean): Promise<Custo...
    method getByEmail (line 83) | getByEmail(email: string, includeAddresses?: boolean): Promise<Custome...
    method createAddress (line 87) | async createAddress(customerId: number): Promise<Address> {
    method createWishlist (line 99) | async createWishlist({
    method generateLoginJwt (line 119) | async generateLoginJwt(customerId: number, redirectTo = '/account/orde...
    method login (line 138) | async login(redirectTo?: string): Promise<Customer> {
    method loginAs (line 146) | async loginAs(customer: Customer, redirectTo?: string): Promise<void> {
    method logout (line 176) | async logout(): Promise<void> {
    method delete (line 185) | async delete(...customerIds: number[]): Promise<void> {
    method deleteAllAddresses (line 191) | async deleteAllAddresses(customerId: number): Promise<void> {
    method deleteAllWishlists (line 199) | async deleteAllWishlists(customerId: number): Promise<void> {
    method withNewPage (line 208) | withNewPage(page: Page): CustomerFixture {
    method cleanup (line 212) | async cleanup() {
    method fakeCreateAddressData (line 222) | private fakeCreateAddressData({
    method fakeCreateCustomerData (line 250) | private fakeCreateCustomerData(

FILE: core/tests/fixtures/customer/session.ts
  class CustomerSessionStore (line 22) | class CustomerSessionStore {
    method updateCustomerSession (line 25) | async updateCustomerSession(fixture: CustomerFixture, customerId: numb...
    method useExistingSession (line 40) | async useExistingSession(fixture: CustomerFixture, customerId: number)...
    method removeCustomerSession (line 72) | private async removeCustomerSession(customerId: number): Promise<void> {
    method ensureStorageFileExists (line 85) | private async ensureStorageFileExists(): Promise<void> {

FILE: core/tests/fixtures/fixture.ts
  method constructor (line 9) | constructor(
  method skipIfReadonly (line 16) | protected skipIfReadonly(): void {

FILE: core/tests/fixtures/index.ts
  type Fixtures (line 22) | interface Fixtures {
  method toBeUuid (line 171) | toBeUuid(received) {

FILE: core/tests/fixtures/order/index.ts
  class OrderFixture (line 5) | class OrderFixture extends Fixture {
    method createWithDefaultProduct (line 8) | async createWithDefaultProduct(customerId?: number): Promise<Order> {
    method create (line 18) | async create(productId: number, customerId?: number): Promise<Order> {
    method deleteAllCustomerOrders (line 28) | async deleteAllCustomerOrders(customerId: number): Promise<void> {
    method cleanup (line 36) | async cleanup() {

FILE: core/tests/fixtures/page.ts
  function extendedPage (line 7) | function extendedPage(page: Page) {
  function normalizeForTrailingSlashEnvVar (line 31) | function normalizeForTrailingSlashEnvVar(url: string): string {
  function toHaveURL (line 51) | async function toHaveURL(

FILE: core/tests/fixtures/promotion/index.ts
  class PromotionFixture (line 4) | class PromotionFixture extends Fixture {
    method createCouponCode (line 7) | async createCouponCode(): Promise<Coupon> {
    method cleanup (line 17) | async cleanup() {

FILE: core/tests/fixtures/redirects/index.ts
  class RedirectsFixture (line 4) | class RedirectsFixture extends Fixture {
    method upsertRedirect (line 7) | async upsertRedirect(data: UpsertRedirectData): Promise<Redirect | und...
    method cleanup (line 19) | async cleanup() {

FILE: core/tests/fixtures/settings/index.ts
  class SettingsFixture (line 4) | class SettingsFixture extends Fixture {
    method getInventorySettings (line 7) | async getInventorySettings(): Promise<InventorySettings> {
    method setInventorySettings (line 13) | async setInventorySettings(settings: InventorySettings): Promise<void> {
    method cleanup (line 21) | async cleanup() {

FILE: core/tests/fixtures/subscribe/index.ts
  class SubscribeFixture (line 3) | class SubscribeFixture extends Fixture {
    method trackSubscription (line 6) | trackSubscription(email: string): void {
    method subscribe (line 10) | async subscribe(email: string, firstName: string, lastName: string): P...
    method unsubscribe (line 18) | async unsubscribe(email: string): Promise<void> {
    method cleanup (line 24) | async cleanup(): Promise<void> {

FILE: core/tests/fixtures/utils/api/blog/index.ts
  type Blog (line 1) | interface Blog {
  type BlogPost (line 6) | interface BlogPost {
  type CreateBlogPostData (line 16) | interface CreateBlogPostData {
  type BlogApi (line 24) | interface BlogApi {

FILE: core/tests/fixtures/utils/api/catalog/index.ts
  type Variant (line 1) | interface Variant {
  type Product (line 17) | interface Product {
  type CreateVariantData (line 36) | interface CreateVariantData {
  type CreateProductData (line 49) | interface CreateProductData {
  type Category (line 71) | interface Category {
  type Brand (line 79) | interface Brand {
  type CatalogApi (line 85) | interface CatalogApi {

FILE: core/tests/fixtures/utils/api/client.ts
  class ApiClientResponse (line 7) | class ApiClientResponse extends Promise<Response> {
    method parse (line 8) | async parse<Out, In = Out>(schema: z.ZodType<Out, z.ZodTypeDef, In>): ...
  function httpRequest (line 15) | function httpRequest(path: string, config: RequestInit): ApiClientRespon...

FILE: core/tests/fixtures/utils/api/currencies/index.ts
  type CurrencyAssignments (line 1) | interface CurrencyAssignments {
  type Currency (line 6) | interface Currency {
  type CurrenciesApi (line 15) | interface CurrenciesApi {

FILE: core/tests/fixtures/utils/api/customers/index.ts
  type Address (line 1) | interface Address {
  type CreateAddressData (line 18) | interface CreateAddressData {
  type Customer (line 32) | interface Customer {
  type CreateCustomerData (line 48) | interface CreateCustomerData {
  type Wishlist (line 62) | interface Wishlist {
  type CreateWishlistData (line 71) | interface CreateWishlistData {
  type CustomersApi (line 78) | interface CustomersApi {

FILE: core/tests/fixtures/utils/api/errors/response-error.ts
  class TestApiClientResponseError (line 1) | class TestApiClientResponseError extends Error {
    method constructor (line 4) | constructor(request: Request, response: Response, responseText?: strin...
    method create (line 18) | static async create(request: Request, response: Response) {

FILE: core/tests/fixtures/utils/api/index.ts
  type ApiClient (line 12) | interface ApiClient {

FILE: core/tests/fixtures/utils/api/orders/index.ts
  type OrderAddress (line 1) | interface OrderAddress {
  type Order (line 16) | interface Order {
  type OrdersApi (line 35) | interface OrdersApi {

FILE: core/tests/fixtures/utils/api/promotions/index.ts
  type Coupon (line 1) | interface Coupon {
  type PromotionWithCoupon (line 6) | interface PromotionWithCoupon {
  type PromotionsApi (line 11) | interface PromotionsApi {

FILE: core/tests/fixtures/utils/api/redirects/index.ts
  type RedirectToType (line 1) | type RedirectToType = 'product' | 'brand' | 'category' | 'page' | 'post'...
  type RedirectToEntity (line 3) | interface RedirectToEntity {
  type RedirectToUrl (line 8) | interface RedirectToUrl {
  type Redirect (line 13) | interface Redirect {
  type UpsertRedirectData (line 20) | interface UpsertRedirectData {
  type RedirectsApi (line 25) | interface RedirectsApi {

FILE: core/tests/fixtures/utils/api/settings/index.ts
  type InventorySettings (line 1) | interface InventorySettings {
  type SettingsApi (line 7) | interface SettingsApi {

FILE: core/tests/fixtures/utils/api/subscribe/index.ts
  type SubscribeApi (line 1) | interface SubscribeApi {

FILE: core/tests/fixtures/utils/api/webpages/index.ts
  type WebPageType (line 1) | type WebPageType = 'page' | 'link' | 'contact_form' | 'raw';
  type ContactFieldType (line 2) | type ContactFieldType = 'fullname' | 'companyname' | 'phone' | 'orderno'...
  type WebPage (line 4) | interface WebPage {
  type CreateWebPageData (line 18) | interface CreateWebPageData {
  type WebPagesApi (line 31) | interface WebPagesApi {

FILE: core/tests/fixtures/webpage/index.ts
  class WebPageFixture (line 6) | class WebPageFixture extends Fixture {
    method getById (line 9) | getById(id: number): Promise<WebPage> {
    method create (line 13) | async create(data?: Partial<CreateWebPageData>): Promise<WebPage> {
    method cleanup (line 23) | async cleanup() {
    method fakeCreatePageData (line 27) | private fakeCreatePageData(data?: Partial<CreateWebPageData>): CreateW...

FILE: core/tests/lib/formatter/index.ts
  function getFormatter (line 6) | function getFormatter() {

FILE: core/tests/lib/i18n/index.ts
  function loadMessages (line 8) | async function loadMessages(): Promise<Messages> {
  function getTranslations (line 24) | async function getTranslations<

FILE: core/tests/tags.ts
  constant TAGS (line 1) | const TAGS = {

FILE: core/tests/ui/e2e/account/addresses.spec.ts
  function fillAddressForm (line 7) | async function fillAddressForm(page: Page) {
  function assertAddressSectionHasAddress (line 46) | async function assertAddressSectionHasAddress(

FILE: core/tests/ui/e2e/account/wishlists.spec.ts
  function logoutInSeparateBrowser (line 14) | async function logoutInSeparateBrowser(fixture: CustomerFixture, browser...

FILE: core/tests/ui/e2e/checkout.spec.ts
  function selectCountry (line 6) | async function selectCountry(page: Page) {
  function selectState (line 18) | async function selectState(page: Page) {
  function selectShippingMethod (line 37) | async function selectShippingMethod(page: Page) {
  function enterAddressDetails (line 46) | async function enterAddressDetails(page: Page) {
  function enterPaymentDetails (line 60) | async function enterPaymentDetails(page: Page) {

FILE: core/tests/ui/e2e/facets.spec.ts
  constant SHOP_ALL_URL (line 3) | const SHOP_ALL_URL = '/shop-all/';
  constant PRODUCT_LE_PARFAIT_JAR (line 5) | const PRODUCT_LE_PARFAIT_JAR = '[Sample] 1 L Le Parfait Jar';
  constant PRODUCT_DUSTPAN_BRUSH (line 6) | const PRODUCT_DUSTPAN_BRUSH = '[Sample] Dustpan & Brush';
  constant PRODUCT_UTILITY_CADDY (line 7) | const PRODUCT_UTILITY_CADDY = '[Sample] Utility Caddy';
  function expandFilterIfNeeded (line 9) | async function expandFilterIfNeeded(page: Page, filterLabel: string) {
  function clickSpecificFilterOption (line 21) | async function clickSpecificFilterOption(page: Page, filterName: string,...

FILE: core/tests/ui/e2e/shipping.spec.ts
  function addProductAndGoToCart (line 7) | async function addProductAndGoToCart(page: Page, catalog: CatalogFixture) {
  function fillOutShippingForm (line 26) | async function fillOutShippingForm(page: Page) {
  function selectRandomShippingOption (line 49) | async function selectRandomShippingOption(page: Page): Promise<string> {

FILE: core/vibes/soul/form/button-radio-group/index.tsx
  type Option (line 8) | interface Option {

FILE: core/vibes/soul/form/card-radio-group/index.tsx
  type Option (line 9) | interface Option {

FILE: core/vibes/soul/form/checkbox-group/index.tsx
  type Option (line 8) | interface Option {
  type Props (line 14) | interface Props {
  function CheckboxGroup (line 27) | function CheckboxGroup({

FILE: core/vibes/soul/form/checkbox/index.tsx
  type CheckboxProps (line 11) | interface CheckboxProps extends ComponentPropsWithoutRef<typeof Checkbox...
  function Checkbox (line 55) | function Checkbox({

FILE: core/vibes/soul/form/date-picker/index.tsx
  type CalendarProps (line 8) | type CalendarProps = ComponentPropsWithoutRef<typeof Calendar>;
  type Props (line 10) | type Props = {

FILE: core/vibes/soul/form/dynamic-form/index.tsx
  type DynamicFormActionArgs (line 50) | interface DynamicFormActionArgs<F extends Field> {
  type Action (line 55) | type Action<F extends Field, S, P> = (
  type State (line 61) | interface State {
  type DynamicFormAction (line 66) | type DynamicFormAction<F extends Field> = Action<F, State, FormData>;
  type DynamicFormProps (line 68) | interface DynamicFormProps<F extends Field> {
  function DynamicForm (line 84) | function DynamicForm<F extends Field>({
  function SubmitButton (line 224) | function SubmitButton({
  function DynamicFormField (line 244) | function DynamicFormField({

FILE: core/vibes/soul/form/dynamic-form/schema.ts
  type PasswordComplexitySettings (line 3) | interface PasswordComplexitySettings {
  type FormErrorTranslationMap (line 13) | type FormErrorTranslationMap = Record<
  type FormField (line 28) | interface FormField {
  type RadioField (line 37) | type RadioField = {
  type SelectField (line 43) | type SelectField = {
  type CheckboxField (line 49) | type CheckboxField = {
  type CheckboxGroupField (line 54) | type CheckboxGroupField = {
  type NumberInputField (line 60) | type NumberInputField = {
  type TextInputField (line 70) | type TextInputField = {
  type EmailInputField (line 76) | type EmailInputField = {
  type TextAreaField (line 81) | type TextAreaField = {
  type DateField (line 86) | type DateField = {
  type SwatchRadioFieldOption (line 93) | type SwatchRadioFieldOption =
  type SwatchRadioField (line 109) | type SwatchRadioField = {
  type CardRadioField (line 115) | type CardRadioField = {
  type ButtonRadioField (line 126) | type ButtonRadioField = {
  type PasswordField (line 137) | type PasswordField = {
  type ConfirmPasswordField (line 141) | type ConfirmPasswordField = {
  type HiddenInputField (line 145) | type HiddenInputField = {
  type Field (line 150) | type Field =
  type FieldGroup (line 167) | type FieldGroup<F> = F[];
  type SchemaRawShape (line 169) | type SchemaRawShape = Record<
  function getPasswordSchema (line 183) | function getPasswordSchema(
  function getFieldSchema (line 250) | function getFieldSchema(
  function schema (line 327) | function schema(

FILE: core/vibes/soul/form/dynamic-form/utils.ts
  function removeOptionsFromField (line 3) | function removeOptionsFromField<F extends Field>(field: F) {
  function removeOptionsFromFields (line 14) | function removeOptionsFromFields<F extends Field>(fields: Array<F | Fiel...

FILE: core/vibes/soul/form/field-error/index.tsx
  function FieldError (line 15) | function FieldError({

FILE: core/vibes/soul/form/form-status/index.tsx
  function FormStatus (line 22) | function FormStatus({

FILE: core/vibes/soul/form/label/index.tsx
  function Label (line 20) | function Label({

FILE: core/vibes/soul/form/radio-group/index.tsx
  type Option (line 8) | interface Option {
  function RadioGroupItem (line 100) | function RadioGroupItem({

FILE: core/vibes/soul/form/range-input/index.tsx
  type Props (line 9) | interface Props {
  function RangeInput (line 32) | function RangeInput({

FILE: core/vibes/soul/form/select/index.tsx
  type Props (line 11) | type Props = {
  function Select (line 68) | function Select({

FILE: core/vibes/soul/form/swatch-radio-group/index.tsx
  type SwatchOption (line 10) | type SwatchOption =

FILE: core/vibes/soul/form/switch/index.tsx
  type Props (line 8) | interface Props {
  type LabelProps (line 97) | interface LabelProps {
  function SwitchLabel (line 105) | function SwitchLabel({ id, label, size = 'medium', state, loading }: Lab...
  function SwitchSkeleton (line 166) | function SwitchSkeleton({

FILE: core/vibes/soul/form/toggle-group/index.tsx
  type Option (line 10) | interface Option {

FILE: core/vibes/soul/lib/streamable.tsx
  type Streamable (line 5) | type Streamable<T> = T | Promise<T>;
  function getObjectKey (line 11) | function getObjectKey(obj: object): string {
  function getCompositeKey (line 33) | function getCompositeKey(streamables: readonly unknown[]): string {
  function weakRefCache (line 37) | function weakRefCache<K, T extends object>() {
  function all (line 62) | function all<T extends readonly unknown[] | []>(
  function from (line 79) | function from<T>(thunk: () => Promise<T>): Streamable<T> {
  function useStreamable (line 88) | function useStreamable<T>(streamable: Streamable<T>): T {
  function UseStreamable (line 92) | function UseStreamable<T>({
  function Stream (line 102) | function Stream<T>({

FILE: core/vibes/soul/primitives/accordion/index.tsx
  type AccordionProps (line 7) | interface AccordionProps extends ComponentPropsWithoutRef<typeof Accordi...
  function AccordionItem (line 36) | function AccordionItem({
  function AnimatedChevron (line 110) | function AnimatedChevron({

FILE: core/vibes/soul/primitives/alert/index.tsx
  type Props (line 7) | interface Props {
  function Alert (line 19) | function Alert({

FILE: core/vibes/soul/primitives/animated-underline/index.tsx
  type AnimatedUnderlineProps (line 3) | interface AnimatedUnderlineProps {
  function AnimatedUnderline (line 21) | function AnimatedUnderline({ className, children }: AnimatedUnderlinePro...

FILE: core/vibes/soul/primitives/badge/index.tsx
  type BadgeProps (line 3) | interface BadgeProps {
  function Badge (line 28) | function Badge({ children, shape = 'rounded', className, variant = 'prim...

FILE: core/vibes/soul/primitives/blog-post-card/index.tsx
  type BlogPostCardBlogPost (line 6) | interface BlogPostCardBlogPost {
  type Props (line 19) | interface Props {
  function BlogPostCard (line 24) | function BlogPostCard({ blogPost, className }: Props) {
  function BlogPostCardSkeleton (line 64) | function BlogPostCardSkeleton({ className }: { className?: string }) {

FILE: core/vibes/soul/primitives/button-link/index.tsx
  type ButtonLinkProps (line 6) | interface ButtonLinkProps extends ComponentPropsWithoutRef<typeof Link> {
  function ButtonLink (line 40) | function ButtonLink({

FILE: core/vibes/soul/primitives/button/index.tsx
  type ButtonProps (line 5) | interface ButtonProps extends ComponentPropsWithoutRef<'button'> {
  function Button (line 45) | function Button({

FILE: core/vibes/soul/primitives/calendar/index.tsx
  type CalendarProps (line 10) | type CalendarProps = ComponentPropsWithoutRef<typeof DayPicker> & {
  function Calendar (line 45) | function Calendar({

FILE: core/vibes/soul/primitives/carousel/index.tsx
  type CarouselApi (line 10) | type CarouselApi = UseEmblaCarouselType[1];
  type UseCarouselParameters (line 11) | type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
  type CarouselOptions (line 12) | type CarouselOptions = UseCarouselParameters[0];
  type CarouselPlugin (line 13) | type CarouselPlugin = UseCarouselParameters[1];
  type CarouselProps (line 15) | interface CarouselProps extends React.ComponentPropsWithoutRef<'div'> {
  type CarouselContextProps (line 23) | type CarouselContextProps = {
  function useCarousel (line 34) | function useCarousel() {
  function Carousel (line 44) | function Carousel({
  function CarouselContent (line 125) | function CarouselContent({ className, ...rest }: React.HTMLAttributes<HT...
  function CarouselItem (line 135) | function CarouselItem({ className, ...rest }: React.HTMLAttributes<HTMLD...
  function CarouselButtons (line 158) | function CarouselButtons({
  function CarouselScrollbar (line 214) | function CarouselScrollbar({

FILE: core/vibes/soul/primitives/chip/index.tsx
  type Props (line 3) | interface Props {

FILE: core/vibes/soul/primitives/compare-card/add-to-cart-form.tsx
  type Action (line 12) | type Action<S, P> = (state: Awaited<S>, payload: P) => S | Promise<S>;
  type State (line 14) | interface State {
  type CompareAddToCartAction (line 19) | type CompareAddToCartAction = Action<State, FormData>;
  type Props (line 21) | interface Props {
  function AddToCartForm (line 30) | function AddToCartForm({

FILE: core/vibes/soul/primitives/compare-card/index.tsx
  type CompareProduct (line 16) | interface CompareProduct extends Product {
  type CompareCardProps (line 24) | interface CompareCardProps {
  function CompareCard (line 56) | function CompareCard({
  function CompareCardSkeleton (line 152) | function CompareCardSkeleton({ className }: { className?: string }) {

FILE: core/vibes/soul/primitives/compare-drawer/index.tsx
  type OptimisticAction (line 22) | interface OptimisticAction {
  type CompareDrawerContext (line 27) | interface CompareDrawerContext {
  function CompareDrawerProvider (line 40) | function CompareDrawerProvider({
  function useCompareDrawer (line 92) | function useCompareDrawer() {
  function getInitials (line 96) | function getInitials(name: string): string {
  type CompareDrawerItem (line 105) | interface CompareDrawerItem {
  type CompareDrawerProps (line 112) | interface CompareDrawerProps {
  function CompareDrawer (line 144) | function CompareDrawer({

FILE: core/vibes/soul/primitives/cursor-pagination/index.tsx
  type CursorPaginationInfo (line 12) | interface CursorPaginationInfo {
  type Props (line 19) | interface Props {
  function CursorPagination (line 27) | function CursorPagination(props: Props) {
  function CursorPaginationResolved (line 35) | function CursorPaginationResolved({
  function PaginationLink (line 101) | function PaginationLink({
  function SkeletonLink (line 126) | function SkeletonLink({ children }: { children: React.ReactNode }) {
  function CursorPaginationSkeleton (line 134) | function CursorPaginationSkeleton() {

FILE: core/vibes/soul/primitives/dropdown-menu/index.tsx
  type DropdownMenuItem (line 9) | interface DropdownMenuItem {
  type Props (line 19) | interface Props extends PropsWithChildren {

FILE: core/vibes/soul/primitives/favorite/heart.tsx
  function Heart (line 5) | function Heart({ filled = false, title = 'Heart' }: { filled?: boolean; ...

FILE: core/vibes/soul/primitives/favorite/index.tsx
  type FavoriteProps (line 5) | interface FavoriteProps {

FILE: core/vibes/soul/primitives/gift-certificate-card/gift-certificate-card-logo.tsx
  type Props (line 6) | interface Props {
  function GiftCertificateCardLogo (line 12) | function GiftCertificateCardLogo({ className, logo: streamableLogo, labe...

FILE: core/vibes/soul/primitives/gift-certificate-card/index.tsx
  type Props (line 8) | interface Props {
  function GiftCertificateCard (line 31) | function GiftCertificateCard({

FILE: core/vibes/soul/primitives/inline-email-form/index.tsx
  type Action (line 16) | type Action<State, Payload> = (
  function InlineEmailForm (line 21) | function InlineEmailForm({

FILE: core/vibes/soul/primitives/logo/index.tsx
  type Props (line 7) | interface Props {
  function Logo (line 29) | function Logo({ className, logo: streamableLogo, href, width, height, la...

FILE: core/vibes/soul/primitives/modal/index.tsx
  type ModalProps (line 7) | interface ModalProps extends React.PropsWithChildren {

FILE: core/vibes/soul/primitives/navigation/index.tsx
  type Link (line 41) | interface Link {
  type Locale (line 54) | interface Locale {
  type Currency (line 59) | interface Currency {
  type Action (line 64) | type Action<State, Payload> = (
  type SearchResult (line 69) | type SearchResult =
  type CurrencyAction (line 87) | type CurrencyAction = Action<SubmissionResult | null, FormData>;
  type SearchAction (line 88) | type SearchAction<S extends SearchResult> = Action<
  type Props (line 98) | interface Props<S extends SearchResult> {
  function handleScroll (line 317) | function handleScroll() {
  function SearchForm (line 666) | function SearchForm<S extends SearchResult>({
  function SubmitButton (line 754) | function SubmitButton({ loading, submitLabel }: { loading: boolean; subm...
  function SearchResults (line 770) | function SearchResults({
  function LocaleSwitcher (line 912) | function LocaleSwitcher({
  function CurrencyForm (line 966) | function CurrencyForm({

FILE: core/vibes/soul/primitives/price-label/index.tsx
  type PriceRange (line 4) | interface PriceRange {
  type PriceSale (line 10) | interface PriceSale {
  type Price (line 16) | type Price = string | PriceRange | PriceSale;
  type Props (line 18) | interface Props {
  function PriceLabel (line 38) | function PriceLabel({ className, colorScheme = 'light', price }: Props) {

FILE: core/vibes/soul/primitives/product-card/compare.tsx
  type CompareDrawerItem (line 10) | interface CompareDrawerItem {
  type Props (line 17) | interface Props {

FILE: core/vibes/soul/primitives/product-card/index.tsx
  type Product (line 13) | interface Product {
  type ProductCardProps (line 26) | interface ProductCardProps {
  function ProductCard (line 61) | function ProductCard({
  function ProductCardSkeleton (line 215) | function ProductCardSkeleton({

FILE: core/vibes/soul/primitives/rating/index.tsx
  type Props (line 3) | interface Props {
  type StarType (line 11) | interface StarType {

FILE: core/vibes/soul/primitives/reveal/index.tsx
  type RevealProps (line 9) | interface RevealProps {
  function Reveal (line 18) | function Reveal({

FILE: core/vibes/soul/primitives/side-panel/index.tsx
  type Props (line 10) | interface Props {
  function Content (line 15) | function Content({ title, children }: Props) {

FILE: core/vibes/soul/primitives/skeleton/index.tsx
  function SkeletonRoot (line 15) | function SkeletonRoot({
  function SkeletonBox (line 38) | function SkeletonBox({ className }: { className?: string }) {
  function SkeletonText (line 42) | function SkeletonText({
  function SkeletonIcon (line 61) | function SkeletonIcon({ className, icon }: { className?: string; icon: R...

FILE: core/vibes/soul/primitives/spinner/index.tsx
  type Props (line 3) | interface Props {

FILE: core/vibes/soul/primitives/toaster/index.tsx
  type ToasterProps (line 8) | type ToasterProps = React.ComponentProps<typeof Sonner>;
  type ToastOptions (line 10) | interface ToastOptions {

FILE: core/vibes/soul/primitives/tooltip/index.tsx
  type Props (line 4) | interface Props extends React.PropsWithChildren {

FILE: core/vibes/soul/primitives/wishlist-item-card/index.tsx
  type WishlistItem (line 14) | interface WishlistItem {
  type WishlistItemCardProps (line 25) | interface WishlistItemCardProps extends Omit<ProductCardProps, 'product'...
  function WishlistItemSkeleton (line 69) | function WishlistItemSkeleton({ className = '' }: { className?: string }) {

FILE: core/vibes/soul/primitives/wishlist-item-card/remove-wishlist-item.tsx
  type Action (line 10) | type Action<State, Payload> = (state: Awaited<State>, payload: Payload) ...
  type RemoveWishlistItemState (line 12) | interface RemoveWishlistItemState {
  type RemoveWishlistItemAction (line 17) | type RemoveWishlistItemAction = Action<RemoveWishlistItemState, FormData>;
  type Props (line 19) | interface Props {

FILE: core/vibes/soul/primitives/wishlist-item-card/wishlist-item-add-to-cart.tsx
  type Action (line 14) | type Action<S, P> = (state: Awaited<S>, payload: P) => S | Promise<S>;
  type State (line 16) | interface State {
  type AddWishlistItemToCartAction (line 22) | type AddWishlistItemToCartAction = Action<State, FormData>;
  type Props (line 24) | interface Props extends Omit<WishlistItem, 'itemId' | 'product'> {
  method onSubmit (line 42) | onSubmit(event, { formData }) {
  function SubmitButton (line 77) | function SubmitButton({ ctaLabel, disabled }: { ctaLabel: string; disabl...

FILE: core/vibes/soul/sections/account-settings/change-password-form.tsx
  type Action (line 17) | type Action<S, P> = (state: Awaited<S>, payload: P) => S | Promise<S>;
  type State (line 19) | interface State {
  type ChangePasswordAction (line 24) | type ChangePasswordAction = Action<State, FormData>;
  type ChangePasswordFormProps (line 26) | interface ChangePasswordFormProps {
  function ChangePasswordForm (line 35) | function ChangePasswordForm({
  function SubmitButton (line 93) | function SubmitButton({ children }: { children: ReactNode }) {

FILE: core/vibes/soul/sections/account-settings/index.tsx
  type AccountSettingsSectionProps (line 10) | interface AccountSettingsSectionProps {
  function AccountSettingsSection (line 44) | function AccountSettingsSection({

FILE: core/vibes/soul/sections/account-settings/newsletter-subscription-form.tsx
  type Action (line 11) | type Action<S, P> = (state: Awaited<S>, payload: P) => S | Promise<S>;
  type State (line 13) | interface State {
  type UpdateNewsletterSubscriptionAction (line 18) | type UpdateNewsletterSubscriptionAction = Action<State, FormData>;
  type NewsletterSubscriptionFormProps (line 20) | interface NewsletterSubscriptionFormProps {
  function NewsletterSubscriptionForm (line 27) | function NewsletterSubscriptionForm({

FILE: core/vibes/soul/sections/account-settings/update-account-form.tsx
  type Action (line 16) | type Action<S, P> = (state: Awaited<S>, payload: P) => S | Promise<S>;
  type UpdateAccountAction (line 18) | type UpdateAccountAction = Action<State, FormData>;
  type Account (line 20) | type Account = z.infer<typeof updateAccountSchema>;
  type State (line 22) | interface State {
  type UpdateAccountFormProps (line 28) | interface UpdateAccountFormProps {
  function UpdateAccountForm (line 38) | function UpdateAccountForm({

FILE: core/vibes/soul/sections/address-list-section/index.tsx
  type Address (line 27) | type Address = z.infer<typeof schema>;
  type DefaultAddressConfiguration (line 29) | interface DefaultAddressConfiguration {
  type Action (line 33) | type Action<F extends Field, S, P> = (
  type State (line 39) | interface State<A extends Address> {
  type DynamicAddressListFormAction (line 45) | type DynamicAddressListFormAction<F extends Field, A extends Address> = ...
  type AddressListSectionProps (line 51) | interface AddressListSectionProps<A extends Address, F extends Field> {
  function AddressListSection (line 84) | function AddressListSection<A extends Address, F extends Field>({
  function Title (line 348) | function Title({ children }: { children: ReactNode }) {
  function AddressPreview (line 363) | function AddressPreview({ address, isDefault = false }: { address: Addre...
  function AddressActionButton (line 384) | function AddressActionButton({

FILE: core/vibes/soul/sections/blog-post-content/index.tsx
  type Tag (line 8) | interface Tag {
  type Image (line 16) | interface Image {
  type BlogPostContentBlogPost (line 21) | interface BlogPostContentBlogPost {
  type Props (line 30) | interface Props {
  function BlogPostContent (line 36) | function BlogPostContent({
  function BlogPostTitleSkeleton (line 104) | function BlogPostTitleSkeleton() {
  function BlogPostAuthorSkeleton (line 112) | function BlogPostAuthorSkeleton() {
  function BlogPostTagsSkeleton (line 120) | function BlogPostTagsSkeleton() {
  function BlogPostImageSkeleton (line 130) | function BlogPostImageSkeleton() {
  function BlogPostBodySkeleton (line 138) | function BlogPostBodySkeleton() {
  function BlogPostContentSkeleton (line 152) | function BlogPostContentSkeleton() {

FILE: core/vibes/soul/sections/blog-post-list/index.tsx
  type Props (line 10) | interface Props {
  function BlogPostList (line 18) | function BlogPostList({
  function BlogPostListSkeleton (line 56) | function BlogPostListSkeleton({
  function BlogPostListEmptyState (line 71) | function BlogPostListEmptyState({

FILE: core/vibes/soul/sections/breadcrumbs/index.tsx
  type Breadcrumb (line 9) | interface Breadcrumb {
  type BreadcrumbsProps (line 14) | interface BreadcrumbsProps {
  function Breadcrumbs (line 34) | function Breadcrumbs({ breadcrumbs: streamableBreadcrumbs, className }: ...
  function BreadcrumbsSkeleton (line 83) | function BreadcrumbsSkeleton({ className }: Pick<BreadcrumbsProps, 'clas...
  function BreadCrumbEmptyState (line 100) | function BreadCrumbEmptyState({ className }: Pick<BreadcrumbsProps, 'cla...

FILE: core/vibes/soul/sections/cart/client.tsx
  type Action (line 35) | type Action<State, Payload> = (state: Awaited<State>, payload: Payload) ...
  type CartLineIteminventoryMessages (line 37) | interface CartLineIteminventoryMessages {
  type CartLineItem (line 45) | interface CartLineItem {
  type CartGiftCertificateLineItem (line 58) | interface CartGiftCertificateLineItem extends CartLineItem {
  type CartSummaryItem (line 70) | interface CartSummaryItem {
  type CartState (line 75) | interface CartState<LineItem extends CartLineItem> {
  type Cart (line 80) | interface Cart<LineItem extends CartLineItem> {
  type CouponCode (line 87) | interface CouponCode {
  type GiftCertificate (line 97) | interface GiftCertificate {
  type ShippingOption (line 107) | interface ShippingOption {
  type Country (line 113) | interface Country {
  type States (line 118) | interface States {
  type Address (line 126) | interface Address {
  type Shipping (line 133) | interface Shipping {
  type CartProps (line 158) | interface CartProps<LineItem extends CartLineItem> {
  function CartClient (line 206) | function CartClient<LineItem extends CartLineItem>({
  function CounterForm (line 510) | function CounterForm({
  function CheckoutButton (line 684) | function CheckoutButton({
  function SubmitButton (line 724) | function SubmitButton({

FILE: core/vibes/soul/sections/cart/coupon-code-form/coupon-chip.tsx
  type CouponChipProps (line 8) | interface CouponChipProps {
  function CouponChip (line 15) | function CouponChip({

FILE: core/vibes/soul/sections/cart/coupon-code-form/index.tsx
  type Action (line 17) | type Action<State, Payload> = (state: Awaited<State>, payload: Payload) ...
  type CouponCodeFormState (line 19) | interface CouponCodeFormState {
  type CouponCodeFormProps (line 24) | interface CouponCodeFormProps {
  function CouponCodeForm (line 34) | function CouponCodeForm({
  function SubmitButton (line 131) | function SubmitButton({ disabled, ...props }: React.ComponentPropsWithou...

FILE: core/vibes/soul/sections/cart/gift-certificate-code-form/gift-certificate-chip.tsx
  type GiftCertificateChipProps (line 8) | interface GiftCertificateChipProps {
  function GiftCertificateChip (line 15) | function GiftCertificateChip({

FILE: core/vibes/soul/sections/cart/gift-certificate-code-form/index.tsx
  type Action (line 17) | type Action<State, Payload> = (state: Awaited<State>, payload: Payload) ...
  type GiftCertificateCodeFormState (line 19) | interface GiftCertificateCodeFormState {
  type GiftCertificateCodeFormProps (line 24) | interface GiftCertificateCodeFormProps {
  function GiftCertificateCodeForm (line 34) | function GiftCertificateCodeForm({
  function SubmitButton (line 134) | function SubmitButton({ disabled, ...props }: React.ComponentPropsWithou...

FILE: core/vibes/soul/sections/cart/index.tsx
  function Cart (line 11) | function Cart<LineItem extends CartLineItem>({
  type CartSkeletonProps (line 30) | interface CartSkeletonProps {
  function CartSkeleton (line 38) | function CartSkeleton({
  type CartEmptyState (line 122) | interface CartEmptyState {
  function CartEmptyState (line 131) | function CartEmptyState({ title, subtitle, cta }: CartEmptyState) {

FILE: core/vibes/soul/sections/cart/shipping-form/index.tsx
  type Action (line 25) | type Action<State, Payload> = (state: Awaited<State>, payload: Payload) ...
  type ShippingFormState (line 27) | interface ShippingFormState {
  type ShippingOption (line 35) | interface ShippingOption {
  type Country (line 41) | interface Country {
  type States (line 46) | interface States {
  type Address (line 54) | interface Address {
  type Props (line 61) | interface Props {
  function ShippingForm (line 86) | function ShippingForm({
  function SubmitButton (line 441) | function SubmitButton(props: React.ComponentPropsWithoutRef<typeof Butto...

FILE: core/vibes/soul/sections/compare-section/index.tsx
  type CompareSectionProps (line 19) | interface CompareSectionProps {
  function CompareSection (line 57) | function CompareSection({
  function CompareSectionSkeleton (line 146) | function CompareSectionSkeleton({
  function CompareSectionEmptyState (line 192) | function CompareSectionEmptyState({

FILE: core/vibes/soul/sections/dynamic-form-section/index.tsx
  type Props (line 12) | interface Props<F extends Field> {
  function DynamicFormSection (line 24) | function DynamicFormSection<F extends Field>({

FILE: core/vibes/soul/sections/error/index.tsx
  type Props (line 3) | interface Props {
  function Error (line 10) | function Error({

FILE: core/vibes/soul/sections/featured-blog-post-list/index.tsx
  type Props (line 8) | interface Props {
  function FeaturedBlogPostList (line 19) | function FeaturedBlogPostList({

FILE: core/vibes/soul/sections/featured-product-carousel/index.tsx
  type Link (line 7) | interface Link {
  type FeaturedProductCarouselProps (line 12) | interface FeaturedProductCarouselProps {
  function FeaturedProductCarousel (line 40) | function FeaturedProductCarousel({

FILE: core/vibes/soul/sections/featured-product-list/index.tsx
  type Link (line 7) | interface Link {
  type FeaturedProductsListProps (line 12) | interface FeaturedProductsListProps {
  function FeaturedProductList (line 36) | function FeaturedProductList({

FILE: core/vibes/soul/sections/footer/index.tsx
  type Image (line 9) | interface Image {
  type Link (line 14) | interface Link {
  type Section (line 19) | interface Section {
  type SocialMediaLink (line 24) | interface SocialMediaLink {
  type ContactInformation (line 29) | interface ContactInformation {
  type FooterProps (line 34) | interface FooterProps {
  function FooterContactSkeleton (line 222) | function FooterContactSkeleton() {
  function SocialMediaLinksSkeleton (line 235) | function SocialMediaLinksSkeleton() {
  function FooterColumnsSkeleton (line 247) | function FooterColumnsSkeleton() {
  function FooterColumnSkeleton (line 265) | function FooterColumnSkeleton() {
  function CopyrightSkeleton (line 277) | function CopyrightSkeleton() {
  function PaymentIconsSkeleton (line 288) | function PaymentIconsSkeleton() {

FILE: core/vibes/soul/sections/forgot-password-section/forgot-password-form.tsx
  type Action (line 16) | type Action<State, Payload> = (state: Awaited<State>, payload: Payload) ...
  type ForgotPasswordAction (line 18) | type ForgotPasswordAction = Action<
  type Props (line 23) | interface Props {
  function ForgotPasswordForm (line 29) | function ForgotPasswordForm({
  function SubmitButton (line 68) | function SubmitButton({ children }: { children: React.ReactNode }) {

FILE: core/vibes/soul/sections/forgot-password-section/index.tsx
  type Props (line 3) | interface Props {
  function ForgotPasswordSection (line 11) | function ForgotPasswordSection({

FILE: core/vibes/soul/sections/gift-certificate-balance-section/index.tsx
  type Action (line 15) | type Action<S, P> = (state: Awaited<S>, payload: P) => S | Promise<S>;
  type State (line 17) | interface State {
  type GetGiftCertificateByCodeAction (line 23) | type GetGiftCertificateByCodeAction = Action<State, FormData>;
  type Props (line 25) | interface Props {
  type GiftCertificateStatus (line 39) | type GiftCertificateStatus = 'ACTIVE' | 'EXPIRED' | 'PENDING' | 'DISABLED';
  type GiftCertificateData (line 41) | interface GiftCertificateData {
  function GiftCertificateCheckBalanceSection (line 65) | function GiftCertificateCheckBalanceSection({
  function CheckBalanceForm (line 158) | function CheckBalanceForm({
  function SubmitButton (line 195) | function SubmitButton({ label }: { label: string }) {

FILE: core/vibes/soul/sections/gift-certificate-purchase-section/index.tsx
  type Props (line 16) | interface Props {
  function GiftCertificatePurchaseSection (line 47) | function GiftCertificatePurchaseSection({

FILE: core/vibes/soul/sections/gift-certificates-section/index.tsx
  type Props (line 8) | interface Props {
  function GiftCertificatesSection (line 32) | function GiftCertificatesSection({

FILE: core/vibes/soul/sections/header-section/index.tsx
  type Props (line 9) | interface Props {

FILE: core/vibes/soul/sections/maintenance/index.tsx
  type Image (line 7) | interface Image {
  type Props (line 12) | interface Props {
  function Maintenance (line 22) | function Maintenance({

FILE: core/vibes/soul/sections/not-found/index.tsx
  type NotFoundProps (line 7) | interface NotFoundProps {
  function NotFound (line 28) | function NotFound({

FILE: core/vibes/soul/sections/order-details-section/index.tsx
  type OrderPayment (line 10) | interface OrderPayment {
  type PaymentsSummary (line 16) | interface PaymentsSummary {
  type Summary (line 21) | interface Summary {
  type Address (line 30) | interface Address {
  type TrackingWithUrl (line 40) | interface TrackingWithUrl {
  type TrackingWithNumber (line 44) | interface TrackingWithNumber {
  type TrackingWithUrlAndNumber (line 48) | interface TrackingWithUrlAndNumber {
  type Shipment (line 53) | interface Shipment {
  type ShipmentLineItem (line 59) | interface ShipmentLineItem {
  type Destination (line 71) | interface Destination {
  type EmailDestination (line 79) | interface EmailDestination {
  type Order (line 85) | interface Order {
  type OrderDetailsSectionProps (line 96) | interface OrderDetailsSectionProps {
  function OrderDetailsSection (line 131) | function OrderDetailsSection({
  function Shipment (line 202) | function Shipment({
  function EmailDestination (line 249) | function EmailDestination({ destination }: { destination: EmailDestinati...
  function ShipmentSkeleton (line 264) | function ShipmentSkeleton({
  function ShipmentTracking (line 304) | function ShipmentTracking({
  function ShipmentLineItem (line 336) | function ShipmentLineItem({ lineItem }: { lineItem: ShipmentLineItem }) {
  function ShipmentLineItemSkeleton (line 408) | function ShipmentLineItemSkeleton() {
  function Summary (line 437) | function Summary({ summary, totalLabel = 'Total' }: { summary: Summary; ...
  function SummarySkeleton (line 464) | function SummarySkeleton({ placeholderCount = 2 }: { placeholderCount?: ...
  function PaymentsSummary (line 487) | function PaymentsSummary({ payments }: { payments: OrderPayment[] }) {
  function PaymentsSummarySkeleton (line 510) | function PaymentsSummarySkeleton({ placeholderCount = 2 }: { placeholder...
  function OrderDetailsSectionSkeleton (line 529) | function OrderDetailsSectionSkeleton({

FILE: core/vibes/soul/sections/order-list/index.tsx
  type Order (line 14) | interface Order {
  type OrderLineItem (line 22) | interface OrderLineItem extends Product {
  type OrderListProps (line 27) | interface OrderListProps {
  function OrderList (line 57) | function OrderList({
  function OrderListSkeleton (line 144) | function OrderListSkeleton() {
  function OrderListEmptyState (line 178) | function OrderListEmptyState({

FILE: core/vibes/soul/sections/product-carousel/index.tsx
  type CarouselProduct (line 19) | type CarouselProduct = Product;
  type ProductCarouselProps (line 21) | interface ProductCarouselProps {
  function ProductCarousel (line 53) | function ProductCarousel({
  function ProductsCarouselSkeleton (line 132) | function ProductsCarouselSkeleton({
  function ProductsCarouselEmptyState (line 166) | function ProductsCarouselEmptyState({

FILE: core/vibes/soul/sections/product-detail/index.tsx
  type ProductDetailProduct (line 24) | interface ProductDetailProduct {
  type ProductDetailProps (line 53) | interface ProductDetailProps<F extends Field> {
  function ProductDetail (line 96) | function ProductDetail<F extends Field>({
  function ProductGallerySkeleton (line 314) | function ProductGallerySkeleton() {
  function PriceLabelSkeleton (line 331) | function PriceLabelSkeleton() {
  function RatingSkeleton (line 335) | function RatingSkeleton() {
  function ProductSummarySkeleton (line 347) | function ProductSummarySkeleton() {
  function ProductDescriptionSkeleton (line 360) | function ProductDescriptionSkeleton() {
  function ProductDetailFormSkeleton (line 374) | function ProductDetailFormSkeleton() {
  function ProductAccordionsSkeleton (line 404) | function ProductAccordionsSkeleton() {
  function ProductDetailSkeleton (line 435) | function ProductDetailSkeleton() {

FILE: core/vibes/soul/sections/product-detail/product-detail-form.tsx
  type Action (line 39) | type Action<S, P> = (state: Awaited<S>, payload: P) => S | Promise<S>;
  type State (line 41) | interface State<F extends Field> {
  type ProductDetailFormAction (line 47) | type ProductDetailFormAction<F extends Field> = Action<State<F>, FormData>;
  type StockDisplayData (line 49) | interface StockDisplayData {
  type BackorderDisplayData (line 54) | interface BackorderDisplayData {
  type ProductDetailFormProps (line 62) | interface ProductDetailFormProps<F extends Field> {
  function ProductDetailForm (line 80) | function ProductDetailForm<F extends Field>({
  function SubmitButton (line 325) | function SubmitButton({ children, disabled }: { children: ReactNode; dis...
  function FormField (line 342) | function FormField({

FILE: core/vibes/soul/sections/product-detail/product-gallery.tsx
  type ProductGalleryLoadMoreAction (line 12) | type ProductGalleryLoadMoreAction = (
  type ProductGalleryProps (line 21) | interface ProductGalleryProps {
  function ProductGallery (line 58) | function ProductGallery({

FILE: core/vibes/soul/sections/product-detail/rating-link.tsx
  type Props (line 5) | interface Props extends RatingProps {
  function RatingLink (line 9) | function RatingLink({ scrollTargetId, ...ratingProps }: Props) {

FILE: core/vibes/soul/sections/product-detail/schema.ts
  type FormField (line 3) | interface FormField {
  type RadioField (line 11) | type RadioField = {
  type SelectField (line 17) | type SelectField = {
  type CheckboxField (line 23) | type CheckboxField = {
  type NumberInputField (line 30) | type NumberInputField = {
  type TextInputField (line 39) | type TextInputField = {
  type TextAreaField (line 45) | type TextAreaField = {
  type DateField (line 53) | type DateField = {
  type SwatchRadioFieldOption (line 59) | type SwatchRadioFieldOption =
  type SwatchRadioField (line 75) | type SwatchRadioField = {
  type CardRadioField (line 81) | type CardRadioField = {
  type ButtonRadioField (line 92) | type ButtonRadioField = {
  type Field (line 103) | type Field =
  type SchemaRawShape (line 115) | interface SchemaRawShape {
  function schema (line 125) | function schema(

FILE: core/vibes/soul/sections/product-list/index.tsx
  type ProductListProps (line 12) | interface ProductListProps {
  function ProductList (line 47) | function ProductList({
  function ProductListSkeleton (line 133) | function ProductListSkeleton({
  function ProductListEmptyState (line 151) | function ProductListEmptyState({

FILE: core/vibes/soul/sections/products-list-section/filter-parsers.ts
  function getFilterParsers (line 6) | function getFilterParsers(filters: Filter[]): Record<string, ParserBuild...

FILE: core/vibes/soul/sections/products-list-section/filters-panel.tsx
  type LinkGroupFilter (line 23) | interface LinkGroupFilter {
  type ToggleGroupFilter (line 29) | interface ToggleGroupFilter {
  type RatingFilter (line 36) | interface RatingFilter {
  type RangeFilter (line 43) | interface RangeFilter {
  type Filter (line 59) | type Filter = ToggleGroupFilter | RangeFilter | RatingFilter | LinkGroup...
  type Props (line 61) | interface Props {
  type InnerProps (line 69) | type InnerProps = Props & { filters: Filter[] };
  function getParamCountLabel (line 71) | function getParamCountLabel(params: Record<string, string | null | strin...
  function FiltersPanel (line 79) | function FiltersPanel({
  function FiltersPanelInner (line 99) | function FiltersPanelInner({
  function FiltersSkeleton (line 315) | function FiltersSkeleton() {
  function AccordionSkeleton (line 333) | function AccordionSkeleton({ children }: { children: React.ReactNode }) {
  function ToggleGroupSkeleton (line 346) | function ToggleGroupSkeleton({ options, seed = 0 }: { options: number; s...
  function RangeSkeleton (line 365) | function RangeSkeleton() {

FILE: core/vibes/soul/sections/products-list-section/index.tsx
  type Props (line 18) | interface Props {
  function ProductsListSection (line 48) | function ProductsListSection({

FILE: core/vibes/soul/sections/products-list-section/sorting.tsx
  type Option (line 9) | interface Option {
  function Sorting (line 14) | function Sorting({
  function SortingSkeleton (line 65) | function SortingSkeleton() {

FILE: core/vibes/soul/sections/reset-password-section/index.tsx
  type Props (line 5) | interface Props {
  function ResetPasswordSection (line 15) | function ResetPasswordSection({

FILE: core/vibes/soul/sections/reset-password-section/reset-password-form.tsx
  type Action (line 16) | type Action<State, Payload> = (state: Awaited<State>, payload: Payload) ...
  type ResetPasswordAction (line 18) | type ResetPasswordAction = Action<
  type Props (line 23) | interface Props {
  function ResetPasswordForm (line 31) | function ResetPasswordForm({

FILE: core/vibes/soul/sections/reviews/index.tsx
  type Review (line 9) | interface Review {
  type Props (line 17) | interface Props {
  function Reviews (line 46) | function Reviews({
  function ReviewsEmptyState (line 191) | function ReviewsEmptyState({
  function ReviewsSkeleton (line 274) | function ReviewsSkeleton({ reviewsLabel = 'Reviews' }: { reviewsLabel?: ...

FILE: core/vibes/soul/sections/reviews/review-form.tsx
  type Action (line 23) | type Action<S, P> = (state: Awaited<S>, payload: P) => S | Promise<S>;
  type SubmitReviewAction (line 25) | type SubmitReviewAction = Action<
  type Props (line 30) | interface Props {
  method onValidate (line 87) | onValidate({ formData }) {
  method onSubmit (line 90) | onSubmit(event, { formData }) {
  function SubmitButton (line 244) | function SubmitButton({ children }: { children: React.ReactNode }) {

FILE: core/vibes/soul/sections/section-layout/index.tsx
  type SectionLayoutProps (line 4) | interface SectionLayoutProps {
  function SectionLayout (line 24) | function SectionLayout({ className, children, containerSize = '2xl' }: S...

FILE: core/vibes/soul/sections/sidebar-menu/index.tsx
  type MenuLink (line 8) | interface MenuLink {
  type Props (line 14) | interface Props {
  function SidebarMenu (line 19) | function SidebarMenu({ links: streamableLinks, placeholderCount = 5 }: P...
  function SidebarMenuSkeleton (line 51) | function SidebarMenuSkeleton({ placeholderCount }: { placeholderCount: n...

FILE: core/vibes/soul/sections/sidebar-menu/sidebar-menu-link.tsx
  function SidebarMenuLink (line 9) | function SidebarMenuLink({

FILE: core/vibes/soul/sections/sidebar-menu/sidebar-menu-select.tsx
  function SidebarMenuSelect (line 6) | function SidebarMenuSelect({ links }: { links: Array<{ href: string; lab...

FILE: core/vibes/soul/sections/sign-in-section/index.tsx
  type Props (line 6) | interface Props {
  function SignInSection (line 30) | function SignInSection({

FILE: core/vibes/soul/sections/sign-in-section/sign-in-form.tsx
  type Action (line 16) | type Action<State, Payload> = (state: Awaited<State>, payload: Payload) ...
  type SignInAction (line 18) | type SignInAction = Action<SubmissionResult | null, FormData>;
  type Props (line 20) | interface Props {
  function SignInForm (line 28) | function SignInForm({
  function SubmitButton (line 107) | function SubmitButton({ children }: { children: React.ReactNode }) {

FILE: core/vibes/soul/sections/slideshow/index.tsx
  type ButtonLinkProps (line 14) | type ButtonLinkProps = ComponentPropsWithoutRef<typeof ButtonLink>;
  type Slide (line 16) | interface Slide {
  type Props (line 31) | interface Props {
  type UseProgressButtonType (line 38) | interface UseProgressButtonType {
  function Slideshow (line 107) | function Slideshow({ slides, playOnInit = true, interval = 5000, classNa...

FILE: core/vibes/soul/sections/sticky-sidebar-layout/index.tsx
  function StickySidebarLayout (line 17) | function StickySidebarLayout({

FILE: core/vibes/soul/sections/subscribe/index.tsx
  type Action (line 7) | type Action<State, Payload> = (state: Awaited<State>, payload: Payload) ...
  function Subscribe (line 9) | function Subscribe({

FILE: core/vibes/soul/sections/wishlist-details/index.tsx
  type Wishlist (line 16) | interface Wishlist {
  type Props (line 34) | interface Props {
  function WishlistItems (line 111) | function WishlistItems({
  function WishlistItemsEmptyState (line 164) | function WishlistItemsEmptyState({
  function WishlistItemsSkeleton (line 185) | function WishlistItemsSkeleton({
  function WishlistDetailSkeleton (line 209) | function WishlistDetailSkeleton({

FILE: core/vibes/soul/sections/wishlist-list-item/index.tsx
  type WishlistItemActions (line 11) | interface WishlistItemActions {
  type Props (line 16) | interface Props {
  function WishlistListItemItems (line 89) | function WishlistListItemItems({
  function WishlistListItemItemsEmptyState (line 127) | function WishlistListItemItemsEmptyState({
  function WishlistListItemItemsSkeleton (line 148) | function WishlistListItemItemsSkeleton({
  function WishlistListItemSkeleton (line 166) | function WishlistListItemSkeleton({

FILE: core/vibes/soul/sections/wishlist-list/index.tsx
  type Props (line 9) | interface Props {
  function WishlistListEmptyState (line 59) | function WishlistListEmptyState({
  function WishlistListSkeleton (line 77) | function WishlistListSkeleton({

FILE: core/vibes/soul/sections/wishlists-section/index.tsx
  type Props (line 7) | interface Props {

FILE: packages/catalyst/src/cli/commands/build.ts
  constant WRANGLER_VERSION (line 12) | const WRANGLER_VERSION = '4.24.3';

FILE: packages/catalyst/src/cli/commands/deploy.spec.ts
  method start (line 191) | start(controller) {
  method start (line 245) | start(controller) {

FILE: packages/catalyst/src/cli/commands/deploy.ts
  constant STEPS (line 24) | const STEPS: Record<z.infer<typeof stepsEnum>, string> = {

FILE: packages/catalyst/src/cli/hooks/telemetry.ts
  function parseArguments (line 9) | function parseArguments(args: string[]) {

FILE: packages/catalyst/src/cli/lib/get-module-cli-path.ts
  function getModuleCliPath (line 6) | function getModuleCliPath() {

FILE: packages/catalyst/src/cli/lib/mk-temp-dir.ts
  function mkTempDir (line 7) | async function mkTempDir(prefix = '/') {

FILE: packages/catalyst/src/cli/lib/project-config.ts
  type ProjectConfigSchema (line 4) | interface ProjectConfigSchema {
  function getProjectConfig (line 13) | function getProjectConfig(rootDir = process.cwd()) {

FILE: packages/catalyst/src/cli/lib/project.ts
  type ProjectListItem (line 12) | interface ProjectListItem {
  function fetchProjects (line 17) | async function fetchProjects(
  type CreateProjectResult (line 58) | interface CreateProjectResult {
  function createProject (line 65) | async function createProject(

FILE: packages/catalyst/src/cli/lib/telemetry.ts
  constant TELEMETRY_KEY_ENABLED (line 9) | const TELEMETRY_KEY_ENABLED = 'telemetry.enabled';
  constant TELEMETRY_KEY_ID (line 10) | const TELEMETRY_KEY_ID = `telemetry.anonymousId`;
  class Telemetry (line 12) | class Telemetry {
    method constructor (line 22) | constructor() {
    method track (line 33) | async track(eventName: string, payload: Record<string, unknown>) {
    method identify (line 54) | async identify(storeHash?: string) {
    method isEnabled (line 81) | isEnabled() {
    method getAnonymousId (line 88) | private getAnonymousId(): string {

FILE: packages/catalyst/src/cli/lib/wrangler-config.ts
  function getWranglerConfig (line 1) | function getWranglerConfig(projectUuid: string, kvNamespaceId: string) {

FILE: packages/catalyst/tests/mocks/handlers.ts
  method start (line 43) | start(controller) {

FILE: packages/catalyst/tests/mocks/spinner.ts
  method text (line 40) | get text() {
  method text (line 43) | set text(value: string) {

FILE: packages/client/src/api-error.ts
  class BigCommerceAPIError (line 1) | class BigCommerceAPIError extends Error {
    method constructor (line 2) | constructor(
    method createFromResponse (line 15) | static async createFromResponse(response: Response) {
  function assertIsErrorResponse (line 28) | function assertIsErrorResponse(value: unknown): asserts value is { error...

FILE: packages/client/src/client.ts
  type Config (line 16) | interface Config<FetcherRequestInit extends RequestInit = RequestInit> {
  type BigCommerceResponseError (line 33) | interface BigCommerceResponseError {
  type BigCommerceResponse (line 42) | interface BigCommerceResponse<T> {
  type GraphQLErrorPolicy (line 47) | type GraphQLErrorPolicy = 'none' | 'all' | 'auth' | 'ignore';
  class Client (line 49) | class Client<FetcherRequestInit extends RequestInit = RequestInit> {
    method constructor (line 63) | constructor(private config: Config<FetcherRequestInit>) {
    method fetch (line 103) | async fetch<TResult, TVariables>({
    method fetchSitemapIndex (line 193) | async fetchSitemapIndex(channelId?: string): Promise<string> {
    method getCanonicalUrl (line 213) | private async getCanonicalUrl(channelId?: string) {
    method getGraphQLEndpoint (line 219) | private async getGraphQLEndpoint(
    method requestLogger (line 237) | private requestLogger(document: string) {
  function createClient (line 262) | function createClient<FetcherRequestInit extends RequestInit = RequestIn...

FILE: packages/client/src/gql-auth-error.ts
  class BigCommerceAuthError (line 3) | class BigCommerceAuthError extends BigCommerceGQLError {
    method constructor (line 6) | constructor(

FILE: packages/client/src/gql-error.ts
  type GQLErrorCode (line 1) | enum GQLErrorCode {
  type GQLError (line 6) | interface GQLError {
  class BigCommerceGQLError (line 16) | class BigCommerceGQLError extends Error {
    method constructor (line 17) | constructor(public errors: GQLError[] = []) {

FILE: packages/client/src/invalid-cat-error.ts
  class InvalidCustomerAccessTokenError (line 4) | class InvalidCustomerAccessTokenError extends BigCommerceAuthError {
    method constructor (line 5) | constructor(public errors: GQLError[] = []) {

FILE: packages/client/src/lib/error.ts
  function parseGraphQLError (line 5) | function parseGraphQLError(result: unknown) {
  function assertIsGQLErrorResponse (line 27) | function assertIsGQLErrorResponse(value: unknown): asserts value is GQLE...

FILE: packages/client/src/missing-cat-error.ts
  class MissingCustomerAccessTokenError (line 4) | class MissingCustomerAccessTokenError extends BigCommerceAuthError {
    method constructor (line 5) | constructor(public errors: GQLError[] = []) {

FILE: packages/client/src/types.ts
  type DocumentDecoration (line 3) | interface DocumentDecoration<Result = Record<string, any>, Variables = R...

FILE: packages/client/src/utils/getOperationName.ts
  function isOperationDefinitionNode (line 3) | function isOperationDefinitionNode(node: DefinitionNode): node is Operat...
  type OperationInfo (line 8) | interface OperationInfo {

FILE: packages/client/src/utils/normalizeQuery.ts
  function normalizeQuery (line 6) | function normalizeQuery(query: string | DocumentNode | DocumentDecoratio...

FILE: packages/client/src/utils/removeEdgesAndNodes.ts
  type Maybe (line 1) | type Maybe<T> = T | null;
  type Connection (line 3) | interface Connection<T> {
  type Edge (line 7) | interface Edge<T> {

FILE: packages/create-catalyst/bin/supported-node-versions.cjs
  constant CATALYST_REQUIRED_NODE_VERSIONS (line 6) | const CATALYST_REQUIRED_NODE_VERSIONS = ['^24'];

FILE: packages/create-catalyst/src/commands/create.ts
  type Channel (line 19) | interface Channel {
  type ChannelsResponse (line 25) | interface ChannelsResponse {
  type InitResponse (line 29) | interface InitResponse {
  type CreateChannelResponse (line 37) | interface CreateChannelResponse {
  type EligibilityResponse (line 45) | interface EligibilityResponse {
  function getPlatformCheckCommand (line 52) | function getPlatformCheckCommand(command: string): string {
  function handleChannelCreation (line 60) | async function handleChannelCreation(bc: Https, cliApi: CliApi) {
  function handleChannelSelection (line 156) | async function handleChannelSelection(bc: Https) {
  function getChannelInit (line 210) | async function getChannelInit(cliApi: CliApi, channelId: number) {
  function setupProject (line 236) | async function setupProject(options: {
  function checkRequiredTools (line 286) | function checkRequiredTools() {
  function isInitResponse (line 546) | function isInitResponse(response: unknown): response is InitResponse {
  function isEligibilityResponse (line 558) | function isEligibilityResponse(response: unknown): response is Eligibili...
  function isCreateChannelResponse (line 570) | function isCreateChannelResponse(response: unknown): response is CreateC...
  function isChannelsResponse (line 583) | function isChannelsResponse(response: unknown): response is ChannelsResp...

FILE: packages/create-catalyst/src/commands/init.ts
  type Channel (line 12) | interface Channel {
  type ChannelsResponse (line 18) | interface ChannelsResponse {
  type InitResponse (line 22) | interface InitResponse {
  function isChannelsResponse (line 30) | function isChannelsResponse(response: unknown): response is ChannelsResp...
  function isInitResponse (line 47) | function isInitResponse(response: unknown): response is InitResponse {

FILE: packages/create-catalyst/src/commands/integration.ts
  type Manifest (line 19) | type Manifest = z.infer<typeof ManifestSchema>;

FILE: packages/create-catalyst/src/hooks/telemetry.ts
  function parseArguments (line 9) | function parseArguments(args: string[]) {

FILE: packages/create-catalyst/src/prompts/multi-select/multi-select.ts
  method renderItem (line 173) | renderItem({ item, isActive }) {

FILE: packages/create-catalyst/src/prompts/multi-select/types.ts
  type SelectTheme (line 4) | interface SelectTheme {
  type Choice (line 21) | interface Choice<Value> {
  type NormalizedChoice (line 31) | interface NormalizedChoice<Value> {
  type MultiSelectConfig (line 40) | interface MultiSelectConfig<
  type Item (line 59) | type Item<Value> = NormalizedChoice<Value> | Separator;

FILE: packages/create-catalyst/src/utils/auth.ts
  type AuthConfig (line 6) | interface AuthConfig {
  class Auth (line 10) | class Auth {
    method constructor (line 14) | constructor({ baseUrl }: AuthConfig) {
    method getDeviceCode (line 18) | async getDeviceCode() {
    method checkDeviceCode (line 47) | async checkDeviceCode(deviceCode: string) {

FILE: packages/create-catalyst/src/utils/checkout-ref.ts
  function checkoutRef (line 5) | function checkoutRef(repoDir: string, ref: string): void {

FILE: packages/create-catalyst/src/utils/cli-api.ts
  type CliApiConfig (line 3) | interface CliApiConfig {
  class CliApi (line 9) | class CliApi {
    method constructor (line 12) | constructor({ origin, storeHash, accessToken }: CliApiConfig) {
    method getChannelInit (line 19) | async getChannelInit(channelId: number | string) {
    method checkEligibility (line 25) | async checkEligibility() {
    method createChannel (line 31) | async createChannel(

FILE: packages/create-catalyst/src/utils/config.ts
  type CatalystConfig (line 5) | interface CatalystConfig {
  type TomlRecord (line 12) | interface TomlRecord {
  function isRecord (line 16) | function isRecord(value: unknown): value is Record<string, unknown> {
  function isCatalystConfig (line 20) | function isCatalystConfig(obj: unknown): obj is CatalystConfig {
  class Config (line 41) | class Config {
    method constructor (line 45) | constructor(projectDir: string) {
    method getAuth (line 50) | getAuth(): { storeHash?: string; accessToken?: string } {
    method setAuth (line 54) | setAuth(storeHash: string, accessToken: string): void {
    method save (line 59) | save(): void {
    method read (line 85) | private read(): CatalystConfig {

FILE: packages/create-catalyst/src/utils/has-github-ssh.ts
  function hasGitHubSSH (line 5) | function hasGitHubSSH(): boolean {

FILE: packages/create-catalyst/src/utils/https.ts
  type HttpsConfig (line 3) | interface HttpsConfig {
  class Https (line 8) | class Https {
    method constructor (line 13) | constructor({ baseUrl, accessToken }: HttpsConfig) {
    method fetch (line 19) | async fetch(path: string, opts: RequestInit = {}) {

FILE: packages/create-catalyst/src/utils/is-exec-exception.ts
  function isExecException (line 3) | function isExecException(error: unknown): error is ExecException {

FILE: packages/create-catalyst/src/utils/login.ts
  type LoginResult (line 9) | interface LoginResult {
  type DeviceCodeCredentials (line 14) | interface DeviceCodeCredentials {
  function pollDeviceCode (line 19) | async function pollDeviceCode(
  function waitForCredentials (line 35) | async function waitForCredentials(
  function waitForKeyPress (line 49) | async function waitForKeyPress(prompt: string): Promise<boolean> {
  function login (line 80) | async function login(baseUrl: string): Promise<LoginResult> {
  function storeCredentials (line 108) | function storeCredentials(projectDir: string, credentials: LoginResult):...

FILE: packages/create-catalyst/src/utils/node-version.spec.ts
  constant REQUIRED_NODE_VERSIONS (line 3) | const REQUIRED_NODE_VERSIONS = ['^24'];

FILE: packages/create-catalyst/src/utils/reset-branch-to-ref.ts
  function resetBranchToRef (line 3) | function resetBranchToRef(projectDir: string, ghRef: string) {

FILE: packages/create-catalyst/src/utils/telemetry/index.ts
  type CatalystTelemetryOptions (line 5) | interface CatalystTelemetryOptions {

FILE: packages/create-catalyst/src/utils/telemetry/telemetry.ts
  constant TELEMETRY_KEY_ENABLED (line 8) | const TELEMETRY_KEY_ENABLED = 'telemetry.enabled';
  constant TELEMETRY_KEY_ID (line 11) | const TELEMETRY_KEY_ID = `telemetry.anonymousId`;
  type Config (line 13) | interface Config {
  class Telemetry (line 20) | class Telemetry {
    method constructor (line 30) | constructor() {
    method track (line 48) | async track(eventName: string, payload: Record<string, unknown>) {
    method identify (line 69) | async identify(storeHash?: string) {
    method isEnabled (line 98) | isEnabled(): boolean {
    method getAnonymousId (line 106) | private getAnonymousId(): string {
Condensed preview — 702 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,132K chars).
[
  {
    "path": ".changeset/cold-foxes-lie.md",
    "chars": 145,
    "preview": "---\n\"@bigcommerce/catalyst-core\": patch\n---\n\nFix cart summary Discounts row not showing manual discounts applied via the"
  },
  {
    "path": ".changeset/config.json",
    "chars": 399,
    "preview": "{\n  \"$schema\": \"https://unpkg.com/@changesets/config@3.0.0/schema.json\",\n  \"changelog\": [\n    \"@changesets/changelog-git"
  },
  {
    "path": ".changeset/correlation-id-header.md",
    "chars": 239,
    "preview": "---\n\"@bigcommerce/catalyst-core\": patch\n---\n\nAdd X-Correlation-ID header to all GraphQL requests. Each page render gets "
  },
  {
    "path": ".changeset/fix-hidden-fields-d35665be.md",
    "chars": 163,
    "preview": "---\n\"@bigcommerce/catalyst-core\": patch\n---\n\nFix DynamicForm not rendering hidden field types, which caused `pageEntityI"
  },
  {
    "path": ".changeset/fix-html-lang-locale.md",
    "chars": 473,
    "preview": "---\n\"@bigcommerce/catalyst-core\": minor\n---\n\nRestore locale-aware `lang` attribute on the root `<html>` tag. The previou"
  },
  {
    "path": ".changeset/translations-patch-d3abeec7.md",
    "chars": 66,
    "preview": "---\n\"@bigcommerce/catalyst-core\": patch\n---\n\nUpdate translations.\n"
  },
  {
    "path": ".changeset/translations-patch-e3d3b994.md",
    "chars": 66,
    "preview": "---\n\"@bigcommerce/catalyst-core\": patch\n---\n\nUpdate translations.\n"
  },
  {
    "path": ".claude/skills/release-catalyst/SKILL.md",
    "chars": 4906,
    "preview": "---\nname: release-catalyst\ndescription: >\n  Cut a new release of Catalyst (`@bigcommerce/catalyst-core` and `@bigcommerc"
  },
  {
    "path": ".claude/skills/release-catalyst-patch/SKILL.md",
    "chars": 8365,
    "preview": "---\nname: release-catalyst-patch\ndescription: >\n  Release a single Catalyst package patch in isolation, without bundling"
  },
  {
    "path": ".claude/skills/sync-makeswift/SKILL.md",
    "chars": 3125,
    "preview": "---\nname: sync-makeswift\ndescription: >\n  Sync the `integrations/makeswift` branch with `canary` in the Catalyst monorep"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 25,
    "preview": "* @bigcommerce/team-trac\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 1065,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Have questions?\n    url: https://github.com/bigcommerce/catalyst/bl"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/🐞📝-bug-report-makeswift.md",
    "chars": 1063,
    "preview": "---\nname: \"\\U0001F41E\\U0001F4DD Makeswift Bug report\"\nabout: You're running into a reproducible error while developing w"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/🐞📝-bug-report.md",
    "chars": 808,
    "preview": "---\nname: \"\\U0001F41E\\U0001F4DD Bug report\"\nabout: You're running into a reproducible error while developing with Cataly"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 584,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: 'npm'\n    directory: '/'\n    schedule:\n      interval: 'weekly'\n      day: 'm"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 528,
    "preview": "## What/Why?\n<!--- \n  A description about what this pull request implements and its purpose.\n  Try to be detailed and de"
  },
  {
    "path": ".github/scripts/__tests__/audit-unlighthouse.test.mts",
    "chars": 7112,
    "preview": "import { describe, it } from 'node:test';\nimport assert from 'node:assert/strict';\n\nimport { buildReport } from '../audi"
  },
  {
    "path": ".github/scripts/__tests__/bundle-size.test.mts",
    "chars": 29758,
    "preview": "import { describe, it, after, beforeEach } from 'node:test';\nimport assert from 'node:assert/strict';\nimport { writeFile"
  },
  {
    "path": ".github/scripts/__tests__/compare-unlighthouse.test.mts",
    "chars": 10137,
    "preview": "import { describe, it } from 'node:test';\nimport assert from 'node:assert/strict';\n\nimport { compareResults } from '../c"
  },
  {
    "path": ".github/scripts/__tests__/post-bundle-comment.test.mts",
    "chars": 6890,
    "preview": "import { describe, it, beforeEach } from 'node:test';\nimport assert from 'node:assert/strict';\nimport { writeFileSync, m"
  },
  {
    "path": ".github/scripts/__tests__/post-unlighthouse-commit-comment.test.mts",
    "chars": 5052,
    "preview": "import { describe, it, beforeEach } from 'node:test';\nimport assert from 'node:assert/strict';\nimport { writeFileSync, m"
  },
  {
    "path": ".github/scripts/__tests__/post-unlighthouse-pr-comment.test.mts",
    "chars": 9087,
    "preview": "import { describe, it, beforeEach } from 'node:test';\nimport assert from 'node:assert/strict';\nimport { writeFileSync, m"
  },
  {
    "path": ".github/scripts/audit-unlighthouse.mts",
    "chars": 4057,
    "preview": "#!/usr/bin/env node\n/* eslint-disable no-console, no-restricted-syntax, no-plusplus */\n\nimport { existsSync, readFileSyn"
  },
  {
    "path": ".github/scripts/bundle-size.mts",
    "chars": 15218,
    "preview": "#!/usr/bin/env node\n/* eslint-disable no-console, no-restricted-syntax, no-plusplus, no-continue */\n\nimport { existsSync"
  },
  {
    "path": ".github/scripts/compare-unlighthouse.mts",
    "chars": 7432,
    "preview": "#!/usr/bin/env node\n/* eslint-disable no-console, no-restricted-syntax, no-plusplus, no-continue */\n\nimport { existsSync"
  },
  {
    "path": ".github/scripts/post-bundle-comment.js",
    "chars": 841,
    "preview": "const fs = require('fs');\n\nmodule.exports = async ({ github, context, reportPath = '/tmp/bundle-report.md' }) => {\n  con"
  },
  {
    "path": ".github/scripts/post-unlighthouse-commit-comment.js",
    "chars": 622,
    "preview": "const fs = require('fs');\n\nmodule.exports = async ({ github, context, reportPath = '/tmp/unlighthouse-report.md' }) => {"
  },
  {
    "path": ".github/scripts/post-unlighthouse-pr-comment.js",
    "chars": 1663,
    "preview": "const fs = require('fs');\n\nmodule.exports = async ({ github, context, provider = 'unknown', reportPath = '/tmp/unlightho"
  },
  {
    "path": ".github/scripts/prevent-invalid-changesets.js",
    "chars": 3441,
    "preview": "const fs = require(\"fs\");\n\nmodule.exports = async ({ core, exec }) => {\n  try {\n    await exec.exec(\"git\", [\n      \"fetc"
  },
  {
    "path": ".github/workflows/basic.yml",
    "chars": 2371,
    "preview": "name: Basic\n\non:\n  push:\n    branches: [canary, integrations/makeswift, integrations/b2b-makeswift]\n  pull_request:\n    "
  },
  {
    "path": ".github/workflows/bundle-size.yml",
    "chars": 4304,
    "preview": "name: Bundle Size\n# Reports the bundle size impact of a PR by comparing the current build against\n# a live build of the "
  },
  {
    "path": ".github/workflows/changesets-release.yml",
    "chars": 1218,
    "preview": "name: Changesets Release\n\non:\n  push:\n    branches:\n      - canary\n      - integrations/makeswift\n\nconcurrency: ${{ gith"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "chars": 3932,
    "preview": "name: Production Tag Deployment\nenv:\n    # secrets is for dependabot compatibility; prefer vars when available\n    VERCE"
  },
  {
    "path": ".github/workflows/e2e.yml",
    "chars": 3683,
    "preview": "name: E2E Tests\n\non:\n  pull_request:\n    types: [opened, synchronize]\n    branches: [canary, integrations/makeswift, int"
  },
  {
    "path": ".github/workflows/native-hosting.yml",
    "chars": 2865,
    "preview": "name: Native Hosting\n\non:\n  push:\n    branches: [canary]\n\njobs:\n  build-and-deploy:\n    name: Build and Deploy\n    runs-"
  },
  {
    "path": ".github/workflows/prevent-invalid-changesets.yml",
    "chars": 654,
    "preview": "name: Prevent invalid packages for Changesets\n\non:\n  pull_request:\n    branches:\n      - integrations/makeswift\n\npermiss"
  },
  {
    "path": ".github/workflows/regression-tests.yml",
    "chars": 9521,
    "preview": "name: Regression Tests\n\non:\n  deployment_status:\n    states: [\"success\"]\n\nenv:\n  VERCEL_PROTECTION_BYPASS: ${{ secrets.V"
  },
  {
    "path": ".github/workflows/translations-changeset.yml",
    "chars": 1474,
    "preview": "name: Create translations patch\n\non:\n  pull_request:\n    types:\n      - opened\n    branches:\n      - canary\n\njobs:\n  cre"
  },
  {
    "path": ".gitignore",
    "chars": 320,
    "preview": "node_modules\ndist\n.turbo\n.vscode/**/*\n!.vscode/settings.example.json\n!.vscode/launch.example.json\n.idea\n.vercel\n.catalys"
  },
  {
    "path": ".nvmrc",
    "chars": 3,
    "preview": "24\n"
  },
  {
    "path": ".vscode/launch.example.json",
    "chars": 1236,
    "preview": "{\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"Catalyst: debug server-side\",\n         "
  },
  {
    "path": ".vscode/settings.example.json",
    "chars": 204,
    "preview": "{\n  \"typescript.tsdk\": \"node_modules/typescript/lib\",\n  \"typescript.enablePromptUseWorkspaceTsdk\": true,\n  \"eslint.worki"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3223,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 7627,
    "preview": "# Contributing to Catalyst\n\nThanks for showing interest in contributing!\n\nThe following is a set of guidelines for contr"
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2023 BigCommerce\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "README.md",
    "chars": 3520,
    "preview": "<a href=\"https://catalyst.dev\" target=\"_blank\" rel=\"noopener norerrer\">\n  <img src=\"https://storage.googleapis.com/bigco"
  },
  {
    "path": "SECURITY.md",
    "chars": 989,
    "preview": "# Reporting security issues\nBigCommerce is dedicated to the responsible disclosure of security vulnerabilities.\nIf you h"
  },
  {
    "path": "core/.eslintignore",
    "chars": 250,
    "preview": "# Dependencies\nnode_modules/\n\n# Build outputs\n.next/\n.wrangler/\n.open-next/\nout/\ndist/\nbuild/\n\n# Generated files\n.turbo/"
  },
  {
    "path": "core/.eslintrc.cjs",
    "chars": 2590,
    "preview": "// @ts-check\n\nrequire('@bigcommerce/eslint-config/patch');\n\n/** @type {import('eslint').Linter.LegacyConfig} */\nconst co"
  },
  {
    "path": "core/.gitignore",
    "chars": 600,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "core/AGENTS.md",
    "chars": 10826,
    "preview": "# AGENTS.md\n\n## BigCommerce Catalyst Codebase Overview\n\nThis document provides guidance for Large Language Models (LLMs)"
  },
  {
    "path": "core/CHANGELOG.md",
    "chars": 263293,
    "preview": "# Changelog\n\n## 1.6.2\n\n### Patch Changes\n\n- [#2947](https://github.com/bigcommerce/catalyst/pull/2947) [`e198d89`](https"
  },
  {
    "path": "core/README.md",
    "chars": 3215,
    "preview": "<a href=\"https://catalyst.dev\" target=\"_blank\" rel=\"noopener norerrer\">\n  <img src=\"https://storage.googleapis.com/bigco"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/change-password/_actions/change-password.ts",
    "chars": 2319,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/change-password/page-data.ts",
    "chars": 935,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { graphql } from '~/client/graphql';\nimport { "
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/change-password/page.tsx",
    "chars": 1530,
    "preview": "/* eslint-disable react/jsx-no-bind */\nimport { Metadata } from 'next';\nimport { getTranslations, setRequestLocale } fro"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/layout.tsx",
    "chars": 449,
    "preview": "import { PropsWithChildren } from 'react';\n\nimport { isLoggedIn } from '~/auth';\nimport { redirect } from '~/i18n/routin"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/login/_actions/login.ts",
    "chars": 2052,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/login/forgot-password/_actions/reset-password.ts",
    "chars": 2316,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/login/forgot-password/page.tsx",
    "chars": 860,
    "preview": "import { Metadata } from 'next';\nimport { getTranslations, setRequestLocale } from 'next-intl/server';\n\nimport { ForgotP"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/login/page.tsx",
    "chars": 2825,
    "preview": "/* eslint-disable react/jsx-no-bind */\nimport { Metadata } from 'next';\nimport { getTranslations, setRequestLocale } fro"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/login/token/[token]/route.ts",
    "chars": 1420,
    "preview": "/*\n * This route is used to accept customer login token JWTs from the\n * [Customer Login API](https://developer.bigcomme"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/logout/route.ts",
    "chars": 621,
    "preview": "import { NextRequest } from 'next/server';\n\nimport { signOut } from '~/auth';\nimport { redirect } from '~/i18n/routing';"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/register/_actions/prefixes.ts",
    "chars": 122,
    "preview": "export const ADDRESS_FIELDS_NAME_PREFIX = 'customAddress_';\nexport const CUSTOMER_FIELDS_NAME_PREFIX = 'customCustomer_'"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/register/_actions/register-customer.ts",
    "chars": 12978,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/register/page-data.ts",
    "chars": 2593,
    "preview": "import { cache } from 'react';\n\nimport { getSessionCustomerAccessToken } from '~/auth';\nimport { client } from '~/client"
  },
  {
    "path": "core/app/[locale]/(default)/(auth)/register/page.tsx",
    "chars": 5197,
    "preview": "import { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { getTranslations, setRequestLocale "
  },
  {
    "path": "core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts",
    "chars": 1140,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { graphql } from '~/client/graphql';\nimport { "
  },
  {
    "path": "core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx",
    "chars": 8617,
    "preview": "import type { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { getFormatter, getTranslations"
  },
  {
    "path": "core/app/[locale]/(default)/(faceted)/category/[slug]/_components/category-viewed.tsx",
    "chars": 1354,
    "preview": "'use client';\n\nimport { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { useEffect, useRef } from 're"
  },
  {
    "path": "core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts",
    "chars": 1668,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { graphql } from '~/client/graphql';\nimport { "
  },
  {
    "path": "core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx",
    "chars": 9987,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport type { Metadata } from 'next';\nimport { notFo"
  },
  {
    "path": "core/app/[locale]/(default)/(faceted)/fetch-compare-products.ts",
    "chars": 1746,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { VariablesOf } from 'gql.tada';\nimport { cac"
  },
  {
    "path": "core/app/[locale]/(default)/(faceted)/fetch-faceted-search.ts",
    "chars": 12781,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { cache } from 'react';\nimport { z } from 'zo"
  },
  {
    "path": "core/app/[locale]/(default)/(faceted)/search/page-data.ts",
    "chars": 819,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { graphql } from '~/client/graphql';\nimport { "
  },
  {
    "path": "core/app/[locale]/(default)/(faceted)/search/page.tsx",
    "chars": 9497,
    "preview": "import { Metadata } from 'next';\nimport { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server';\nimp"
  },
  {
    "path": "core/app/[locale]/(default)/[...rest]/page.tsx",
    "chars": 102,
    "preview": "import { notFound } from 'next/navigation';\n\nexport default function CatchAllPage() {\n  notFound();\n}\n"
  },
  {
    "path": "core/app/[locale]/(default)/_components/slideshow/index.tsx",
    "chars": 1310,
    "preview": "import { useTranslations } from 'next-intl';\n\nimport { Slideshow as SlideshowSection } from '~/vibes/soul/sections/slide"
  },
  {
    "path": "core/app/[locale]/(default)/account/addresses/_actions/address-action.ts",
    "chars": 1067,
    "preview": "import { SubmissionResult } from '@conform-to/react';\n\nimport { Field, FieldGroup } from '@/vibes/soul/form/dynamic-form"
  },
  {
    "path": "core/app/[locale]/(default)/account/addresses/_actions/create-address.ts",
    "chars": 7892,
    "preview": "import { BigCommerceAPIError, BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { parseWithZod } from '@"
  },
  {
    "path": "core/app/[locale]/(default)/account/addresses/_actions/delete-address.ts",
    "chars": 3132,
    "preview": "import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { parseWithZod } from '@conform-to/zod';\nimpo"
  },
  {
    "path": "core/app/[locale]/(default)/account/addresses/_actions/update-address.ts",
    "chars": 8532,
    "preview": "import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { parseWithZod } from '@conform-to/zod';\nimpo"
  },
  {
    "path": "core/app/[locale]/(default)/account/addresses/page-data.ts",
    "chars": 2639,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { cache } from 'react';\n\nimport { getSessionC"
  },
  {
    "path": "core/app/[locale]/(default)/account/addresses/page.tsx",
    "chars": 3203,
    "preview": "import { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { getTranslations, setRequestLocale "
  },
  {
    "path": "core/app/[locale]/(default)/account/layout.tsx",
    "chars": 1079,
    "preview": "import { getTranslations, setRequestLocale } from 'next-intl/server';\nimport { PropsWithChildren } from 'react';\n\nimport"
  },
  {
    "path": "core/app/[locale]/(default)/account/orders/[id]/page-data.tsx",
    "chars": 5465,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { cache } from 'react';\n\nimport { getSessionC"
  },
  {
    "path": "core/app/[locale]/(default)/account/orders/[id]/page.tsx",
    "chars": 1301,
    "preview": "import { notFound } from 'next/navigation';\nimport { getFormatter, getTranslations, setRequestLocale } from 'next-intl/s"
  },
  {
    "path": "core/app/[locale]/(default)/account/orders/fragment.ts",
    "chars": 828,
    "preview": "import { graphql } from '~/client/graphql';\n\nexport const OrderItemFragment = graphql(`\n  fragment OrderItemFragment on "
  },
  {
    "path": "core/app/[locale]/(default)/account/orders/page-data.ts",
    "chars": 4471,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { cache } from 'react';\n\nimport { getSessionC"
  },
  {
    "path": "core/app/[locale]/(default)/account/orders/page.tsx",
    "chars": 1841,
    "preview": "import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server';\n\nimport { Order, OrderList } from '@"
  },
  {
    "path": "core/app/[locale]/(default)/account/settings/_actions/change-password.ts",
    "chars": 2578,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { parseWithZod } from '@confor"
  },
  {
    "path": "core/app/[locale]/(default)/account/settings/_actions/update-customer.ts",
    "chars": 2995,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { parseWithZod } from '@confor"
  },
  {
    "path": "core/app/[locale]/(default)/account/settings/_actions/update-newsletter-subscription.ts",
    "chars": 3917,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/account/settings/page-data.tsx",
    "chars": 2892,
    "preview": "import { cache } from 'react';\n\nimport { getSessionCustomerAccessToken } from '~/auth';\nimport { client } from '~/client"
  },
  {
    "path": "core/app/[locale]/(default)/account/settings/page.tsx",
    "chars": 2441,
    "preview": "/* eslint-disable react/jsx-no-bind */\nimport { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimpo"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/[id]/_actions/add-to-cart.tsx",
    "chars": 2417,
    "preview": "'use server';\n\nimport { BigCommerceAPIError, BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { Submiss"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/[id]/_components/visibility-switch.tsx",
    "chars": 1275,
    "preview": "'use client';\n\nimport { useActionState, useEffect, useTransition } from 'react';\n\nimport { Switch } from '@/vibes/soul/f"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/[id]/_components/wishlist-actions.tsx",
    "chars": 2687,
    "preview": "import { SwitchSkeleton } from '@/vibes/soul/form/switch';\nimport { Streamable } from '@/vibes/soul/lib/streamable';\nimp"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/[id]/_components/wishlist-analytics-provider.tsx",
    "chars": 1834,
    "preview": "'use client';\n\nimport { PropsWithChildren, Suspense } from 'react';\nimport { z } from 'zod';\n\nimport { Streamable, useSt"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/[id]/page-data.ts",
    "chars": 1645,
    "preview": "import { cache } from 'react';\n\nimport { getSessionCustomerAccessToken } from '~/auth';\nimport { client } from '~/client"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/[id]/page.tsx",
    "chars": 5173,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { getFormatter, getTranslations, setRequestLo"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/_actions/change-wishlist-visibility.ts",
    "chars": 2659,
    "preview": "'use server';\n\nimport { BigCommerceAuthError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@c"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/_actions/delete-wishlist.ts",
    "chars": 2518,
    "preview": "'use server';\n\nimport { BigCommerceAuthError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@c"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/_actions/mutation.ts",
    "chars": 1160,
    "preview": "import { graphql } from '~/client/graphql';\n\nexport const CreateWishlistMutation = graphql(`\n  mutation CreateWishlistMu"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/_actions/new-wishlist.ts",
    "chars": 2430,
    "preview": "'use server';\n\nimport { BigCommerceAuthError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@c"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/_actions/remove-wishlist-item.ts",
    "chars": 2656,
    "preview": "'use server';\n\nimport { BigCommerceAuthError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@c"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/_actions/rename-wishlist.ts",
    "chars": 2355,
    "preview": "'use server';\n\nimport { BigCommerceAuthError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@c"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/_actions/schema.ts",
    "chars": 1112,
    "preview": "import { z } from 'zod';\n\nconst wishlistId = z.number().nonnegative().min(1);\n\nconst wishlistItemSchema = z.object({\n  p"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/_components/new-wishlist-button.tsx",
    "chars": 1264,
    "preview": "'use client';\n\nimport { useState } from 'react';\n\nimport { Button } from '@/vibes/soul/primitives/button';\nimport { toas"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/_components/wishlist-actions-menu.tsx",
    "chars": 4714,
    "preview": "'use client';\n\nimport { EllipsisIcon } from 'lucide-react';\nimport { useReducer } from 'react';\n\nimport { Button } from "
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/modals.tsx",
    "chars": 3857,
    "preview": "import { getTranslations } from 'next-intl/server';\n\nimport { Wishlist } from '@/vibes/soul/sections/wishlist-details';\n"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/page-data.ts",
    "chars": 1516,
    "preview": "import { cache } from 'react';\n\nimport { getSessionCustomerAccessToken } from '~/auth';\nimport { client } from '~/client"
  },
  {
    "path": "core/app/[locale]/(default)/account/wishlists/page.tsx",
    "chars": 4774,
    "preview": "import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server';\nimport { SearchParams } from 'nuqs';"
  },
  {
    "path": "core/app/[locale]/(default)/blog/[blogId]/page-data.ts",
    "chars": 1152,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { graphql, VariablesOf } from '~/client/graphq"
  },
  {
    "path": "core/app/[locale]/(default)/blog/[blogId]/page.tsx",
    "chars": 2918,
    "preview": "import type { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { getFormatter, getTranslations"
  },
  {
    "path": "core/app/[locale]/(default)/blog/page-data.ts",
    "chars": 2943,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { getFormatter } from 'next-intl/server';\nimp"
  },
  {
    "path": "core/app/[locale]/(default)/blog/page.tsx",
    "chars": 3406,
    "preview": "import type { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { getTranslations, setRequestLo"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/add-shipping-cost.ts",
    "chars": 1299,
    "preview": "'use server';\n\nimport { revalidateTag } from 'next/cache';\n\nimport { getSessionCustomerAccessToken } from '~/auth';\nimpo"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/add-shipping-info.ts",
    "chars": 3336,
    "preview": "'use server';\n\nimport { revalidateTag } from 'next/cache';\n\nimport { getSessionCustomerAccessToken } from '~/auth';\nimpo"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/apply-coupon-code.ts",
    "chars": 1374,
    "preview": "'use server';\n\nimport { revalidateTag } from 'next/cache';\n\nimport { getSessionCustomerAccessToken } from '~/auth';\nimpo"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/apply-gift-certificate.ts",
    "chars": 1531,
    "preview": "'use server';\n\nimport { revalidateTag } from 'next/cache';\n\nimport { getSessionCustomerAccessToken } from '~/auth';\nimpo"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/remove-coupon-code.ts",
    "chars": 1399,
    "preview": "'use server';\n\nimport { revalidateTag } from 'next/cache';\n\nimport { getSessionCustomerAccessToken } from '~/auth';\nimpo"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/remove-gift-certificate.ts",
    "chars": 1556,
    "preview": "'use server';\n\nimport { revalidateTag } from 'next/cache';\n\nimport { getSessionCustomerAccessToken } from '~/auth';\nimpo"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/remove-item.ts",
    "chars": 1699,
    "preview": "'use server';\n\nimport { revalidateTag } from 'next/cache';\nimport { getTranslations } from 'next-intl/server';\n\nimport {"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/update-coupon-code.ts",
    "chars": 3846,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/update-gift-certificate.ts",
    "chars": 4074,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/update-line-item.ts",
    "chars": 12328,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/update-quantity.ts",
    "chars": 2257,
    "preview": "'use server';\n\nimport { revalidatePath } from 'next/cache';\nimport { getTranslations } from 'next-intl/server';\n\nimport "
  },
  {
    "path": "core/app/[locale]/(default)/cart/_actions/update-shipping-info.ts",
    "chars": 5175,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { parseWithZod } from '@confor"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_components/cart-analytics-provider.tsx",
    "chars": 2614,
    "preview": "'use client';\n\nimport { PropsWithChildren, Suspense } from 'react';\nimport { z } from 'zod';\n\nimport { Streamable, useSt"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_components/cart-viewed.tsx",
    "chars": 1692,
    "preview": "'use client';\n\nimport { useEffect, useRef } from 'react';\n\nimport { FragmentOf } from '~/client/graphql';\nimport { useAn"
  },
  {
    "path": "core/app/[locale]/(default)/cart/_components/checkout-preconnect.tsx",
    "chars": 156,
    "preview": "'use client';\n\nimport { preconnect } from 'react-dom';\n\nexport function CheckoutPreconnect({ url }: { url: string }) {\n "
  },
  {
    "path": "core/app/[locale]/(default)/cart/loading.tsx",
    "chars": 228,
    "preview": "import { useTranslations } from 'next-intl';\n\nimport { CartSkeleton } from '@/vibes/soul/sections/cart';\n\nexport default"
  },
  {
    "path": "core/app/[locale]/(default)/cart/page-data.ts",
    "chars": 6909,
    "preview": "import { cache } from 'react';\n\nimport { getSessionCustomerAccessToken } from '~/auth';\nimport { client } from '~/client"
  },
  {
    "path": "core/app/[locale]/(default)/cart/page.tsx",
    "chars": 15812,
    "preview": "import { Metadata } from 'next';\nimport { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server';\n\nim"
  },
  {
    "path": "core/app/[locale]/(default)/checkout/route.ts",
    "chars": 3625,
    "preview": "import { BigCommerceAuthError } from '@bigcommerce/catalyst-client';\nimport { unstable_rethrow as rethrow } from 'next/n"
  },
  {
    "path": "core/app/[locale]/(default)/compare/_actions/add-to-cart.tsx",
    "chars": 2609,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/compare/_components/compare-analytics-provider.tsx",
    "chars": 1796,
    "preview": "'use client';\n\nimport { PropsWithChildren, Suspense } from 'react';\nimport { z } from 'zod';\n\nimport { Streamable, useSt"
  },
  {
    "path": "core/app/[locale]/(default)/compare/page-data.ts",
    "chars": 2059,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { cache } from 'react';\n\nimport { client } fr"
  },
  {
    "path": "core/app/[locale]/(default)/compare/page.tsx",
    "chars": 4800,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { Metadata } from 'next';\nimport { getFormatt"
  },
  {
    "path": "core/app/[locale]/(default)/error.tsx",
    "chars": 449,
    "preview": "'use client';\n\nimport { useTranslations } from 'next-intl';\n\nimport { Error as ErrorSection } from '@/vibes/soul/section"
  },
  {
    "path": "core/app/[locale]/(default)/gift-certificates/balance/_actions/get-gift-certificate-by-code.ts",
    "chars": 3460,
    "preview": "'use server';\n\nimport { SubmissionResult } from '@conform-to/react';\nimport { parseWithZod } from '@conform-to/zod';\nimp"
  },
  {
    "path": "core/app/[locale]/(default)/gift-certificates/balance/fragment.ts",
    "chars": 433,
    "preview": "import { graphql } from '~/client/graphql';\n\nexport const GiftCertificateFragment = graphql(`\n  fragment GiftCertificate"
  },
  {
    "path": "core/app/[locale]/(default)/gift-certificates/balance/page.tsx",
    "chars": 2079,
    "preview": "import type { Metadata } from 'next';\nimport { getTranslations, setRequestLocale } from 'next-intl/server';\n\nimport { Gi"
  },
  {
    "path": "core/app/[locale]/(default)/gift-certificates/page-data.ts",
    "chars": 1305,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { graphql } from '~/client/graphql';\nimport { "
  },
  {
    "path": "core/app/[locale]/(default)/gift-certificates/page.tsx",
    "chars": 1769,
    "preview": "import type { Metadata } from 'next';\nimport { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server'"
  },
  {
    "path": "core/app/[locale]/(default)/gift-certificates/purchase/_actions/add-to-cart.tsx",
    "chars": 5440,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/gift-certificates/purchase/fragment.ts",
    "chars": 565,
    "preview": "import { graphql } from '~/client/graphql';\n\nexport const GiftCertificateSettingsFragment = graphql(`\n  fragment GiftCer"
  },
  {
    "path": "core/app/[locale]/(default)/gift-certificates/purchase/page-data.ts",
    "chars": 1542,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { graphql } from '~/client/graphql';\nimport { "
  },
  {
    "path": "core/app/[locale]/(default)/gift-certificates/purchase/page.tsx",
    "chars": 5968,
    "preview": "import { ResultOf } from 'gql.tada';\nimport { Metadata } from 'next';\nimport { getFormatter, getTranslations } from 'nex"
  },
  {
    "path": "core/app/[locale]/(default)/layout.tsx",
    "chars": 515,
    "preview": "import { setRequestLocale } from 'next-intl/server';\nimport { PropsWithChildren } from 'react';\n\nimport { Footer } from "
  },
  {
    "path": "core/app/[locale]/(default)/page-data.ts",
    "chars": 2415,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { graphql } from '~/client/graphql';\nimport { "
  },
  {
    "path": "core/app/[locale]/(default)/page.tsx",
    "chars": 3897,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { Metadata } from 'next';\nimport { getFormatt"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_actions/add-to-cart.tsx",
    "chars": 6199,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_actions/get-more-images.ts",
    "chars": 1565,
    "preview": "'use server';\n\nimport { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\n\nimport { getSessionCustomerAccessTok"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_actions/submit-review.ts",
    "chars": 3027,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_actions/wishlist-action.ts",
    "chars": 8560,
    "preview": "'use server';\n\nimport {\n  BigCommerceAPIError,\n  BigCommerceGQLError,\n  removeEdgesAndNodes,\n} from '@bigcommerce/cataly"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/product-analytics-provider.tsx",
    "chars": 1591,
    "preview": "'use client';\n\nimport { PropsWithChildren, Suspense } from 'react';\nimport { z } from 'zod';\n\nimport { Streamable, useSt"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/product-review-schema/fragment.ts",
    "chars": 249,
    "preview": "import { graphql } from '~/client/graphql';\n\nexport const ProductReviewSchemaFragment = graphql(`\n  fragment ProductRevi"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/product-review-schema/product-review-schema.tsx",
    "chars": 1383,
    "preview": "'use client';\n\n// eslint-disable-next-line import/no-named-as-default\nimport DOMPurify from 'dompurify';\nimport { useFor"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/product-schema/fragment.ts",
    "chars": 459,
    "preview": "import { graphql } from '~/client/graphql';\n\nexport const ProductSchemaFragment = graphql(`\n  fragment ProductSchemaFrag"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/product-schema/index.tsx",
    "chars": 2737,
    "preview": "import { Product as ProductSchemaType, WithContext } from 'schema-dts';\n\nimport { PricingFragment } from '~/client/fragm"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/product-viewed/fragment.ts",
    "chars": 364,
    "preview": "import { graphql } from '~/client/graphql';\n\nexport const ProductViewedFragment = graphql(`\n  fragment ProductViewedFrag"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/product-viewed/index.tsx",
    "chars": 1055,
    "preview": "'use client';\n\nimport { useEffect, useRef } from 'react';\n\nimport { PricingFragment } from '~/client/fragments/pricing';"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/reviews.tsx",
    "chars": 6655,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { getFormatter, getTranslations } from 'next-"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/search-params-router-refresh.tsx",
    "chars": 719,
    "preview": "'use client';\n\nimport { useSearchParams } from 'next/navigation';\nimport { SearchParams } from 'nuqs';\nimport { useEffec"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/wishlist-button/add-to-new-wishlist-modal.tsx",
    "chars": 1844,
    "preview": "'use client';\n\nimport { useSearchParams } from 'next/navigation';\n\nimport { Modal } from '~/components/modal';\nimport { "
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/wishlist-button/dropdown.tsx",
    "chars": 2768,
    "preview": "'use client';\n\nimport { clsx } from 'clsx';\nimport { CheckIcon, PlusIcon, XIcon } from 'lucide-react';\nimport { useSearc"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/wishlist-button/form.tsx",
    "chars": 1674,
    "preview": "import { getTranslations } from 'next-intl/server';\nimport { SearchParams } from 'nuqs';\n\nimport { Streamable } from '@/"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/_components/wishlist-button/index.tsx",
    "chars": 4174,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { getTranslations } from 'next-intl/server';\n"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/page-data.ts",
    "chars": 11099,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { PricingFragment } from '~/client/fragments/p"
  },
  {
    "path": "core/app/[locale]/(default)/product/[slug]/page.tsx",
    "chars": 19645,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { Metadata } from 'next';\nimport { notFound }"
  },
  {
    "path": "core/app/[locale]/(default)/webpages/[id]/_components/web-page.tsx",
    "chars": 2703,
    "preview": "import { Stream, Streamable } from '@/vibes/soul/lib/streamable';\nimport { Breadcrumb, Breadcrumbs, BreadcrumbsSkeleton "
  },
  {
    "path": "core/app/[locale]/(default)/webpages/[id]/contact/_actions/submit-contact-form.ts",
    "chars": 3845,
    "preview": "'use server';\n\nimport { BigCommerceGQLError } from '@bigcommerce/catalyst-client';\nimport { SubmissionResult } from '@co"
  },
  {
    "path": "core/app/[locale]/(default)/webpages/[id]/contact/page-data.ts",
    "chars": 985,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { graphql, VariablesOf } from '~/client/graphq"
  },
  {
    "path": "core/app/[locale]/(default)/webpages/[id]/contact/page.tsx",
    "chars": 5711,
    "preview": "import type { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { getTranslations, setRequestLo"
  },
  {
    "path": "core/app/[locale]/(default)/webpages/[id]/layout.tsx",
    "chars": 2172,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { setRequestLocale } from 'next-intl/server';"
  },
  {
    "path": "core/app/[locale]/(default)/webpages/[id]/normal/page-data.ts",
    "chars": 943,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { graphql, VariablesOf } from '~/client/graphq"
  },
  {
    "path": "core/app/[locale]/(default)/webpages/[id]/normal/page.tsx",
    "chars": 2489,
    "preview": "import type { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { getTranslations, setRequestLo"
  },
  {
    "path": "core/app/[locale]/(default)/wishlist/[token]/page-data.ts",
    "chars": 2017,
    "preview": "import { cache } from 'react';\n\nimport { client } from '~/client';\nimport { PaginationFragment } from '~/client/fragment"
  },
  {
    "path": "core/app/[locale]/(default)/wishlist/[token]/page.tsx",
    "chars": 6497,
    "preview": "import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';\nimport { Metadata } from 'next';\nimport { notFound }"
  },
  {
    "path": "core/app/[locale]/error.tsx",
    "chars": 391,
    "preview": "'use client';\n\nimport { useTranslations } from 'next-intl';\n\nimport { Error as ErrorSection } from '@/vibes/soul/section"
  },
  {
    "path": "core/app/[locale]/layout.tsx",
    "chars": 5641,
    "preview": "import { Analytics } from '@vercel/analytics/react';\nimport { SpeedInsights } from '@vercel/speed-insights/next';\nimport"
  },
  {
    "path": "core/app/[locale]/maintenance/page.tsx",
    "chars": 2118,
    "preview": "import { Metadata } from 'next';\nimport { getTranslations, setRequestLocale } from 'next-intl/server';\nimport { ReactNod"
  },
  {
    "path": "core/app/[locale]/not-found.tsx",
    "chars": 556,
    "preview": "import { getTranslations } from 'next-intl/server';\n\nimport { NotFound as NotFoundSection } from '@/vibes/soul/sections/"
  },
  {
    "path": "core/app/admin/route.ts",
    "chars": 711,
    "preview": "import { defaultLocale } from '~/i18n/locales';\nimport { redirect } from '~/i18n/routing';\n\nconst canonicalDomain: strin"
  },
  {
    "path": "core/app/api/auth/[...nextauth]/route.ts",
    "chars": 75,
    "preview": "import { handlers } from '~/auth';\n\nexport const { GET, POST } = handlers;\n"
  },
  {
    "path": "core/app/fonts.ts",
    "chars": 477,
    "preview": "import { DM_Serif_Text, Inter, Roboto_Mono } from 'next/font/google';\n\nconst inter = Inter({\n  display: 'swap',\n  subset"
  },
  {
    "path": "core/app/layout.tsx",
    "chars": 582,
    "preview": "import { PropsWithChildren } from 'react';\n\n// Since we have a `not-found.tsx` at the root, a layout file is required ev"
  },
  {
    "path": "core/app/not-found.tsx",
    "chars": 2333,
    "preview": "import { clsx } from 'clsx';\n\nimport '../globals.css';\n\nimport { fonts } from '~/app/fonts';\n\n// Renders for non-localiz"
  },
  {
    "path": "core/app/notifications.tsx",
    "chars": 598,
    "preview": "'use client';\n\nimport { useEffect, useRef } from 'react';\n\nimport { toast } from '@/vibes/soul/primitives/toaster';\nimpo"
  },
  {
    "path": "core/app/providers.tsx",
    "chars": 344,
    "preview": "'use client';\n\nimport { PropsWithChildren } from 'react';\n\nimport { Toaster } from '@/vibes/soul/primitives/toaster';\nim"
  },
  {
    "path": "core/app/robots.txt/route.ts",
    "chars": 1539,
    "preview": "/* eslint-disable check-file/folder-naming-convention */\n/*\n * Robots.txt route\n *\n * This route pulls robots.txt conten"
  },
  {
    "path": "core/app/sitemap.xml/route.ts",
    "chars": 519,
    "preview": "/* eslint-disable check-file/folder-naming-convention */\n/*\n * Proxy to the existing BigCommerce sitemap index on the ca"
  },
  {
    "path": "core/app/xmlsitemap.php/route.ts",
    "chars": 520,
    "preview": "/* eslint-disable check-file/folder-naming-convention */\nimport { defaultLocale } from '~/i18n/locales';\nimport { perman"
  },
  {
    "path": "core/auth/anonymous-session.ts",
    "chars": 2523,
    "preview": "import { cookies, headers } from 'next/headers';\nimport { AnonymousUser } from 'next-auth';\nimport { decode, encode } fr"
  },
  {
    "path": "core/auth/customer-login-api.ts",
    "chars": 2424,
    "preview": "import { randomUUID } from 'crypto';\nimport { SignJWT } from 'jose';\n\n/**\n * Build a Customer Login API JWT which can be"
  },
  {
    "path": "core/auth/index.ts",
    "chars": 9512,
    "preview": "import { decodeJwt } from 'jose';\nimport NextAuth, { type NextAuthConfig, User } from 'next-auth';\nimport 'next-auth/jwt"
  },
  {
    "path": "core/auth/types.ts",
    "chars": 472,
    "preview": "import { User } from 'next-auth';\n\ndeclare module 'next-auth' {\n  interface Session {\n    user?: User;\n  }\n\n  interface "
  },
  {
    "path": "core/build-config/reader.ts",
    "chars": 453,
    "preview": "import rawBuildConfig from './build-config.json';\nimport { buildConfigSchema, BuildConfigSchema } from './schema';\n\nclas"
  }
]

// ... and 502 more files (download for full content)

About this extraction

This page contains the full source code of the bigcommerce/catalyst GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 702 files (2.8 MB), approximately 771.8k tokens, and a symbol index with 1273 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!