From da702cc17e3dd50bfd421545f33867397d0163be Mon Sep 17 00:00:00 2001 From: Chancellor Clark Date: Tue, 6 Jan 2026 13:28:26 -0700 Subject: [PATCH 01/69] chore(ci): remove conditionally based on deployments (#2807) --- .github/workflows/deploy.yml | 5 +++-- .github/workflows/regression-tests.yml | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 36899bfe75..49423e3825 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,7 +1,8 @@ name: Production Tag Deployment env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + # 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: diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index afcbc46c2f..ef81ebc18e 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -13,7 +13,6 @@ concurrency: jobs: unlighthouse-audit: - if: ${{ contains(fromJson('["Production – catalyst-canary", "Preview – catalyst-canary"]'), github.event.deployment_status.environment) }} name: Unlighthouse Audit - ${{ matrix.device }} runs-on: ubuntu-latest strategy: From dd559b2d9f354387e19d7c81a809eed97bfc9be3 Mon Sep 17 00:00:00 2001 From: Jorge Moya Date: Tue, 6 Jan 2026 14:40:09 -0600 Subject: [PATCH 02/69] fix(core): minor UX improvements for Reviews components (#2809) --- .changeset/two-years-flow.md | 8 ++++++++ .../(default)/product/[slug]/_components/reviews.tsx | 8 ++++++++ core/vibes/soul/sections/reviews/index.tsx | 6 +++--- 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 .changeset/two-years-flow.md diff --git a/.changeset/two-years-flow.md b/.changeset/two-years-flow.md new file mode 100644 index 0000000000..9699fcb78c --- /dev/null +++ b/.changeset/two-years-flow.md @@ -0,0 +1,8 @@ +--- +"@bigcommerce/catalyst-core": patch +--- + +Minor UX improvements for the Reviews section: +- Show `totalCount` for reviews. +- Show `averageRating` up to the first decimal. +- Hide `averageRating` next to rating stars when there are no reviews. diff --git a/core/app/[locale]/(default)/product/[slug]/_components/reviews.tsx b/core/app/[locale]/(default)/product/[slug]/_components/reviews.tsx index 5fae6d44b7..4bfc58e359 100644 --- a/core/app/[locale]/(default)/product/[slug]/_components/reviews.tsx +++ b/core/app/[locale]/(default)/product/[slug]/_components/reviews.tsx @@ -35,6 +35,7 @@ const ReviewsQuery = graphql( product(entityId: $entityId) { reviewSummary { averageRating + numberOfReviews } reviews(first: $first, after: $after, before: $before, last: $last) { pageInfo { @@ -161,6 +162,12 @@ export const Reviews = async ({ return { email: session?.user?.email ?? '', name: obfuscatedName }; }); + const streamableTotalCount = Streamable.from(async () => { + const product = await streamableReviewsData; + + return product?.reviewSummary.numberOfReviews ?? 0; + }); + return ( <> {(product) => diff --git a/core/vibes/soul/sections/reviews/index.tsx b/core/vibes/soul/sections/reviews/index.tsx index 35ac2e69b0..a56b61d583 100644 --- a/core/vibes/soul/sections/reviews/index.tsx +++ b/core/vibes/soul/sections/reviews/index.tsx @@ -17,7 +17,7 @@ interface Review { interface Props { reviews: Streamable; averageRating: Streamable; - totalCount?: Streamable; + totalCount?: Streamable; paginationInfo?: Streamable; nextLabel?: Streamable; previousLabel?: Streamable; @@ -117,7 +117,7 @@ export function Reviews({ {(averageRating) => ( <>
- {averageRating} + {parseFloat(averageRating.toFixed(1))}
@@ -221,7 +221,7 @@ export function ReviewsEmptyState({
0
- + } sidebarSize="medium" From b57bffaf28b8c9714f99f7af581871f6e9f6f944 Mon Sep 17 00:00:00 2001 From: Chancellor Clark Date: Tue, 6 Jan 2026 14:55:54 -0700 Subject: [PATCH 03/69] fix: clear pagination cursors when sort option changes (#2811) When the sort option is changed in the product filter, the before/after pagination query params are now removed. This prevents stale pagination cursors from being used with the new sort order, which could lead to incorrect results or empty pages. Changes: - Updated Sorting component to use useQueryStates instead of useQueryState - Now manages sort, before, and after params together - Clears before and after params when sort value changes Co-authored-by: Claude --- .changeset/clear-pagination-on-sort.md | 5 +++++ .../products-list-section/sorting.tsx | 20 +++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 .changeset/clear-pagination-on-sort.md diff --git a/.changeset/clear-pagination-on-sort.md b/.changeset/clear-pagination-on-sort.md new file mode 100644 index 0000000000..db507d2143 --- /dev/null +++ b/.changeset/clear-pagination-on-sort.md @@ -0,0 +1,5 @@ +--- +"@bigcommerce/catalyst-core": patch +--- + +Fix pagination cursor persistence when changing sort order. The `before` and `after` query parameters are now cleared when the sort option changes, preventing stale pagination cursors from causing incorrect results or empty pages. diff --git a/core/vibes/soul/sections/products-list-section/sorting.tsx b/core/vibes/soul/sections/products-list-section/sorting.tsx index fde97d65a5..b041425607 100644 --- a/core/vibes/soul/sections/products-list-section/sorting.tsx +++ b/core/vibes/soul/sections/products-list-section/sorting.tsx @@ -1,6 +1,6 @@ 'use client'; -import { parseAsString, useQueryState } from 'nuqs'; +import { parseAsString, useQueryStates } from 'nuqs'; import { useOptimistic, useTransition } from 'react'; import { SelectField } from '@/vibes/soul/form/select-field'; @@ -24,11 +24,15 @@ export function Sorting({ defaultValue?: string; placeholder?: Streamable; }) { - const [param, setParam] = useQueryState( - paramName, - parseAsString.withDefault(defaultValue).withOptions({ shallow: false, history: 'push' }), + const [params, setParams] = useQueryStates( + { + [paramName]: parseAsString.withDefault(defaultValue), + before: parseAsString, + after: parseAsString, + }, + { shallow: false, history: 'push' }, ); - const [optimisticParam, setOptimisticParam] = useOptimistic(param); + const [optimisticParam, setOptimisticParam] = useOptimistic(params[paramName] ?? defaultValue); const [isPending, startTransition] = useTransition(); const options = useStreamable(streamableOptions); const label = useStreamable(streamableLabel) ?? 'Sort'; @@ -42,7 +46,11 @@ export function Sorting({ onValueChange={(value) => { startTransition(async () => { setOptimisticParam(value); - await setParam(value); + await setParams({ + [paramName]: value, + before: null, + after: null, + }); }); }} options={options} From ea9d6337d2bb8e5c166cb1de3385631e12fea4a3 Mon Sep 17 00:00:00 2001 From: Jorge Moya Date: Wed, 7 Jan 2026 16:16:00 -0600 Subject: [PATCH 04/69] fix(core): delete duplicate select component (#2813) * fix(core): delete duplicate select component * fix: remove console.log --- .changeset/fuzzy-needles-attend.md | 5 ++ core/vibes/soul/form/select-field/index.tsx | 73 ------------------- core/vibes/soul/form/select/index.tsx | 4 +- .../sections/cart/shipping-form/index.tsx | 6 +- .../product-detail/product-detail-form.tsx | 4 +- .../products-list-section/sorting.tsx | 4 +- 6 files changed, 15 insertions(+), 81 deletions(-) create mode 100644 .changeset/fuzzy-needles-attend.md delete mode 100644 core/vibes/soul/form/select-field/index.tsx diff --git a/.changeset/fuzzy-needles-attend.md b/.changeset/fuzzy-needles-attend.md new file mode 100644 index 0000000000..0fde34205b --- /dev/null +++ b/.changeset/fuzzy-needles-attend.md @@ -0,0 +1,5 @@ +--- +"@bigcommerce/catalyst-core": patch +--- + +Delete duplicate Select component. diff --git a/core/vibes/soul/form/select-field/index.tsx b/core/vibes/soul/form/select-field/index.tsx deleted file mode 100644 index f7775c2edc..0000000000 --- a/core/vibes/soul/form/select-field/index.tsx +++ /dev/null @@ -1,73 +0,0 @@ -'use client'; - -import { clsx } from 'clsx'; -import * as React from 'react'; - -import { FieldError } from '@/vibes/soul/form/field-error'; -import { Label } from '@/vibes/soul/form/label'; -import { Select, type Props as SelectProps } from '@/vibes/soul/form/select'; - -export interface SelectFieldProps extends SelectProps { - label: string; - name: string; - value: string; - hideLabel?: boolean; - className?: string; -} - -export function SelectField({ - label, - className, - hideLabel = false, - name, - value, - colorScheme, - pending, - placeholder, - variant, - options, - errors, - onFocus, - onBlur, - onOptionMouseEnter, - onValueChange, - ...rest -}: SelectFieldProps) { - const id = React.useId(); - - return ( -
- - {/* Workaround for https://github.com/radix-ui/primitives/issues/3198, remove when fixed */} - - + {Array.isArray(countries) ? ( -
{Array.isArray(states) ? ( - Date: Thu, 8 Jan 2026 11:40:33 -0700 Subject: [PATCH 05/69] feat: pass in additional attributes for script tags (#2816) --- .changeset/script-attributes-support.md | 5 ++ core/data-transformers/scripts-transformer.ts | 56 +++++++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 .changeset/script-attributes-support.md diff --git a/.changeset/script-attributes-support.md b/.changeset/script-attributes-support.md new file mode 100644 index 0000000000..ce924e8ff9 --- /dev/null +++ b/.changeset/script-attributes-support.md @@ -0,0 +1,5 @@ +--- +"@bigcommerce/catalyst-core": patch +--- + +Add support for additional HTML attributes on script tags. The scripts transformer now extracts and passes through attributes like `async`, `defer`, `crossorigin`, and `data-*` attributes from BigCommerce script tags to the C15T consent manager, ensuring scripts load with their intended behavior. diff --git a/core/data-transformers/scripts-transformer.ts b/core/data-transformers/scripts-transformer.ts index 3af032a3ee..a7d7ec32ec 100644 --- a/core/data-transformers/scripts-transformer.ts +++ b/core/data-transformers/scripts-transformer.ts @@ -19,17 +19,54 @@ const BC_TO_C15T_CONSENT_CATEGORY_MAP = { type ScriptInfo = { textContent: string } | { src: string } | null; +/** + * Extracts HTML attributes from a script tag's opening element. + * Handles both boolean attributes (async, defer) and key-value attributes (data-*, crossorigin). + * @param {string} scriptTag - The full script tag HTML string + * @returns {Record} Record of attribute name-value pairs (boolean attrs get empty string value) + */ +function extractAttributes(scriptTag: string): Record { + // Extract the opening tag content (everything between