Conversation
Tagging OptionsShould a new tag be published when this PR is merged?
|
📝 WalkthroughWalkthroughAdds event-driven recommendation functionality: new event actions (productView, recommendationClick, recommendationView), a recommendation start-session action, recommendation loaders (productList and sources), OpenAPI spec and BFF client wiring, utilities for origin/cookie handling, type and transform updates, manifest and mod adjustments to register these modules. (34 words) Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant App as Deco App
participant BFF as Recommendations BFF
participant Checkout as Checkout API
Client->>App: Request recommendations / send event (props, headers, cookies)
App->>App: resolve origin, parse cookies, determine userId/segment (maybe auto-start session)
App->>BFF: POST /recommend-bff/v2/recommendations or POST /events/* (include x-vtex-rec-origin, cookie, user-agent)
BFF-->>App: recommendations payload or event ack / start-session response (+Set-Cookie)
alt start-session set cookie
App->>Client: proxy Set-Cookie header
end
App-->>Client: Product[] or { ok: true } or session response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment |
There was a problem hiding this comment.
1 issue found across 14 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="vtex/actions/recommendation/startSession.ts">
<violation number="1" location="vtex/actions/recommendation/startSession.ts:39">
P2: Logging response data and Set-Cookie headers can leak sensitive session/auth information into logs. Remove or sanitize these logs in production.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@vtex/actions/events/productView.ts`:
- Around line 12-14: Update the JSDoc for the "Product ID" prop in the
product-view loader so the description reflects a view event rather than a
click: locate the comment block with `@title` "Product ID" in productView.ts (the
product-view loader) and change the phrase "The ID of the product that was
clicked." to something like "The ID of the product that was viewed." to match
the emitted product-view event.
In `@vtex/actions/recommendation/startSession.ts`:
- Line 39: Remove the sensitive console.log in the startSession action: delete
the line that logs "data" and the result of getSetCookies(response.headers) in
the startSession function, since it exposes session/recommendation identifiers;
if you need runtime visibility, replace it with a non-sensitive log such as
response.status or a sanitized message (e.g., "startSession succeeded") and
never log raw cookies or full response body—avoid calling getSetCookies for
logging or redact its output before any log.
In `@vtex/loaders/recommendations/productList.ts`:
- Around line 106-112: For rec-cross-v2 and rec-similar-v2 ensure a source
product exists before building the products/productIds: check campaignType and
validate props.products?.[0] is present; if missing, bail out (throw, return an
error response, or skip calling the BFF) instead of constructing products =
[props.products?.[0]] which yields undefined and productIds = "". Update the
logic around the products and productIds variables (referencing campaignType,
products, productIds, and props.products) to perform this guard and handle the
missing-product case explicitly.
- Line 123: The headers spread is dropping the segment cookie because
withSegmentCookie(segment) returns a Headers instance which doesn't spread into
a plain object; change the merge to preserve entries by converting the Headers
to a plain object (e.g. Object.fromEntries(withSegmentCookie(segment))) or build
a Headers object and call .set("x-vtex-rec-origin", origin) so the segment
cookie from withSegmentCookie(segment) is retained; update the code that
currently uses headers: { ...withSegmentCookie(segment), "x-vtex-rec-origin":
origin } to one of these approaches (refer to withSegmentCookie and the headers
property in productList.ts).
In `@vtex/utils/openapi/recommendations-bff.openapi.json`:
- Around line 266-269: The documentation for the "userId" query parameter
references the wrong cookie name; update its description to mention the correct
cookie "vtex-rec-user-id" (replace `vtex-recommendations-user` with
`vtex-rec-user-id`) so it matches the session flow and utilities, and scan the
OpenAPI spec for any other occurrences of `vtex-recommendations-user` to replace
them with `vtex-rec-user-id`.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
vtex/actions/events/productView.tsvtex/actions/events/recommendationClick.tsvtex/actions/events/recommendationView.tsvtex/actions/recommendation/startSession.tsvtex/loaders/recommendations/productList.tsvtex/loaders/recommendations/productListFromCart.tsvtex/loaders/recommendations/productListFromPage.tsvtex/manifest.gen.tsvtex/mod.tsvtex/utils/openapi/recommendations-bff.openapi.gen.tsvtex/utils/openapi/recommendations-bff.openapi.jsonvtex/utils/recommendations.tsvtex/utils/transform.tsvtex/utils/types.ts
| * @title Product ID | ||
| * @description The ID of the product that was clicked. | ||
| */ |
There was a problem hiding this comment.
Fix product-view prop description typo.
Line 13 says the product was “clicked”, but this loader emits a product-view event.
✏️ Suggested edit
- * `@description` The ID of the product that was clicked.
+ * `@description` The ID of the product that was viewed.📝 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.
| * @title Product ID | |
| * @description The ID of the product that was clicked. | |
| */ | |
| * `@title` Product ID | |
| * `@description` The ID of the product that was viewed. | |
| */ |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vtex/actions/events/productView.ts` around lines 12 - 14, Update the JSDoc
for the "Product ID" prop in the product-view loader so the description reflects
a view event rather than a click: locate the comment block with `@title` "Product
ID" in productView.ts (the product-view loader) and change the phrase "The ID of
the product that was clicked." to something like "The ID of the product that was
viewed." to match the emitted product-view event.
| const products = | ||
| campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2" | ||
| ? [props.products?.[0]] | ||
| : props.products; | ||
|
|
||
| const productIds = products?.filter(Boolean).join(","); | ||
|
|
There was a problem hiding this comment.
Validate required product context for cross/similar campaigns.
Lines 106-112 allow missing product IDs for rec-cross-v2 / rec-similar-v2, which can send an empty products parameter and cause avoidable BFF failures.
🛡️ Suggested fix
- const products =
- campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2"
- ? [props.products?.[0]]
- : props.products;
+ const requiresSingleProduct =
+ campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2";
+
+ if (requiresSingleProduct && !props.products?.[0]) {
+ throw new Error(
+ `campaignType "${props.campaignType}" requires one product ID`,
+ );
+ }
+
+ const products = requiresSingleProduct
+ ? [props.products![0]]
+ : props.products;📝 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.
| const products = | |
| campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2" | |
| ? [props.products?.[0]] | |
| : props.products; | |
| const productIds = products?.filter(Boolean).join(","); | |
| const requiresSingleProduct = | |
| campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2"; | |
| if (requiresSingleProduct && !props.products?.[0]) { | |
| throw new Error( | |
| `campaignType "${props.campaignType}" requires one product ID`, | |
| ); | |
| } | |
| const products = requiresSingleProduct | |
| ? [props.products![0]] | |
| : props.products; | |
| const productIds = products?.filter(Boolean).join(","); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vtex/loaders/recommendations/productList.ts` around lines 106 - 112, For
rec-cross-v2 and rec-similar-v2 ensure a source product exists before building
the products/productIds: check campaignType and validate props.products?.[0] is
present; if missing, bail out (throw, return an error response, or skip calling
the BFF) instead of constructing products = [props.products?.[0]] which yields
undefined and productIds = "". Update the logic around the products and
productIds variables (referencing campaignType, products, productIds, and
props.products) to perform this guard and handle the missing-product case
explicitly.
| products: productIds, | ||
| salesChannel: segment?.payload?.channel || ctx.salesChannel, | ||
| }, { | ||
| headers: { ...withSegmentCookie(segment), "x-vtex-rec-origin": origin }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check vtex/utils/segment.ts for withSegmentCookie definition and Headers usage
echo "=== Checking vtex/utils/segment.ts ==="
rg -n -C3 'export const withSegmentCookie|new Headers\(|return h' vtex/utils/segment.ts
echo ""
echo "=== Checking vtex/loaders/recommendations/productList.ts line 123 ==="
rg -n -C3 '\.\.\.withSegmentCookie\(segment\)' vtex/loaders/recommendations/productList.tsRepository: deco-cx/apps
Length of output: 858
Segment cookie is dropped when spreading Headers into a plain object.
Line 123 spreads withSegmentCookie(segment) (which returns a Headers instance) into a plain object literal. A Headers object doesn't preserve its entries when spread into {} because it's a Web API object with custom iteration semantics, not a plain object with enumerable properties. This silently drops the segment cookie and breaks segment-aware recommendations.
Fix
- const response = await bff["GET /api/recommend-bff/v2/recommendations"]({
+ const response = await bff["GET /api/recommend-bff/v2/recommendations"]({
an: account,
campaignVrn,
pickupPoint: props.pickupPoint,
regionId: props.regionId,
zipCode: props.zipCode,
userId,
products: productIds,
salesChannel: segment?.payload?.channel || ctx.salesChannel,
}, {
- headers: { ...withSegmentCookie(segment), "x-vtex-rec-origin": origin },
+ headers: withSegmentCookie(
+ segment,
+ new Headers({ "x-vtex-rec-origin": origin }),
+ ),
}).then((res) => res.json());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vtex/loaders/recommendations/productList.ts` at line 123, The headers spread
is dropping the segment cookie because withSegmentCookie(segment) returns a
Headers instance which doesn't spread into a plain object; change the merge to
preserve entries by converting the Headers to a plain object (e.g.
Object.fromEntries(withSegmentCookie(segment))) or build a Headers object and
call .set("x-vtex-rec-origin", origin) so the segment cookie from
withSegmentCookie(segment) is retained; update the code that currently uses
headers: { ...withSegmentCookie(segment), "x-vtex-rec-origin": origin } to one
of these approaches (refer to withSegmentCookie and the headers property in
productList.ts).
| "name": "userId", | ||
| "in": "query", | ||
| "description": "User ID, required for personalized recommendations based on user behavior. It can be found in the `vtex-recommendations-user` cookie.", | ||
| "required": false, |
There was a problem hiding this comment.
Fix inconsistent cookie name in userId parameter docs.
Line 268 references vtex-recommendations-user, but the session flow and utilities use vtex-rec-user-id. This inconsistency can cause integration mistakes.
🛠️ Proposed fix
- "description": "User ID, required for personalized recommendations based on user behavior. It can be found in the `vtex-recommendations-user` cookie.",
+ "description": "User ID, required for personalized recommendations based on user behavior. It can be found in the `vtex-rec-user-id` cookie.",📝 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.
| "name": "userId", | |
| "in": "query", | |
| "description": "User ID, required for personalized recommendations based on user behavior. It can be found in the `vtex-recommendations-user` cookie.", | |
| "required": false, | |
| "name": "userId", | |
| "in": "query", | |
| "description": "User ID, required for personalized recommendations based on user behavior. It can be found in the `vtex-rec-user-id` cookie.", | |
| "required": false, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vtex/utils/openapi/recommendations-bff.openapi.json` around lines 266 - 269,
The documentation for the "userId" query parameter references the wrong cookie
name; update its description to mention the correct cookie "vtex-rec-user-id"
(replace `vtex-recommendations-user` with `vtex-rec-user-id`) so it matches the
session flow and utilities, and scan the OpenAPI spec for any other occurrences
of `vtex-recommendations-user` to replace them with `vtex-rec-user-id`.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
vtex/actions/cart/updateCustomData.ts (1)
15-18: Update JSDoc to reflect actual functionality.The title and description reference "Attachment" but this action updates custom data, not attachments.
📝 Proposed fix
/** - * `@title` Update Attachment - * `@description` Update an attachment in the cart + * `@title` Update Custom Data + * `@description` Update custom data in the order form */🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vtex/actions/cart/updateCustomData.ts` around lines 15 - 18, The JSDoc above the action is misleading: change the `@title` and `@description` that currently say "Update Attachment" to reflect the actual behavior of this module (updating cart custom data). Update the block in vtex/actions/cart/updateCustomData.ts (the JSDoc that documents the updateCustomData action) so the `@title` reads something like "Update Custom Data" and the `@description` explains that it updates custom data in the cart (mentioning the specific custom field or namespace if applicable).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@vtex/actions/cart/updateCustomData.ts`:
- Line 6: Remove the unused import getSegmentFromBag from the top of the file
(the import statement importing getSegmentFromBag from "../../utils/segment.ts")
so the CI error goes away; ensure no other references to getSegmentFromBag exist
in the updateCustomData module (e.g., within updateCustomData) and run lint/type
checks to confirm the unused-import is resolved.
---
Nitpick comments:
In `@vtex/actions/cart/updateCustomData.ts`:
- Around line 15-18: The JSDoc above the action is misleading: change the `@title`
and `@description` that currently say "Update Attachment" to reflect the actual
behavior of this module (updating cart custom data). Update the block in
vtex/actions/cart/updateCustomData.ts (the JSDoc that documents the
updateCustomData action) so the `@title` reads something like "Update Custom Data"
and the `@description` explains that it updates custom data in the cart
(mentioning the specific custom field or namespace if applicable).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 29b4428f-2fc4-47bc-abf8-d6123429bbbc
📒 Files selected for processing (3)
vtex/actions/cart/updateCustomData.tsvtex/manifest.gen.tsvtex/utils/client.ts
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="vtex/utils/types.ts">
<violation number="1" location="vtex/utils/types.ts:53">
P2: `CustomData` requires `customFields`, but VTEX orderForm `customData` is documented/schema-defined around `customApps` and may omit that property, creating an API contract mismatch.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
|
||
| export interface CustomData { | ||
| customApps: CustomApp[]; | ||
| customFields: unknown[]; |
There was a problem hiding this comment.
P2: CustomData requires customFields, but VTEX orderForm customData is documented/schema-defined around customApps and may omit that property, creating an API contract mismatch.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At vtex/utils/types.ts, line 53:
<comment>`CustomData` requires `customFields`, but VTEX orderForm `customData` is documented/schema-defined around `customApps` and may omit that property, creating an API contract mismatch.</comment>
<file context>
@@ -42,6 +42,17 @@ export interface OrderForm {
+
+export interface CustomData {
+ customApps: CustomApp[];
+ customFields: unknown[];
+}
+
</file context>
| customFields: unknown[]; | |
| customFields?: unknown[]; |
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
vtex/loaders/recommendations/productList.ts (1)
107-112:⚠️ Potential issue | 🟠 MajorValidate required product context for cross/similar campaigns.
For
rec-cross-v2andrec-similar-v2campaign types, ifprops.productsis undefined or empty, this createsproducts = [undefined]which then becomes an empty string after filtering. This sends an emptyproductsparameter to the BFF, likely causing API failures.🛡️ Suggested fix
- const products = - campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2" - ? [props.products?.[0]] - : props.products; + const requiresSingleProduct = + campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2"; + + if (requiresSingleProduct && !props.products?.[0]) { + console.warn( + `campaignType "${props.campaignType}" requires at least one product ID`, + ); + return null; + } + + const products = requiresSingleProduct + ? [props.products![0]] + : props.products;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vtex/loaders/recommendations/productList.ts` around lines 107 - 112, When campaignType is "rec-cross-v2" or "rec-similar-v2" the current logic sets products = [props.products?.[0]] which can produce [undefined] when props.products is missing and later yields an empty productIds; update the logic in the block that sets products (referencing campaignType, props.products, and products) to first validate that props.products exists and has at least one truthy entry before creating the single-item array, and if not, handle the missing context (e.g., set products = undefined/null or bail out early) so productIds (computed from products?.filter(Boolean).join(",")) is never an empty string sent to the BFF.
🧹 Nitpick comments (1)
vtex/loaders/recommendations/productList.ts (1)
169-172: Consider adding context to error logging.The generic
console.error(error)loses context about which campaign or configuration failed. Adding identifiers would help debugging in production.💡 Optional improvement
} catch (error) { - console.error(error); + console.error("Failed to fetch recommendations", { + campaignType: props.campaignType, + campaignId: props.campaignId, + error, + }); return null; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vtex/loaders/recommendations/productList.ts` around lines 169 - 172, In the catch block inside the productList loader (the catch in the productList / loadProductList function), replace the generic console.error(error) with a contextual log that includes the relevant identifiers available in scope (for example campaignId, campaign?.id or campaign?.name, or config?.id) along with the error object; ensure the log message clearly states "Error loading product list" and includes those identifiers before returning null so you can trace which campaign/config failed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@vtex/actions/recommendation/startSession.ts`:
- Around line 12-13: The request is posting a start-session to the BFF even when
parseOrderformCookie(req.headers) returns no orderFormId; update the logic in
startSession.ts to skip the BFF start-session call whenever orderFormId is falsy
(do not post body: { orderFormId } or call the BFF at all), and similarly apply
the same guard in the other branch around the second POST (the block referenced
at lines 34-39) so you only start a session when orderFormId is present; use the
existing variables parseOrderformCookie, parseRecommendationsCookie, orderFormId
and userId to gate the calls.
- Around line 21-25: The code unconditionally overwrites the caller-provided
x-vtex-rec-origin header; change it to respect an incoming header first by
reading the request header (e.g., ctx.request.headers.get('x-vtex-rec-origin')
or equivalent) and only set headers.set('x-vtex-rec-origin',
`${ctx.account}/storefront/deco.recommendations@1.x`) when that incoming value
is missing/empty. Update the logic around the local headers variable in
startSession (where headers is created) to use the existing header value if
present, otherwise fall back to the default constructed from ctx.account and
storefront/deco.recommendations@1.x.
In `@vtex/loaders/recommendations/productList.ts`:
- Around line 132-168: The mapping over response.products can throw if products
is undefined or not an array; update the product list loader to defensively
handle this by checking response.products before mapping (e.g., ensure
Array.isArray(response.products) and fallback to an empty array), then call
.map(...) using the same toProduct(...) call and getFirstItemAvailable helper;
ensure the code still returns an empty array or appropriate default when
products is missing so downstream code doesn't receive undefined.
- Around line 114-128: Replace the raw cookie header when calling the
recommendations BFF so the request includes the VTEX segment token: import
withSegmentCookie from ../../utils/segment.ts, ensure you use the `segment`
value obtained from `getSegmentFromBag(ctx)`, and update the headers passed to
bff["GET /api/recommend-bff/v2/recommendations"] to spread
`withSegmentCookie(segment)` and include "x-vtex-rec-origin": origin instead of
using req.headers.get("cookie") directly; keep the rest of the request payload
(userId, products, salesChannel, etc.) unchanged.
---
Duplicate comments:
In `@vtex/loaders/recommendations/productList.ts`:
- Around line 107-112: When campaignType is "rec-cross-v2" or "rec-similar-v2"
the current logic sets products = [props.products?.[0]] which can produce
[undefined] when props.products is missing and later yields an empty productIds;
update the logic in the block that sets products (referencing campaignType,
props.products, and products) to first validate that props.products exists and
has at least one truthy entry before creating the single-item array, and if not,
handle the missing context (e.g., set products = undefined/null or bail out
early) so productIds (computed from products?.filter(Boolean).join(",")) is
never an empty string sent to the BFF.
---
Nitpick comments:
In `@vtex/loaders/recommendations/productList.ts`:
- Around line 169-172: In the catch block inside the productList loader (the
catch in the productList / loadProductList function), replace the generic
console.error(error) with a contextual log that includes the relevant
identifiers available in scope (for example campaignId, campaign?.id or
campaign?.name, or config?.id) along with the error object; ensure the log
message clearly states "Error loading product list" and includes those
identifiers before returning null so you can trace which campaign/config failed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4ced8bca-1f48-4236-8faf-c4c3d054e2af
📒 Files selected for processing (4)
vtex/actions/events/productView.tsvtex/actions/recommendation/startSession.tsvtex/loaders/recommendations/productList.tsvtex/utils/recommendations.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- vtex/actions/events/productView.ts
- vtex/utils/recommendations.ts
| const { orderFormId } = parseOrderformCookie(req.headers); | ||
| const { userId } = parseRecommendationsCookie(req.headers); |
There was a problem hiding this comment.
Skip the BFF call when orderFormId is missing.
parseOrderformCookie() can return no orderFormId, but this branch still posts body: { orderFormId }. vtex/loaders/recommendations/productList.ts:89-95 auto-starts the session whenever userId is absent, so first requests without the orderForm cookie will still make an avoidable start-session call and likely fail.
Also applies to: 34-39
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vtex/actions/recommendation/startSession.ts` around lines 12 - 13, The
request is posting a start-session to the BFF even when
parseOrderformCookie(req.headers) returns no orderFormId; update the logic in
startSession.ts to skip the BFF start-session call whenever orderFormId is falsy
(do not post body: { orderFormId } or call the BFF at all), and similarly apply
the same guard in the other branch around the second POST (the block referenced
at lines 34-39) so you only start a session when orderFormId is present; use the
existing variables parseOrderformCookie, parseRecommendationsCookie, orderFormId
and userId to gate the calls.
| const headers = new Headers(); | ||
| headers.set( | ||
| "x-vtex-rec-origin", | ||
| `${ctx.account}/storefront/deco.recommendations@1.x`, | ||
| ); |
There was a problem hiding this comment.
Respect caller-provided x-vtex-rec-origin.
The PR contract says callers can send {account}/{source}/{app}, but Lines 21-25 always overwrite that with the default value. That loses source/app attribution for any non-default integration. Prefer the inbound header and only fall back to the default when it is missing.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vtex/actions/recommendation/startSession.ts` around lines 21 - 25, The code
unconditionally overwrites the caller-provided x-vtex-rec-origin header; change
it to respect an incoming header first by reading the request header (e.g.,
ctx.request.headers.get('x-vtex-rec-origin') or equivalent) and only set
headers.set('x-vtex-rec-origin',
`${ctx.account}/storefront/deco.recommendations@1.x`) when that incoming value
is missing/empty. Update the logic around the local headers variable in
startSession (where headers is created) to use the existing header value if
present, otherwise fall back to the default constructed from ctx.account and
storefront/deco.recommendations@1.x.
| const response = await bff["GET /api/recommend-bff/v2/recommendations"]({ | ||
| an: account, | ||
| campaignVrn, | ||
| pickupPoint: props.pickupPoint, | ||
| regionId: props.regionId, | ||
| zipCode: props.zipCode, | ||
| userId, | ||
| products: productIds, | ||
| salesChannel: segment?.payload?.channel || ctx.salesChannel, | ||
| }, { | ||
| headers: { | ||
| cookie: req.headers.get("cookie") ?? "", | ||
| "x-vtex-rec-origin": origin, | ||
| }, | ||
| }).then((res) => res.json()); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how other VTEX loaders handle segment cookie
echo "=== How withSegmentCookie is used elsewhere ==="
rg -n "withSegmentCookie" --type=ts -C3 vtex/loaders/
echo ""
echo "=== Check segment utility implementation ==="
cat vtex/utils/segment.ts | head -60Repository: deco-cx/apps
Length of output: 15921
🏁 Script executed:
cat vtex/utils/segment.ts | sed -n '30,120p'Repository: deco-cx/apps
Length of output: 2752
🏁 Script executed:
rg -n "withSegmentCookie" vtex/utils/segment.ts -A 20Repository: deco-cx/apps
Length of output: 509
🏁 Script executed:
sed -n '100,140p' vtex/loaders/recommendations/productList.tsRepository: deco-cx/apps
Length of output: 1413
🏁 Script executed:
sed -n '1,115p' vtex/loaders/recommendations/productList.ts | tail -40Repository: deco-cx/apps
Length of output: 1486
🏁 Script executed:
sed -n '1,20p' vtex/loaders/recommendations/productList.tsRepository: deco-cx/apps
Length of output: 696
Use withSegmentCookie() to pass segment context to the recommendations API.
The segment variable is available from getSegmentFromBag(ctx) but is only used for salesChannel and currencyCode values. The cookie header should use withSegmentCookie(segment) instead of the raw request cookie, consistent with all other VTEX loaders (intelligentSearch, legacy, categories). This ensures the recommendations API receives the proper vtex_segment token for contextualized results.
Import withSegmentCookie from ../../utils/segment.ts and update the headers:
headers: {
...withSegmentCookie(segment),
"x-vtex-rec-origin": origin,
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vtex/loaders/recommendations/productList.ts` around lines 114 - 128, Replace
the raw cookie header when calling the recommendations BFF so the request
includes the VTEX segment token: import withSegmentCookie from
../../utils/segment.ts, ensure you use the `segment` value obtained from
`getSegmentFromBag(ctx)`, and update the headers passed to bff["GET
/api/recommend-bff/v2/recommendations"] to spread `withSegmentCookie(segment)`
and include "x-vtex-rec-origin": origin instead of using
req.headers.get("cookie") directly; keep the rest of the request payload
(userId, products, salesChannel, etc.) unchanged.
| return (response.products as unknown as LegacyProduct[]).map((product) => | ||
| toProduct( | ||
| product, | ||
| product.items.find(getFirstItemAvailable) ?? product.items[0], | ||
| 0, | ||
| { | ||
| baseUrl, | ||
| priceCurrency: segment?.payload?.currencyCode ?? "BRL", | ||
| isVariantOfAdditionalProperty: [ | ||
| { | ||
| "@type": "PropertyValue", | ||
| name: "correlationId", | ||
| value: response.correlationId, | ||
| valueReference: "RECOMMENDATION", | ||
| }, | ||
| { | ||
| "@type": "PropertyValue", | ||
| name: "campaignId", | ||
| value: response.campaign?.id, | ||
| valueReference: "RECOMMENDATION", | ||
| }, | ||
| { | ||
| "@type": "PropertyValue", | ||
| name: "campaignTitle", | ||
| value: response.campaign?.title, | ||
| valueReference: "RECOMMENDATION", | ||
| }, | ||
| { | ||
| "@type": "PropertyValue", | ||
| name: "campaignType", | ||
| value: response.campaign?.type, | ||
| valueReference: "RECOMMENDATION", | ||
| }, | ||
| ], | ||
| }, | ||
| ) | ||
| ); |
There was a problem hiding this comment.
Add defensive check for response.products before mapping.
The code assumes response.products exists and is iterable. If the BFF returns an error response or a malformed payload, calling .map() on undefined/non-array will throw a TypeError.
🛡️ Suggested fix
const baseUrl = new URL(req.url).origin;
+ if (!Array.isArray(response.products)) {
+ console.warn("BFF returned no products or invalid response", response);
+ return null;
+ }
+
return (response.products as unknown as LegacyProduct[]).map((product) =>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vtex/loaders/recommendations/productList.ts` around lines 132 - 168, The
mapping over response.products can throw if products is undefined or not an
array; update the product list loader to defensively handle this by checking
response.products before mapping (e.g., ensure Array.isArray(response.products)
and fallback to an empty array), then call .map(...) using the same
toProduct(...) call and getFirstItemAvailable helper; ensure the code still
returns an empty array or appropriate default when products is missing so
downstream code doesn't receive undefined.
What is this Contribution About?
Implementation of VTEX Recommendations BFF API
Summary by cubic
Adds VTEX Recommendations BFF integration to fetch product recommendations and track events, with session bootstrap and product metadata enrichment. Also adds a cart action to update orderForm custom data.
New Features
correlationIdand campaign info.vtex-rec-user-idcookie or provided userId).customData.advancedConfigs.autoStartRecommendationSessionto start a session when userId is missing.OrderForm.customDataand aligned update custom data action body with VTEX API.Migration
x-vtex-rec-originas {account}/{source}/{app} when calling loaders/actions; default is {account}/storefront/deco.recommendations@1.x.autoStartRecommendationSession, expect one extra call only on the first request without a session (skipped whenvtex-rec-user-idexists); requires orderForm cookie.loaders/recommendations/productListand passcorrelationIdto emit recommendation-view and recommendation-click events.Written for commit c8840f5. Summary will update on new commits.
Summary by CodeRabbit
New Features
Developer API