Skip to content

feat(personalize): Contentstack Personalize variant delivery + seed - #36

Draft
vaishnavi-kedar-cstk wants to merge 2 commits into
docs/connector-wikifrom
feat/personalize-variant-delivery
Draft

vaishnavi-kedar-cstk wants to merge 2 commits into
docs/connector-wikifrom
feat/personalize-variant-delivery

Conversation

@vaishnavi-kedar-cstk

Copy link
Copy Markdown
Contributor

What & why

Adds Contentstack Personalize support to the connector so a Spartacus storefront can serve audience-specific / returning-visitor variants, and ships a ready-to-import Personalize config in the starter pack.

Demo condition: new vs. returning visitor.

  • New shopper → base carousel heading.
  • Returning shopper (has_visited = true, stamped on product view) → variant heading "Welcome back — pick up where you left off." (alias cs_personalize_0_0).

How it works

App resolves the Personalize user manifest → variant aliases → connector threads them into the Delivery API (.variants(...)) → Contentstack returns the composite (personalized) entry. SAP OCC still supplies live price/stock.

Changes — connector (library)

  • New ContentstackPersonalizeService seam — the connector asks it for the current shopper's variant aliases on every CMS fetch. Default is a no-op (reads optional static contentstack.personalize.variantAliases), so apps without personalization are unchanged; apps override it with an SDK-backed implementation.
  • Client service threads aliases into all four Delivery API calls via .variants(...) and folds them into the TransferState cache key (so one segment's composite entry is never replayed for another). Call arity preserved when there are no aliases.
  • Page + component adapters resolve aliases from the seam and pass them through.
  • Product-carousel normalizer now maps the entry title (was dropped) — this is what makes an authored/personalized heading render.
  • Config: contentstack.personalize.variantAliases (static demo/test aliases).
  • Type shims: .variants() on the delivery SDK; title on CmsProductCarouselComponent.
  • Seam exported from the public API. Tests: 171 passing (+ carousel-title and alias-threading tests).

Changes — starter pack (seed)

  • personalize/ — project config: attribute has_visited, audience returning_user (has_visited IS TRUE), Segmented experience (short UID 0) linked to product_carousel_component. Source stack key / org / user ids stripped.
  • Carousel entry variant (seed_carousel) overriding the heading to "Welcome back…".
  • README: personalization section + operational notes (org project quota, unique project name, single-run import, edge-verify commands).

A single csdx cm:stacks:import recreates content + a new Personalize project (auto-linked) + the carousel variant.

Validated

  • One cm:stacks:import into a fresh stack: 7/7 modules 100%, incl. variant entries; new Personalize project auto-created; edge decisioning live (new→base, returning→variant).
  • App (ng add @spartacus/schematics → connector → import) renders the base; returning variant verified via Delivery API + edge manifest.

Notes

  • Based on docs/connector-wiki (the personalization work sits on top of that branch's connector changes), so this diff is personalization-only. Retarget to main after the docs branch merges if preferred.
  • App-side wiring (SDK-backed PersonalizeService, provideAppInitializer) lives in the consuming apps, not this library.

🤖 Generated with Claude Code

Add Contentstack Personalize support so a Spartacus storefront can serve
audience/returning-visitor variants, and ship a ready-to-import Personalize
config in the starter pack.

Connector (library):
- New ContentstackPersonalizeService seam — asked for the current shopper's
  variant aliases on every CMS fetch. Default no-op (reads optional static
  contentstack.personalize.variantAliases), so apps without personalization are
  unchanged; apps override it with an SDK-backed implementation.
- Client service threads aliases into all four Delivery API calls via
  .variants(...) and folds them into the TransferState cache key (so one
  segment's composite entry is never replayed for another). Call arity is
  preserved when there are no aliases.
- Page + component adapters resolve aliases from the seam and pass them through.
- Product-carousel normalizer now maps the entry title (was dropped) so an
  authored/personalized heading renders.
- Config gains contentstack.personalize.variantAliases (static demo/test aliases).
- Type shims: .variants() on the delivery SDK, title on CmsProductCarouselComponent.
- Export the seam from the public API. Tests updated/added (171 passing).

Starter pack (seed):
- personalize/ — project config: attribute has_visited, audience returning_user
  (has_visited IS TRUE), Segmented experience (short UID 0) linked to
  product_carousel_component. Source stack key / org / user ids stripped.
- Carousel entry variant (seed_carousel) overriding the heading to
  "Welcome back..." (alias cs_personalize_0_0).
- README: personalization section + operational notes (org project quota,
  unique project name, single-run import, edge-verify commands).

A single `csdx cm:stacks:import` recreates content + a new Personalize project
(auto-linked) + the carousel variant; the app resolves new-vs-returning per
shopper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vaishnavi-kedar-cstk
vaishnavi-kedar-cstk requested a review from a team as a code owner November 3, 2026 05:59
@snyk-io

snyk-io Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues
✅ Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

🔒 Security Scan Results

ℹ️ Note: Only vulnerabilities with available fixes (upgrades or patches) are counted toward thresholds.

Check Type Count (with fixes) Without fixes Threshold Result
🔴 Critical Severity 0 2 10 ✅ Passed
🟠 High Severity 0 23 25 ✅ Passed
🟡 Medium Severity 0 18 500 ✅ Passed
🔵 Low Severity 0 1 1000 ✅ Passed

⏱️ SLA Breach Summary

✅ No SLA breaches detected. All vulnerabilities are within acceptable time thresholds.

Severity Breaches (with fixes) Breaches (no fixes) SLA Threshold (with/no fixes) Status
🔴 Critical 0 0 15 / 30 days ✅ Passed
🟠 High 0 0 30 / 120 days ✅ Passed
🟡 Medium 0 0 90 / 365 days ✅ Passed
🔵 Low 0 0 180 / 365 days ✅ Passed

ℹ️ Vulnerabilities Without Available Fixes (Informational Only)

The following vulnerabilities were detected but do not have fixes available (no upgrade or patch). These are excluded from failure thresholds:

  • Critical without fixes: 2
  • High without fixes: 23
  • Medium without fixes: 18
  • Low without fixes: 1

✅ BUILD PASSED - All security checks passed

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

🔒 Security Scan Results

ℹ️ Note: Only vulnerabilities with available fixes (upgrades or patches) are counted toward thresholds.

Check Type Count (with fixes) Without fixes Threshold Result
🔴 Critical Severity 0 2 10 ✅ Passed
🟠 High Severity 0 23 25 ✅ Passed
🟡 Medium Severity 0 18 500 ✅ Passed
🔵 Low Severity 0 1 1000 ✅ Passed

⏱️ SLA Breach Summary

✅ No SLA breaches detected. All vulnerabilities are within acceptable time thresholds.

Severity Breaches (with fixes) Breaches (no fixes) SLA Threshold (with/no fixes) Status
🔴 Critical 0 0 15 / 30 days ✅ Passed
🟠 High 0 0 30 / 120 days ✅ Passed
🟡 Medium 0 0 90 / 365 days ✅ Passed
🔵 Low 0 0 180 / 365 days ✅ Passed

ℹ️ Vulnerabilities Without Available Fixes (Informational Only)

The following vulnerabilities were detected but do not have fixes available (no upgrade or patch). These are excluded from failure thresholds:

  • Critical without fixes: 2
  • High without fixes: 23
  • Medium without fixes: 18
  • Low without fixes: 1

✅ BUILD PASSED - All security checks passed

@timbenniks-contentstack

Copy link
Copy Markdown

Two things before this lands:

  • Nothing in this code calls Personalize. The connector only asks getVariantAliases(). Out of the box that reads a static config list, which gives every visitor the same variant. Anyone wanting real per-shopper personalization has to write the Edge SDK integration themselves, and that code lives in the demo app, not here. I suspect most developers won’t realize this and will assume it works out of the box. Can we ship an opt-in SDK-backed default (configured with just a projectUid), or at least document the app-side wiring in the README with a full example?

  • The manifest has to load before the first CMS fetch. getVariantAliases() is synchronous, so if the manifest isn’t ready yet, the first page renders the base entry. With SSR, the aliases also need to be scoped to each request; a singleton caching them could serve one shopper’s variant to another. That should be explicit in the docs, or better, handled by the connector.

Smaller things:

  • The seeded variant title reads "Bestsellers (managed in Contentstack , Welcome back) pick up where you left off.", which doesn’t match the README or the tests. It looks like a leftover from authoring.

  • Aliases are read once per language/permissions emission, so a page that’s already loaded won’t refetch after has_visited flips. That’s fine for a demo, but worth a note.

  • The static variantAliases config should be clearly marked test-only so nobody ships it to production.

  • Tests: there are tests for the page adapter, but none that check the alias reaches .variants() in the client service or that the cache key changes. I’d add one for each.

  • Tracking: there’s nothing for impressions or conversions, so experience analytics will stay empty unless the app sends events itself. Contentstack Perosnalize has endpoints for this.

  • Base branch: the PR targets docs/connector-wiki, not main, so it’s worth agreeing on the merge order.

  • Live preview: it’s unclear whether Visual Builder or live preview shows variants. It would help to know how editors preview the “Welcome back” version.

@vaishnavi-kedar-cstk

Copy link
Copy Markdown
Contributor Author

Thanks Tim, this is really useful.

This PR mainly covers the foundation — it makes the connector capable of delivering variants; the demo app shows one way to decide them (client-side). It was put together before the epic discussion we had, so it's a capability proof, not a full-fledged Contentstack Personalize PR. The complete approach — proper decisioning, SSR, tracking, previews — is what we'll define in the PRD and TRD for the epic (DX-20947), and we'll reframe the tickets off that. So treat this as the foundation; the real scope lands through the epic.

I wanted eyes on the direction before we build the full thing on top of it. I'll incorporate your feedback and suggestions, and create the tickets off the PRD and TRD once we have them for the epic.

@vaishnavi-kedar-cstk
vaishnavi-kedar-cstk marked this pull request as draft November 3, 2026 10:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants