Skip to content

feat(core): add RecommendationShelf section with personalization tracking#3403

Open
thiagopereira-vtex wants to merge 30 commits into
devfrom
feat/recommendation-shelf
Open

feat(core): add RecommendationShelf section with personalization tracking#3403
thiagopereira-vtex wants to merge 30 commits into
devfrom
feat/recommendation-shelf

Conversation

@thiagopereira-vtex

@thiagopereira-vtex thiagopereira-vtex commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What's the purpose of this pull request?

Adds a CMS-configurable RecommendationShelf section that renders VTEX personalization recommendations, and wires up the anonymous personalization session/tracking globally.

How it works?

  • RecommendationShelf section: fetches recommendations for a given campaignVrn (resolving the campaign type from the VRN) and renders them with the shared core ProductCard. A mapRecommendationToProductCard adapter maps the VTEX RecommendationProduct into the ProductSummary_ProductFragment shape, so recommendation cards look identical to regular shelves.
  • useStartSession (in src/sdk/analytics/hooks): mounted globally from Layout, it starts the anonymous personalization session once per browser session (guarded by a cookie, with backoff) and reports a product view on PDPs. The product is derived from the page props (same pattern as usePageViewEvent) because usePDP() has no context above PageProvider.
  • All side effects run client-side in effects (after hydration), so there's no SSR/TTFB or Lighthouse render-metric impact.

How to test it?

  1. Add a RecommendationShelf section via the CMS with a valid campaignVrn.
  2. Open a PDP and confirm the shelf renders recommended products as standard product cards.
  3. Verify (Network tab) that StartSession fires once per browser session and SendProductViewEvent fires on PDPs.

References

N/A

Summary by CodeRabbit

  • New Features

    • Added a new recommendation shelf section that can display personalized product carousels on supported pages.
    • Store owners can now enable recommendation-related experiences through a new optional setting.
    • Product pages now include richer product metadata for sharing and previewing.
  • Bug Fixes

    • Improved handling for missing or unavailable recommendation data so empty or error states no longer render broken content.

…king

Adds a CMS-configurable RecommendationShelf section that renders VTEX
personalization recommendations using the shared ProductCard, plus a global
useStartSession hook (mounted in Layout) that starts the anonymous
personalization session once per browser session and reports PDP product
views.
@thiagopereira-vtex thiagopereira-vtex requested a review from a team as a code owner June 23, 2026 14:51
@thiagopereira-vtex thiagopereira-vtex requested review from emersonlaurentino and hellofanny and removed request for a team June 23, 2026 14:51
@thiagopereira-vtex thiagopereira-vtex added the contributing Pull request submitted by the community label Jun 23, 2026
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

Adds recommendation GraphQL contracts and VTEX wiring, shelf support hooks and rendering, CMS registration, and client-side session startup with product metadata updates.

Changes

Recommendation shelf and session flow

Layer / File(s) Summary
GraphQL API and VTEX wiring
packages/api/src/platforms/vtex/typeDefs/*, packages/api/src/platforms/vtex/clients/*, packages/api/src/platforms/vtex/resolvers/*, packages/api/test/integration/schema.test.ts, packages/core/test/server/index.test.ts
Adds recommendation query and session-start GraphQL types, VTEX clients/resolvers, and schema validation updates.
Shared types and helpers
packages/core/src/components/RecommendationShelf/{vrn.ts,RecommendationShelf.types.ts}, packages/core/src/sdk/analytics/utils.ts, packages/core/src/utils/retry.ts, packages/core/test/components/RecommendationShelf/vrn.test.ts, packages/core/test/sdk/analytics/utils.test.ts, packages/core/test/utils/retry.test.ts
Defines VRN parsing, shelf prop contracts, cookie lookup, retry behavior, and helper tests.
Recommendation hooks
packages/core/src/components/RecommendationShelf/{useRecommendationUserId.ts,useRecommendations.ts}, packages/core/test/components/RecommendationShelf/useRecommendations.test.ts
Adds hooks for resolving the anonymous recommendation user id and fetching recommendation data, with tests for the recommendation query hook.
Shelf rendering
packages/core/src/components/RecommendationShelf/{RecommendationShelf.tsx,RecommendationShelf.module.scss,index.ts}, packages/core/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
Renders the recommendation shelf carousel, applies analytics attributes, styles the shelf, and validates browser behavior.
CMS schema and registry
packages/core/cms/faststore/components/cms_component__RecommendationShelf.jsonc, packages/core/cms/faststore/schema.json, packages/core/src/components/cms/global/Components.ts
Adds the RecommendationShelf CMS schema, allows it in the FastStore schema, and registers the dynamic component.
Session startup and product metadata
packages/core/src/Layout.tsx, packages/core/src/sdk/analytics/hooks/{useStartSession.ts,useStartRecommendationSession.ts}, packages/core/test/sdk/analytics/useStartRecommendationSession.test.ts, packages/core/src/pages/[slug]/p.tsx
Adds session-start hooks, calls the recommendation session hook from Layout, validates the startup hook, and emits product meta tags on PDP pages.

Sequence Diagram(s)

Recommendation shelf flow

sequenceDiagram
  participant RecommendationShelf
  participant useRecommendationUserId
  participant usePDP
  participant useRecommendations
  participant ProductShelfSkeleton
  participant Carousel
  participant ProductCard
  RecommendationShelf->>useRecommendationUserId: resolve userId
  RecommendationShelf->>usePDP: read productGroupID
  RecommendationShelf->>useRecommendations: fetch campaignVrn and products
  useRecommendations-->>RecommendationShelf: data / error / isLoading
  alt loading
    RecommendationShelf->>ProductShelfSkeleton: render loading state
  else loaded
    RecommendationShelf->>Carousel: render shelf
    loop products
      RecommendationShelf->>ProductCard: render mapped card
    end
  end
Loading

Recommendation session startup

sequenceDiagram
  participant Layout
  participant useStartRecommendationSession
  participant getCookie
  participant retry
  participant runStartRecommendationSession
  participant startRecommendationSession
  Layout->>useStartRecommendationSession: call after usePageViewEvent(props)
  useStartRecommendationSession->>getCookie: read vtex-rec-user-start-session
  alt cookie missing
    useStartRecommendationSession->>retry: retry session start
    retry->>runStartRecommendationSession: execute mutation
    runStartRecommendationSession->>startRecommendationSession: start session
    startRecommendationSession-->>runStartRecommendationSession: Boolean!
    runStartRecommendationSession-->>retry: defined result
  else cookie present
    useStartRecommendationSession-->>Layout: skip
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • vtex/faststore#3198: Also changes packages/core/src/Layout.tsx around usePageViewEvent and session-related hook invocation.
  • vtex/faststore#3312: Also updates CMS component allow-list wiring used by FastStore section registration.

Suggested labels

enhancement

Suggested reviewers

  • lucasfp13
  • ommeirelles
  • renatomaurovtex

Poem

A shelf of stars begins to spin,
with VRNs and cookies tucked within.
The session wakes, the carousel sings,
and tiny analytics gain bright new wings ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a RecommendationShelf section with personalization tracking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/recommendation-shelf

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (5)
packages/core/src/components/RecommendationShelf/vrn.ts (1)

22-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid unsafe casting for campaignVrnType during parsing.

campaignType as RecommendationVrnType suppresses type safety at the boundary. Validate and narrow before returning typed data so invalid VRNs fail in a typed path, not through a late fallback.

♻️ Suggested refactor
+const VRN_TYPES = new Set<RecommendationVrnType>([
+  'rec-cross-v2',
+  'rec-similar-v2',
+  'rec-persona-v2',
+  'rec-last-v2',
+  'rec-top-items-v2',
+  'rec-search-v2',
+  'rec-next-v2',
+  'rec-visual-v2',
+])
+
 function parseCampaignVrn(campaignVrn: string) {
   const [_, __, accountName, campaignType, campaignId] = campaignVrn.split(':')
+  if (!campaignType || !VRN_TYPES.has(campaignType as RecommendationVrnType)) {
+    throw new Error(`Unknown campaign type: ${campaignType}`)
+  }

   return {
     accountName,
     campaignId,
-    campaignVrnType: campaignType as RecommendationVrnType,
+    campaignVrnType: campaignType,
   }
 }

As per path instructions, packages/**/*.{ts,tsx} should "Ensure type safety and avoid type assertions when possible."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/RecommendationShelf/vrn.ts` around lines 22 -
29, The `parseCampaignVrn` function uses an unsafe type assertion `campaignType
as RecommendationVrnType` which bypasses type safety at the boundary. Instead of
casting, validate that the extracted `campaignType` value is actually a valid
`RecommendationVrnType` before returning it. Implement validation logic such as
checking against the allowed enum or type values, and throw an error or return a
failure result if the value is invalid, ensuring type safety is maintained
throughout the parsing path without relying on assertions.

Source: Path instructions

packages/core/src/components/RecommendationShelf/useRecommendations.ts (2)

165-165: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid the type assertion fallback for query variables.

Line 165 uses ({} as RecommendationInput), which weakens type safety. Prefer a typed default object or update useQuery to accept nullable variables with doNotRun.

As per path instructions, packages/**/*.{ts,tsx}: “Ensure type safety and avoid type assertions when possible”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/RecommendationShelf/useRecommendations.ts` at
line 165, In the useRecommendations hook, the useQuery call on line 165 uses a
type assertion ({} as RecommendationInput) when args is undefined, which weakens
type safety. Replace this type assertion with either a properly typed default
object that matches the RecommendationInput structure, or refactor the useQuery
invocation to use the doNotRun option to handle cases where variables are
nullable instead of relying on the assertion fallback.

Source: Path instructions


15-144: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Trim the query to only fields consumed by RecommendationShelf/ProductCard mapping.

This selection set is much larger than what Line 170+ downstream mapping/rendering needs, increasing payload and client work on PDP. Keep only fields used by mapRecommendationToProductCard and card rendering.

As per path instructions, packages/**/src/components/**: “Check rendering performance (avoid heavy operations on render...)”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/RecommendationShelf/useRecommendations.ts`
around lines 15 - 144, The GraphQL query in the products selection set is
requesting many fields that are not consumed by the downstream
mapRecommendationToProductCard function and card rendering logic. Review the
mapRecommendationToProductCard function and the ProductCard component rendering
to identify which fields from the products query are actually used, then remove
all unused fields from the products selection set. Keep only the fields that are
directly referenced in the mapping function and rendering logic to reduce
payload size and improve rendering performance.

Source: Path instructions

packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx (1)

4-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Alias @faststore/ui imports with UI prefix in core components.

This file imports ProductShelf and Carousel directly from @faststore/ui. In this path, please use UIProductShelf, UICarousel, etc., to match the local composition convention.

As per coding guidelines, packages/core/src/components/**/*.{ts,tsx}: “Import from @faststore/ui and alias with UI prefix to avoid local name collisions when composing library components”.

Also applies to: 136-141

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx` at
line 4, Update the import statement at line 4 in RecommendationShelf.tsx to
alias ProductShelf and Carousel with the UI prefix by using the `as` keyword
(e.g., `import { ProductShelf as UIProductShelf, Carousel as UICarousel }`).
Then update all usages of ProductShelf and Carousel throughout the component to
use their new aliased names UIProductShelf and UICarousel. Also apply the same
aliasing pattern to any other imports from `@faststore/ui` in the file,
particularly at lines 136-141, to follow the core components naming convention
and avoid local name collisions.

Source: Coding guidelines

packages/core/src/components/RecommendationShelf/index.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Re-export component props/types from the barrel.

This barrel exports only the component symbol. Please also re-export RecommendationShelfProps (and any future variant types) so consumers can type against the barrel path.

As per coding guidelines, packages/{components,core}/src/components/**/*.{ts,tsx}: “All Props and variant types MUST be re-exported from the barrel index.ts”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/RecommendationShelf/index.ts` at line 1, The
barrel index.ts file in the RecommendationShelf component directory is only
re-exporting the component itself, but not the associated prop types. Update the
index.ts file to also explicitly re-export RecommendationShelfProps and any
variant types that exist in the RecommendationShelf module. This ensures
consumers can import and use the component props by importing directly from the
barrel path, following the coding guidelines that require all Props and variant
types to be re-exported from barrel index.ts files in the components directory.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/core/cms/faststore/components/cms_component__RecommendationShelf.jsonc`:
- Around line 13-16: The `campaignVrn` property definition in the schema has an
example format in its description that does not match the VRN format accepted by
the `RecommendationShelf/vrn.ts` runtime parser, allowing users to configure
invalid values that fail at runtime. Update the example in the description field
to match the correct VRN format expected by the parser, and add a `pattern`
constraint to the `campaignVrn` property schema to validate the VRN format
during configuration time, preventing invalid values from being accepted in the
CMS.

In `@packages/core/src/components/RecommendationShelf/queries/request.ts`:
- Around line 51-62: The code currently defaults to using GET requests for
GraphQL queries and places variables (including userId) in the URL query string,
which creates a security risk for information leakage. Change the method
selection logic to use POST as the default instead of GET, and either remove the
GET conditional path entirely or create an explicit whitelist of safe operations
that do not contain personalization data. Update the method variable assignment
in the conditional expression to prioritize POST, and ensure that the
URLSearchParams construction does not include variables when using GET requests.
- Around line 85-106: The baseRequest function can return null when the response
content type is unrecognized (neither JSON nor plain text), causing callers that
destructure the response to fail. Additionally, there is a duplicate if
(response.ok) block that is unreachable. Remove the second if (response.ok)
block entirely, and in the first block, ensure that when responseBody is null,
provide a fallback by calling response.json() to attempt parsing the response
body as JSON, so that a non-null value is always returned on a successful
response.

In `@packages/core/src/components/RecommendationShelf/queries/useLazyQuery.ts`:
- Around line 12-29: The useLazyQuery hook has a cache key mismatch where the
initial cache key is generated using the initial variables parameter (in the
getKey call), but the execute function accepts queryVariables which may differ.
When execute calls response.mutate with different queryVariables than what
initialized the hook, the cache update happens under the wrong key. Fix this by
either dynamically generating the cache key within the execute function using
the queryVariables parameter instead of the initial variables, or by
restructuring the code to ensure the SWR cache key always matches the variables
actually being used for the request. The key should be recalculated based on
whatever variables are passed to execute so that response.mutate updates the
correct cache entry.

In `@packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx`:
- Around line 103-110: The console.error call in the error handling block logs
the entire recommendationArgs object, which contains sensitive user identifiers
like userId. Instead of logging the full recommendationArgs parameter, replace
it with a sanitized version that only includes non-sensitive metadata such as
campaign type or correlation id presence. Extract and log only the safe,
non-identifying fields from recommendationArgs to protect user privacy while
still maintaining useful debugging information.
- Around line 84-101: The async cookie retrieval logic with getWithRetry is
currently running during render, which causes it to execute on every render and
be duplicated in React Strict Mode. Move the entire conditional block that calls
getWithRetry and setUserId into a useEffect hook with userId as a dependency.
This ensures the async operation only runs once when userId becomes available,
preventing duplicate retries and unnecessary state updates.

In `@packages/core/src/components/RecommendationShelf/useRecommendations.ts`:
- Around line 5-153: The GraphQL query in the gql template at the root level is
attempting to query a field called "recommendations" that does not exist on the
Query type according to the schema, causing schema validation to fail. Identify
the correct query field name by consulting the GraphQL schema documentation or
backend schema definition, then replace the "recommendations" field name in the
query with the actual field name that exists in the schema. Ensure all the
nested fields and variables being queried match what the actual schema field
definition expects.

In `@packages/core/src/sdk/analytics/hooks/useStartSession.ts`:
- Around line 5-9: Move the utility functions checkIsMobile and
getUserIdFromCookie from the RecommendationShelf component module to an
appropriate SDK-level utility module (create a new one in src/sdk if needed).
Update the import statements in useStartSession.ts to import these functions
from the new SDK utility location instead of from
src/components/RecommendationShelf/utils. This ensures the analytics SDK logic
remains decoupled from UI component modules.
- Around line 131-137: The issue is that lastProductViewSentRef.current is being
set before the runSendProductViewEvent mutation completes. If the network call
fails or times out, the ref retains the productId value and prevents any future
retry attempts for that product view. Move the assignment of
lastProductViewSentRef.current = productId to execute only after the
runSendProductViewEvent call succeeds by either awaiting the promise result and
updating the ref in a success callback, or by removing the void annotation and
properly handling the promise chain to ensure the ref is only updated on
successful completion.
- Around line 60-63: Replace the `any` type for the `props` parameter in the
`useStartSession` function with a properly typed interface that captures the
nested structure being accessed. Define a minimal type that includes the path
`data?.product?.isVariantOf?.productGroupID` with appropriate property types
(productGroupID should be a string), then update the function signature to use
this new type instead of `any` to restore compile-time type safety.
- Around line 16-30: In the useStartSession hook, refactor the imports and
logic: first, change the import of useLazyQuery to come from
src/sdk/graphql/useLazyQuery instead of the component folder path, second,
extract checkIsMobile and getUserIdFromCookie utility functions to
src/sdk/analytics/utils.ts and import them from there, third, replace the
props?: any parameter with a properly typed interface similar to the pattern
used in usePageViewEvent.ts to ensure type safety, and fourth, fix the
deduplication logic by moving the assignment of lastProductViewSentRef.current =
productId to execute only after the sendProductViewEventMutation succeeds
(inside the success callback of runSendProductViewEvent) rather than before the
mutation call, ensuring the ref is only updated when the mutation actually
completes.

---

Nitpick comments:
In `@packages/core/src/components/RecommendationShelf/index.ts`:
- Line 1: The barrel index.ts file in the RecommendationShelf component
directory is only re-exporting the component itself, but not the associated prop
types. Update the index.ts file to also explicitly re-export
RecommendationShelfProps and any variant types that exist in the
RecommendationShelf module. This ensures consumers can import and use the
component props by importing directly from the barrel path, following the coding
guidelines that require all Props and variant types to be re-exported from
barrel index.ts files in the components directory.

In `@packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx`:
- Line 4: Update the import statement at line 4 in RecommendationShelf.tsx to
alias ProductShelf and Carousel with the UI prefix by using the `as` keyword
(e.g., `import { ProductShelf as UIProductShelf, Carousel as UICarousel }`).
Then update all usages of ProductShelf and Carousel throughout the component to
use their new aliased names UIProductShelf and UICarousel. Also apply the same
aliasing pattern to any other imports from `@faststore/ui` in the file,
particularly at lines 136-141, to follow the core components naming convention
and avoid local name collisions.

In `@packages/core/src/components/RecommendationShelf/useRecommendations.ts`:
- Line 165: In the useRecommendations hook, the useQuery call on line 165 uses a
type assertion ({} as RecommendationInput) when args is undefined, which weakens
type safety. Replace this type assertion with either a properly typed default
object that matches the RecommendationInput structure, or refactor the useQuery
invocation to use the doNotRun option to handle cases where variables are
nullable instead of relying on the assertion fallback.
- Around line 15-144: The GraphQL query in the products selection set is
requesting many fields that are not consumed by the downstream
mapRecommendationToProductCard function and card rendering logic. Review the
mapRecommendationToProductCard function and the ProductCard component rendering
to identify which fields from the products query are actually used, then remove
all unused fields from the products selection set. Keep only the fields that are
directly referenced in the mapping function and rendering logic to reduce
payload size and improve rendering performance.

In `@packages/core/src/components/RecommendationShelf/vrn.ts`:
- Around line 22-29: The `parseCampaignVrn` function uses an unsafe type
assertion `campaignType as RecommendationVrnType` which bypasses type safety at
the boundary. Instead of casting, validate that the extracted `campaignType`
value is actually a valid `RecommendationVrnType` before returning it. Implement
validation logic such as checking against the allowed enum or type values, and
throw an error or return a failure result if the value is invalid, ensuring type
safety is maintained throughout the parsing path without relying on assertions.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 01045e87-6903-4f0a-8704-6c901d1ab199

📥 Commits

Reviewing files that changed from the base of the PR and between b7819e7 and 6c426a8.

⛔ Files ignored due to path filters (2)
  • packages/core/@generated/gql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
  • packages/core/@generated/graphql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
📒 Files selected for processing (15)
  • packages/core/cms/faststore/components/cms_component__RecommendationShelf.jsonc
  • packages/core/src/Layout.tsx
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.module.scss
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.types.ts
  • packages/core/src/components/RecommendationShelf/index.ts
  • packages/core/src/components/RecommendationShelf/mapRecommendationToProductCard.ts
  • packages/core/src/components/RecommendationShelf/queries/request.ts
  • packages/core/src/components/RecommendationShelf/queries/useLazyQuery.ts
  • packages/core/src/components/RecommendationShelf/queries/useQuery.ts
  • packages/core/src/components/RecommendationShelf/useRecommendations.ts
  • packages/core/src/components/RecommendationShelf/utils.ts
  • packages/core/src/components/RecommendationShelf/vrn.ts
  • packages/core/src/components/cms/global/Components.ts
  • packages/core/src/sdk/analytics/hooks/useStartSession.ts

Comment thread packages/core/cms/faststore/components/cms_component__RecommendationShelf.jsonc Outdated
Comment thread packages/core/src/components/RecommendationShelf/queries/request.ts Outdated
Comment thread packages/core/src/components/RecommendationShelf/queries/request.ts Outdated
Comment thread packages/core/src/components/RecommendationShelf/queries/useLazyQuery.ts Outdated
Comment thread packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx Outdated
Comment thread packages/core/src/components/sections/RecommendationShelf/useRecommendations.ts Outdated
Comment thread packages/core/src/sdk/analytics/hooks/useStartSession.ts Outdated
Comment thread packages/core/src/sdk/analytics/hooks/useStartSession.ts Outdated
Comment thread packages/core/src/sdk/analytics/hooks/useStartSession.ts Outdated
Comment thread packages/core/src/sdk/analytics/hooks/useStartSession.ts Outdated
Codegen regenerates @generated/schema.graphql from the API plus the
src/graphql customization folders, so the operations used by the
RecommendationShelf (recommendations, startSession, sendProductViewEvent)
must be defined as thirdParty typeDefs to be valid in CI. Adds the schema
extension and the recommendation types (referencing existing Advertisement
and SkuSpecification), fixing the @faststore/core#generate build failure.
@codesandbox-ci

codesandbox-ci Bot commented Jun 23, 2026

Copy link
Copy Markdown

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@pkg-pr-new

pkg-pr-new Bot commented Jun 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

@faststore/api

npm i https://pkg.pr.new/vtex/faststore/@faststore/api@0e36d26

@faststore/cli

npm i https://pkg.pr.new/vtex/faststore/@faststore/cli@0e36d26

@faststore/components

npm i https://pkg.pr.new/vtex/faststore/@faststore/components@0e36d26

@faststore/core

npm i https://pkg.pr.new/vtex/faststore/@faststore/core@0e36d26

@faststore/diagnostics

npm i https://pkg.pr.new/vtex/faststore/@faststore/diagnostics@0e36d26

@faststore/lighthouse

npm i https://pkg.pr.new/vtex/faststore/@faststore/lighthouse@0e36d26

@faststore/sdk

npm i https://pkg.pr.new/vtex/faststore/@faststore/sdk@0e36d26

@faststore/ui

npm i https://pkg.pr.new/vtex/faststore/@faststore/ui@0e36d26

commit: 0e36d26

Adds recommendations, startSession and sendProductViewEvent to the expected
query/mutation lists in the server schema test, reflecting the new thirdParty
typeDefs.
Defines the recommendations query, the startSession and sendProductViewEvent
mutations and their types (RecommendationResponse, RecommendationProduct,
ProductViewSource, etc.) in the VTEX platform typeDefs. These are operations
shipped by @faststore/core, so they must live in @faststore/api to be part
of the schema generated for any consuming store (store-level src/graphql
customizations are not visible to core features). Updates the schema
assertion tests accordingly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/api/src/platforms/vtex/typeDefs/query.graphql`:
- Around line 473-481: The recommendations field in the GraphQL query schema is
marked with `@cacheControl`(scope: "public", ...) but returns personalized data
based on userId and campaign context, which can expose one shopper's
recommendations to others through shared caches. Change the scope from "public"
to "private" in the `@cacheControl` directive on the recommendations field to
ensure personalized responses are not cached in shared caches that could be
accessed by different users.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3274e94b-f00c-4810-be61-0a8b7bbcb4fe

📥 Commits

Reviewing files that changed from the base of the PR and between 5f3999b and 961112a.

⛔ Files ignored due to path filters (2)
  • packages/api/src/__generated__/schema.ts is excluded by !**/__generated__/** and included by packages/**
  • packages/core/@generated/graphql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
📒 Files selected for processing (5)
  • packages/api/src/platforms/vtex/typeDefs/mutation.graphql
  • packages/api/src/platforms/vtex/typeDefs/query.graphql
  • packages/api/src/platforms/vtex/typeDefs/recommendation.graphql
  • packages/api/test/integration/schema.test.ts
  • packages/core/test/server/index.test.ts
💤 Files with no reviewable changes (1)
  • packages/api/src/platforms/vtex/typeDefs/recommendation.graphql

Comment thread packages/api/src/platforms/vtex/typeDefs/query.graphql Outdated
Addresses SonarQube findings on the new recommendation code: replaces
negated conditions and nested ternaries with early returns, uses optional
chaining, removes a dead duplicated branch in the request helper, drops a
redundant always-falsy check and uses globalThis.window for the SSR guard.
No behavior change.
Adds unit and component tests for the recommendation feature (vrn parsing,
utils, product mapping, request/query helpers, useRecommendations,
useStartSession and the RecommendationShelf component), bringing coverage on
the new code above the 80% quality gate.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/core/test/sdk/analytics/useStartSession.test.ts (1)

100-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep this test scoped to the “no product” behavior.

Line 106 asserts runStartSession was not called, but that outcome is driven by the session cookie setup, not by missing product data. This adds unrelated coupling and can make the test fail for the wrong reason.

Suggested cleanup
   it('does not send a product view when there is no product', async () => {
     getCookie.mockReturnValue('already-started')

     renderHook(() => useStartSession({ data: {} }))

-    await waitFor(() => {
-      expect(runStartSession).not.toHaveBeenCalled()
-    })
     expect(runSendProductViewEvent).not.toHaveBeenCalled()
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/test/sdk/analytics/useStartSession.test.ts` around lines 100 -
107, The test "does not send a product view when there is no product" is tightly
coupled to the session cookie setup
(getCookie.mockReturnValue('already-started')) which is unrelated to testing the
"no product" behavior. Remove the getCookie mock setup so that the test
specifically validates that runStartSession is not called due to the missing
product data in the data object, not due to session state. This keeps the test
focused on its intended purpose and prevents it from failing for the wrong
reason.
packages/core/test/components/RecommendationShelf/mapRecommendationToProductCard.test.ts (1)

30-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove as any fixtures in TS tests to preserve type safety

These broad assertions weaken compile-time guarantees in a critical mapping test. Prefer a typed fixture helper (e.g., createRecommendationProduct(overrides: Partial<RecommendationProduct>)) so invalid fixture shapes are caught during test authoring.
As per path instructions: “TypeScript files: Ensure type safety and avoid type assertions when possible.”

Also applies to: 69-69, 78-78

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/core/test/components/RecommendationShelf/mapRecommendationToProductCard.test.ts`
at line 30, The test file uses `as any` type assertions when creating test
fixtures (appearing at lines 30, 69, and 78), which weakens type safety and
allows invalid fixture shapes to slip through undetected. Create a typed fixture
helper function named `createRecommendationProduct` that accepts an optional
`Partial<RecommendationProduct>` parameter for overrides and returns a fully
typed `RecommendationProduct` object with sensible defaults. Then replace all
instances where `} as any` appears with calls to this helper function, passing
any necessary overrides as arguments, thereby eliminating the type assertions
and ensuring compile-time validation of fixture shapes.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/test/components/RecommendationShelf/request.test.ts`:
- Around line 96-104: The test assertion for the explicit method option is
checking for lowercase 'put', but the request implementation normalizes the
method to uppercase before calling fetch. Update the expect statement in the
"honors an explicit method from fetchOptions" test to check for 'PUT' instead of
'put' to match the actual behavior of the baseRequest function which uppercases
the fetchOptions.method value.

---

Nitpick comments:
In
`@packages/core/test/components/RecommendationShelf/mapRecommendationToProductCard.test.ts`:
- Line 30: The test file uses `as any` type assertions when creating test
fixtures (appearing at lines 30, 69, and 78), which weakens type safety and
allows invalid fixture shapes to slip through undetected. Create a typed fixture
helper function named `createRecommendationProduct` that accepts an optional
`Partial<RecommendationProduct>` parameter for overrides and returns a fully
typed `RecommendationProduct` object with sensible defaults. Then replace all
instances where `} as any` appears with calls to this helper function, passing
any necessary overrides as arguments, thereby eliminating the type assertions
and ensuring compile-time validation of fixture shapes.

In `@packages/core/test/sdk/analytics/useStartSession.test.ts`:
- Around line 100-107: The test "does not send a product view when there is no
product" is tightly coupled to the session cookie setup
(getCookie.mockReturnValue('already-started')) which is unrelated to testing the
"no product" behavior. Remove the getCookie mock setup so that the test
specifically validates that runStartSession is not called due to the missing
product data in the data object, not due to session state. This keeps the test
focused on its intended purpose and prevents it from failing for the wrong
reason.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2cdb346c-3468-4629-ac8a-4d20bb3421a7

📥 Commits

Reviewing files that changed from the base of the PR and between 961112a and 1108aa3.

📒 Files selected for processing (12)
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx
  • packages/core/src/components/RecommendationShelf/queries/request.ts
  • packages/core/src/components/RecommendationShelf/queries/useQuery.ts
  • packages/core/src/components/RecommendationShelf/utils.ts
  • packages/core/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
  • packages/core/test/components/RecommendationShelf/mapRecommendationToProductCard.test.ts
  • packages/core/test/components/RecommendationShelf/queries.test.ts
  • packages/core/test/components/RecommendationShelf/request.test.ts
  • packages/core/test/components/RecommendationShelf/useRecommendations.test.ts
  • packages/core/test/components/RecommendationShelf/utils.test.ts
  • packages/core/test/components/RecommendationShelf/vrn.test.ts
  • packages/core/test/sdk/analytics/useStartSession.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/core/src/components/RecommendationShelf/utils.ts
  • packages/core/src/components/RecommendationShelf/queries/useQuery.ts
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx
  • packages/core/src/components/RecommendationShelf/queries/request.ts

Comment thread packages/core/test/components/RecommendationShelf/request.test.ts Outdated
thiagopereira-vtex and others added 4 commits June 23, 2026 14:13
Resolves the remaining SonarQube finding: globalThis.window is always a
valid reference, so the typeof guard is unnecessary.
…dationShelf.jsonc

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
The recommendations query is personalized (userId + campaign context), so a
public/shared cache scope could leak one shopper's response to others via a
CDN. Switch it to scope "private".
- Fix malformed JSON in the RecommendationShelf CMS schema (stray brace).
- Move the userId cookie retry into a useEffect so it no longer kicks off
  async work and setState during render.
- Stop logging recommendation args (which carry the userId) on fetch errors;
  log only non-identifying context.
- In useStartSession, only mark a product view as sent after the mutation
  succeeds, so a failed request isn't permanently suppressed.
- Extend tests to cover the new failure paths.
@thiagopereira-vtex

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Decouples the useStartSession SDK hook from a UI component module: checkIsMobile
and getUserIdFromCookie now live in src/sdk/analytics/utils. The component-level
utils keeps only getWithRetry. Tests updated/split accordingly.
Point useRecommendations and useStartSession at src/sdk/graphql/*
(persisted-query GET allowlist, cache busting, session-aware cache keys)
and drop the duplicated RecommendationShelf/queries folder. Also type the
useStartSession props instead of `any`.
@vtex vtex deleted a comment from coderabbitai Bot Jun 23, 2026
…urable

Expose carouselConfiguration (variant, infiniteMode, controls and per-viewport
itemsPerPageDesktop/itemsPerPageMobile) and productCardConfiguration
(showDiscountBadge, bordered) through the CMS, and add a code-level
mapProductToProductCard override (defaults to mapRecommendationToProductCard,
not exposed via CMS). Regenerate cms/faststore/schema.json accordingly.
@vtex-pr-sentinel

Copy link
Copy Markdown

🛡️ SDD Check — action required

I couldn't detect an SDD in this PR. Please check one option below (requires write access to the repo):

  • SDD lives in another PR — paste the SDD PR URL here:
  • This PR doesn't need an SDD
  • SDD applies, but I'm not adopting it in this PR

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/test/sdk/analytics/utils.test.ts`:
- Around line 1-3: The test file is forcing jsdom in a `.test.ts` spec, but
`utils.test.ts` should run in Node unless it is explicitly a browser test.
Remove the `@vitest-environment jsdom` directive from the top of this file if
the tests only need stubbed `window`, or rename the spec to
`utils.browser.test.ts` if it truly requires browser behavior; use the
`@vitest-environment` header and the `utils.test.ts`/`browser.test` naming
convention as the key places to adjust.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 178c070a-ffa2-4b68-a166-65a949d0d5b3

📥 Commits

Reviewing files that changed from the base of the PR and between e369181 and 402816e.

⛔ Files ignored due to path filters (1)
  • packages/core/@generated/graphql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
📒 Files selected for processing (13)
  • packages/core/cms/faststore/components/cms_component__RecommendationShelf.jsonc
  • packages/core/cms/faststore/schema.json
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.types.ts
  • packages/core/src/components/RecommendationShelf/useRecommendations.ts
  • packages/core/src/components/RecommendationShelf/utils.ts
  • packages/core/src/sdk/analytics/hooks/useStartSession.ts
  • packages/core/src/sdk/analytics/utils.ts
  • packages/core/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
  • packages/core/test/components/RecommendationShelf/useRecommendations.test.ts
  • packages/core/test/components/RecommendationShelf/utils.test.ts
  • packages/core/test/sdk/analytics/useStartSession.test.ts
  • packages/core/test/sdk/analytics/utils.test.ts
💤 Files with no reviewable changes (1)
  • packages/core/src/components/RecommendationShelf/utils.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/core/src/sdk/analytics/utils.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.types.ts
  • packages/core/test/components/RecommendationShelf/useRecommendations.test.ts
  • packages/core/src/components/RecommendationShelf/useRecommendations.ts
  • packages/core/src/sdk/analytics/hooks/useStartSession.ts
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx
  • packages/core/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
  • packages/core/test/sdk/analytics/useStartSession.test.ts

Comment thread packages/core/test/sdk/analytics/utils.test.ts Outdated
Eliminate the SendProductViewEvent mutation and its associated logic from the useStartSession hook. The product view event is now captured through meta tags on the PDP, streamlining the session management process. Update tests to reflect these changes and ensure proper functionality without the removed mutation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/core/test/sdk/analytics/useStartSession.test.ts (1)

24-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a retry-path test for the new loop.

useStartSession() now retries until runStartSession() returns a defined result, but this setup only exercises first-attempt success. A fake-timers test for undefined -> success would lock the backoff behavior and catch regressions in the new control flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/test/sdk/analytics/useStartSession.test.ts` at line 24, Add a
retry-path test for useStartSession() that covers the new loop when
runStartSession() first returns undefined and then succeeds, since the current
mockReturnValue only tests immediate success. Use fake timers in the existing
useStartSession.test.ts suite to advance the backoff between attempts, and
assert that the hook keeps retrying until a defined result is returned from
runStartSession() via useLazyQueryMock.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/pages/`[slug]/p.tsx:
- Around line 226-229: The structured data in the [slug]/p.tsx page is
hard-coding the product:condition tag to new, which can misreport refurbished or
used items. Update the SEO/structured-data generation path in the page’s
metadata logic to read the real condition from the product data already
available via offers.offers[].itemCondition, and only emit the product:condition
tag when a valid condition exists. Use the existing page/data builder symbols in
this file to locate and adjust the tag construction.
- Around line 202-205: The `product:id` meta tag in `Page` is using `product.id`
instead of the previous PDP tracking identifier, which can break recommendation
attribution. Update the `content` value in the meta-tag/SEO tag block to use
`product.isVariantOf?.productGroupID` unless the contract was intentionally
changed, and keep the existing `Page` structured-data/meta-tag flow aligned with
the prior PDP product-view path.

---

Nitpick comments:
In `@packages/core/test/sdk/analytics/useStartSession.test.ts`:
- Line 24: Add a retry-path test for useStartSession() that covers the new loop
when runStartSession() first returns undefined and then succeeds, since the
current mockReturnValue only tests immediate success. Use fake timers in the
existing useStartSession.test.ts suite to advance the backoff between attempts,
and assert that the hook keeps retrying until a defined result is returned from
runStartSession() via useLazyQueryMock.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6cb5a061-6559-4bae-9050-4a9c900bb38d

📥 Commits

Reviewing files that changed from the base of the PR and between 402816e and 4658ec0.

⛔ Files ignored due to path filters (2)
  • packages/core/@generated/gql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
  • packages/core/@generated/graphql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
📒 Files selected for processing (4)
  • packages/core/src/Layout.tsx
  • packages/core/src/pages/[slug]/p.tsx
  • packages/core/src/sdk/analytics/hooks/useStartSession.ts
  • packages/core/test/sdk/analytics/useStartSession.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/Layout.tsx

Comment thread packages/core/src/pages/[slug]/p.tsx
Comment thread packages/core/src/pages/[slug]/p.tsx Outdated
- Centralize campaign-type taxonomy in a single map in vrn.ts and make
  getTypeFromVrn total (returns null instead of throwing during render).
- Extract cookie/userId orchestration into a useRecommendationUserId hook.
- Use the canonical useScreenResize hook for paging instead of a bespoke
  checkIsMobile helper.
- Unify the two ad-hoc retry mechanisms into a single src/utils/retry util
  (with backoff and AbortSignal support).
- Document why the recommendations query fetches the full product shape
  (mapProductToProductCard is overridable).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/core/src/components/RecommendationShelf/useRecommendationUserId.ts (1)

23-46: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

campaignVrn only feeds an error log but drives the effect.

The cookie lookup doesn't depend on campaignVrn for its result — it's used solely in the catch log. Re-running the whole retry loop whenever campaignVrn changes is wasteful. If the log context matters, keep it but consider an empty dep array (cookie resolution is global), or read campaignVrn via a ref to avoid restarting the retry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/RecommendationShelf/useRecommendationUserId.ts`
around lines 23 - 46, The useEffect in useRecommendationUserId is being re-run
unnecessarily because campaignVrn is only used in the catch log and does not
affect getUserIdFromCookie or retry behavior. Update the dependency handling so
the cookie lookup runs once for this global value, either by using an empty
dependency array or by moving campaignVrn into a ref if you still need it in the
error log. Keep the existing AbortController cleanup and the setUserId logic
intact.
packages/core/src/utils/retry.ts (1)

53-66: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Abort doesn't interrupt an in-flight delay.

When signal aborts during the setTimeout wait, the loop only notices after the full delay elapses (up to maxDelayMs). For the unmount case in useRecommendationUserId, this leaves a pending timer running after cleanup. Consider wiring the timeout to the signal so it resolves early.

♻️ Resolve the delay early on abort
-    await new Promise((resolve) => setTimeout(resolve, delay))
-
-    if (signal?.aborted) {
-      break
-    }
+    await new Promise<void>((resolve) => {
+      const timer = setTimeout(resolve, delay)
+      signal?.addEventListener(
+        'abort',
+        () => {
+          clearTimeout(timer)
+          resolve()
+        },
+        { once: true }
+      )
+    })
+
+    if (signal?.aborted) {
+      break
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/utils/retry.ts` around lines 53 - 66, The retry delay in
`retry` does not stop early when the abort `signal` fires, so the pending
`setTimeout` keeps running until the full delay completes. Update the wait
inside `retry` to be abort-aware by wiring the timer to `signal` (or using an
abortable promise) so the sleep resolves/rejects immediately on abort, then keep
the existing `signal?.aborted` guard before the next `fn()` call.
packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx (1)

4-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Alias @faststore/ui components with the UI prefix.

This component file imports and renders ProductShelf/Carousel directly from @faststore/ui; the repo convention asks these imports to use UI aliases.

♻️ Proposed fix
-import { ProductShelf, Carousel } from '`@faststore/ui`'
+import { ProductShelf as UIProductShelf, Carousel as UICarousel } from '`@faststore/ui`'
-        <ProductShelf>
-          <Carousel
+        <UIProductShelf>
+          <UICarousel
             id={id}
             itemsPerPage={itemsPerPage}
             variant="scroll"
             infiniteMode={false}
             {...carouselProps}
           >
             {items.map((item, index) => (
               <div
                 key={item.productId}
                 className={styles.recommendationShelfItem}
@@
                 />
               </div>
             ))}
-          </Carousel>
-        </ProductShelf>
+          </UICarousel>
+        </UIProductShelf>

As per coding guidelines, “Import from @faststore/ui and alias with UI prefix to avoid local name collisions when composing library components.”

Also applies to: 134-165

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx` at
line 4, The RecommendationShelf component is importing and using `@faststore/ui`
primitives without the required UI prefix alias. Update the imports in
RecommendationShelf.tsx to alias ProductShelf and Carousel with the UI prefix,
then adjust all usages in the RecommendationShelf render logic to reference
those aliased names consistently. Keep the change local to the
RecommendationShelf component and preserve existing behavior while following the
repository convention.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/core/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx`:
- Around line 126-127: The null-user test in
RecommendationShelf.browser.test.tsx is out of sync with the skip-fetch
contract. Update the test around useRecommendationUserId and
getRecommendationArguments to assert that useRecommendations receives null and
that the shelf renders empty when there is no resolved userId, unless the
component contract is intentionally changed to fetch without a user ID.

---

Nitpick comments:
In `@packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx`:
- Line 4: The RecommendationShelf component is importing and using `@faststore/ui`
primitives without the required UI prefix alias. Update the imports in
RecommendationShelf.tsx to alias ProductShelf and Carousel with the UI prefix,
then adjust all usages in the RecommendationShelf render logic to reference
those aliased names consistently. Keep the change local to the
RecommendationShelf component and preserve existing behavior while following the
repository convention.

In `@packages/core/src/components/RecommendationShelf/useRecommendationUserId.ts`:
- Around line 23-46: The useEffect in useRecommendationUserId is being re-run
unnecessarily because campaignVrn is only used in the catch log and does not
affect getUserIdFromCookie or retry behavior. Update the dependency handling so
the cookie lookup runs once for this global value, either by using an empty
dependency array or by moving campaignVrn into a ref if you still need it in the
error log. Keep the existing AbortController cleanup and the setUserId logic
intact.

In `@packages/core/src/utils/retry.ts`:
- Around line 53-66: The retry delay in `retry` does not stop early when the
abort `signal` fires, so the pending `setTimeout` keeps running until the full
delay completes. Update the wait inside `retry` to be abort-aware by wiring the
timer to `signal` (or using an abortable promise) so the sleep resolves/rejects
immediately on abort, then keep the existing `signal?.aborted` guard before the
next `fn()` call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4529d040-ee71-490e-a04b-261cdf683bb6

📥 Commits

Reviewing files that changed from the base of the PR and between 4658ec0 and 814da62.

📒 Files selected for processing (11)
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx
  • packages/core/src/components/RecommendationShelf/useRecommendationUserId.ts
  • packages/core/src/components/RecommendationShelf/useRecommendations.ts
  • packages/core/src/components/RecommendationShelf/vrn.ts
  • packages/core/src/sdk/analytics/hooks/useStartSession.ts
  • packages/core/src/sdk/analytics/utils.ts
  • packages/core/src/utils/retry.ts
  • packages/core/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
  • packages/core/test/components/RecommendationShelf/vrn.test.ts
  • packages/core/test/sdk/analytics/utils.test.ts
  • packages/core/test/utils/retry.test.ts
💤 Files with no reviewable changes (1)
  • packages/core/src/sdk/analytics/utils.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/sdk/analytics/hooks/useStartSession.ts
  • packages/core/src/components/RecommendationShelf/useRecommendations.ts

Comment thread packages/api/src/platforms/vtex/typeDefs/mutation.graphql Outdated
Comment thread packages/api/src/platforms/vtex/typeDefs/mutation.graphql Outdated
Comment thread packages/api/src/platforms/vtex/typeDefs/recommendation.graphql Outdated
Comment thread packages/api/test/integration/schema.test.ts Outdated
"type": "string",
"pattern": "^vrn:recommendations:[^:]+:(rec-cross-v2|rec-similar-v2|rec-persona-v2|rec-last-v2|rec-top-items-v2|rec-search-v2|rec-next-v2|rec-visual-v2):[^:]+$"
},
"carouselConfiguration": {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that is missing is the itemContext: https://github.com/vtex-apps/recommendation-shelf/blob/master/react/RecommendationShelf.tsx#L93

So the user can select wether we use the pdp product as reference or the cart items

}
: {})}
>
<ProductCard

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about receiving a custom ProductCard?

* - `null`: lookup finished without a usable id (e.g. the pixel never set it);
* - `string`: the resolved user id.
*/
export function useRecommendationUserId(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

* Starts the anonymous recommendation/personalization session once per browser
* session.
*
* Mounted globally from `Layout` so it runs on every page, regardless of

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to discuss this FastStore team. Since recommendations is an optional feature, we might need to either use the GlobalTracker approach or hide this under a feature flag. We don't want to receive requests from Accounts that are not active in our side

Comment thread packages/core/src/Layout.tsx Outdated
usePageViewEvent(props)
// Implemented here because the personalization session must be initiated once
// per browser session on each page.
useStartSession()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

"""
Returns personalized product recommendations for a given campaign.
"""
recommendations(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the implementation for this recommendations query?

…n and update related logic

- Updated GraphQL schema and mutation definitions to reflect the renaming of `startSession` to `startRecommendationSession`.
- Adjusted integration tests and hooks to utilize the new mutation name.
- Introduced a new hook, `useStartRecommendationSession`, to manage the session initiation process.
- Updated the `Layout` component to call the new hook, ensuring the recommendation session starts correctly per browser session.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/core/src/sdk/analytics/hooks/useStartRecommendationSession.ts (1)

42-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the empty-object assertion.

Line 42 does not need as StartRecommendationSessionVariables; {} already satisfies Record<string, never>, so the cast only suppresses type checking in a file where we explicitly want to avoid assertions when possible. As per path instructions, "Ensure type safety and avoid type assertions when possible."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/sdk/analytics/hooks/useStartRecommendationSession.ts` at
line 42, Remove the empty-object type assertion in useStartRecommendationSession
so the default variables value is passed as a plain empty object instead of
being cast to StartRecommendationSessionVariables. Update the
useStartRecommendationSession hook to rely on the inferred Record<string, never>
shape and keep type safety without suppressing checks.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/sdk/analytics/hooks/useStartRecommendationSession.ts`:
- Around line 61-68: The retry in useStartRecommendationSession only handles
undefined results, so transient request rejections from
runStartRecommendationSession exit immediately instead of backing off. Update
the retry path around retry(...) and runStartRecommendationSession so thrown
request/GraphQL errors are treated as retryable (for example by catching and
returning a retryable failure or otherwise preserving the retry loop), and make
sure the discarded void promise does not leave an unhandled rejection when the
endpoint is not ready.

---

Nitpick comments:
In `@packages/core/src/sdk/analytics/hooks/useStartRecommendationSession.ts`:
- Line 42: Remove the empty-object type assertion in
useStartRecommendationSession so the default variables value is passed as a
plain empty object instead of being cast to StartRecommendationSessionVariables.
Update the useStartRecommendationSession hook to rely on the inferred
Record<string, never> shape and keep type safety without suppressing checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bf4f57b5-c8c2-42c0-ad4d-cc0a7d238c59

📥 Commits

Reviewing files that changed from the base of the PR and between 814da62 and f3ef796.

⛔ Files ignored due to path filters (3)
  • packages/api/src/__generated__/schema.ts is excluded by !**/__generated__/** and included by packages/**
  • packages/core/@generated/gql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
  • packages/core/@generated/graphql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
📒 Files selected for processing (8)
  • packages/api/src/platforms/vtex/typeDefs/mutation.graphql
  • packages/api/src/platforms/vtex/typeDefs/recommendation.graphql
  • packages/api/test/integration/schema.test.ts
  • packages/core/src/Layout.tsx
  • packages/core/src/sdk/analytics/hooks/useStartRecommendationSession.ts
  • packages/core/src/sdk/analytics/hooks/useStartSession.ts
  • packages/core/test/sdk/analytics/useStartRecommendationSession.test.ts
  • packages/core/test/server/index.test.ts
💤 Files with no reviewable changes (1)
  • packages/api/src/platforms/vtex/typeDefs/recommendation.graphql
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/sdk/analytics/hooks/useStartSession.ts
  • packages/core/src/Layout.tsx

Comment thread packages/core/src/sdk/analytics/hooks/useStartRecommendationSession.ts Outdated
…ent interface

Resolve recommendations in @faststore/api (recommendations query and
startRecommendationSession mutation) and map the BFF payload straight to the
normalized StoreProduct shape via pickBestSku + enhanceSku, so recommendation
shelves expose the same product interface as the rest of the storefront.

Update RecommendationShelf and useRecommendations to consume StoreProduct
directly, with a configurable product card component and mapper, and drop the
now-unused RecommendationProduct mapper.
…nResult and update related logic

- Renamed `startSession` to `startRecommendationSession` in the recommendation client and updated the corresponding types.
- Adjusted the resolver to call the new session initiation method.
- Removed the now-unnecessary `useStartSession` hook to streamline the session management process.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/core/src/components/RecommendationShelf/useRecommendations.ts (1)

42-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the forced RecommendationInput cast.

args ?? ({} as RecommendationInput) sidesteps the type-safety rule for this path and makes this hook easier to break later if the skip condition changes. A nullable useQuery variables type keeps the call safe without an assertion. As per path instructions, TypeScript files should “Ensure type safety and avoid type assertions when possible.”

Possible cleanup
 export const useRecommendations = (args: RecommendationInput | null) => {
   const { data, isLoading, error } = useQuery<
     FetchRecommendationsQueryQuery,
-    RecommendationInput
-  >(query, args ?? ({} as RecommendationInput), {
+    RecommendationInput | null
+  >(query, args, {
     doNotRun: args === null,
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/RecommendationShelf/useRecommendations.ts`
around lines 42 - 48, The useRecommendations hook is forcing a
RecommendationInput assertion with args ?? ({} as RecommendationInput), which
bypasses type safety and can break if the doNotRun logic changes. Update the
useQuery call in useRecommendations to accept nullable variables instead of
asserting a non-null RecommendationInput, and pass args directly so the hook
remains type-safe without a cast.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/api/src/platforms/vtex/clients/recommendation/index.ts`:
- Around line 21-24: The Recommendation client is using an Options type that is
not declared in this module, so update the VTEX recommendation client to import
and reuse the shared client options type used by the other VTEX client
factories. Locate the Recommendation function signature and replace the
unresolved Options reference with the shared type import so the module compiles
consistently with the other client implementations.

In `@packages/api/src/platforms/vtex/resolvers/recommendations.ts`:
- Around line 26-32: Validate the inputs in the VTEX recommendations resolver
before calling the platform client. In the recommendations resolver, reject
empty or malformed values for campaignVrn, userId, and products instead of
forwarding them as-is, and surface a GraphQL validation error locally. Add
server-side checks around the recommendations() call so only clean, well-formed
payloads reach recommendation.recommendations, while keeping locale and
salesChannel handling unchanged.

In `@packages/api/src/platforms/vtex/resolvers/startRecommendationSession.ts`:
- Around line 16-18: The mutation currently reports success unconditionally even
when `ctx.clients.recommendation.startSession()` returns `undefined`, so update
`startRecommendationSession` to validate the `StartSessionResult` before
returning. Use the return type from `RecommendationClient.startSession` and the
success payload shape from `StartSessionResult` to detect missing session data,
and only return true when a session is actually created; otherwise surface an
appropriate GraphQL error so the caller can retry.

---

Nitpick comments:
In `@packages/core/src/components/RecommendationShelf/useRecommendations.ts`:
- Around line 42-48: The useRecommendations hook is forcing a
RecommendationInput assertion with args ?? ({} as RecommendationInput), which
bypasses type safety and can break if the doNotRun logic changes. Update the
useQuery call in useRecommendations to accept nullable variables instead of
asserting a non-null RecommendationInput, and pass args directly so the hook
remains type-safe without a cast.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 68c32c2d-c870-4e19-8407-5d65c32ecc7c

📥 Commits

Reviewing files that changed from the base of the PR and between f3ef796 and 1ea6afa.

⛔ Files ignored due to path filters (3)
  • packages/api/src/__generated__/schema.ts is excluded by !**/__generated__/** and included by packages/**
  • packages/core/@generated/gql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
  • packages/core/@generated/graphql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
📒 Files selected for processing (12)
  • packages/api/src/platforms/vtex/clients/index.ts
  • packages/api/src/platforms/vtex/clients/recommendation/index.ts
  • packages/api/src/platforms/vtex/clients/recommendation/types/RecommendationResult.ts
  • packages/api/src/platforms/vtex/resolvers/mutation.ts
  • packages/api/src/platforms/vtex/resolvers/query.ts
  • packages/api/src/platforms/vtex/resolvers/recommendations.ts
  • packages/api/src/platforms/vtex/resolvers/startRecommendationSession.ts
  • packages/api/src/platforms/vtex/typeDefs/recommendation.graphql
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.types.ts
  • packages/core/src/components/RecommendationShelf/useRecommendations.ts
  • packages/core/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/api/src/platforms/vtex/clients/recommendation/types/RecommendationResult.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/components/RecommendationShelf/RecommendationShelf.tsx
  • packages/core/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx

Comment on lines +21 to +24
export const Recommendation = (
{ account, environment }: Options,
ctx: GraphqlContext
) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether `Options` is declared globally or should be imported from a shared VTEX module.
rg -n --type=ts '\b(interface|type)\s+Options\b|\bdeclare global\b' packages/api/src/platforms/vtex
sed -n '1,40p' packages/api/src/platforms/vtex/clients/recommendation/index.ts
fd -i 'index.ts' packages/api/src/platforms/vtex/clients

Repository: vtex/faststore

Length of output: 1610


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n-- recommendation index --\n'
sed -n '1,220p' packages/api/src/platforms/vtex/clients/recommendation/index.ts

printf '\n-- search index --\n'
sed -n '1,220p' packages/api/src/platforms/vtex/clients/search/index.ts

printf '\n-- search ProductSearchResult types --\n'
sed -n '1,220p' packages/api/src/platforms/vtex/clients/search/types/ProductSearchResult.ts

printf '\n-- all Option type declarations under vtex clients --\n'
rg -n --type=ts '\b(interface|type)\s+Options\b' packages/api/src/platforms/vtex/clients

Repository: vtex/faststore

Length of output: 12176


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n-- all exported Options types in repo --\n'
rg -n --type=ts 'export\s+(interface|type)\s+Options\b' packages

printf '\n-- all imports of Options in vtex clients --\n'
rg -n --type=ts "import type \{[^}]*\bOptions\b[^}]*\}" packages/api/src/platforms/vtex/clients packages/api/src/platforms/vtex

printf '\n-- recommendation client neighbors --\n'
sed -n '1,220p' packages/api/src/platforms/vtex/clients/index.ts
sed -n '1,220p' packages/api/src/platforms/vtex/clients/commerce/index.ts

Repository: vtex/faststore

Length of output: 302


Import the shared VTEX client options type.
Options is referenced here but never imported or declared in this module, so TypeScript will fail to compile this file. Reuse the same client options type used by the other VTEX clients.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/api/src/platforms/vtex/clients/recommendation/index.ts` around lines
21 - 24, The Recommendation client is using an Options type that is not declared
in this module, so update the VTEX recommendation client to import and reuse the
shared client options type used by the other VTEX client factories. Locate the
Recommendation function signature and replace the unresolved Options reference
with the shared type import so the module compiles consistently with the other
client implementations.

Comment thread packages/api/src/platforms/vtex/resolvers/recommendations.ts Outdated
Comment thread packages/api/src/platforms/vtex/resolvers/startRecommendationSession.ts Outdated
…xt and carousel settings

- Introduced `itemsContext` prop to allow selection between 'PDP' and 'CART' for product context in recommendations.
- Updated `RecommendationShelf` to utilize the new `itemsContext` for fetching recommendations based on the selected context.
- Refactored carousel configuration to directly accept `itemsPerPageDesktop`, `itemsPerPageMobile`, `variant`, `infiniteMode`, and `controls` as props, improving customization options.
- Adjusted related types and documentation to reflect these changes.

@renatomaurovtex renatomaurovtex left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — feat(core): add RecommendationShelf section with personalization tracking

Reviewed the api client/resolvers (recommendation, recommendations, startRecommendationSession), the core component + hooks (RecommendationShelf, useRecommendations, useRecommendationUserId, useStartRecommendationSession, vrn, retry) and the p.tsx/Layout wiring. Solid, well-tested feature (browser test + unit tests for vrn/retry/useRecommendations/utils). Boundaries are clean: BFF returns the IS Product shape so pickBestSku+enhanceSku reuse is correct and avoids a second search round-trip; recommendations is @cacheControl(scope:"private") which is right for personalized output; error logging deliberately omits userId. Good.

🟠 Meta-tag overlap with open PR #3383 (same author)
p.tsx adds product:id/sku/name/category/url/brand/condition to additionalMetaTags. Open PR #3383 ("pdp product SKU meta tag", also yours) adds product:sku/name/category/url to the same array. If both land on dev you'll get duplicate product:sku/product:name/etc. meta tags. This PR is a superset — recommend closing/rebasing #3383 onto this, or dropping the tag block from whichever merges second. Please de-conflict before merge.

🟡 Non-personalized campaigns are gated on the pixel cookie
getRecommendationArguments returns null whenever !userId, so TOP_ITEMS and SEARCH_BASED campaigns — which don't conceptually need an anonymous shopper id — won't render at all if the personalization pixel never sets vtex-rec-user-id (ad-blockers, consent-gated cookies, slow pixel beyond the retry budget). Consider allowing those campaign types to fetch with an empty userId so they degrade to non-personalized results instead of a hidden shelf.

🟡 usePDP() in a globally-mounted component
RecommendationShelf is registered as a CMS global component and calls usePDP() directly, yet the PR body notes usePDP() "has no context above PageProvider" (which is why useStartRecommendationSession reads page props instead). Please confirm usePDP() returns { data: undefined } safely (rather than throwing) when the shelf is placed on a non-PDP page — the productDetailPage?.product... optional chaining suggests it's safe, but worth verifying given the body's caveat.

💬 Minor / nits

  • shouldAddAFAttr checks productIds.length where productIds is a comma-joined string; it works (empty when no items) but reads like an array-length check — items.length > 0 would be clearer.
  • Several CodeRabbit "Major" comments reference queries/request.ts / queries/useLazyQuery.ts that no longer exist in the diff — looks like you refactored to the core useQuery/useLazyQuery, which resolves their "variables in GET query string" privacy concern. Worth confirming the core useQuery path doesn't surface userId in a CDN-cached GET URL for this scope:private op.
  • retry returns the last value even on budget exhaustion (documented) — callers handle the give-up case correctly.

CI: FastStore build/test, codesandbox, node-ci green. SonarQube red (check new-code coverage/quality gate on the new files); UI Tests pending (Chromatic baselines).

Verdict: Approved with comments. No hard blockers, but please de-conflict the product:* meta tags with #3383 before merge and confirm the usePDP()-on-non-PDP safety.

const headers: HeadersInit = withCookie({
accept: 'application/json',
'content-type': 'application/json',
'x-vtex-rec-origin': `${account}/${REC_ORIGIN_SUFFIX}`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it is best to forward all the cookies, otherwise you will need to open the segment and parse all the infos.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — that's already what we do here. The client forwards all cookies through getWithCookie/withCookie: it sets the cookie header to the full normalized ctx.headers.cookie (including vtex_segment, checkout.vtex.com, etc.) merged with any updates from ctx.storage.cookies. So the BFF receives the whole segment and we never open/parse it here.

The salesChannel/locale query params come pre-resolved from ctx.storage.channel/ctx.storage.locale (parsed once upstream by FastStore, not re-parsed in this client), so they're just a convenience on top of the forwarded cookies. Happy to drop them and rely solely on the forwarded vtex_segment if we're confident the recommend-bff reads them from the cookie — let me know and I'll simplify.

Comment thread packages/api/src/platforms/vtex/clients/recommendation/index.ts Outdated
"enum": ["PDP", "CART"],
"enumNames": ["Product page", "Cart"]
},
"itemsPerPageDesktop": {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are segregating the product card configuration into its own object I would sugest doing the same here for the carrousel, wdyt?

controls,
productCardConfiguration,
}: RecommendationShelfProps<TCardProps>) {
const CardComponent = (ProductCard ??

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

usePageViewEvent(props)
// Implemented here because the personalization session must be initiated once
// per browser session on each page.
useStartRecommendationSession()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, how do we disable this for stores that are not using Recommendations?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question.
Should all the accounts have the recommendation feature and have requests made to FS API -> recommendations API?
Maybe we can introduce a new feature/experimental flag so the store can choose to enable or disable it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — this shouldn't run for stores that aren't using Recommendations. Following the same pattern we already use for other opt-in features, I suggest gating it behind an experimental flag in discovery.config so it's off by default and each store explicitly opts in.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eduardoformiga one question, if we do it via feature flag, how is it enabled? Can someone do it via admin or does it require a code change/deploy? Asking because one of our requirements is that a Growth person can enable and configure Recommendations in a VTEX store, so without code changes...

Co-authored-by: Felipe Sales <sales.felipeb@gmail.com>

@eduardoformiga eduardoformiga left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job.
That's a Big PR, though. I'd recommend splitting into small PRs to make the review easier the next time, hahah. I left some comments during the review 😉

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Não existe uma regra específica, mas penso que em vez de ser um novo client para recommendations, poderia ser um namespace dentro do vtex Commerce client. Você enxerga mais operações existentes nesse cliente além das adicionadas até o momento? o que achas?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thiagopereira-vtex essa acho que cabe mais como um namespace dentro do client de commerce que ja existe, ao invés de criar um novo client. O que achas?

userId: String
products: [String!]
): RecommendationResponse!
@cacheControl(scope: "private", sMaxAge: 120, staleWhileRevalidate: 3600)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A recomendação é feita pra cada usuário (userId)? Acho que, nesse caso, faz sentido ser private: ficar no browser do usuário e não "vazar" para cdn ou outra camada compartilhada. Caso contrário, seria melhor public.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename it to the lowercase cms_component__recommendationshelf.jsonc? just to keep the pattern with the other files.

Comment on lines +2 to +5
"$extends": ["#/$defs/base-component"],
"$componentKey": "RecommendationShelf",
"$componentTitle": "RecommendationShelf",
"type": "object",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I miss the title and description here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to regenerate it after changing the packages/core/cms/faststore/components/cms_component__RecommendationShelf.jsonc as well

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section comes from CMS, right? What about moving all the RecommendationShelf files to the packages/core/src/components/sections folder?

Comment on lines +17 to +26
const VRN_TYPE_TO_RECOMMENDATION = {
'rec-cross-v2': 'CROSS_SELL',
'rec-similar-v2': 'SIMILAR_ITEMS',
'rec-persona-v2': 'PERSONALIZED',
'rec-last-v2': 'LAST_SEEN',
'rec-top-items-v2': 'TOP_ITEMS',
'rec-search-v2': 'SEARCH_BASED',
'rec-next-v2': 'NEXT_INTERACTION',
'rec-visual-v2': 'VISUAL_SIMILARITY',
} as const satisfies Record<string, RecommendationType>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By any chance, could this list be expanded, and would it be better to get it from an API instead of adding it here manually?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! We don't have this on our roadmap for now, so I'd rather keep it out of the scope of this PR. @salesfelipe what do you think — do you see this as something we should prioritize later?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is something that rarely changes, I would rather keep it like this, isn't worth addding a new network call that could make u slower to render...

usePageViewEvent(props)
// Implemented here because the personalization session must be initiated once
// per browser session on each page.
useStartRecommendationSession()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question.
Should all the accounts have the recommendation feature and have requests made to FS API -> recommendations API?
Maybe we can introduce a new feature/experimental flag so the store can choose to enable or disable it.

Comment thread packages/core/src/pages/[slug]/p.tsx Outdated
},
{
property: 'product:condition',
content: 'new',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new for all products?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it important to the PR to add all these new seo additionalMetaTags?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this PR, the only essential ones are product:id and product:sku — those are what we actually rely on here. The remaining product:* tags aren't required, they're complementary and improve SEO / social previews.

… and enable opt-in feature

- Added server-side validation for `campaignVrn`, `userId`, and `products` in the recommendations resolver to ensure clean input.
- Introduced an opt-in flag `enableRecommendations` in the discovery configuration to control the initiation of recommendation sessions and API calls.
- Updated `RecommendationShelf` and related components to respect the opt-in flag, preventing unnecessary API calls when recommendations are disabled.
- Enhanced tests to cover scenarios for the new validation and opt-in feature.
…t recommendations

- Introduced the `RecommendationShelf` component to display a carousel of personalized product recommendations based on a VTEX Recommendations campaign.
- Implemented configuration options for carousel behavior, including items per page for desktop and mobile, navigation controls, and infinite scrolling.
- Added types and hooks to manage recommendation fetching and user identification.
- Created associated styles and tests to ensure functionality and maintainability.
- Updated existing components and tests to integrate the new `RecommendationShelf` structure.
…te handling

- Changed the prop type for `RecommendationShelf` from `RecommendationShelfProps<TCardProps>` to `Readonly<RecommendationShelfProps<TCardProps>>` for better type safety.
- Modified the empty state return from an empty fragment to `null` for clearer intent and improved rendering performance.
// response, so the shelf renders identical product cards to regular shelves.
// We select `...ProductSummary_product` (the fragment consumed by `ProductCard`)
// to keep this interface consistent with the rest of the components.
const query = gql(`query FetchRecommendationsQuery(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to keep the pattern: Server side queries starts with Server, Client side queries starts with Client. We'll need to regenerate the schemas afterward.

Suggested change
const query = gql(`query FetchRecommendationsQuery(
const query = gql(`query ClientRecommendationsQuery(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is still out of the pattern.

Comment on lines +65 to +66
[getComponentKey(RecommendationShelf, 'RecommendationShelf')]:
RecommendationShelf,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thiagopereira-vtex Looks like this section should be added to PDP/PLPs instead of the global one, right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thiagopereira-vtex essa acho que cabe mais como um namespace dentro do client de commerce que ja existe, ao invés de criar um novo client. O que achas?

- Fold the recommendation client into the VtexCommerce client as a
  `commerce.recommendation` namespace (same account host) and drop the
  standalone client.
- Rename the client query to `ClientRecommendationsQuery` to follow the
  client/server prefix convention and regenerate the GraphQL types.
- Align the shelf styles with the section pattern: `section.module.scss`,
  `.section` root class and a `data-fs-recommendation-shelf-item` attribute.
…S6759

- Add unit tests for the `recommendations` and `startRecommendationSession`
  resolvers, the `commerce.recommendation` client namespace and the
  `useRecommendationUserId` hook to raise new-code coverage above the gate.
- Mark `RecommendationShelfProps` members as `readonly` (instead of the
  `Readonly<>` wrapper, which Sonar S6759 doesn't detect on a generic
  component) to satisfy the read-only props rule.
Replaces the `(value) => Boolean(value)` wrapper flagged by Sonar with a
direct `Boolean` reference.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/api/src/platforms/vtex/clients/commerce/index.ts`:
- Around line 1119-1148: The recommendations() call is not forwarding BFF
Set-Cookie headers because it invokes fetchAPI with only headers and never
passes the storeCookies callback used by startRecommendationSession(). Update
recommendations() in the commerce client to accept and forward the same
cookie-storage hook (via fetchAPI’s third argument or equivalent) so vtex-rec-*
cookies are persisted through ctx.storage.cookies instead of being dropped.

In
`@packages/core/src/components/sections/RecommendationShelf/RecommendationShelf.tsx`:
- Around line 182-184: The RecommendationShelf heading can become empty when
both title and data?.campaign.title are missing, so update the h2 in
RecommendationShelf to always render accessible text or an ARIA label. Adjust
the title fallback logic in RecommendationShelf.tsx so the heading uses a
non-empty fallback string (or equivalent accessible labeling) instead of
rendering nothing, keeping the semantic h2 while preventing an empty
announcement for screen readers.
- Around line 100-121: The PDP fallback in RecommendationShelf is still unsafe
because productDetailPage?.product.isVariantOf.productGroupID can throw when
data.product is missing. Update the contextProducts useMemo in
RecommendationShelf.tsx to guard each nested access with optional chaining all
the way through product, isVariantOf, and productGroupID, so the fallback
returns an empty array when PDP data is incomplete.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4a96312d-a524-4353-89e0-14117c91ab80

📥 Commits

Reviewing files that changed from the base of the PR and between f3ef796 and 1852ee1.

⛔ Files ignored due to path filters (3)
  • packages/api/src/__generated__/schema.ts is excluded by !**/__generated__/** and included by packages/**
  • packages/core/@generated/gql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
  • packages/core/@generated/graphql.ts is excluded by !**/@generated/**, !**/@generated/** and included by packages/**
📒 Files selected for processing (26)
  • packages/api/src/platforms/vtex/clients/commerce/index.ts
  • packages/api/src/platforms/vtex/clients/commerce/types/RecommendationResult.ts
  • packages/api/src/platforms/vtex/resolvers/mutation.ts
  • packages/api/src/platforms/vtex/resolvers/query.ts
  • packages/api/src/platforms/vtex/resolvers/recommendations.ts
  • packages/api/src/platforms/vtex/resolvers/startRecommendationSession.ts
  • packages/api/src/platforms/vtex/typeDefs/query.graphql
  • packages/api/src/platforms/vtex/typeDefs/recommendation.graphql
  • packages/api/test/integration/schema.test.ts
  • packages/api/test/unit/platforms/vtex/clients/commerce.test.ts
  • packages/api/test/unit/platforms/vtex/resolvers/recommendations.test.ts
  • packages/api/test/unit/platforms/vtex/resolvers/startRecommendationSession.test.ts
  • packages/core/cms/faststore/components/cms_component__recommendationshelf.jsonc
  • packages/core/cms/faststore/schema.json
  • packages/core/discovery.config.default.js
  • packages/core/src/Layout.tsx
  • packages/core/src/components/cms/global/Components.ts
  • packages/core/src/components/sections/RecommendationShelf/RecommendationShelf.tsx
  • packages/core/src/components/sections/RecommendationShelf/RecommendationShelf.types.ts
  • packages/core/src/components/sections/RecommendationShelf/index.ts
  • packages/core/src/components/sections/RecommendationShelf/section.module.scss
  • packages/core/src/components/sections/RecommendationShelf/useRecommendationUserId.ts
  • packages/core/src/components/sections/RecommendationShelf/useRecommendations.ts
  • packages/core/src/components/sections/RecommendationShelf/vrn.ts
  • packages/core/src/pages/[slug]/p.tsx
  • packages/core/src/sdk/analytics/hooks/useStartRecommendationSession.ts
💤 Files with no reviewable changes (3)
  • packages/core/src/components/sections/RecommendationShelf/index.ts
  • packages/core/src/components/sections/RecommendationShelf/useRecommendationUserId.ts
  • packages/core/src/components/sections/RecommendationShelf/vrn.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/api/src/platforms/vtex/clients/commerce/types/RecommendationResult.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/api/test/integration/schema.test.ts
  • packages/core/src/Layout.tsx
  • packages/core/src/pages/[slug]/p.tsx
  • packages/core/src/sdk/analytics/hooks/useStartRecommendationSession.ts
  • packages/api/src/platforms/vtex/typeDefs/recommendation.graphql
  • packages/core/src/components/cms/global/Components.ts
  • packages/api/src/platforms/vtex/resolvers/mutation.ts
  • packages/api/src/platforms/vtex/typeDefs/query.graphql
  • packages/core/cms/faststore/schema.json

Comment thread packages/api/src/platforms/vtex/clients/commerce/index.ts
Comment on lines +182 to +184
<h2 className="text__title-section layout__content">
{title ?? data?.campaign.title}
</h2>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Heading can render empty when neither title nor campaign.title exist.

<h2>{title ?? data?.campaign.title}</h2> — if both are undefined, the heading renders with no text content, which is a minor a11y regression (screen readers announce an empty heading). As per path instructions, packages/**/src/components/** component code should "Use semantic HTML and proper ARIA labels for accessibility".

♿ Proposed fallback
-        <h2 className="text__title-section layout__content">
-          {title ?? data?.campaign.title}
-        </h2>
+        {(title ?? data?.campaign.title) && (
+          <h2 className="text__title-section layout__content">
+            {title ?? data?.campaign.title}
+          </h2>
+        )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<h2 className="text__title-section layout__content">
{title ?? data?.campaign.title}
</h2>
{(title ?? data?.campaign.title) && (
<h2 className="text__title-section layout__content">
{title ?? data?.campaign.title}
</h2>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/core/src/components/sections/RecommendationShelf/RecommendationShelf.tsx`
around lines 182 - 184, The RecommendationShelf heading can become empty when
both title and data?.campaign.title are missing, so update the h2 in
RecommendationShelf to always render accessible text or an ARIA label. Adjust
the title fallback logic in RecommendationShelf.tsx so the heading uses a
non-empty fallback string (or equivalent accessible labeling) instead of
rendering nothing, keeping the semantic h2 while preventing an empty
announcement for screen readers.

Source: Path instructions

- Updated the VtexCommerce client to include the `storeCookies` option in the fetchAPI call for recommendations.
- Enhanced unit tests to verify the presence of the `storeCookies` property in the API call.
- Refactored the RecommendationShelf component to ensure safe access to product group ID.
@sonar-workflows

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributing Pull request submitted by the community

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants