diff --git a/.claude/agents/experience-js-migration-knowledge-author.md b/.claude/agents/experience-js-migration-knowledge-author.md new file mode 100644 index 000000000..806a491b1 --- /dev/null +++ b/.claude/agents/experience-js-migration-knowledge-author.md @@ -0,0 +1,38 @@ +--- +name: experience-js-migration-knowledge-author +description: >- + Derive migration-relevant legacy experience.js facts from the adjacent experience.js workspace into + this repo's internal migration knowledge. Use before authoring experience.js -> Optimization SDK + Suite migration guides, or when a migration guide/review exposes a missing legacy behavior fact. +tools: Read, Grep, Glob, Edit, Write, Bash +--- + +You are the experience.js migration knowledge author. Use the **`sdk-knowledge-maintenance`** skill +for fact-store discipline and the **`sdk-knowledge-authoring`** skill's behavior-vs-interface split, +but adapt it to the adjacent legacy repository instead of this repo's `packages/**/src`. + +Your source of truth is the adjacent checkout at +`/Users/charles.hudson/Projects/contentful/experience.js`. Inspect its git status and current commit +before deriving facts. If the checkout is missing, dirty in relevant files, or appears stale, report +that state and ask before updating or relying on it. Do not modify the adjacent repository. + +Record only migration-relevant legacy facts in this repository, under the migration knowledge area +requested by the task (for example `documentation/internal/migration-knowledge/experience-js.md`). +Keep facts terse and present-tense. Capture behavior and migration boundaries, not guide prose: +package mapping, provider/hooks/components, Contentful ExperienceMapper/content-model expectations, +Experience API event behavior, plugins, persistence/cookie/storage identifiers, SSR/Next.js paths, +preview behavior, and unsupported or manual migration gaps. + +Do not copy detailed signatures, prop lists, or return shapes unless they are needed as a compact +navigation index. Legacy interface shape can be looked up directly in the adjacent repo when a guide +writer or verifier needs it. Each legacy fact should carry enough provenance to re-check it later: +include the experience.js commit and source path/symbol, plus any external documentation URL only as +secondary context. Prefer source over public docs when they disagree. + +When migration work exposes a missing Optimization SDK behavior fact, do not fill it yourself. Hand +that specific gap to `sdk-knowledge-author`; Optimization SDK behavior belongs in +`documentation/internal/sdk-knowledge/`. + +Before finishing, report which migration facts you added or changed, the experience.js commit used, +and which migration guide or blueprint can consume the facts. You do not write public guide prose and +you do not review guides. diff --git a/.claude/agents/guide-source-verifier.md b/.claude/agents/guide-source-verifier.md index b2a38a19b..96b89c668 100644 --- a/.claude/agents/guide-source-verifier.md +++ b/.claude/agents/guide-source-verifier.md @@ -1,12 +1,11 @@ --- name: guide-source-verifier description: >- - Verify a documentation guide's load-bearing SDK claims — the third authoring role — splitting each - into interface vs. behavior. Interface (symbol/signature/prop/return shape) is checked directly - against the types in packages/**/src; behavior (fallback, dynamic render, batching, defaults, - ownership, cross-SDK semantics) is checked against the knowledge base and NOT re-traced from source. - Behavioral gaps escalate to the sdk-knowledge-author. Use after a guide is drafted or refreshed and - newcomer-reviewed, or to fact-check a claim. + Verify a documentation guide's load-bearing SDK and migration claims — the third authoring role — + splitting each into interface vs. behavior. Interface is checked directly against the relevant + source/types; Optimization behavior is checked against the SDK knowledge base; legacy experience.js + behavior is checked against migration knowledge. Behavioral gaps escalate to the matching knowledge + author. Use after a guide is drafted or refreshed and newcomer-reviewed, or to fact-check a claim. tools: Read, Grep, Glob, Bash --- @@ -15,18 +14,27 @@ You are the technical-foundation reviewer for Optimization SDK guides. Follow th against its authority: - **Interface** (a symbol's existence, signature, prop/config-key names & types, optionality, union - shape, return type, import path) — verify directly against the types in `packages/**/src`. Reading - source for interface is expected and cheap; a mismatch is a guide bug → correction to the writer. + shape, return type, import path) — verify directly against the relevant source/types. For + Optimization SDK claims, use this repo's `packages/**/src`. For legacy experience.js migration + claims, use the adjacent checkout at `/Users/charles.hudson/Projects/contentful/experience.js`. + Reading source for interface is expected and cheap; a mismatch is a guide bug → correction to the + writer. - **Behavior** (fallback contracts, dynamic-render forcing, batching/chunking, defaults, identifier - ownership, cross-SDK semantics) — confirm against the knowledge base - (`documentation/internal/sdk-knowledge/`); a claim is **confirmed** when a matching fact exists and - `pnpm knowledge:check` passes, **contradicted** when the base says otherwise (guide bug → writer). - Do NOT re-trace behavior from source. A behavioral claim with **no backing fact** escalates to the - **`sdk-knowledge-author`** — either the base is missing a fact it should hold, or the claim is - unfounded and comes out of the guide. (An unbacked interface claim is not an escalation — you just - checked it against the types.) + ownership, cross-SDK semantics) — confirm against the appropriate fact store. Optimization SDK + behavior comes from `documentation/internal/sdk-knowledge/`; legacy experience.js behavior comes + from `documentation/internal/migration-knowledge/`. A claim is **confirmed** when a matching fact + exists, **contradicted** when the fact store says otherwise (guide bug → writer), and + **behavioral-no-backing-fact** when no matching fact exists. Do NOT re-trace behavior from either + source repo during verification. + +Escalate behavioral gaps to the owner of the missing fact store: + +- Optimization SDK behavior gaps → **`sdk-knowledge-author`**. +- Legacy experience.js behavior gaps → **`experience-js-migration-knowledge-author`**. + +An unbacked interface claim is not an escalation — you just checked it against source/types. You do not edit the knowledge base or the guide. Return a per-claim verdict (interface or behavior; confirmed / contradicted / behavioral-no-backing-fact) with evidence — `file:symbol` for interface, -the KB fact for behavior — guide corrections routed to the writer, behavioral fact gaps to the -knowledge author. +the KB or migration-knowledge fact for behavior — guide corrections routed to the writer, behavioral +fact gaps routed to the matching knowledge author. diff --git a/.claude/agents/migration-guide-architect.md b/.claude/agents/migration-guide-architect.md new file mode 100644 index 000000000..dfc34237e --- /dev/null +++ b/.claude/agents/migration-guide-architect.md @@ -0,0 +1,29 @@ +--- +name: migration-guide-architect +description: >- + Create or revise the authoring structure for experience.js -> Optimization SDK Suite migration + guides. Use after legacy migration facts exist and before guide-writer drafts reader-facing + migration prose. +tools: Read, Grep, Glob, Edit, Write, Bash +--- + +You are the migration guide architect for the Optimization SDK Suite. Follow the +**`optimization-guide-authoring`** skill for guide-authoring ownership boundaries, but work at the +authoring-input layer rather than drafting the public guide. + +Create or revise the minimal recipe/blueprint structure needed for an `experience.js` -> +Optimization SDK Suite migration guide. The recipe owns the migration-guide spine and repeatable +rules. The blueprint owns this migration's reader goal, quick-start proof, section map, teaching +priorities, and links to fact sources. Keep SDK behavior out of recipes and blueprints; route legacy +behavior to `documentation/internal/migration-knowledge/` and Optimization behavior to +`documentation/internal/sdk-knowledge/`. + +Do not invent migration facts and do not re-trace SDK behavior. If the required legacy fact is +missing, hand it to `experience-js-migration-knowledge-author`. If the required Optimization fact is +missing, hand it to `sdk-knowledge-author`. Interface names may appear only as routing or section +labels; detailed signatures remain in source/types. + +Prefer the smallest structure that lets `guide-writer` draft the migration guide without guessing: +one migration recipe only if existing recipes do not fit, one migration blueprint for the +experience.js migration, and targeted updates to authoring indexes only when needed. You do not write +the public migration guide and you do not review guides. diff --git a/.codex/agents/experience-js-migration-knowledge-author.toml b/.codex/agents/experience-js-migration-knowledge-author.toml new file mode 100644 index 000000000..2f1d97b2f --- /dev/null +++ b/.codex/agents/experience-js-migration-knowledge-author.toml @@ -0,0 +1,32 @@ +name = "experience-js-migration-knowledge-author" +description = "Derive migration-relevant legacy experience.js facts from the adjacent experience.js workspace into this repo's internal migration knowledge. Use before authoring experience.js -> Optimization SDK Suite migration guides, or when a migration guide/review exposes a missing legacy behavior fact." +developer_instructions = """ +You are the experience.js migration knowledge author. Use the **`sdk-knowledge-maintenance`** skill +for fact-store discipline and the **`sdk-knowledge-authoring`** skill's behavior-vs-interface split, +but adapt it to the adjacent legacy repository instead of this repo's `packages/**/src`. + +Your source of truth is the adjacent checkout at +`/Users/charles.hudson/Projects/contentful/experience.js`. Inspect its git status and current commit +before deriving facts. If the checkout is missing, dirty in relevant files, or appears stale, report +that state and ask before updating or relying on it. Do not modify the adjacent repository. + +Record only migration-relevant legacy facts in this repository, under the migration knowledge area +requested by the task (for example `documentation/internal/migration-knowledge/experience-js.md`). +Keep facts terse and present-tense. Capture behavior and migration boundaries, not guide prose: +package mapping, provider/hooks/components, Contentful ExperienceMapper/content-model expectations, +Experience API event behavior, plugins, persistence/cookie/storage identifiers, SSR/Next.js paths, +preview behavior, and unsupported or manual migration gaps. + +Do not copy detailed signatures, prop lists, or return shapes unless they are needed as a compact +navigation index. Legacy interface shape can be looked up directly in the adjacent repo when a guide +writer or verifier needs it. Each legacy fact should carry enough provenance to re-check it later: +include the experience.js commit and source path/symbol, plus any external documentation URL only as +secondary context. Prefer source over public docs when they disagree. + +When migration work exposes a missing Optimization SDK behavior fact, do not fill it yourself. Hand +that specific gap to `sdk-knowledge-author`; Optimization SDK behavior belongs in +`documentation/internal/sdk-knowledge/`. + +Before finishing, report which migration facts you added or changed, the experience.js commit used, +and which migration guide or blueprint can consume the facts. You do not write public guide prose and +you do not review guides.""" diff --git a/.codex/agents/guide-source-verifier.toml b/.codex/agents/guide-source-verifier.toml index dc69d3e0f..893b6a26f 100644 --- a/.codex/agents/guide-source-verifier.toml +++ b/.codex/agents/guide-source-verifier.toml @@ -1,23 +1,32 @@ name = "guide-source-verifier" -description = "Verify a documentation guide's load-bearing SDK claims — the third authoring role — splitting each into interface vs. behavior. Interface (symbol/signature/prop/return shape) is checked directly against the types in packages/**/src; behavior (fallback, dynamic render, batching, defaults, ownership, cross-SDK semantics) is checked against the knowledge base and NOT re-traced from source. Behavioral gaps escalate to the sdk-knowledge-author. Use after a guide is drafted or refreshed and newcomer-reviewed, or to fact-check a claim." +description = "Verify a documentation guide's load-bearing SDK and migration claims — the third authoring role — splitting each into interface vs. behavior. Interface is checked directly against the relevant source/types; Optimization behavior is checked against the SDK knowledge base; legacy experience.js behavior is checked against migration knowledge. Behavioral gaps escalate to the matching knowledge author. Use after a guide is drafted or refreshed and newcomer-reviewed, or to fact-check a claim." developer_instructions = """ You are the technical-foundation reviewer for Optimization SDK guides. Follow the **`guide-source-verification`** skill. Split every load-bearing claim into two kinds and check each against its authority: - **Interface** (a symbol's existence, signature, prop/config-key names & types, optionality, union - shape, return type, import path) — verify directly against the types in `packages/**/src`. Reading - source for interface is expected and cheap; a mismatch is a guide bug → correction to the writer. + shape, return type, import path) — verify directly against the relevant source/types. For + Optimization SDK claims, use this repo's `packages/**/src`. For legacy experience.js migration + claims, use the adjacent checkout at `/Users/charles.hudson/Projects/contentful/experience.js`. + Reading source for interface is expected and cheap; a mismatch is a guide bug → correction to the + writer. - **Behavior** (fallback contracts, dynamic-render forcing, batching/chunking, defaults, identifier - ownership, cross-SDK semantics) — confirm against the knowledge base - (`documentation/internal/sdk-knowledge/`); a claim is **confirmed** when a matching fact exists and - `pnpm knowledge:check` passes, **contradicted** when the base says otherwise (guide bug → writer). - Do NOT re-trace behavior from source. A behavioral claim with **no backing fact** escalates to the - **`sdk-knowledge-author`** — either the base is missing a fact it should hold, or the claim is - unfounded and comes out of the guide. (An unbacked interface claim is not an escalation — you just - checked it against the types.) + ownership, cross-SDK semantics) — confirm against the appropriate fact store. Optimization SDK + behavior comes from `documentation/internal/sdk-knowledge/`; legacy experience.js behavior comes + from `documentation/internal/migration-knowledge/`. A claim is **confirmed** when a matching fact + exists, **contradicted** when the fact store says otherwise (guide bug → writer), and + **behavioral-no-backing-fact** when no matching fact exists. Do NOT re-trace behavior from either + source repo during verification. + +Escalate behavioral gaps to the owner of the missing fact store: + +- Optimization SDK behavior gaps → **`sdk-knowledge-author`**. +- Legacy experience.js behavior gaps → **`experience-js-migration-knowledge-author`**. + +An unbacked interface claim is not an escalation — you just checked it against source/types. You do not edit the knowledge base or the guide. Return a per-claim verdict (interface or behavior; confirmed / contradicted / behavioral-no-backing-fact) with evidence — `file:symbol` for interface, -the KB fact for behavior — guide corrections routed to the writer, behavioral fact gaps to the -knowledge author.""" +the KB or migration-knowledge fact for behavior — guide corrections routed to the writer, behavioral +fact gaps routed to the matching knowledge author.""" diff --git a/.codex/agents/migration-guide-architect.toml b/.codex/agents/migration-guide-architect.toml new file mode 100644 index 000000000..eb6f8801d --- /dev/null +++ b/.codex/agents/migration-guide-architect.toml @@ -0,0 +1,23 @@ +name = "migration-guide-architect" +description = "Create or revise the authoring structure for experience.js -> Optimization SDK Suite migration guides. Use after legacy migration facts exist and before guide-writer drafts reader-facing migration prose." +developer_instructions = """ +You are the migration guide architect for the Optimization SDK Suite. Follow the +**`optimization-guide-authoring`** skill for guide-authoring ownership boundaries, but work at the +authoring-input layer rather than drafting the public guide. + +Create or revise the minimal recipe/blueprint structure needed for an `experience.js` -> +Optimization SDK Suite migration guide. The recipe owns the migration-guide spine and repeatable +rules. The blueprint owns this migration's reader goal, quick-start proof, section map, teaching +priorities, and links to fact sources. Keep SDK behavior out of recipes and blueprints; route legacy +behavior to `documentation/internal/migration-knowledge/` and Optimization behavior to +`documentation/internal/sdk-knowledge/`. + +Do not invent migration facts and do not re-trace SDK behavior. If the required legacy fact is +missing, hand it to `experience-js-migration-knowledge-author`. If the required Optimization fact is +missing, hand it to `sdk-knowledge-author`. Interface names may appear only as routing or section +labels; detailed signatures remain in source/types. + +Prefer the smallest structure that lets `guide-writer` draft the migration guide without guessing: +one migration recipe only if existing recipes do not fit, one migration blueprint for the +experience.js migration, and targeted updates to authoring indexes only when needed. You do not write +the public migration guide and you do not review guides.""" diff --git a/documentation/authoring/README.md b/documentation/authoring/README.md index 4e1a496d2..2a16e8afc 100644 --- a/documentation/authoring/README.md +++ b/documentation/authoring/README.md @@ -16,6 +16,8 @@ run and prevents an editorial iteration from repeating expensive SDK comprehensi - **Refresh docs** when SDK source changed. - **Iterate on a guide** for wording, structure, sequence, or shared-copy changes. - **Review a guide** to check an existing or newly written guide before shipping it. +- **Author a migration guide** when an existing integration is moving from a legacy SDK to the + Optimization SDK Suite. ### Run the workflows @@ -62,6 +64,20 @@ Use this workflow as the final quality gate or when an existing claim looks susp 3. Use `sdk-knowledge-authoring` only when verification exposes a genuine behavioral gap in the KB. 4. Run `pnpm knowledge:check` and `pnpm guides:check` after resolving the findings. +#### Author a migration guide + +Use this workflow when a guide moves an existing integration from a legacy SDK to the Optimization +SDK Suite. + +1. Read the migration recipe, the matching migration blueprint, and the migration knowledge file. +2. Read the target SDK blueprint and KB sections linked by the migration blueprint. +3. Draft the guide with `optimization-guide-authoring` and the guide-writer role. Keep runtime + migrations split; use the plugin/privacy/preview and content-model migration guides for shared + replacement work. +4. Hand missing legacy facts to the migration-knowledge author and missing target behavior facts to + `sdk-knowledge-authoring`; do not invent either in the guide. +5. Run the newcomer and technical-foundation reviews before shipping. + The relevant skill instructions are: - [`optimization-guide-authoring`](../../skills/optimization-guide-authoring/SKILL.md) — teaching @@ -125,6 +141,7 @@ do not make the technical writer trace implementation behavior. | ------------------------------------------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------- | | **Recipe** (`recipes/`) | Archetype-wide section spine, allowed categories, and output rules | Per-SDK section choices or SDK facts | | **Blueprint** (`blueprints/`) | One SDK's reader goal, quick-start proof, milestones, section plan, and teaching priorities | SDK behavior, detailed API shape, or reader-facing prose | +| **Migration blueprint** (`migration-blueprints/`) | One legacy-to-target guide's route, guide order, section plan, and fact links | Legacy facts, SDK behavior, detailed API shape, or prose | | **Shared copy** (`fragments/`) | Reader-facing wording that must stay consistent across several guides | Hidden per-SDK decisions or SDK behavior | | **Knowledge base** (`../internal/sdk-knowledge/`) | Verified behavioral facts and provenance | Editorial sequence or guide prose | | **Types/source** (`../../packages/`) | Exact interface shape: exports, signatures, props, and returns | Editorial decisions | @@ -222,6 +239,38 @@ blueprint-to-guide section and category agreement. `pnpm knowledge:check` valida integrity and unresolved escalations. Neither command proves behavioral truth; semantic freshness comes from the scoped knowledge-author and technical-foundation reviews. +Migration blueprints live in `migration-blueprints/` instead of `blueprints/` because the current +`guides:check` contract treats every file in `blueprints/` as a rendered integration guide blueprint. +Until migration guides are added to that validator, validate migration authoring inputs with +Prettier, link review, and the migration-blueprint structural review below. + +### Migration-blueprint structural review + +Apply this review to every migration blueprint before handing it to a guide writer. It is the +dedicated migration-blueprint check until `guides:check` covers migration guides. + +- Frontmatter has `migration`, `archetype: migration`, `source`, and `guide`; the `guide` link and + Reader goal guide file name agree. +- Reader goal names the legacy state, target result, guide file, and guide order dependency. +- Migration route covers each legacy surface the guide promises to replace and assigns a target route + plus a detail owner; unsupported or manual replacements route to migration knowledge, not guessed + guide prose. +- Section plan rows match the intended `###` sections. Each row has a reader purpose, and the Must + route to column names the target guide, supplemental guide, concept, or decision the writer should + use. +- Fact sources are links only; legacy behavior points to migration knowledge, target behavior points + to SDK KB or shared concepts, and interface details are left for source/types lookup while writing. +- Cross-guide prerequisites are explicit: content-model migration before runtime rendering when + legacy mapper output is involved; plugin/privacy/preview migration after the app has a target + runtime. +- Validation obligations include a first performable authored variant or baseline check, accepted and + blocked event observability when events/consent are touched, first-page-event ownership when server + and browser can both report it, and cache/dynamic consequences for request-bound personalization. +- Ownership boundaries are explicit for app-owned config, consent records, vendor forwarding, manual + Node/Web hybrid cookies, and SDK-owned target profile cookies. +- Handoffs list only missing legacy or target behavior facts; no `None.` if a section relies on an + unlinked behavior claim. + Blueprints and KB files currently cover the six JS/TS integration guides — Node, Web, React Web, Next.js App Router, Next.js Pages Router, and React Native — plus the two native iOS guides (SwiftUI and UIKit) and the two native Android guides (Jetpack Compose and XML Views). Each native family @@ -239,6 +288,7 @@ recipes/ integration.md decision.md supplemental-recipe.md + migration.md blueprints/ _template.md node.md @@ -255,4 +305,14 @@ fragments/ personalization-explainer.md authored-variant-gotcha.md optimization-client-id.md +migration-blueprints/ + _template.md + experience-js-web.md + experience-js-react-web.md + experience-js-nextjs-router-choice.md + experience-js-nextjs-app-router.md + experience-js-nextjs-pages-router.md + experience-js-node-ssr-esr.md + experience-js-plugins-preview-privacy.md + experience-js-contentful-model.md ``` diff --git a/documentation/authoring/migration-blueprints/_template.md b/documentation/authoring/migration-blueprints/_template.md new file mode 100644 index 000000000..9b424014a --- /dev/null +++ b/documentation/authoring/migration-blueprints/_template.md @@ -0,0 +1,53 @@ +--- +migration: experience-js +archetype: migration +source: ../../internal/migration-knowledge/experience-js.md +guide: ../../guides/migrating--to-.md +--- + +# blueprint + +## Reader goal + +- **Use when:** +- **Target result:** +- **Guide file:** `documentation/guides/.md` +- **Write after:** +- **First verification:** + +## Migration route + +| Legacy surface | Target route | Detail owner | +| -------------- | ------------ | ------------ | +| ... | ... | ... | + +## Section plan + +Each row becomes one migration section. Link to facts; do not restate behavior. + +| Section | Purpose | Must route to | Fact sources | +| ------- | ------- | ------------- | ------------ | +| ... | ... | ... | ... | + +Every applicable section plan must make these ownership and sequence checks explicit: + +- Content-model migration comes before runtime rendering when legacy `nt_*` fields, mapper output, + or runtime experience configuration arrays are involved. +- Runtime configuration, consent records, first page events, analytics forwarding, preview behavior, + and profile/cookie continuity each have one owner. +- Event migrations include accepted-event evidence and blocked-event diagnostics when the target + runtime supports them. +- Server and framework migrations name per-request dynamic or cache consequences before rendering + replacement. +- Manual Node/Web hybrid migrations keep cookie read/write/clear behavior app-owned and route + framework-cookie behavior to the framework guide or SDK KB. + +## Handoffs + +List missing legacy or target behavior facts. Write `None.` when there are no known gaps. + +## Link roles + +Use exact links for existing target guides, supplemental guides, concepts, and maintained reference +implementations. Planned sibling migration guides may be named as code until they exist. diff --git a/documentation/authoring/migration-blueprints/experience-js-contentful-model.md b/documentation/authoring/migration-blueprints/experience-js-contentful-model.md new file mode 100644 index 000000000..b23675fa7 --- /dev/null +++ b/documentation/authoring/migration-blueprints/experience-js-contentful-model.md @@ -0,0 +1,48 @@ +--- +migration: experience-js +archetype: migration +source: ../../internal/migration-knowledge/experience-js.md +guide: ../../guides/migrating-experience-js-contentful-model-to-optimization.md +--- + +# experience.js Contentful model migration blueprint + +## Reader goal + +- **Use when:** A space uses Ninetailed Contentful experience entries, mapper utilities, or + baseline entries linked to legacy `nt_*` fields. +- **Target result:** The reader understands which authored data must move to the Optimization + content model before runtime migration can verify personalized entries. +- **Guide file:** `documentation/guides/migrating-experience-js-contentful-model-to-optimization.md` +- **Write after:** None; runtime migration guides should link here before entry-rendering steps. +- **First verification:** One baseline entry and one authored all-visitors variant resolve in a + target runtime before broader runtime migration. + +## Migration route + +| Legacy surface | Target route | Detail owner | +| --------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------- | +| `@ninetailed/experience.js-utils-contentful` mapper | Built-in Optimization content model and target entry resolution | Runtime integration guide rendering sections | +| Legacy `nt_*` fields | Optimization-authored experiences and variants | Contentful product documentation plus target rendering KB | +| Custom mapper functions | App-owned content adaptation before target SDK entry resolution | This migration guide plus runtime rendering sections | + +## Section plan + +| Section | Purpose | Must route to | Fact sources | +| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Inventory legacy authored entries and mapper usage | Find the Contentful data that cannot be preserved as runtime config arrays. | Legacy model and mapper facts. | [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries) | +| Plan target Optimization authoring | Route authoring work to Contentful product docs and target SDK entry-resolution requirements. | Existing Contentful personalization docs and target rendering guide sections. | [entry-source boundary](../../internal/sdk-knowledge/shared/concepts.md#entry-source-boundary-managed-or-manual), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution), [baseline fallback](../../internal/sdk-knowledge/shared/concepts.md#baseline-fallback) | +| Replace mapper-dependent runtime code | Stop building legacy `ExperienceConfiguration` arrays and route runtime code to target SDK entry resolution. | Runtime-specific Web, React Web, Next.js, or Node rendering sections. | [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [Web rendering](../../internal/sdk-knowledge/web/web.md#render--entry-resolution), [React Web rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution), [Node rendering](../../internal/sdk-knowledge/node/node.md#render--entry-resolution) | +| Validate content migration before code migration | Make the first runtime verification performable by checking authored variants and fallback expectations. | Target runtime quick start and troubleshooting. | [baseline fallback](../../internal/sdk-knowledge/shared/concepts.md#baseline-fallback), [Web fallback](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior), [React Web fallback](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior), [Node fallback](../../internal/sdk-knowledge/node/node.md#failure--fallback-behavior) | + +## Handoffs + +None. + +## Link roles + +- [Entry personalization and variant resolution concept](../../concepts/entry-personalization-and-variant-resolution.md). +- [Web SDK integration guide](../../guides/integrating-the-web-sdk-in-a-web-app.md). +- [React Web integration guide](../../guides/integrating-the-react-web-sdk-in-a-react-app.md). +- [Node SDK integration guide](../../guides/integrating-the-node-sdk-in-a-node-app.md). +- [Contentful personalization authoring](https://www.contentful.com/developers/docs/personalization/). diff --git a/documentation/authoring/migration-blueprints/experience-js-nextjs-app-router.md b/documentation/authoring/migration-blueprints/experience-js-nextjs-app-router.md new file mode 100644 index 000000000..9cc06ee40 --- /dev/null +++ b/documentation/authoring/migration-blueprints/experience-js-nextjs-app-router.md @@ -0,0 +1,51 @@ +--- +migration: experience-js +archetype: migration +source: ../../internal/migration-knowledge/experience-js.md +guide: ../../guides/migrating-experience-js-next-to-nextjs-app-router.md +--- + +# experience.js Next.js to App Router migration blueprint + +## Reader goal + +- **Use when:** A Next.js App Router app carries legacy Next.js, ESR, SSR plugin, or React + experience.js wiring. +- **Target result:** The app uses the App Router SDK for request context, server first paint, + browser takeover, and policy-dependent client features. +- **Guide file:** `documentation/guides/migrating-experience-js-next-to-nextjs-app-router.md` +- **Write after:** `choosing-a-nextjs-migration-path-from-experience-js.md`. +- **First verification:** One dynamic App Router route renders an all-visitors variant and verifies + both accepted and denied-consent event paths. + +## Migration route + +| Legacy surface | Target route | Detail owner | +| -------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | +| Legacy Next provider and tracker | Bound App Router root, handler, and page tracker | [App Router blueprint](../blueprints/nextjs-app-router.md) | +| SSR/ESR profile helpers | App Router request context and profile cookie | [App Router identifiers](../../internal/sdk-knowledge/web/nextjs-app-router.md#identifier-ownership) | +| React render hooks/components | App Router `OptimizedEntry` and browser takeover | [App Router rendering](../../internal/sdk-knowledge/web/nextjs-app-router.md#render--entry-resolution) | +| Legacy plugins | Supplemental plugin migration | `migrating-experience-js-plugins-and-preview.md` | + +## Section plan + +| Section | Purpose | Must route to | Fact sources | +| ----------------------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Remove legacy Next.js package assumptions | Prevent writers from treating ESR middleware/selector source as supported imports. | Legacy package mapping and unsupported boundaries. | [package mapping](../../internal/migration-knowledge/experience-js.md#package-mapping), [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries) | +| Install and bind the App Router SDK | Restore the App Router request/root shape before replacing render calls. | App Router setup, package, and runtime sections. | [package](../../internal/sdk-knowledge/web/nextjs-app-router.md#package--entry-points), [setup](../../internal/sdk-knowledge/web/nextjs-app-router.md#setup--initialization-and-binding), [runtime](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks) | +| Replace SSR/ESR profile continuity | Move profile cookie and request evaluation to target request context with app-owned policy. | Request context, identifiers, consent, and first-event ownership. | [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [legacy identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence), [target identifiers](../../internal/sdk-knowledge/web/nextjs-app-router.md#identifier-ownership), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking) | +| Replace server-rendered personalization | Route legacy mapped experiences and React wrappers to target entry resolution and server first paint. | App Router rendering and content-model migration. | [React legacy](../../internal/migration-knowledge/experience-js.md#react-render-and-hooks), [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [rendering](../../internal/sdk-knowledge/web/nextjs-app-router.md#render--entry-resolution), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) | +| Replace browser takeover features | Move flags, analytics forwarding, preview, and live updates through the target client runtime. | App Router browser/client sections and supplemental plugin guide. | [plugins](../../internal/migration-knowledge/experience-js.md#plugins-and-preview), [flag views](../../internal/sdk-knowledge/shared/concepts.md#custom-flag-views), [event streams](../../internal/sdk-knowledge/shared/concepts.md#stateful-event-forwarding-streams), [preview](../../internal/sdk-knowledge/shared/concepts.md#preview-overrides), [live updates](../../internal/sdk-knowledge/shared/concepts.md#live-updates) | +| Validate App Router migration | Verify server HTML, hydration stability, profile continuity, event ownership, and cache safety. | App Router production checks and troubleshooting. | [runtime](../../internal/sdk-knowledge/web/nextjs-app-router.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/web/nextjs-app-router.md#failure--fallback-behavior), [events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking) | + +## Handoffs + +None. + +## Link roles + +- [Next.js App Router integration guide](../../guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md). +- [Profile synchronization concept](../../concepts/profile-synchronization-between-client-and-server.md). +- [App Router reference implementation](../../../implementations/nextjs-sdk_app-router/README.md). +- [Contentful model migration guide](../../guides/migrating-experience-js-contentful-model-to-optimization.md). +- [Plugin, privacy, analytics, and preview migration guide](../../guides/migrating-experience-js-plugins-and-preview.md). diff --git a/documentation/authoring/migration-blueprints/experience-js-nextjs-pages-router.md b/documentation/authoring/migration-blueprints/experience-js-nextjs-pages-router.md new file mode 100644 index 000000000..9d54148fe --- /dev/null +++ b/documentation/authoring/migration-blueprints/experience-js-nextjs-pages-router.md @@ -0,0 +1,51 @@ +--- +migration: experience-js +archetype: migration +source: ../../internal/migration-knowledge/experience-js.md +guide: ../../guides/migrating-experience-js-next-to-nextjs-pages-router.md +--- + +# experience.js Next.js to Pages Router migration blueprint + +## Reader goal + +- **Use when:** A Pages Router app uses `@ninetailed/experience.js-next`, SSR plugin behavior, or + legacy React surfaces. +- **Target result:** The app uses the Pages Router SDK for server data functions, provider wiring, + page tracking, and target entry resolution. +- **Guide file:** `documentation/guides/migrating-experience-js-next-to-nextjs-pages-router.md` +- **Write after:** `choosing-a-nextjs-migration-path-from-experience-js.md`. +- **First verification:** One Pages Router server data path renders an all-visitors variant and + verifies accepted server evaluation plus denied-consent behavior. + +## Migration route + +| Legacy surface | Target route | Detail owner | +| ------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| Next provider/tracker | Pages Router provider and page tracker | [Pages Router blueprint](../blueprints/nextjs-pages-router.md) | +| SSR plugin profile continuity | Pages Router request context and profile cookie | [Pages identifiers](../../internal/sdk-knowledge/web/nextjs-pages-router.md#identifier-ownership) | +| React components and hooks | Pages Router `OptimizedEntry` and React Web hooks | [Pages rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution) | +| Third-party plugin integrations | Supplemental plugin migration | `migrating-experience-js-plugins-and-preview.md` | + +## Section plan + +| Section | Purpose | Must route to | Fact sources | +| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Inventory legacy Pages Router wiring | Identify provider placement, tracker override, SSR plugin use, and profile cookie assumptions. | Legacy Next.js and identifiers. | [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence) | +| Install and bind the Pages Router SDK | Establish target package, factory, server props, and provider before rendering changes. | Pages setup and runtime sections. | [package](../../internal/sdk-knowledge/web/nextjs-pages-router.md#package--entry-points), [setup](../../internal/sdk-knowledge/web/nextjs-pages-router.md#setup--initialization-and-binding), [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks) | +| Replace server profile and page evaluation | Map SSR plugin and tracker behavior to target request context and duplicate-event controls. | Pages events, identifiers, and consent. | [legacy SSR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [target identifiers](../../internal/sdk-knowledge/web/nextjs-pages-router.md#identifier-ownership), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking), [page events](../../internal/sdk-knowledge/shared/concepts.md#page-events) | +| Replace personalized rendering | Move legacy React components and Contentful mapper output to Pages Router entry resolution. | Pages rendering and content-model migration. | [React legacy](../../internal/migration-knowledge/experience-js.md#react-render-and-hooks), [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [rendering](../../internal/sdk-knowledge/web/nextjs-pages-router.md#render--entry-resolution), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) | +| Replace client-side extras | Route flags, analytics forwarding, preview, and consent diagnostics through React Web client behavior. | Pages/React Web events plus supplemental plugin guide. | [plugins](../../internal/migration-knowledge/experience-js.md#plugins-and-preview), [flag views](../../internal/sdk-knowledge/shared/concepts.md#custom-flag-views), [event streams](../../internal/sdk-knowledge/shared/concepts.md#stateful-event-forwarding-streams), [preview](../../internal/sdk-knowledge/shared/concepts.md#preview-overrides) | +| Validate Pages Router migration | Verify server props, rendered variant/baseline, tracker ownership, profile continuity, and cache boundaries. | Pages production checks and troubleshooting. | [runtime](../../internal/sdk-knowledge/web/nextjs-pages-router.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/web/nextjs-pages-router.md#failure--fallback-behavior), [events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking) | + +## Handoffs + +None. + +## Link roles + +- [Next.js Pages Router integration guide](../../guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md). +- [Profile synchronization concept](../../concepts/profile-synchronization-between-client-and-server.md). +- [Pages Router reference implementation](../../../implementations/nextjs-sdk_pages-router/README.md). +- [Contentful model migration guide](../../guides/migrating-experience-js-contentful-model-to-optimization.md). +- [Plugin, privacy, analytics, and preview migration guide](../../guides/migrating-experience-js-plugins-and-preview.md). diff --git a/documentation/authoring/migration-blueprints/experience-js-nextjs-router-choice.md b/documentation/authoring/migration-blueprints/experience-js-nextjs-router-choice.md new file mode 100644 index 000000000..13c60badf --- /dev/null +++ b/documentation/authoring/migration-blueprints/experience-js-nextjs-router-choice.md @@ -0,0 +1,49 @@ +--- +migration: experience-js +archetype: migration +source: ../../internal/migration-knowledge/experience-js.md +guide: ../../guides/choosing-a-nextjs-migration-path-from-experience-js.md +--- + +# experience.js Next.js router-choice migration blueprint + +## Reader goal + +- **Use when:** A Next.js app uses `@ninetailed/experience.js-next`, + `@ninetailed/experience.js-next-esr`, or the SSR plugin and must choose App Router, Pages Router, + or a manual Node/Web hybrid target. +- **Target result:** The reader picks the right target migration guide before changing code. +- **Guide file:** `documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md` +- **Write after:** None. +- **First verification:** The reader can name the target router path, first-page-event owner, + cookie/consent owner, and whether request-bound personalization can be dynamic. + +## Migration route + +| Legacy surface | Target route | Detail owner | +| -------------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------- | +| Pages-Router-oriented Next package | Pages Router SDK migration | `migrating-experience-js-next-to-nextjs-pages-router.md` | +| App Router application using legacy wrappers | App Router SDK migration | `migrating-experience-js-next-to-nextjs-app-router.md` | +| ESR helpers or SSR plugin | App Router SDK where possible; otherwise manual Node/Web hybrid | This decision guide plus Node/Web migration guides | + +## Section plan + +| Section | Purpose | Must route to | Fact sources | +| ------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Identify the current Next.js integration | Classify router, SSR plugin, ESR helpers, and profile cookie usage before target selection. | Legacy Next.js, SSR, ESR, and identifier facts. | [package mapping](../../internal/migration-knowledge/experience-js.md#package-mapping), [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence) | +| Choose App Router, Pages Router, or manual hybrid | Give a default target by actual app router and supported target SDK package. | Target Next.js package guides and Node/Web fallback. | [App Router package](../../internal/sdk-knowledge/web/nextjs-app-router.md#package--entry-points), [Pages Router package](../../internal/sdk-knowledge/web/nextjs-pages-router.md#package--entry-points), [Node package](../../internal/sdk-knowledge/node/node.md#package--entry-points), [Web package](../../internal/sdk-knowledge/web/web.md#package--entry-points) | +| Route SSR and first page event ownership | Prevent duplicate or missing page evaluation when moving from legacy SSR plugin/tracker behavior. | Target guide sections for request context and first-event ownership. | [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [App events](../../internal/sdk-knowledge/web/nextjs-app-router.md#events--tracking), [Pages events](../../internal/sdk-knowledge/web/nextjs-pages-router.md#events--tracking), [page events](../../internal/sdk-knowledge/shared/concepts.md#page-events) | +| Route cookie and profile continuity | Keep continuity decisions explicit across legacy `ntaid` and target app-owned persistence. | Target identifier sections. | [identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence), [App identifiers](../../internal/sdk-knowledge/web/nextjs-app-router.md#identifier-ownership), [Pages identifiers](../../internal/sdk-knowledge/web/nextjs-pages-router.md#identifier-ownership) | + +## Handoffs + +None. + +## Link roles + +- [Next.js App Router integration guide](../../guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md). +- [Next.js Pages Router integration guide](../../guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md). +- [Node SDK integration guide](../../guides/integrating-the-node-sdk-in-a-node-app.md). +- [Web SDK integration guide](../../guides/integrating-the-web-sdk-in-a-web-app.md). +- [App Router migration guide](../../guides/migrating-experience-js-next-to-nextjs-app-router.md). +- [Pages Router migration guide](../../guides/migrating-experience-js-next-to-nextjs-pages-router.md). diff --git a/documentation/authoring/migration-blueprints/experience-js-node-ssr-esr.md b/documentation/authoring/migration-blueprints/experience-js-node-ssr-esr.md new file mode 100644 index 000000000..14d56f02d --- /dev/null +++ b/documentation/authoring/migration-blueprints/experience-js-node-ssr-esr.md @@ -0,0 +1,52 @@ +--- +migration: experience-js +archetype: migration +source: ../../internal/migration-knowledge/experience-js.md +guide: ../../guides/migrating-experience-js-node-ssr-and-esr.md +--- + +# experience.js Node, SSR, and ESR migration blueprint + +## Reader goal + +- **Use when:** A server integration uses `@ninetailed/experience.js-node`, SSR plugin helpers, ESR + helpers, or a manual server-to-browser handoff. +- **Target result:** The server uses the Node SDK for request-scoped evaluation and hands browser + continuity to the appropriate Web or framework SDK. +- **Guide file:** `documentation/guides/migrating-experience-js-node-ssr-and-esr.md` +- **Write after:** `choosing-a-nextjs-migration-path-from-experience-js.md` for Next.js apps; + otherwise None. +- **First verification:** One accepted request and one denied-consent request prove event results, + rendered content, cache boundaries, and any manual Node/Web cookie handoff. + +## Migration route + +| Legacy surface | Target route | Detail owner | +| ------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| `NinetailedAPIClient` | Node SDK request client and event calls | [Node blueprint](../blueprints/node.md) | +| SSR plugin profile handoff | App-owned cookie/profile continuity | [Node identifiers](../../internal/sdk-knowledge/node/node.md#identifier-ownership) | +| ESR helper preflight behavior | App Router SDK where possible; Node/Web hybrid otherwise | Next.js router-choice migration | +| Server-rendered content mapping | Node entry resolution or framework SDK rendering | [Node rendering](../../internal/sdk-knowledge/node/node.md#render--entry-resolution) | + +## Section plan + +| Section | Purpose | Must route to | Fact sources | +| -------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Inventory server profile and event ownership | Identify which server path creates profiles, emits page events, and persists visitor identity. | Legacy Node, SSR, ESR, and identifiers. | [package mapping](../../internal/migration-knowledge/experience-js.md#package-mapping), [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence) | +| Replace Node API client calls | Move server mutations and event handling to request-scoped Node SDK calls. | Node setup, context, events, and consent sections. | [legacy runtime](../../internal/migration-knowledge/experience-js.md#legacy-runtime-surfaces), [Node setup](../../internal/sdk-knowledge/node/node.md#setup--initialization-and-binding), [Node events](../../internal/sdk-knowledge/node/node.md#events--tracking), [Node consent](../../internal/sdk-knowledge/node/node.md#consent--persistence) | +| Replace SSR and ESR handoff | Pick framework SDK when available and keep manual hybrid as an escape hatch, not the default. | Next.js router-choice guide; Node/Web continuity sections. | [Next.js SSR ESR](../../internal/migration-knowledge/experience-js.md#nextjs-ssr-and-esr), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries), [Node identifiers](../../internal/sdk-knowledge/node/node.md#identifier-ownership), [Web identifiers](../../internal/sdk-knowledge/web/web.md#identifier-ownership) | +| Replace server content resolution | Move from legacy experience-config mapping to target entry resolution and fallback behavior. | Node rendering and content-model migration guide. | [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries), [Node rendering](../../internal/sdk-knowledge/node/node.md#render--entry-resolution), [entry resolution](../../internal/sdk-knowledge/shared/concepts.md#entry-resolution) | +| Validate server migration | Verify accepted or blocked events, profile continuity, rendered content, and cache safety. | Node production checks and troubleshooting. | [Node events](../../internal/sdk-knowledge/node/node.md#events--tracking), [Node fallback](../../internal/sdk-knowledge/node/node.md#failure--fallback-behavior), [Node runtime](../../internal/sdk-knowledge/node/node.md#version--runtime-quirks) | + +## Handoffs + +None. + +## Link roles + +- [Node SDK integration guide](../../guides/integrating-the-node-sdk-in-a-node-app.md). +- [Web SDK integration guide](../../guides/integrating-the-web-sdk-in-a-web-app.md). +- [Profile synchronization concept](../../concepts/profile-synchronization-between-client-and-server.md). +- [Node reference implementation](../../../implementations/node-sdk/README.md). +- [Node plus Web reference implementation](../../../implementations/node-sdk+web-sdk/README.md). +- [Next.js migration path decision guide](../../guides/choosing-a-nextjs-migration-path-from-experience-js.md). diff --git a/documentation/authoring/migration-blueprints/experience-js-plugins-preview-privacy.md b/documentation/authoring/migration-blueprints/experience-js-plugins-preview-privacy.md new file mode 100644 index 000000000..05f2f7af9 --- /dev/null +++ b/documentation/authoring/migration-blueprints/experience-js-plugins-preview-privacy.md @@ -0,0 +1,51 @@ +--- +migration: experience-js +archetype: migration +source: ../../internal/migration-knowledge/experience-js.md +guide: ../../guides/migrating-experience-js-plugins-and-preview.md +--- + +# experience.js plugins, privacy, analytics, and preview migration blueprint + +## Reader goal + +- **Use when:** An app uses experience.js analytics, privacy, preview, insights, or third-party + plugin packages. +- **Target result:** The app replaces plugin packages with target SDK consent policy, event streams, + preview panel, insights behavior, and vendor-specific forwarding code where needed. +- **Guide file:** `documentation/guides/migrating-experience-js-plugins-and-preview.md` +- **Write after:** At least one runtime migration guide for the app's target runtime. +- **First verification:** The target runtime shows one accepted event, one blocked diagnostic, and + preview behavior without legacy plugin globals. + +## Migration route + +| Legacy surface | Target route | Detail owner | +| --------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | +| Privacy plugin | App-owned consent plus target SDK consent inputs | Runtime integration guide consent sections | +| Analytics and vendor plugins | Event-stream forwarding or target insights | [Analytics forwarding guide](../../guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md) | +| Preview plugin and preview bridge | Optimization preview panel and override behavior | Target runtime preview sections | +| Insights plugin batching | Target event delivery and forwarding seams | Runtime integration guide events sections | + +## Section plan + +| Section | Purpose | Must route to | Fact sources | +| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Inventory plugin packages and globals | Make plugin replacement explicit and prevent one-for-one package assumptions. | Legacy plugin and unsupported-boundary facts. | [plugins](../../internal/migration-knowledge/experience-js.md#plugins-and-preview), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries) | +| Replace privacy plugin behavior | Route consent storage, accepted/blocked behavior, and queue expectations to target app-owned policy. | Runtime consent sections and blocked-event diagnostics. | [legacy identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence), [plugins](../../internal/migration-knowledge/experience-js.md#plugins-and-preview), [shared consent](../../internal/sdk-knowledge/shared/concepts.md#consent--persistence), [event streams](../../internal/sdk-knowledge/shared/concepts.md#stateful-event-forwarding-streams) | +| Replace analytics and tag-manager plugins | Route vendor forwarding to the supplemental analytics guide instead of recreating plugin packages. | Event stream forwarding, dedupe, blocked diagnostics, and vendor examples. | [plugins](../../internal/migration-knowledge/experience-js.md#plugins-and-preview), [event streams](../../internal/sdk-knowledge/shared/concepts.md#stateful-event-forwarding-streams), [Web events](../../internal/sdk-knowledge/web/web.md#events--tracking), [React Web events](../../internal/sdk-knowledge/web/react-web.md#events--tracking) | +| Replace preview plugin behavior | Route authoring/debugging behavior to the target preview panel and clearly identify parity boundaries. | Runtime preview sections and shared preview override facts. | [plugins](../../internal/migration-knowledge/experience-js.md#plugins-and-preview), [preview overrides](../../internal/sdk-knowledge/shared/concepts.md#preview-overrides), [Web runtime](../../internal/sdk-knowledge/web/web.md#version--runtime-quirks), [React Web runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks) | +| Validate plugin migration | Verify consent-blocked diagnostics, accepted forwarding, preview attachment, and removed legacy globals/packages. | Runtime guide production checks and analytics supplemental validation. | [event streams](../../internal/sdk-knowledge/shared/concepts.md#stateful-event-forwarding-streams), [preview overrides](../../internal/sdk-knowledge/shared/concepts.md#preview-overrides), [Web consent](../../internal/sdk-knowledge/web/web.md#consent--persistence), [React Web consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence) | + +## Handoffs + +None. + +## Link roles + +- [Analytics forwarding supplemental guide](../../guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md). +- [Consent management concept](../../concepts/consent-management-in-the-optimization-sdk-suite.md). +- [Web SDK integration guide](../../guides/integrating-the-web-sdk-in-a-web-app.md). +- [React Web integration guide](../../guides/integrating-the-react-web-sdk-in-a-react-app.md). +- [Next.js App Router integration guide](../../guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md). +- [Next.js Pages Router integration guide](../../guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md). diff --git a/documentation/authoring/migration-blueprints/experience-js-react-web.md b/documentation/authoring/migration-blueprints/experience-js-react-web.md new file mode 100644 index 000000000..185a21938 --- /dev/null +++ b/documentation/authoring/migration-blueprints/experience-js-react-web.md @@ -0,0 +1,52 @@ +--- +migration: experience-js +archetype: migration +source: ../../internal/migration-knowledge/experience-js.md +guide: ../../guides/migrating-experience-js-react-to-react-web.md +--- + +# experience.js React to React Web migration blueprint + +## Reader goal + +- **Use when:** A React app uses `@ninetailed/experience.js-react` provider, hooks, components, or + flags. +- **Target result:** The app uses the React Web provider, `OptimizedEntry`, target hooks, and + app-owned consent/identity policy. +- **Guide file:** `documentation/guides/migrating-experience-js-react-to-react-web.md` +- **Write after:** `migrating-experience-js-to-the-web-sdk.md` only if the app also owns a custom + lower-level Web adapter. +- **First verification:** One page event is accepted or intentionally blocked and one all-visitors + `OptimizedEntry` renders a variant or baseline fallback. + +## Migration route + +| Legacy surface | Target route | Detail owner | +| -------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------- | +| `NinetailedProvider` | React Web `OptimizationProvider` composition | [React Web blueprint](../blueprints/react-web.md) | +| `Personalize`, `Experience`, and hooks | `OptimizedEntry` and target state hooks | [React Web rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution) | +| Legacy flag hooks | Target flag reads and manual flag-view route | [Shared flag views](../../internal/sdk-knowledge/shared/concepts.md#custom-flag-views) | +| Legacy plugin behavior | Supplemental plugin/privacy/preview migration | `migrating-experience-js-plugins-and-preview.md` | + +## Section plan + +| Section | Purpose | Must route to | Fact sources | +| ------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Inventory provider, hooks, and render components | Separate root wiring from component-level replacement work. | Legacy React facts and target provider setup. | [React legacy](../../internal/migration-knowledge/experience-js.md#react-render-and-hooks), [setup](../../internal/sdk-knowledge/web/react-web.md#setup--initialization-and-binding), [components](../../internal/sdk-knowledge/web/react-web.md#components--hooks) | +| Replace the provider and readiness model | Move root setup and async readiness without keeping legacy global assumptions. | React Web provider and readiness sections. | [legacy runtime](../../internal/migration-knowledge/experience-js.md#legacy-runtime-surfaces), [runtime](../../internal/sdk-knowledge/web/react-web.md#version--runtime-quirks), [fallback](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior) | +| Replace personalized entry rendering | Route legacy component/hook rendering to entry resolution and target render props. | React Web entry rendering guide section plus content-model migration guide. | [React legacy](../../internal/migration-knowledge/experience-js.md#react-render-and-hooks), [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [rendering](../../internal/sdk-knowledge/web/react-web.md#render--entry-resolution) | +| Replace flags and tracking side effects | Make flag-view behavior and interaction tracking explicit instead of preserving hook names. | Flag-view facts, React Web events, and target hooks. | [React legacy](../../internal/migration-knowledge/experience-js.md#react-render-and-hooks), [flag views](../../internal/sdk-knowledge/shared/concepts.md#custom-flag-views), [events](../../internal/sdk-knowledge/web/react-web.md#events--tracking) | +| Replace consent, identity, analytics, and preview | Keep policy/vendor-specific work out of the core render migration. | React Web policy sections and supplemental plugin guide. | [identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence), [plugins](../../internal/migration-knowledge/experience-js.md#plugins-and-preview), [consent](../../internal/sdk-knowledge/web/react-web.md#consent--persistence), [preview](../../internal/sdk-knowledge/shared/concepts.md#preview-overrides) | +| Validate React migration | Verify provider placement, rendered variant/baseline, event stream, and absence of legacy wrappers. | React Web production checks and troubleshooting. | [events](../../internal/sdk-knowledge/web/react-web.md#events--tracking), [fallback](../../internal/sdk-knowledge/web/react-web.md#failure--fallback-behavior), [event streams](../../internal/sdk-knowledge/shared/concepts.md#stateful-event-forwarding-streams) | + +## Handoffs + +None. + +## Link roles + +- [React Web integration guide](../../guides/integrating-the-react-web-sdk-in-a-react-app.md). +- [Web SDK integration guide](../../guides/integrating-the-web-sdk-in-a-web-app.md). +- [React Web reference implementation](../../../implementations/react-web-sdk/README.md). +- [Contentful model migration guide](../../guides/migrating-experience-js-contentful-model-to-optimization.md). +- [Plugin, privacy, analytics, and preview migration guide](../../guides/migrating-experience-js-plugins-and-preview.md). diff --git a/documentation/authoring/migration-blueprints/experience-js-web.md b/documentation/authoring/migration-blueprints/experience-js-web.md new file mode 100644 index 000000000..20aa1c4d5 --- /dev/null +++ b/documentation/authoring/migration-blueprints/experience-js-web.md @@ -0,0 +1,49 @@ +--- +migration: experience-js +archetype: migration +source: ../../internal/migration-knowledge/experience-js.md +guide: ../../guides/migrating-experience-js-to-the-web-sdk.md +--- + +# experience.js to Web SDK migration blueprint + +## Reader goal + +- **Use when:** A plain browser or custom JavaScript adapter uses `@ninetailed/experience.js`. +- **Target result:** The app owns one Optimization Web SDK instance, emits page events, resolves + Contentful entries, and replaces legacy plugin behavior through current SDK seams. +- **Guide file:** `documentation/guides/migrating-experience-js-to-the-web-sdk.md` +- **Write after:** None. +- **First verification:** One accepted page event populates selections and one all-visitors + Optimization-authored entry renders before policy-dependent replacements. + +## Migration route + +| Legacy surface | Target route | Detail owner | +| --------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------ | +| Browser `Ninetailed` client | Web SDK setup, lifecycle, events, and state subscriptions | [Web integration blueprint](../blueprints/web.md) | +| Legacy profile globals and storage keys | Target identifier and persistence policy | [Web SDK KB](../../internal/sdk-knowledge/web/web.md#identifier-ownership) | +| `Personalize`-like manual rendering | Entry resolution and Web Components where useful | [Web SDK KB](../../internal/sdk-knowledge/web/web.md#render--entry-resolution) | +| Plugin analytics, privacy, and preview | Supplemental migration guides | `migrating-experience-js-plugins-and-preview.md` | + +## Section plan + +| Section | Purpose | Must route to | Fact sources | +| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Inventory the legacy browser integration | Help the reader find the single client, event calls, plugins, and Contentful resolution boundary before editing. | Legacy client, storage, and package mapping; target Web setup. | [package mapping](../../internal/migration-knowledge/experience-js.md#package-mapping), [legacy runtime](../../internal/migration-knowledge/experience-js.md#legacy-runtime-surfaces), [setup](../../internal/sdk-knowledge/web/web.md#setup--initialization-and-binding) | +| Replace client construction and lifecycle | Move from legacy client/plugin construction to one Web SDK instance without preserving legacy plugin arrays. | Web SDK integration guide lifecycle sections. | [legacy runtime](../../internal/migration-knowledge/experience-js.md#legacy-runtime-surfaces), [Web runtime](../../internal/sdk-knowledge/web/web.md#version--runtime-quirks) | +| Replace page, track, identify, and reset calls | Map imperative event calls and identity lifecycle to target event and consent ownership. | Web events, consent, identity, and reset sections. | [legacy runtime](../../internal/migration-knowledge/experience-js.md#legacy-runtime-surfaces), [identifiers](../../internal/migration-knowledge/experience-js.md#identifiers-and-persistence), [events](../../internal/sdk-knowledge/web/web.md#events--tracking), [consent](../../internal/sdk-knowledge/web/web.md#consent--persistence) | +| Replace entry and flag rendering | Move legacy mapped experience configs and flag reads to Optimization content-model entry resolution and flag APIs. | Web rendering plus content-model migration guide for authored data. | [content model](../../internal/migration-knowledge/experience-js.md#contentful-model-and-mapper), [unsupported boundaries](../../internal/migration-knowledge/experience-js.md#unsupported-or-manual-migration-boundaries), [rendering](../../internal/sdk-knowledge/web/web.md#render--entry-resolution), [flag views](../../internal/sdk-knowledge/shared/concepts.md#custom-flag-views) | +| Validate browser migration | Verify rendered variant or baseline, accepted events, blocked diagnostics, and removal of legacy packages. | Web production checks and troubleshooting. | [events](../../internal/sdk-knowledge/web/web.md#events--tracking), [fallback](../../internal/sdk-knowledge/web/web.md#failure--fallback-behavior), [event streams](../../internal/sdk-knowledge/shared/concepts.md#stateful-event-forwarding-streams) | + +## Handoffs + +None. + +## Link roles + +- [Web SDK integration guide](../../guides/integrating-the-web-sdk-in-a-web-app.md). +- [Analytics forwarding supplemental guide](../../guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md). +- [Vanilla Web reference implementation](../../../implementations/web-sdk/README.md). +- [Plugin, privacy, analytics, and preview migration guide](../../guides/migrating-experience-js-plugins-and-preview.md). +- [Contentful model migration guide](../../guides/migrating-experience-js-contentful-model-to-optimization.md). diff --git a/documentation/authoring/recipes/migration.md b/documentation/authoring/recipes/migration.md new file mode 100644 index 000000000..872f5c1ac --- /dev/null +++ b/documentation/authoring/recipes/migration.md @@ -0,0 +1,83 @@ +--- +archetype: migration +filename: migrating-*.md +--- + +## Context + +For guides that move an existing integration from a legacy SDK to the Optimization SDK Suite. The +reader goal is not a clean install; it is replacing a working surface without preserving obsolete +architecture. The `guide-writer` renders the **Template** below; it never emits this Context. + +Use this recipe when an integration guide is too start-from-zero and a supplemental recipe is too +narrow. Keep the guide scoped to one runtime or one cross-runtime migration concern. Do not collapse +browser, React, Next.js, server, plugins, and content-model migration into one guide. + +The migration blueprint owns the guide file, source package route, target guide route, section plan, +fact routing, and unresolved handoffs. Legacy facts come from the migration knowledge base. Target +SDK facts come from the SDK knowledge base. Interface shapes still come from package source/types +while writing. + +Migration guides must make ownership changes visible before code changes. The reader should know +which layer owns Contentful authoring, runtime configuration, consent records, cookies, first page +events, analytics forwarding, preview behavior, and cache boundaries before deleting legacy packages. +Route those decisions to target guides, concepts, or supplemental migration guides instead of +recreating SDK facts here. + +For runtime migrations, put content-model prerequisites before rendering work when the app depends +on legacy `nt_*` fields, mapper output, or runtime experience configuration arrays. The first +performable content check should use one authored all-visitors or otherwise always-matching variant +so the reader can separate authoring/setup failures from audience-rule failures. + +For event, consent, and analytics migrations, require both accepted-event and blocked-event +observability when the target runtime supports it. Link runtime-specific consent sections rather +than generalizing consent mechanics in the migration guide. Blocked diagnostics are evidence, not a +replay queue. + +For server, framework, and hybrid migrations, require the guide to name the owner of the first page +event, the target profile-cookie boundary, and any per-request dynamic or cache consequence before +the rendering replacement. Manual Node/Web hybrids are app-owned at the cookie boundary; target +framework adapters may own more of that handoff. + +## Template + +# ⟨Migration guide title⟩ + +Use this guide when ⟨the legacy integration being replaced and the target result⟩. + +## What changes + +⟨A short routing summary: source package(s), target package(s), and the main ownership change. Link +to the target integration guide before detail.⟩ + +## Before you migrate + +⟨Inputs the reader gathers from the existing app and Contentful space: package usage, rendered +surfaces, consent/analytics policy, profile continuity, authored content, target SDK values, +configuration ownership, first-event ownership, and request/cache constraints.⟩ + +## Migration path + +⟨A numbered path that removes legacy wiring, installs target packages, restores the first working +result, then adds policy-dependent replacements. If legacy Contentful mapper output is involved, +route to content-model migration before runtime rendering. Each step links to the target integration +or supplemental guide when that guide owns the detailed procedure.⟩ + +## Replace legacy surfaces + +⟨Sections from the blueprint. Each section names the legacy surface, the target surface or manual +replacement, and the verification the reader should perform.⟩ + +## Validate the migration + +⟨Checks for target rendering, an authored all-visitors or always-matching variant, fallback, accepted +and blocked event evidence, consent, profile continuity, first-event ownership, cache/dynamic +boundaries, analytics forwarding, preview behavior, and dead legacy package removal as applicable.⟩ + +## Troubleshooting + +⟨Only reader-observable migration failures from the blueprint.⟩ + +## Related guides + +⟨Target integration guides, supplemental guides, and concepts.⟩ diff --git a/documentation/guides/README.md b/documentation/guides/README.md index ba768bcab..8c82c9500 100644 --- a/documentation/guides/README.md +++ b/documentation/guides/README.md @@ -7,6 +7,14 @@ children: - ./integrating-the-react-web-sdk-in-a-react-app.md - ./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md - ./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md + - ./migrating-experience-js-contentful-model-to-optimization.md + - ./migrating-experience-js-to-the-web-sdk.md + - ./migrating-experience-js-react-to-react-web.md + - ./choosing-a-nextjs-migration-path-from-experience-js.md + - ./migrating-experience-js-next-to-nextjs-app-router.md + - ./migrating-experience-js-next-to-nextjs-pages-router.md + - ./migrating-experience-js-node-ssr-and-esr.md + - ./migrating-experience-js-plugins-and-preview.md - ./integrating-the-react-native-sdk-in-a-react-native-app.md - ./integrating-the-optimization-ios-sdk-in-a-swiftui-app.md - ./integrating-the-optimization-ios-sdk-in-a-uikit-app.md @@ -53,6 +61,23 @@ Native and mobile SDK guides route to beta SDK surfaces. | [Android SDK in Jetpack Compose](./integrating-the-optimization-android-sdk-in-a-compose-app.md) | Native Android app built with Jetpack Compose | `com.contentful.java:optimization-android` | | [Android SDK in Android Views](./integrating-the-optimization-android-sdk-in-a-views-app.md) | Native Android app built with Android Views or XML layouts | `com.contentful.java:optimization-android` | +## experience.js migration guides + +Use these guides when an app already uses `@ninetailed/experience.js` packages and you are replacing +that integration with the Optimization SDK Suite. Start with the Contentful model guide when legacy +`nt_*` fields or mapper utilities feed personalized rendering. + +| Guide | Legacy integration type | Target path | +| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------- | +| [Contentful model migration](./migrating-experience-js-contentful-model-to-optimization.md) | Legacy Ninetailed-authored entries, `nt_*` fields, mapper utilities | Optimization-authored experiences and target entry resolution | +| [Web SDK migration](./migrating-experience-js-to-the-web-sdk.md) | Plain browser, static site, SPA, Angular, or custom JavaScript runtime | `@contentful/optimization-web` | +| [React Web migration](./migrating-experience-js-react-to-react-web.md) | React provider, hook, component, or flag surfaces | `@contentful/optimization-react-web` | +| [Next.js migration path choice](./choosing-a-nextjs-migration-path-from-experience-js.md) | Legacy Next.js, SSR plugin, ESR, or manual hybrid wiring | App Router, Pages Router, or manual Node/Web path | +| [Next.js App Router migration](./migrating-experience-js-next-to-nextjs-app-router.md) | Next.js app moving to App Router | `@contentful/optimization-nextjs/app-router` | +| [Next.js Pages Router migration](./migrating-experience-js-next-to-nextjs-pages-router.md) | Pages Router app with legacy SSR/plugin wiring | `@contentful/optimization-nextjs/pages-router` | +| [Node, SSR, and ESR migration](./migrating-experience-js-node-ssr-and-esr.md) | Server API-client, SSR, ESR, or manual handoff code | Node SDK, framework SDK, or manual Node/Web hybrid | +| [Plugins and preview migration](./migrating-experience-js-plugins-and-preview.md) | Legacy privacy, analytics, insights, or preview plugins | Target consent, event streams, forwarding, and preview panel | + ## Supplemental guides - [Building a custom JavaScript Optimization adapter](./building-a-custom-javascript-optimization-adapter.md) - diff --git a/documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md b/documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md new file mode 100644 index 000000000..aedb811db --- /dev/null +++ b/documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md @@ -0,0 +1,127 @@ +# Choosing a Next.js migration path from experience.js + +Use this guide when a Next.js app uses `@ninetailed/experience.js-next`, +`@ninetailed/experience.js-next-esr`, or SSR plugin behavior and you need to choose App Router, +Pages Router, or a manual Node/Web hybrid target before changing code. + +## What changes + +Legacy Next.js surfaces mix React provider behavior, route tracking, SSR profile continuity, and ESR +helpers. The Optimization SDK Suite splits the target by actual runtime: + +- App Router apps use `@contentful/optimization-nextjs/app-router`. +- Pages Router apps use `@contentful/optimization-nextjs/pages-router` and + `@contentful/optimization-nextjs/pages-router/server`. +- Non-Next or unsupported server-rendering shapes use `@contentful/optimization-node` on the server + plus Web or React Web in the browser. + +After choosing, follow the target migration guide instead of mixing router patterns. + +## Before you migrate + +Gather these inputs: + +- Whether the app renders through `app/`, `pages/`, or both. +- Use of `@ninetailed/experience.js-next`, `@ninetailed/experience.js-next-esr`, SSR plugin helpers, + route trackers, or `ntaid`. +- Where the first page event is emitted today: server, browser tracker, or both. +- Where visitor identity is persisted and whether the browser must continue the same profile. +- Whether the target route can be per-request dynamic. + +Use these terms consistently: + +- App Router means routes under `app/`, including Server Components and route handlers. +- Pages Router means routes under `pages/`, especially pages personalized in `getServerSideProps`. +- SSR plugin means legacy experience.js server-side profile and cookie behavior. +- ESR means legacy edge-side rendering helpers from `@ninetailed/experience.js-next-esr`. +- Manual Node/Web hybrid means the app uses the Node SDK on a custom server boundary and the Web or + React Web SDK in the browser. + +## Migration path + +1. Classify the current router and legacy SSR/ESR surfaces. +2. Choose the target package by the route that owns personalization. +3. Decide which layer owns the first page event. +4. Decide how profile continuity moves from `ntaid` to the target `ctfl-opt-aid` policy. +5. Follow the selected runtime migration guide. + +## Replace legacy surfaces + +### Identify the current Next.js integration + +Classify the app by the route that renders personalized content: + +- Use the App Router path when personalized content lives in `app/` routes or Server Components. +- Use the Pages Router path when personalized pages use `pages/` and `getServerSideProps`. +- Use a manual Node/Web hybrid only when the app has a custom server-rendering boundary that the + Next.js adapters do not cover. + +Do not treat unexported ESR middleware or selector source as supported import surfaces. Some ESR +helper files exist in the legacy package source but are not exported from the package entry, so they +were not stable import contracts. If the legacy integration depended on ESR helpers, prefer the App +Router SDK when the route can move there; otherwise treat the replacement as a manual Node/Web +handoff. + +### Choose App Router, Pages Router, or manual hybrid + +Decide whether personalized first paint may be per-request dynamic before choosing the adapter. App +Router server personalization reads request data and makes the affected route dynamic, so it is not +compatible with routes that must stay SSG or ISR. Pages Router `getServerSideProps` is already +per-request. A manual Node/Web hybrid has the same cache responsibility as any custom SSR path: +never share personalized output across visitors. + +| Current app shape | Target | +| ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| App Router owns the personalized route | [App Router migration](./migrating-experience-js-next-to-nextjs-app-router.md) | +| Pages Router and `getServerSideProps` own the personalized route | [Pages Router migration](./migrating-experience-js-next-to-nextjs-pages-router.md) | +| Custom SSR outside the Next.js adapters | [Node, SSR, and ESR migration](./migrating-experience-js-node-ssr-and-esr.md), then Web or React Web browser migration | + +Use the highest-level adapter that matches the app. The adapter owns the framework request, +provider, tracker, state handoff, and cookie mechanics that a manual hybrid would otherwise need to +rebuild. + +### Route SSR and first page event ownership + +Avoid duplicate page evaluation. Legacy Next tracking emitted page events on the first route and on +route changes, while SSR helpers could also evaluate the first request. + +In the target App Router path, the configured request handler can own the first page event for +matching requests. In the target Pages Router path, `getServerSideOptimizationProps()` can own the +first page event and pass an `initialPageEvent` value to the browser tracker. If the server did not +report a consented view, the browser tracker can emit the initial page event. + +### Route cookie and profile continuity + +Legacy continuity commonly used `ntaid`. Target Web, React Web, and Next.js browser/framework SDKs +use `ctfl-opt-aid` for the SDK-owned anonymous profile cookie. In a manual Node/Web hybrid, the Node +SDK only exports the `ANONYMOUS_ID_COOKIE` constant; app code must read, write, and clear that +cookie and pass the profile ID through `forRequest({ profile })`. Decide whether migration resets +visitor identity or whether the app reads the legacy cookie and writes the target continuity value +as a one-time operational handoff. + +The target consent record remains app-owned. Do not reuse `__nt-consent__` as if it were an SDK +contract. + +## Validate the migration + +- The selected guide matches the route that renders personalized content. +- Exactly one layer owns the first page event for the first route. +- The browser tracker skips or emits the first route intentionally. +- Cookie and consent ownership are documented in app code before deleting legacy packages. + +## Troubleshooting + +| Symptom | Check | +| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| Both server and browser emit the first page event | Use the target guide's initial-page-event skip path when server evaluation succeeded. | +| App Router route no longer behaves statically | Server personalization reads request data and makes the route dynamic; use Pages Router or a browser-only path if static output is required. | +| ESR migration has no matching import | The legacy ESR package did not export every helper present in source; use App Router or a manual Node/Web hybrid. | + +## Related guides + +- [Migrating experience.js Next.js to App Router](./migrating-experience-js-next-to-nextjs-app-router.md) +- [Migrating experience.js Next.js to Pages Router](./migrating-experience-js-next-to-nextjs-pages-router.md) +- [Migrating experience.js Node, SSR, and ESR](./migrating-experience-js-node-ssr-and-esr.md) +- [Next.js App Router integration guide](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md) +- [Next.js Pages Router integration guide](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) +- [Profile synchronization between client and server](../concepts/profile-synchronization-between-client-and-server.md) diff --git a/documentation/guides/migrating-experience-js-contentful-model-to-optimization.md b/documentation/guides/migrating-experience-js-contentful-model-to-optimization.md new file mode 100644 index 000000000..38b469ae9 --- /dev/null +++ b/documentation/guides/migrating-experience-js-contentful-model-to-optimization.md @@ -0,0 +1,145 @@ +# Migrating an experience.js Contentful model to Optimization + +Use this guide when a Contentful space uses Ninetailed-authored experience entries, legacy `nt_*` +fields, or `@ninetailed/experience.js-utils-contentful` mapper code and you need the authored +content ready for Optimization SDK rendering. + +## What changes + +The legacy mapper turns Ninetailed Contentful entries into runtime experience configuration arrays. +The Optimization SDK Suite reads Optimization-authored links from the entries you fetch and resolves +the entry at render time. Runtime guides own rendering; this guide owns the authoring inventory and +handoff. + +Before changing runtime code, read the target guide for your app: + +- [Web SDK](./integrating-the-web-sdk-in-a-web-app.md) +- [React Web SDK](./integrating-the-react-web-sdk-in-a-react-app.md) +- [Next.js App Router SDK](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md) +- [Next.js Pages Router SDK](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) +- [Node SDK](./integrating-the-node-sdk-in-a-node-app.md) + +## Before you migrate + +Gather these inputs from your existing app and Contentful space: + +- The content types and entries that contain legacy `nt_name`, `nt_description`, `nt_type`, + `nt_config`, `nt_audience`, `nt_variants`, `nt_experience_id`, or `nt_experiences` fields. +- Every call to `ExperienceMapper`, `mapCustomExperience`, async custom mappers, or code that builds + `ExperienceConfiguration` arrays. +- The baseline entries your app renders, the variants attached to them, and the audience rules that + must still apply after migration. +- The target runtime and guide that will render those entries. +- A first verification target: one baseline entry, one Optimization experience linked from that + baseline, and one variant entry. Configure that test experience so every visitor is eligible, so + baseline fallback and a missing authored variant are easy to tell apart. + +There is no SDK-provided content migration codemod or official field mapping for converting +Ninetailed-authored entries to the Optimization content model. Treat the content migration as an +operational authoring task in Contentful, then verify it through the target SDK. + +## Migration path + +1. Inventory the legacy authored entries and mapper usage. +2. Author the target Optimization experiences and variants in Contentful. +3. Stop producing legacy runtime configuration arrays from `nt_config`. +4. Update runtime code to pass fetched Contentful entries to the target SDK's entry-resolution path. +5. Verify one authored all-visitors variant in the target runtime. +6. Remove `@ninetailed/experience.js-utils-contentful` after no runtime path imports it. + +## Replace legacy surfaces + +### Inventory legacy authored entries and mapper usage + +Find the entries and code paths that make personalization depend on the legacy model: + +- Baseline entries with `fields.nt_experiences`. +- Experience entries with `nt_config`, `nt_audience`, `nt_variants`, and `nt_experience_id`. +- Mapper code that converts traffic, distribution, audiences, or replacement components into + runtime objects. +- Custom variant mapping that adds application fields before rendering. + +Do not preserve `ExperienceConfiguration` arrays as the target data model. In Optimization, a +baseline entry links to Optimization experience entries through SDK-owned fields, and matching +experiences link to replacement entries. If the links are absent or unresolved in the fetched +payload, the SDK returns the baseline entry. + +### Plan target Optimization authoring + +Use Contentful personalization authoring to create the target experiences and variants. For the +minimum first proof, identify one legacy baseline entry, create or reuse one target baseline entry, +link one Optimization experience from the baseline entry's `nt_experiences` field, and link one +replacement entry from that experience's `nt_variants` field. Configure that experience with no +restrictive audience, or with an audience rule that matches every visitor in your test route. + +Keep the runtime expectation simple: + +- Fetch one concrete locale, such as `en-US`; do not use all-locale payloads. +- Include linked entries deeply enough for the page or section you render. The default managed + entry-fetching path uses `include: 10`; for manual fetching, set an equivalent include depth in + your app's Contentful query. +- Keep the Contentful client in the app. With manual fetching, your app fetches the baseline entry + and passes it as `baselineEntry` or to `resolveOptimizedEntry()`. With managed fetching, your app + gives the SDK its `contentful.js` client and the SDK fetches explicit entry IDs for the supported + target runtime. + +For the first verification, choose one baseline entry and one variant that targets all visitors. +That proves the authoring links and runtime resolution before you migrate more rules. + +### Replace mapper-dependent runtime code + +Remove mapper output from the rendering path. Replace code that passes mapped experience arrays to +experience.js components with the target runtime's entry-resolution surface: + +- Web: [Resolving entries and rendering the result](./integrating-the-web-sdk-in-a-web-app.md#resolving-entries-and-rendering-the-result) + or [Web Components entry rendering](./integrating-the-web-sdk-in-a-web-app.md#web-components-entry-rendering). +- React Web: + [Resolving entries and rendering the result](./integrating-the-react-web-sdk-in-a-react-app.md#resolving-entries-and-rendering-the-result) + with `OptimizedEntry`, `useOptimizedEntry`, or `resolveOptimizedEntry()`. +- Next.js App Router: + [Personalizing first paint on the server](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md#personalizing-first-paint-on-the-server). +- Next.js Pages Router: + [Personalizing entries](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md#personalizing-entries) + and + [managed entry rendering](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md#personalizing-entries). +- Node: + [Fetch and resolve Contentful entries](./integrating-the-node-sdk-in-a-node-app.md#fetch-and-resolve-contentful-entries). + +If custom mapper code added application-specific fields, keep that adaptation outside the SDK +handoff. Fetch or transform the app's own content first, then pass the baseline Contentful entry to +the target resolver. + +### Validate content migration before code migration + +Before migrating the full runtime, verify the first authored entry can resolve in the target guide's +quick path: + +- The baseline entry renders when no visitor matches or links are missing. +- The all-visitors variant renders when the target guide emits the required page or request event. +- The Contentful fetch uses one locale and includes linked Optimization entries. +- No runtime code still imports the legacy Contentful mapper. + +## Validate the migration + +- Search the app for `@ninetailed/experience.js-utils-contentful`, `ExperienceMapper`, `nt_config`, + and runtime `ExperienceConfiguration` construction. +- Verify one target runtime renders both baseline fallback and an authored variant. +- Confirm the same entry is fetched with a concrete locale and linked entries included. +- Confirm authors know which entries remain to be migrated in Contentful before the next runtime + surface moves. + +## Troubleshooting + +| Symptom | Check | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| The target runtime always renders baseline | Confirm the fetched baseline entry contains linked Optimization experiences and variant entries, and that the test experience targets the visitor. | +| The runtime crashes or returns empty fields | Confirm the app did not fetch all locales and that linked entries are resolved in the payload. | +| Mapper removal breaks custom fields | Move custom application adaptation before or after SDK entry resolution; do not rebuild legacy experience arrays. | + +## Related guides + +- [Entry personalization and variant resolution](../concepts/entry-personalization-and-variant-resolution.md) +- [Web SDK integration guide](./integrating-the-web-sdk-in-a-web-app.md) +- [React Web integration guide](./integrating-the-react-web-sdk-in-a-react-app.md) +- [Node SDK integration guide](./integrating-the-node-sdk-in-a-node-app.md) +- [Contentful personalization authoring](https://www.contentful.com/developers/docs/personalization/) diff --git a/documentation/guides/migrating-experience-js-next-to-nextjs-app-router.md b/documentation/guides/migrating-experience-js-next-to-nextjs-app-router.md new file mode 100644 index 000000000..e412e4531 --- /dev/null +++ b/documentation/guides/migrating-experience-js-next-to-nextjs-app-router.md @@ -0,0 +1,135 @@ +# Migrating experience.js Next.js to the App Router SDK + +Use this guide when a Next.js App Router app carries legacy Next.js, ESR, SSR plugin, or React +experience.js wiring and you want to move to `@contentful/optimization-nextjs/app-router`. + +## What changes + +The App Router SDK provides bound server and client components for request context, server first +paint, browser takeover, route tracking, and entry resolution. Legacy Next provider, tracker, SSR +plugin, ESR helper, React component, and plugin behavior should be replaced by the App Router SDK +plus the shared migration guides. + +Start with the +[Next.js App Router integration guide](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md). + +## Before you migrate + +Gather these inputs: + +- Current `@ninetailed/experience.js-next`, `@ninetailed/experience.js-next-esr`, SSR plugin, and + React imports. +- Existing middleware, proxy, route tracker, and page-event ownership. +- Use of `ntaid` or browser legacy storage for profile continuity. +- Personalized Server Components, Client Components, and Contentful fetches. +- Consent policy and whether server personalization may make affected routes dynamic. + +## Migration path + +1. Confirm this app should use App Router through + [Choosing a Next.js migration path from experience.js](./choosing-a-nextjs-migration-path-from-experience-js.md). + Then open the + [App Router integration quick start](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md#quick-start) + and make the request handler run before replacing render components. +2. Migrate authored Contentful entries when legacy mapper output is still required. See + [Migrating an experience.js Contentful model to Optimization](./migrating-experience-js-contentful-model-to-optimization.md). +3. Install and bind the App Router SDK. +4. Replace SSR/ESR profile continuity with target request context and cookie behavior. +5. Replace server-rendered personalization with bound `OptimizedEntry`. +6. Replace browser takeover features and plugins through target client surfaces. +7. Remove legacy Next, ESR, React, and plugin packages after imports are gone. + +## Replace legacy surfaces + +### Remove legacy Next.js package assumptions + +Do not carry forward package-root or ESR helper assumptions. The target App Router import path is +`@contentful/optimization-nextjs/app-router`, with browser hooks from +`@contentful/optimization-nextjs/client`. Legacy ESR middleware and selector files are not supported +public import surfaces. + +Remove legacy tracker and provider wiring before adding the bound target root, so one route owner is +responsible for server data and browser tracking. + +### Install and bind the App Router SDK + +Create bound App Router components once using the target integration guide. Configure the request +handler file and export in +[Request context and the profile cookie](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md#request-context-and-the-profile-cookie): +Next.js 16 uses `proxy.ts` with a `proxy` export, while Next.js 15 uses `middleware.ts` with a +`middleware` export. + +With `server.enabled: true`, a missing or misnamed handler is not a baseline fallback path; the +bound root expects server data and throws when the handler did not run. Verify the handler runs by +loading a matched route and confirming the root no longer reports missing server optimization data +before replacing many render surfaces. + +### Replace SSR/ESR profile continuity + +Move profile continuity to the App Router request context and target SDK cookie behavior. The target +profile cookie is `ctfl-opt-aid`; it must be browser-readable so browser takeover can continue the +same visitor. The app still owns the consent record and the server consent resolver. + +When the configured request handler reports the initial page view, pass +`initialPageEvent="skip"` to `NextAppAutoPageTracker` so the browser tracker skips the duplicate. +When server consent denies the event or a route is browser-owned, use `initialPageEvent="emit"` and +let the browser tracker emit according to policy. + +### Replace server-rendered personalization + +Replace legacy mapped experiences and React wrappers with the bound App Router `OptimizedEntry`. +Use `baselineEntry` when your app fetched the entry manually. Use `entryId` only when the factory +has `contentful` config for managed server fetching; if the browser must continue that managed +entry, also provide matching `prefetchManagedEntries` or `prefetchedManagedEntries` on the bound +root. The server component resolves against the current request's selected optimizations and returns +the variant or baseline. + +Server personalization reads request data and makes the route dynamic. Do not keep ISR or static +assumptions on routes that render request-specific personalized output. + +### Replace browser takeover features + +Client-side flags, analytics forwarding, preview, and live updates use the React Web runtime behind +the App Router SDK: + +- Flag reads auto-attempt flag-view tracking when consent and profile state allow it. +- Accepted and blocked event streams are available on the live client SDK. +- Preview panel attachment is a browser concern and forces live updates while open. +- Legacy plugin behavior moves to + [Migrating experience.js plugins and preview](./migrating-experience-js-plugins-and-preview.md). + +### Validate App Router migration + +Verify server HTML, hydration, and browser takeover together: + +- The request handler runs for the personalized route. +- Server-rendered content uses the expected variant or baseline. +- Browser hydration does not briefly revert to empty optimization state. +- The browser tracker skips the first page event when the server already reported it. +- Personalized HTML and resolved outputs are not shared across visitors through caching. + +## Validate the migration + +- Search for remaining `@ninetailed/experience.js-next`, `@ninetailed/experience.js-next-esr`, and + legacy React imports. +- Verify one all-visitors variant on a dynamic App Router route. +- Verify denied consent and accepted consent event paths. +- Verify preview and analytics forwarding only after the core route works. + +## Troubleshooting + +| Symptom | Check | +| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| The bound root throws instead of showing baseline | Confirm the App Router request handler file and export match your Next.js version and route matcher. | +| The route conflicts with static generation | Server personalization is request-specific; use a dynamic route or move personalization to a browser-only path. | +| Hydration changes the entry | Pass the same baseline entry path or matching managed-entry handoff to the browser. | +| Duplicate page events appear | Skip the browser initial page event when the server already reported the first view. | + +## Related guides + +- [Next.js App Router integration guide](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md) +- [Choosing a Next.js migration path from experience.js](./choosing-a-nextjs-migration-path-from-experience-js.md) +- [Migrating an experience.js Contentful model to Optimization](./migrating-experience-js-contentful-model-to-optimization.md) +- [Migrating experience.js plugins and preview](./migrating-experience-js-plugins-and-preview.md) +- [Profile synchronization between client and server](../concepts/profile-synchronization-between-client-and-server.md) +- [App Router reference implementation](../../implementations/nextjs-sdk_app-router/README.md) diff --git a/documentation/guides/migrating-experience-js-next-to-nextjs-pages-router.md b/documentation/guides/migrating-experience-js-next-to-nextjs-pages-router.md new file mode 100644 index 000000000..b925d931f --- /dev/null +++ b/documentation/guides/migrating-experience-js-next-to-nextjs-pages-router.md @@ -0,0 +1,131 @@ +# Migrating experience.js Next.js to the Pages Router SDK + +Use this guide when a Pages Router app uses `@ninetailed/experience.js-next`, SSR plugin behavior, +or legacy React surfaces and you want to move to the Optimization Pages Router SDK. + +## What changes + +The Pages Router target uses `@contentful/optimization-nextjs/pages-router` for browser components +and `@contentful/optimization-nextjs/pages-router/server` for `getServerSideProps`. Server props +own request evaluation and profile continuity; the browser root receives server state and continues +with React Web behavior. + +Start with the +[Next.js Pages Router integration guide](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md). + +## Before you migrate + +Gather these inputs: + +- Provider and tracker placement in `_app.tsx`. +- Every `getServerSideProps` path that uses SSR plugin behavior or `ntaid`. +- Legacy React components, hooks, flags, and mapper-dependent entries. +- Consent cookie, profile cookie, and initial page-event behavior. +- Any analytics, privacy, preview, or insights plugins. + +## Migration path + +1. Confirm this app should use Pages Router through + [Choosing a Next.js migration path from experience.js](./choosing-a-nextjs-migration-path-from-experience-js.md). +2. Migrate authored Contentful entries when legacy mapper output is still required. See + [Migrating an experience.js Contentful model to Optimization](./migrating-experience-js-contentful-model-to-optimization.md). +3. Install and bind the Pages Router client and server factories. +4. Replace SSR plugin profile and page evaluation in `getServerSideProps`. +5. Replace personalized rendering with the bound Pages Router `OptimizedEntry`. +6. Replace client-side extras through React Web behavior and the plugin migration guide. +7. Remove legacy Next, React, and plugin packages after imports are gone. + +## Replace legacy surfaces + +### Inventory legacy Pages Router wiring + +Record where the legacy provider and tracker mount, whether `onRouteChange` replaces default page +calls, and which pages use SSR plugin helpers. Also record any code that reads or writes `ntaid`, +because target profile continuity uses the SDK-owned `ctfl-opt-aid` cookie. + +### Install and bind the Pages Router SDK + +Use the target guide to create both factories: + +- Client factory from `@contentful/optimization-nextjs/pages-router`. +- Server factory from `@contentful/optimization-nextjs/pages-router/server`. + +Mount the target `OptimizationRoot` and `NextPagesAutoPageTracker` in `_app.tsx`, passing +`pageProps.contentfulOptimization`. That object is the SDK handoff returned from +`getServerSideOptimizationProps()`; it can contain browser consent defaults, server optimization +state, managed-entry prefetch handoff, and the required `initialPageEvent` value. + +If migrated components will use ``, configure the server factory with the +app's `contentful` client and pass `prefetchManagedEntries` in the server helper options before +switching those components. The Pages Router client factory does not fetch managed entries by +itself. + +### Replace server profile and page evaluation + +Move SSR plugin behavior into `getServerSideOptimizationProps(context)`. The server helper binds +request consent, emits the first page event when allowed, writes the anonymous-id cookie, and passes +server optimization state through page props. Observe accepted server evaluation by checking that +`contentfulOptimization.serverOptimizationState` is present and `initialPageEvent` is `skip`; +observe denied consent by checking that no Experience API call is made and `initialPageEvent` is +`emit`. + +The target server helper resolves `initialPageEvent` so the browser tracker skips the first route +when the server reported it and emits when the server did not. Keep that value wired to the tracker +instead of duplicating legacy route-change code. + +### Replace personalized rendering + +Replace legacy React wrappers and mapper output with the Pages Router `OptimizedEntry`. It accepts a +manual `baselineEntry` or an `entryId` path backed by server-managed prefetch. It can use per-entry +loading, error, and live-update props because it is the React Web component bound for Pages Router. + +If the first render depends on legacy `nt_*` fields, migrate the Contentful model before replacing +the component. + +### Replace client-side extras + +Client features use the React Web runtime: + +- Flags use target flag reads and optional `trackFlagView()`. +- Analytics vendors use accepted and blocked event streams. +- Consent uses app-owned policy passed to server and browser SDK surfaces. +- Preview attaches to the live browser SDK through the preview panel package. + +Use [Migrating experience.js plugins and preview](./migrating-experience-js-plugins-and-preview.md) +for plugin-specific replacement. + +### Validate Pages Router migration + +Verify the server and browser handoff: + +- `getServerSideOptimizationProps()` runs on the personalized page. +- `pageProps.contentfulOptimization` reaches `_app.tsx`. +- The browser tracker uses the server-provided `initialPageEvent`. +- A target `OptimizedEntry` renders a variant or baseline. +- Personalized results are not cached outside the request boundary. + +## Validate the migration + +- Search for `@ninetailed/experience.js-next`, SSR plugin imports, `ntaid`, and legacy React + surfaces. +- Verify accepted server evaluation and denied-consent behavior. +- Verify all-locale Contentful payloads are not used for optimized entries. +- Verify client-side plugin replacements only after the route and rendering work. + +## Troubleshooting + +| Symptom | Check | +| ------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| First page events duplicate | Pass the server `initialPageEvent` value from page props into `NextPagesAutoPageTracker`. | +| `getServerSideProps` returns a 500 on API failure | Wrap the server helper and render baseline on failure when your app needs graceful fallback. | +| Browser render cannot find managed entries | Use server-side managed prefetch and pass `prefetchedManagedEntries` through page props. | +| Hooks import fails | Import React Web hooks from `@contentful/optimization-nextjs/client`, not `/pages-router`. | + +## Related guides + +- [Next.js Pages Router integration guide](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) +- [Choosing a Next.js migration path from experience.js](./choosing-a-nextjs-migration-path-from-experience-js.md) +- [Migrating an experience.js Contentful model to Optimization](./migrating-experience-js-contentful-model-to-optimization.md) +- [Migrating experience.js plugins and preview](./migrating-experience-js-plugins-and-preview.md) +- [Profile synchronization between client and server](../concepts/profile-synchronization-between-client-and-server.md) +- [Pages Router reference implementation](../../implementations/nextjs-sdk_pages-router/README.md) diff --git a/documentation/guides/migrating-experience-js-node-ssr-and-esr.md b/documentation/guides/migrating-experience-js-node-ssr-and-esr.md new file mode 100644 index 000000000..f0868e0a5 --- /dev/null +++ b/documentation/guides/migrating-experience-js-node-ssr-and-esr.md @@ -0,0 +1,120 @@ +# Migrating experience.js Node, SSR, and ESR + +Use this guide when server code uses `@ninetailed/experience.js-node`, SSR plugin helpers, ESR +helpers, or a manual server-to-browser handoff. + +## What changes + +Legacy server code commonly uses `NinetailedAPIClient`, SSR plugin continuity, `ntaid`, or ESR +preflight helpers. The Optimization Node SDK is stateless: create one process-level SDK, bind each +incoming request with `forRequest()`, and let the app own cookies, consent, profile persistence, +request context, and caching. + +Follow the [Node SDK integration guide](./integrating-the-node-sdk-in-a-node-app.md) unless a +Next.js adapter owns the route. + +## Before you migrate + +Gather these inputs: + +- Server paths that create profiles, emit page events, or call `NinetailedAPIClient`. +- SSR or ESR helpers and any manual server-to-browser profile handoff. +- Cookie reads/writes for `ntaid` and any browser continuation logic. +- Server-rendered Contentful entries and mapper-dependent experience configuration. +- Consent policy, request locale, user agent, URL, IP/location forwarding, and cache boundaries. + +## Migration path + +1. For Next.js apps, choose the App Router or Pages Router path before building a manual hybrid. +2. Install `@contentful/optimization-node` for non-Next server paths. +3. Replace server API-client calls with request-bound Node SDK calls. +4. Replace SSR/ESR profile handoff with app-owned persistence and target browser continuity. +5. Replace server content resolution with Node or framework entry resolution. +6. Remove legacy Node, SSR, ESR, and browser packages after imports are gone. + +## Replace legacy surfaces + +### Inventory server profile and event ownership + +Identify which server code owns each responsibility: + +- Creates or reads an anonymous profile ID. +- Emits the first page event. +- Persists profile continuity. +- Resolves Contentful entries before rendering. +- Hands state to the browser. + +This inventory prevents one request from being evaluated twice or from losing the profile before +browser takeover. + +### Replace Node API client calls + +Create one `ContentfulOptimization` instance per process and call `forRequest()` for each incoming +request. Event methods live on the request-bound client, not on the singleton. The minimum legal +shape is `forRequest({ consent })`; a migration request usually adds `locale`, `profile`, and +`eventContext`. `locale` selects the request locale, `profile` carries the app-owned anonymous +profile ID, and `eventContext` carries URL, user-agent, referrer, query, and other page data the SDK +cannot infer from your server framework. + +Accepted request-bound `page()` or `identify()` results carry the profile, selected optimizations, +and flag changes for that request. Consent-blocked events return blocked results or diagnostics +without throwing. + +### Replace SSR and ESR handoff + +Use a framework SDK when available. For Next.js, prefer the App Router or Pages Router migration +guide so the adapter owns request state, provider handoff, page-event dedupe, and cookie behavior. + +For a manual Node/Web hybrid, the app owns the profile cookie. The Node SDK exports +`ANONYMOUS_ID_COOKIE` from `@contentful/optimization-node/constants`, and its value is +`ctfl-opt-aid`, but Node does not read, write, or clear cookies for you. Read the cookie from the +incoming request, pass it as `forRequest({ profile: { id } })`, write the returned profile ID only +when persistence consent allows it, and keep the cookie browser-readable if the Web SDK must +continue the same visitor. + +### Replace server content resolution + +Stop building legacy experience configuration arrays. Fetch a baseline Contentful entry with one +concrete locale and enough linked entries, then resolve it with the request's selected +optimizations. The singleton resolver has no ambient visitor state, so pass selections explicitly +or use the request-bound `fetchOptimizedEntry()` path after an accepted request-bound `page()` or +`identify()` call. + +Do not cache personalized outputs across visitors. Raw Contentful baseline entries can follow your +normal content-cache policy, but rendered personalized HTML, merge-tag values, and Experience +responses are request-specific. + +### Validate server migration + +Verify the request boundary: + +- Accepted events return profile data when consent allows them. +- Blocked events do not throw and surface diagnostics. +- The app persists the profile only when persistence consent allows it. +- Entry resolution uses request selections. +- Browser takeover uses a compatible target SDK path. + +## Validate the migration + +- Search for `@ninetailed/experience.js-node`, SSR plugin imports, ESR helper imports, and `ntaid`. +- Verify one accepted request and one denied-consent request. +- Verify a resolved server entry falls back to baseline with no selection. +- Verify cache keys do not share personalized output across visitors. + +## Troubleshooting + +| Symptom | Check | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Server event methods are missing | Call `forRequest()` first; event methods live on the request-bound client. | +| Non-sticky interaction tracking throws | Bind a request profile ID or use the event flow that derives one before sending Insights interactions. | +| Browser takeover starts a different visitor | Persist and pass the target anonymous ID according to the Web or framework SDK guide. | +| Personalized HTML leaks between visitors | Remove shared caching around request-specific responses and rendered output. | + +## Related guides + +- [Node SDK integration guide](./integrating-the-node-sdk-in-a-node-app.md) +- [Web SDK integration guide](./integrating-the-web-sdk-in-a-web-app.md) +- [Choosing a Next.js migration path from experience.js](./choosing-a-nextjs-migration-path-from-experience-js.md) +- [Profile synchronization between client and server](../concepts/profile-synchronization-between-client-and-server.md) +- [Node reference implementation](../../implementations/node-sdk/README.md) +- [Node plus Web reference implementation](../../implementations/node-sdk+web-sdk/README.md) diff --git a/documentation/guides/migrating-experience-js-plugins-and-preview.md b/documentation/guides/migrating-experience-js-plugins-and-preview.md new file mode 100644 index 000000000..8ff65d3a3 --- /dev/null +++ b/documentation/guides/migrating-experience-js-plugins-and-preview.md @@ -0,0 +1,132 @@ +# Migrating experience.js plugins and preview + +Use this guide when an app uses experience.js analytics, privacy, preview, insights, or third-party +plugin packages and you need to replace them with Optimization SDK Suite surfaces. + +## What changes + +Legacy plugin packages are not one-for-one target packages. Privacy moves to app-owned consent plus +SDK consent inputs. Vendor analytics moves to event-stream forwarding or app-owned business events. +Preview moves to the Optimization preview panel attached to the live Web SDK. Insights delivery is +part of the target SDK event and interaction model. + +Use this after at least one target runtime guide is in place. + +## Before you migrate + +Gather these inputs: + +- Every `@ninetailed/experience.js-plugin-*` package and plugin constructor. +- Code that reads `window.ninetailed.plugins`, `window.ninetailed.consent`, or preview globals. +- Current accepted and blocked consent behavior. +- Vendor destinations such as Google Tag Manager, Segment, Contentsquare, or product analytics. +- Preview workflows authors depend on. +- The target runtime guide for Web, React Web, App Router, or Pages Router. + +## Migration path + +1. Inventory plugin packages and globals. +2. Replace privacy plugin behavior with app-owned consent policy and target SDK consent inputs. +3. Replace analytics and tag-manager plugins with event-stream forwarding. +4. Replace preview plugin behavior with the Optimization preview panel. +5. Validate accepted events, blocked diagnostics, preview overrides, and package removal. + +## Replace legacy surfaces + +### Inventory plugin packages and globals + +List each plugin by purpose, not by package name: + +- Privacy and consent filtering. +- Third-party analytics or tag-manager forwarding. +- Preview widget and bridge behavior. +- Insights batching or custom event delivery. + +Then search for globals. Target SDK code should not depend on `window.ninetailed`, plugin globals, +or `__nt-consent__`. + +### Replace privacy plugin behavior + +The legacy privacy plugin stored consent and filtered events, traits, properties, profile merging, +and features. In the target SDK, the app owns the consent record and passes the current decision to +the SDK. Event consent and persistence consent are separate axes. + +Use the runtime guide's consent section to decide: + +- Which events may be admitted before explicit consent. +- Whether profile persistence is allowed. +- Where the app stores the visitor's consent decision. +- How blocked events are observed through `onEventBlocked` or `states.blockedEventStream`. + +Use the exact runtime section for the app: +[Web consent](./integrating-the-web-sdk-in-a-web-app.md#consent-and-privacy-handoff), +[React Web consent](./integrating-the-react-web-sdk-in-a-react-app.md#consent-and-privacy-handoff), +[App Router consent](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md#consent-identity-profile-and-reset), +or +[Pages Router consent](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md#consent-identity-profile-and-reset). + +Do not expect blocked legacy payloads to replay after consent changes. Re-emit application events +that still need to happen under the accepted policy. + +### Replace analytics and tag-manager plugins + +Replace vendor plugins with the +[analytics forwarding guide](./forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md). +Subscribe to accepted event streams in stateful browser runtimes and use request-local event results +on server runtimes. Dedupe accepted events by `messageId`, and forward only approved primitive +fields. `states.eventStream` carries accepted events; `states.blockedEventStream` carries only +consent-blocked diagnostics. `onEventBlocked` receives the same blocked diagnostic as a callback, +which is useful when you want logging without a browser-state subscription. + +Keep vendor consent separate from SDK consent. SDK consent controls SDK event emission; your app's +analytics policy controls whether data leaves your application boundary for a vendor. + +### Replace preview plugin behavior + +Use the Optimization preview panel for Web and React Web based runtimes. The panel attaches to the +live Web SDK, reads audience and experience definitions from a Contentful client or pre-fetched +entries, and applies preview overrides to selected optimizations and flag changes. Provide either a +`contentful.js` client through `contentful` so the panel can fetch preview entries, or pass +pre-fetched `entries: { audiences, experiences }`. + +Preview overrides can force audiences, variants, and inline-variable flag values. Validate a forced +variant by opening the panel, selecting a non-baseline variant, and watching the optimized entry +change without a page reload. Validate an inline flag by forcing a flag value in the panel and +checking that `getFlag()` or `states.flag(name)` returns that value while the panel is open. While +the panel is open, optimized entries live-update so authors can inspect changes. Legacy preview +widget globals are not target extension points. + +### Validate plugin migration + +Run these checks after the core runtime migration works: + +- Denied consent produces blocked diagnostics for events your policy blocks. +- Accepted SDK events appear once in the forwarding destination. +- Preview panel attaches to the live SDK and can force a variant or inline flag value. +- No legacy plugin package or `window.ninetailed` plugin global remains. + +## Validate the migration + +- Search for `@ninetailed/experience.js-plugin`, `window.ninetailed.plugins`, + `window.ninetailed.consent`, and `__nt-consent__`. +- Verify accepted and blocked event streams in the target runtime. +- Verify the analytics forwarding guide's dedupe behavior with one SDK activity. +- Verify the preview panel only loads in the environments where your app enables it. + +## Troubleshooting + +| Symptom | Check | +| --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| Blocked events disappear | Observe `states.blockedEventStream` or `onEventBlocked`; blocked events are diagnostics, not a replay queue. | +| Vendor events duplicate | Dedupe forwarded accepted events by `messageId` outside component or subscriber lifecycles. | +| Preview attaches but does not update entries | Attach to the live Web SDK and confirm the panel has audience and experience entries. | +| Consent appears accepted in the app but SDK blocks events | Confirm the app passes the current consent decision into the target SDK runtime and that `allowedEventTypes` matches policy. | + +## Related guides + +- [Forwarding Optimization SDK context to analytics and tag-management tools](./forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md) +- [Consent management in the Optimization SDK Suite](../concepts/consent-management-in-the-optimization-sdk-suite.md) +- [Web SDK integration guide](./integrating-the-web-sdk-in-a-web-app.md) +- [React Web integration guide](./integrating-the-react-web-sdk-in-a-react-app.md) +- [Next.js App Router integration guide](./integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md) +- [Next.js Pages Router integration guide](./integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md) diff --git a/documentation/guides/migrating-experience-js-react-to-react-web.md b/documentation/guides/migrating-experience-js-react-to-react-web.md new file mode 100644 index 000000000..1f86b887c --- /dev/null +++ b/documentation/guides/migrating-experience-js-react-to-react-web.md @@ -0,0 +1,136 @@ +# Migrating experience.js React to the React Web SDK + +Use this guide when a React app uses `@ninetailed/experience.js-react` providers, hooks, +components, or flags and you want to move to `@contentful/optimization-react-web`. + +## What changes + +Legacy React surfaces wrap the browser `Ninetailed` client and render through `Personalize`, +`Experience`, `usePersonalize`, `useExperience`, and flag hooks. React Web uses +`OptimizationRoot`, target hooks, and `OptimizedEntry` over the Optimization content model. The app +keeps Contentful fetching, consent policy, identity policy, routing, analytics, and rendering +components. + +Follow the [React Web integration guide](./integrating-the-react-web-sdk-in-a-react-app.md) for the +target provider and rendering setup. + +## Before you migrate + +Gather these inputs: + +- The root where `NinetailedProvider` is mounted and any existing injected client. +- Every `Personalize`, `Experience`, `usePersonalize`, `useExperience`, `useProfile`, `useFlag`, + `useFlagWithManualTracking`, and `TrackHasSeenComponent` call. +- Router page-tracking code, including whether the app uses React Router, TanStack Router, a custom + router, or no router abstraction. +- Consent, identity, and analytics policy. +- Contentful entries that still depend on legacy mapper output or `nt_*` fields. + +## Migration path + +1. Migrate Contentful authoring when legacy `nt_*` fields feed React rendering. See + [Migrating an experience.js Contentful model to Optimization](./migrating-experience-js-contentful-model-to-optimization.md). +2. Install `@contentful/optimization-react-web`. +3. Replace root wiring with `OptimizationRoot` and the appropriate router page tracker: React + Router, TanStack Router, or an app-owned `trackPageView` call for a custom router. +4. Replace personalized rendering with `OptimizedEntry` or target entry hooks. +5. Replace flags, interactions, consent, identity, analytics, and preview through target surfaces. +6. Remove `@ninetailed/experience.js-react` and legacy browser packages after imports are gone. + +## Replace legacy surfaces + +### Inventory provider, hooks, and render components + +Separate root setup from component-level rendering. Root setup includes `NinetailedProvider`, +plugins, page tracking, consent defaults, and any injected client. Component-level rendering +includes every `Personalize`, `Experience`, hook, flag read, and tracking wrapper. + +This split keeps the first migration step small: get the target root and page event working before +rewriting every personalized component. + +### Replace the provider and readiness model + +Mount one `OptimizationRoot` around the tree that uses personalization. React Web creates the +underlying Web SDK after React commits, so readiness and loading state matter. Provider children +still render; optimized entries handle baseline and loading behavior at the entry boundary. + +Do not rely on `window.ninetailed` or legacy provider prop changes. The target root owns the Web SDK +instance for the browser runtime, and app policy owns consent defaults. + +### Replace personalized entry rendering + +Replace legacy component and hook rendering with target entry resolution: + +- Use `OptimizedEntry` when a component renders a Contentful entry. +- Use `useOptimizedEntry` or `resolveOptimizedEntry()` when you need custom control. +- Keep your existing presentational components; render the resolved entry through them. + +Use one entry source per component: + +- Use `baselineEntry` when your app already fetched the Contentful entry and owns the query, + caching, and include depth. +- Use `entryId` only when `OptimizationRoot` has a `contentful` client for managed fetching. + +Both paths return the baseline when no selection matches or linked entries are missing. If legacy +mapper code still builds experience arrays, migrate the Contentful model first. + +### Replace flags and tracking side effects + +Do not preserve legacy flag hook names as wrappers unless your app needs a temporary compatibility +layer. Use the target SDK flag APIs: + +- `sdk.getFlag()` for a one-off read. +- `sdk.states.flag(name)` for a reactive read. +- `sdk.trackFlagView()` when you need explicit manual flag-view tracking. + +React Web optimized entries track views, clicks, and hovers by default when consent and profile +state allow them. Accepted interaction events appear on the live SDK's `states.eventStream`; consent +blocks appear on `states.blockedEventStream`. If a legacy wrapper existed only to track component +views, replace it with `OptimizedEntry` tracking rather than a custom component. + +### Replace consent, identity, analytics, and preview + +Move policy and vendor work out of the core render migration: + +- Consent uses app-owned state passed to the SDK through target consent actions or defaults. +- Identity uses target `identifyUser` and `resetUser` actions. +- Analytics vendors subscribe to accepted and blocked event streams instead of legacy plugins. +- Preview uses the Optimization preview panel package attached to the live Web SDK. + +Use [Migrating experience.js plugins and preview](./migrating-experience-js-plugins-and-preview.md) +for those replacements. + +### Validate React migration + +Verify these outcomes before deleting the legacy packages: + +- The React root mounts one Optimization SDK runtime. +- One page event is accepted or intentionally blocked by policy. +- A target `OptimizedEntry` renders the authored variant or baseline fallback. +- Interaction and flag-view events follow the target consent policy. +- No legacy React component or hook imports remain. + +## Validate the migration + +- Search for `@ninetailed/experience.js-react`, `Personalize`, `Experience`, `usePersonalize`, + `useExperience`, `useFlag`, and `TrackHasSeenComponent`. +- Verify the app renders the same baseline entry with no matching variant. +- Verify an all-visitors variant renders after the page event. +- Verify accepted events and blocked diagnostics through the target SDK streams. + +## Troubleshooting + +| Symptom | Check | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| The first render has no SDK state | React Web initializes after commit; use entry loading and baseline behavior instead of reading live state during root setup. | +| `OptimizedEntry` renders baseline | Confirm the Contentful payload includes linked Optimization entries and that a page or identify event populated selections. | +| Interaction events are missing | Check consent, profile availability, root/per-entry tracking opt-outs, and whether the rendered node carries target tracking attributes. | +| Preview does not attach | Attach the preview panel to the live Web SDK, not the initial snapshot runtime. | + +## Related guides + +- [React Web integration guide](./integrating-the-react-web-sdk-in-a-react-app.md) +- [Web SDK integration guide](./integrating-the-web-sdk-in-a-web-app.md) +- [Migrating an experience.js Contentful model to Optimization](./migrating-experience-js-contentful-model-to-optimization.md) +- [Migrating experience.js plugins and preview](./migrating-experience-js-plugins-and-preview.md) +- [React Web reference implementation](../../implementations/react-web-sdk/README.md) diff --git a/documentation/guides/migrating-experience-js-to-the-web-sdk.md b/documentation/guides/migrating-experience-js-to-the-web-sdk.md new file mode 100644 index 000000000..50cb75b89 --- /dev/null +++ b/documentation/guides/migrating-experience-js-to-the-web-sdk.md @@ -0,0 +1,126 @@ +# Migrating experience.js to the Optimization Web SDK + +Use this guide when a plain browser app or custom JavaScript adapter uses +`@ninetailed/experience.js` and you want to move to `@contentful/optimization-web`. + +## What changes + +The legacy browser client owns plugin arrays, legacy storage keys, browser globals, and imperative +event methods. The Web SDK uses one `ContentfulOptimization` instance for the browser runtime. Your +app owns Contentful fetching, routing, rendering, consent records, identity policy, and any +third-party forwarding. + +Follow the [Web SDK integration guide](./integrating-the-web-sdk-in-a-web-app.md) for the target +setup details. + +## Before you migrate + +Gather these inputs: + +- The module that constructs `Ninetailed`, any injected instance, and every plugin passed to it. +- Calls to `page`, `track`, `identify`, `batch`, `reset`, `debug`, flag reads, and manual rendering + helpers. +- Use of `window.ninetailed`, `__nt_anonymous_id__`, `__anon_id`, `__nt_profile__`, + `__nt_experiences__`, `__nt_changes__`, `__nt_debug__`, `__nt-consent__`, or `ntaid`. +- The app's consent and analytics policy. +- The Contentful entries that were resolved through legacy mapped experiences. +- The target Optimization `clientId`, environment, locale, and browser-visible config convention. + These values identify the Contentful project and locale the browser runtime may call; keep secrets + out of them and follow your bundler's public-variable convention. + +## Migration path + +1. Migrate authored Contentful entries first when the app depends on legacy `nt_*` fields. Start + with one authored all-visitors variant. See + [Migrating an experience.js Contentful model to Optimization](./migrating-experience-js-contentful-model-to-optimization.md). +2. Install `@contentful/optimization-web` and create one Web SDK instance for the browser runtime. +3. Replace the first `page()` call and entry resolution using the Web SDK guide. +4. Replace `identify`, `track`, flags, reset, and consent with target SDK calls and app-owned policy. +5. Move analytics, privacy, and preview plugins through + [Migrating experience.js plugins and preview](./migrating-experience-js-plugins-and-preview.md). +6. Remove `@ninetailed/experience.js` and legacy plugin packages after imports are gone. + +## Replace legacy surfaces + +### Inventory the legacy browser integration + +Start with the file that creates `new Ninetailed(...)` or injects an existing instance into a +provider. List every plugin and every module that reads `window.ninetailed`. Legacy provider props +did not re-create the client after construction, so treat client setup as a single runtime boundary +rather than a reactive config surface. + +Also inventory persistence. Legacy browser storage and the `ntaid` cookie do not map directly to +the target cookie name or consent model. Decide whether to reset visitor identity during migration +or perform an app-owned continuity handoff. + +### Replace client construction and lifecycle + +Create one `ContentfulOptimization` instance and reuse it. The Web SDK is ready immediately after +construction, but optimization state is empty until an accepted event such as `page()` or +`identify()` returns selections. An accepted event returns `{ accepted: true, data? }`; stateful +browser runtimes also expose accepted events on `states.eventStream`. Keep the order: + +1. Construct the instance. +2. Apply the visitor's current consent defaults. +3. Emit the page or identity event your policy allows. +4. Resolve entries or read flags after state is available. + +Do not preserve legacy plugin arrays. Replace each plugin concern with the target SDK surface or an +app-owned integration. + +### Replace page, track, identify, and reset calls + +Map imperative calls by ownership: + +- `page()` and route tracking move to Web SDK event methods or `trackCurrentPage()`. +- `identify({ userId, traits })` requires a user ID and returns an accepted or blocked result. +- `track({ event, properties })` owns application events. +- `reset()` clears SDK profile state and the SDK-owned `ctfl-opt-aid` cookie, but it does not clear + your consent record. + +The target consent model has two axes: event consent controls whether SDK events may be sent, and +persistence consent controls whether the SDK may store the profile-id cookie. With the default +pre-consent allow-list, `page` and `identify` can be admitted before explicit consent; set +`allowedEventTypes: []` if your policy requires every event to wait. + +### Replace entry and flag rendering + +Stop feeding mapped experience configuration into browser rendering. Pass a fetched baseline entry +to `resolveOptimizedEntry()` or use the Web Components path from the Web guide. When no selection +exists, links are unresolved, or the payload uses all locales, the resolver returns the baseline. + +Replace legacy flag hooks with target flag reads. Stateful flag reads auto-attempt flag-view +tracking when consent and profile state allow it; `trackFlagView()` is the explicit manual path. + +### Validate browser migration + +Use the Web guide's production checks, then add migration-specific checks: + +- One accepted page event populates selections before entry resolution. +- Denied or undecided consent behaves according to your app policy. +- Accepted events appear on `states.eventStream`; consent-blocked diagnostics appear on + `states.blockedEventStream`. +- No code reads `window.ninetailed` or legacy storage keys. + +## Validate the migration + +- Run a browser route with a test all-visitors variant and verify variant rendering. +- Repeat with consent denied and verify blocked diagnostics or baseline behavior. +- Verify analytics forwarding, if any, dedupes by `messageId`. +- Search for remaining `@ninetailed/experience.js`, `window.ninetailed`, and legacy storage keys. + +## Troubleshooting + +| Symptom | Check | +| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Entry resolution always returns baseline | Confirm an accepted page or identify event returned selections before resolution, and confirm the Contentful payload contains linked Optimization entries. | +| A second SDK instance throws | Keep one Web SDK instance per browser runtime; destroy only during teardown. | +| Vendor analytics stopped receiving events | Replace legacy plugins with event-stream forwarding from the target SDK. | +| Flags read but views are missing | Confirm consent, profile availability, and whether you need explicit `trackFlagView()`. | + +## Related guides + +- [Web SDK integration guide](./integrating-the-web-sdk-in-a-web-app.md) +- [Migrating an experience.js Contentful model to Optimization](./migrating-experience-js-contentful-model-to-optimization.md) +- [Migrating experience.js plugins and preview](./migrating-experience-js-plugins-and-preview.md) +- [Forwarding Optimization SDK context to analytics and tag-management tools](./forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md) diff --git a/documentation/internal/migration-knowledge/experience-js.md b/documentation/internal/migration-knowledge/experience-js.md new file mode 100644 index 000000000..f4d615eb3 --- /dev/null +++ b/documentation/internal/migration-knowledge/experience-js.md @@ -0,0 +1,86 @@ +# experience.js migration knowledge + +Source checkout: `/Users/charles.hudson/Projects/contentful/experience.js` at `824370e`. + +Internal migration reference. Facts only; not guide prose. Source pointers use +`experience.js@824370e:#`. + +## Package mapping + +| experience.js package | Migration target | Boundary | +| -------------------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `@ninetailed/experience.js` | `@contentful/optimization-web` or `@contentful/optimization-node` | Browser stateful client maps to Web; server event construction maps to Node request clients. source: experience.js@824370e:packages/sdks/javascript/package.json#name; experience.js@824370e:packages/sdks/javascript/src/index.ts | +| `@ninetailed/experience.js-react` | `@contentful/optimization-react-web` | Provider/hooks/components migrate to React Web root/actions/state hooks and `OptimizedEntry`. source: experience.js@824370e:packages/sdks/react/package.json#name; experience.js@824370e:packages/sdks/react/src/lib/index.ts | +| `@ninetailed/experience.js-next` | `@contentful/optimization-nextjs/pages-router` or `/app-router` | Legacy package is Pages-Router-oriented and re-exports React + SSR plugin; target choice depends on the app router. source: experience.js@824370e:packages/sdks/nextjs/package.json#name; experience.js@824370e:packages/sdks/nextjs/src/index.ts | +| `@ninetailed/experience.js-next-esr` | `@contentful/optimization-nextjs/app-router` or manual Node/Web hybrid | Legacy ESR exports only edge context/profile helpers; middleware/selector are not exported. source: experience.js@824370e:packages/sdks/nextjs-esr/src/index.ts; experience.js@824370e:packages/sdks/nextjs-esr/src/lib/middleware.ts | +| `@ninetailed/experience.js-node` | `@contentful/optimization-node` | Legacy `NinetailedAPIClient` sends server-side identify/track through `upsertManyProfiles`; target Node SDK is request-scoped and app-owned persistence. source: experience.js@824370e:packages/sdks/nodejs/src/lib/Client/Client.ts#NinetailedAPIClient | +| `@ninetailed/experience.js-utils-contentful` | Built-in Contentful entry model + managed/manual entry source | Legacy mapper converts Ninetailed Contentful entries into Experience.js configs; target SDK reads Optimization content-model fields directly during entry resolution. source: experience.js@824370e:packages/utils/contentful/src/lib/ExperienceMapper.ts#ExperienceMapper | +| `@ninetailed/experience.js-plugin-*` | Mostly manual replacements; preview maps to preview panel; insights maps to built-in insights | Third-party analytics/privacy plugin semantics are not one-for-one SDK packages. source: experience.js@824370e:packages/plugins/analytics/src/index.ts; experience.js@824370e:packages/plugins/preview/src/index.ts | + +## Legacy runtime surfaces + +- `NinetailedProvider` constructs a `Ninetailed` instance once with `useMemo([])` unless an existing instance is injected; prop changes do not re-create the instance. source: experience.js@824370e:packages/sdks/react/src/lib/NinetailedProvider.tsx#NinetailedProvider. +- The browser `Ninetailed` client wraps the `analytics` package, flattens plugin arrays, injects credentials into plugins that accept them, injects an `EventBuilder` into plugins that require it, and exposes profile/experiences on `window.ninetailed` after profile changes. source: experience.js@824370e:packages/sdks/javascript/src/lib/Ninetailed.ts#Ninetailed. +- Core browser profile state starts as loading and falls back to cached profile/experiences/changes on API failure; with no fallback profile, profile becomes `null` and the profile-change payload carries the error. source: experience.js@824370e:packages/sdks/javascript/src/lib/NinetailedCorePlugin/NinetailedCorePlugin.ts#NinetailedCorePlugin. +- Browser event methods `page`, `track`, `identify`, `batch`, sticky component view, variable view, and component view enqueue analytics events and then flush the core queue. source: experience.js@824370e:packages/sdks/javascript/src/lib/Ninetailed.ts#Ninetailed. +- `identify` with an empty user id uses `nt:empty-merge-id`; the core plugin skips identify when that empty merge id has no traits. source: experience.js@824370e:packages/sdks/javascript/src/lib/Ninetailed.ts#Ninetailed; experience.js@824370e:packages/sdks/javascript/src/lib/NinetailedCorePlugin/NinetailedCorePlugin.ts#NinetailedCorePlugin. +- API base URL defaults to `https://experience.ninetailed.co`; profile mutations post to `/v2/organizations/{clientId}/environments/{environment}/profiles...`; 503 responses retry, other non-OK responses bail. source: experience.js@824370e:packages/sdks/shared/src/lib/api/NinetailedApiClient.ts#NinetailedApiClient. +- API mutation requests default to `Content-Type: text/plain`, set `type=preflight` for preflight requests, add `locale` query param, and add `X-Force-IP` when `ip` is supplied. source: experience.js@824370e:packages/sdks/shared/src/lib/api/NinetailedApiClient.ts#NinetailedApiClient. + +## React render and hooks + +- `useProfile` subscribes to profile changes, suppresses deep-equal updates, and omits `experiences` from the hook result. source: experience.js@824370e:packages/sdks/react/src/lib/useProfile.ts#useProfile. +- `usePersonalize` selects among a baseline and variants from the current profile; `Personalize` hides the baseline wrapper during loading unless a custom loading component is supplied, then wraps rendered content in `TrackHasSeenComponent`. source: experience.js@824370e:packages/sdks/react/src/lib/usePersonalize.ts#usePersonalize; experience.js@824370e:packages/sdks/react/src/lib/Personalize.tsx#Personalize. +- `useExperience` subscribes to `onSelectVariant` and starts from the baseline while loading; `Experience` observes the rendered element for views and optional clicks/hovers, renders a marker for non-forwardRef components, and renders nothing but a marker for hidden variants. source: experience.js@824370e:packages/sdks/react/src/lib/Experience/useExperience.ts#useExperience; experience.js@824370e:packages/sdks/react/src/lib/Experience/Experience.tsx#Experience. +- `useFlag` auto-tracks a successful flag by default; `useFlagWithManualTracking` unwraps `{ value }` change payloads, falls back to the default value, and tracks flags as variable component views with id `Variable-{flagKey}`. source: experience.js@824370e:packages/sdks/react/src/lib/useFlag.ts#useFlag; experience.js@824370e:packages/sdks/react/src/lib/useFlagWithManualTracking.ts#useFlagWithManualTracking. + +## Contentful model and mapper + +- Contentful experience entries are expected to expose `nt_name`, `nt_description`, `nt_type`, `nt_config`, `nt_audience`, `nt_variants`, and `nt_experience_id`; missing `nt_config` defaults to zero traffic, two-way `0.5/0.5` distribution, no components, and non-sticky. source: experience.js@824370e:packages/utils/contentful/src/types/ExperienceEntry.ts#ExperienceEntryFields. +- `ExperienceMapper.mapExperience` validates the entry, maps each variant entry to include `id: variant.sys.id`, then delegates to the generic ExperienceMapper. source: experience.js@824370e:packages/utils/contentful/src/lib/ExperienceMapper.ts#ExperienceMapper. +- `ExperienceMapper.mapCustomExperience` and async variant let apps supply their own variant mapping; `mapBaselineWithExperiences` reads `entry.fields.nt_experiences`, filters valid experience entries, and maps them. source: experience.js@824370e:packages/utils/contentful/src/lib/ExperienceMapper.ts#ExperienceMapper. +- Generic mapper converts config `traffic` to `trafficAllocation`, cumulative `distribution` ranges, and entry-replacement components by matching variant references against mapped variants; unsupported component types throw. source: experience.js@824370e:packages/utils/javascript/src/lib/ExperienceMapper.ts#ExperienceMapper. +- Experience matching requires traffic allocation to beat a stable profile-derived random value and requires the profile to contain the audience id when the experience has an audience. source: experience.js@824370e:packages/sdks/shared/src/lib/utils/experiences/isExperienceMatch.ts#isExperienceMatch. +- Variant selection returns baseline when no variants exist, no distribution matches, distribution index is control, or the selected variant metadata is missing. source: experience.js@824370e:packages/sdks/shared/src/lib/utils/experiences/selectVariant.ts#selectVariant. + +## Identifiers and persistence + +| Identifier | Legacy owner | Migration note | source | +| -------------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `__nt_anonymous_id__` | Browser analytics storage | Core browser profile id storage key; SSR plugin syncs it from `ntaid`. | experience.js@824370e:packages/sdks/javascript/src/lib/NinetailedCorePlugin/constants.ts#ANONYMOUS_ID; experience.js@824370e:packages/plugins/ssr/src/lib/analytics/plugin.ts#NinetailedSsrPlugin | +| `__anon_id` | Browser analytics storage | Legacy key migrated into `__nt_anonymous_id__` on core plugin initialize. | experience.js@824370e:packages/sdks/javascript/src/lib/NinetailedCorePlugin/NinetailedCorePlugin.ts#NinetailedCorePlugin | +| `__nt_profile__`, `__nt_experiences__`, `__nt_changes__` | Browser analytics storage | Fallback caches used when profile flush fails. | experience.js@824370e:packages/sdks/javascript/src/lib/NinetailedCorePlugin/constants.ts#PROFILE_FALLBACK_CACHE | +| `__nt_debug__` | Browser analytics storage | Enables console logging when present; `debug(true)` writes it. | experience.js@824370e:packages/sdks/javascript/src/lib/NinetailedCorePlugin/constants.ts#DEBUG_FLAG | +| `__nt-consent__` | Browser analytics storage | Privacy plugin stores accepted consent here. | experience.js@824370e:packages/plugins/privacy/src/NinetailedPrivacyPlugin.ts#NinetailedPrivacyPlugin | +| `ntaid` | Cookie | SSR/ESR profile continuity cookie; SSR plugin writes/removes it and defaults expiry to 365 days. | experience.js@824370e:packages/sdks/shared/src/lib/constants.ts#NINETAILED_ANONYMOUS_ID_COOKIE; experience.js@824370e:packages/plugins/ssr/src/lib/analytics/plugin.ts#NinetailedSsrPlugin | +| `window.ninetailed` | Browser global | Exposes reset, profile/experiences, privacy consent, and preview/plugin APIs. | experience.js@824370e:packages/sdks/javascript/src/lib/Ninetailed.ts#Ninetailed; experience.js@824370e:packages/plugins/preview/src/lib/plugin/NinetailedPreviewPlugin.ts#NinetailedPreviewPlugin | + +## Next.js, SSR, and ESR + +- Next.js package provider wraps React provider and mounts a tracker that calls `ninetailed.page()` on initial route and `routeChangeComplete`, deduping by URL and a `tracked` sentinel. source: experience.js@824370e:packages/sdks/nextjs/src/lib/NinetailedProvider.tsx#NinetailedProvider; experience.js@824370e:packages/sdks/nextjs/src/lib/Tracker.tsx#Tracker. +- `onRouteChange` replaces the default page call and receives `{ isInitialRoute }` plus the `Ninetailed` instance. source: experience.js@824370e:packages/sdks/nextjs/src/lib/Tracker.tsx#Tracker. +- `decodeExperienceVariantsMap` parses `experienceId=variantIndex` pairs separated by commas and skips invalid or zero-like variant indexes. source: experience.js@824370e:packages/sdks/nextjs/src/lib/decodeExperienceVariantsMap.ts#decodeExperienceVariantsMap. +- SSR plugin initializes browser storage from the `ntaid` cookie, writes `ntaid` on profile change, and removes it on profile reset. source: experience.js@824370e:packages/plugins/ssr/src/lib/analytics/plugin.ts#NinetailedSsrPlugin. +- `getServerSideProfile` builds a page event, uses cookie `ntaid` or a new UUID as profile id, upserts through a supplied `NinetailedApiClient`, and returns the profile. source: experience.js@824370e:packages/plugins/ssr/src/lib/analytics/get-server-side-profile.ts#getServerSideProfile. +- ESR helper `buildNinetailedEdgeRequestContext` derives URL, locale, referrer, and user agent from `NextRequest`; `fetchEdgeProfile` sends a preflight page upsert using `ntaid`, optional IP, and optional location. source: experience.js@824370e:packages/sdks/nextjs-esr/src/lib/build-esr-context.ts#buildNinetailedEdgeRequestContext; experience.js@824370e:packages/sdks/nextjs-esr/src/lib/fetchEdgeProfile.ts#fetchEdgeProfile. +- ESR middleware and profile selector are present in source but not exported from the package entry. source: experience.js@824370e:packages/sdks/nextjs-esr/src/index.ts; experience.js@824370e:packages/sdks/nextjs-esr/src/lib/middleware.ts; experience.js@824370e:packages/sdks/nextjs-esr/src/lib/selector.ts#selectNinetailedProfile. + +## Plugins and preview + +- Analytics plugin base dedupes seen elements by element + sanitized payload, dedupes variables by variable id + sanitized payload, ignores seen elements without experience or audience, and maps index `0` to `control`. source: experience.js@824370e:packages/plugins/analytics/src/lib/NinetailedAnalyticsPlugin.ts#NinetailedAnalyticsPlugin. +- Google Analytics plugin only sends deprecated component events via `window.gtag`; it does nothing for experience view events. source: experience.js@824370e:packages/plugins/google-analytics/src/NinetailedGoogleAnalyticsPlugin.ts#NinetailedGoogleAnalyticsPlugin. +- Google Tag Manager, Segment, and Contentsquare plugins forward experience view payloads to `window.dataLayer`, Segment `analytics.track`, or `window.ninetailed.plugins.contentsquare.dataLayer`. source: experience.js@824370e:packages/plugins/google-tagmanager/src/NinetailedGoogleTagmanagerPlugin.ts#NinetailedGoogleTagmanagerPlugin; experience.js@824370e:packages/plugins/segment/src/NinetailedSegmentPlugin.ts#NinetailedSegmentPlugin; experience.js@824370e:packages/plugins/contentsquare/src/NinetailedContentsquarePlugin.ts#NinetailedContentsquarePlugin. +- Privacy plugin defaults to allowing only page events and no traits/track properties/profile merging/features; accepted consent allows page/track/identify/component/click/hover, all traits/properties/events, profile merging, and all features. source: experience.js@824370e:packages/plugins/privacy/src/NinetailedPrivacyPlugin.ts#DEFAULT_PRIVACY_CONFIG; experience.js@824370e:packages/plugins/privacy/src/NinetailedPrivacyPlugin.ts#DEFAULT_ACCEPTED_CONSENT_CONFIG. +- Privacy plugin exposes `window.ninetailed.consent`, writes `__nt-consent__`, updates enabled features when consent storage changes, filters properties/traits by wildcard allow-lists, blocks disallowed events by aborting analytics start events, and queues blocked payloads internally without replay. source: experience.js@824370e:packages/plugins/privacy/src/NinetailedPrivacyPlugin.ts#NinetailedPrivacyPlugin. +- Preview plugin attaches a Zoid widget at `https://preview.widgets.ninetailed.io/v2` by default, exposes `window.ninetailed.plugins.preview`, and only one active plugin instance attaches. source: experience.js@824370e:packages/plugins/preview/src/lib/plugin/NinetailedPreviewPlugin.ts#NinetailedPreviewPlugin; experience.js@824370e:packages/plugins/preview-bridge/src/PreviewBridge.ts#PreviewBridge. +- Preview plugin implements experience-selection middleware for entry replacements and changes-modification middleware for inline variable overrides; audience activation/deactivation and forced variant indexes rewrite active audiences, selected variants, and variable changes in memory. source: experience.js@824370e:packages/plugins/preview/src/lib/plugin/NinetailedPreviewPlugin.ts#NinetailedPreviewPlugin. +- Insights plugin builds component view/click/hover/variable events through injected `EventBuilder`, consent-gates them by dispatching `*_START` actions, batches by previous profile, flushes when queued events reach 25, and uses `navigator.sendBeacon` on page hidden. source: experience.js@824370e:packages/plugins/insights/src/lib/NinetailedInsightsPlugin.ts#NinetailedInsightsPlugin; experience.js@824370e:packages/plugins/insights/src/lib/api/NinetailedInsightsApiClient.ts#NinetailedInsightsApiClient. + +## Unsupported or manual migration boundaries + +- Legacy `Personalize`, `usePersonalize`, `useFlag`, `useFlagWithManualTracking`, and `TrackHasSeenComponent` are legacy data-model/flag surfaces; target guides should route entry replacement through `OptimizedEntry` / `resolveOptimizedEntry` and flags through target SDK flag APIs rather than preserving legacy component shapes. source: experience.js@824370e:packages/sdks/react/src/lib/Personalize.tsx#Personalize; experience.js@824370e:packages/sdks/react/src/lib/useFlag.ts#useFlag. +- Legacy Contentful `ExperienceMapper` is a migration aid, not a target pattern; target SDK guides should not ask readers to build `ExperienceConfiguration` arrays from `nt_config`. source: experience.js@824370e:packages/utils/contentful/src/lib/ExperienceMapper.ts#ExperienceMapper. +- Checked sources do not expose an official Contentful-side content migration/export tool or field mapping for moving legacy Ninetailed-authored entries to the target Optimization content model; the guide should treat content migration as an operational authoring step, not an SDK-provided codemod. source: experience.js@824370e:package.json; experience.js@824370e:packages/playgrounds/easy-hr/contentful/setup.js; experience.js@824370e:packages/playgrounds/easy-hr/contentful/export.js; extern:https://www.contentful.com/developers/docs/personalization/quick-start-guide/; extern:https://www.contentful.com/developers/docs/references/experience-api/. +- Legacy third-party analytics plugins have no direct package mapping in the Optimization SDK Suite; migration is manual event-stream forwarding or target insights behavior. source: experience.js@824370e:packages/plugins/google-tagmanager/src/NinetailedGoogleTagmanagerPlugin.ts#NinetailedGoogleTagmanagerPlugin; experience.js@824370e:packages/plugins/segment/src/NinetailedSegmentPlugin.ts#NinetailedSegmentPlugin. +- Legacy privacy plugin consent rules are not the target consent model; target SDK uses app-owned consent plus SDK `consent` / `defaults` / `allowedEventTypes`. source: experience.js@824370e:packages/plugins/privacy/src/NinetailedPrivacyPlugin.ts#NinetailedPrivacyPlugin. +- Legacy Next ESR package has unexported middleware and selector files; migration guides should not treat them as supported import surfaces. source: experience.js@824370e:packages/sdks/nextjs-esr/src/index.ts. diff --git a/documentation/internal/sdk-knowledge/shared/concepts.md b/documentation/internal/sdk-knowledge/shared/concepts.md index a21fe2878..a8221037f 100644 --- a/documentation/internal/sdk-knowledge/shared/concepts.md +++ b/documentation/internal/sdk-knowledge/shared/concepts.md @@ -120,14 +120,37 @@ skip the duplicate (per-SDK `initialPageEvent` / tracker prop). Interaction even (view/click/hover) are consent-gated browser activity and use the resolved entry id. source: react-web-sdk#auto-page/useAutoPageEmitter.ts; react-web-sdk#router/next-app.tsx -## Event streams and blocked events +## Custom flag views + +Stateful flag reads auto-attempt flag-view tracking: `getFlag(name)` tracks the read immediately, +and `states.flag(name)` tracks `.current`, `subscribe()`, and `subscribeOnce()` reads. The explicit +manual equivalent is `trackFlagView()`, which builds a `component` view event with +`componentType: 'Variable'`. +source: core-sdk#CoreStatefulEventEmitter.ts#getFlag; core-sdk#CoreStatefulEventEmitter.ts#getFlagObservable; core-sdk#CoreStatefulEventEmitter.ts#trackFlagView; core-sdk#events/EventBuilder.ts#buildFlagView + +Auto flag-view attempts require consent for `trackFlagView` and a current profile id. A pre-consent +or pre-profile flag read does not suppress a later accepted same-value track, active +`states.flag(name)` subscriptions re-attempt when tracking becomes allowed, and accepted attempts are +deduped by flag value, component id, experience id, variant index, and profile id. +source: core-sdk#CoreStatefulEventEmitter.ts#attemptFlagViewTracking; core-sdk#CoreStatefulEventEmitter.ts#initializeFlagViewConsentEffect; core-sdk#CoreStatefulEventEmitter.ts#buildFlagViewTrackingSignature + +## Stateful event forwarding streams Stateful JS SDK event forwarding is a current-value signal surface, not a durable event queue. -`states.eventStream` and `states.blockedEventStream` emit the current value immediately on subscribe -and then later signal updates; the exposed streams keep only the latest accepted or blocked event -value while Experience/Insights delivery queues remain internal. A late subscriber must dedupe from -the events it observes; the SDK does not replay a full event history through these observables. -source: core-sdk#CoreStateful.ts#CoreStates; core-sdk#CoreStateful.ts#CoreStateful; core-sdk#signals/Observable.ts#toObservable; core-sdk#signals/signals.ts#event; core-sdk#signals/signals.ts#blockedEvent; core-sdk#queues/ExperienceQueue.ts#ExperienceQueue; core-sdk#queues/InsightsQueue.ts#InsightsQueue +`states.eventStream` emits the most recent accepted Experience or Insights event after event +interceptors and schema validation; `states.blockedEventStream` emits only consent-blocked calls as +`{ reason: 'consent', method, args }`. Both observables emit the current value immediately on +subscribe and then later signal updates; the exposed streams keep only the latest accepted or +blocked event value while Experience/Insights delivery queues remain internal. A late subscriber +must dedupe from the events it observes; the SDK does not replay a full event history through these +observables. Blocked callback failures are logged rather than thrown. +source: core-sdk#CoreStateful.ts#CoreStates; core-sdk#CoreStateful.ts#CoreStateful; core-sdk#signals/Observable.ts#toObservable; core-sdk#signals/signals.ts#event; core-sdk#signals/signals.ts#blockedEvent; core-sdk#queues/ExperienceQueue.ts#ExperienceQueue; core-sdk#queues/InsightsQueue.ts#InsightsQueue; core-sdk#events/BlockedEvent.ts#BlockedEvent; core-sdk#CoreStatefulEventEmitter.ts#reportBlockedEvent + +Event-stream payloads carry each event's normal schema plus universal event fields such as +`messageId`, `channel`, `context`, and timestamps. Optimized-entry interactions add `optimization` +context only to the stream payload, not to the strict API payload. Flag-view stream events are not +enriched with `optimization`. +source: api-schemas#experience/event/UniversalEventProperties.ts#UniversalEventProperties; core-sdk#queues/ExperienceQueue.ts#send; core-sdk#queues/InsightsQueue.ts#send; core-sdk#events/OptimizationEventStreamEvent.ts#OptimizationEventStreamEvent Consent-blocked stateful events stop before API delivery or queueing: Experience methods return `{ accepted: false }`, Insights methods return without enqueueing, and Core writes only @@ -137,12 +160,6 @@ blocked attempts as accepted, so a later caller/effect can retry the same curren fresh payload under the current consent state. source: core-sdk#CoreStatefulEventEmitter.ts#sendExperienceEventWithResult; core-sdk#CoreStatefulEventEmitter.ts#sendInsightsEvent; core-sdk#CoreStatefulEventEmitter.ts#reportBlockedEvent; core-sdk#CoreStateful.ts#consent; core-sdk#tracking/AcceptedCurrentStateTracker.ts#AcceptedCurrentStateTracker; web-sdk#ContentfulOptimization.ts#trackCurrentPage; optimization-js-bridge#index.ts#Bridge -Event-stream `optimization` is runtime-only forwarding enrichment. Core attaches it only to the -`eventStream` value when an optimized-entry interaction has an `EventOptimizationContext`; -Experience and Insights API queues validate and send the original event payload without that -property. -source: core-sdk#events/OptimizationEventStreamEvent.ts#OptimizationEventStreamEvent; core-sdk#queues/ExperienceQueue.ts#send; core-sdk#queues/InsightsQueue.ts#send - ## Experience response payload An accepted Experience API request (page/identify/screen/track) returns `OptimizationData` @@ -222,3 +239,22 @@ fields are present, while preview overrides update their clean baselines only fr unchanged; own present `undefined` is a resettable baseline, and override derivation falls back to empty arrays only when no baseline exists. source: core-sdk#runtime/SnapshotRuntime.ts#SnapshotRuntime; core-sdk#preview-support/PreviewOverrideManager.ts#PreviewOverrideManager; core-sdk#CoreBase.ts#LifecycleInterceptors + +## Preview overrides + +The web preview panel attaches through the SDK bridge, registers a `PreviewOverrideManager`, and +mutates the stateful SDK's `selectedOptimizations` and `changes` signals from a clean API baseline +plus current overrides. Opening the panel sets `previewPanelOpen`, which forces optimized entries to +live-update while the panel is open. +source: preview-panel#attachOptimizationPreviewPanel.ts#attachOptimizationPreviewPanelToSdk; core-sdk#bridge-support/capabilities.ts#installCoreBridgeCapabilities; core-sdk#preview-support/PreviewOverrideManager.ts#syncOverridesToSignal; web-sdk#presentation/OptimizedEntryController.ts#resolveShouldLiveUpdate + +Audience overrides activate all associated experiences at variant index `1` or deactivate them at +variant index `0`; single-experience overrides replace that experience's `variantIndex`, appending a +selection with an empty `variants` map when the API baseline did not include it. Reset restores the +cached API baseline. +source: core-sdk#preview-support/PreviewOverrideManager.ts#activateAudience; core-sdk#preview-support/PreviewOverrideManager.ts#deactivateAudience; core-sdk#preview-support/PreviewOverrideManager.ts#setVariantOverride; core-sdk#preview-support/applyOptimizationOverrides.ts#applyOptimizationOverrides; core-sdk#preview-support/PreviewOverrideManager.ts#resetAll + +Inline-variable preview overrides are represented as `Variable` changes so `getFlag()` and +`states.flag(name)` resolve the preview-selected value; variant index `0` and out-of-range variant +indexes use the component baseline value. +source: core-sdk#preview-support/applyChangeOverrides.ts#applyChangeOverrides; core-sdk#preview-support/PreviewOverrideManager.ts#deriveChanges diff --git a/documentation/internal/sdk-knowledge/web/nextjs-app-router.md b/documentation/internal/sdk-knowledge/web/nextjs-app-router.md index c626c5c8d..f64ebffa9 100644 --- a/documentation/internal/sdk-knowledge/web/nextjs-app-router.md +++ b/documentation/internal/sdk-knowledge/web/nextjs-app-router.md @@ -202,6 +202,13 @@ source: `nextjs-sdk#app-router-server.tsx#renderBoundRootTree`; `nextjs-sdk#app- histories, blocked events are diagnostic only, and event-stream `optimization` is not sent to Experience/Insights API payloads. source: `nextjs-sdk#app-router-server.tsx#toClientRootConfig`; `react-web-sdk#provider/OptimizationProvider.tsx#OptimizationProvider`; kb:shared/concepts.md; kb:web/react-web.md +- Browser/client flags use the React Web stateful runtime: `sdk.getFlag()` and `sdk.states.flag()` + auto-attempt flag-view tracking, while `sdk.trackFlagView()` is the explicit/manual path. + source: `react-web-sdk#hooks/useOptimization.ts#useOptimizationContext`; core-sdk#CoreStatefulEventEmitter.ts#getFlag; core-sdk#CoreStatefulEventEmitter.ts#getFlagObservable; core-sdk#CoreStatefulEventEmitter.ts#trackFlagView +- Preview-panel attachment is a browser/client concern; once attached to the live client SDK, its + audience, variant, and inline-variable overrides use the shared preview override behavior and + force live updates while open. + source: preview-panel#attachOptimizationPreviewPanel.ts#attachOptimizationPreviewPanelToSdk; core-sdk#preview-support/PreviewOverrideManager.ts#setVariantOverride; core-sdk#preview-support/applyChangeOverrides.ts#applyChangeOverrides; react-web-sdk#provider/LiveUpdatesProvider.tsx#LiveUpdatesProvider ## Consent & persistence diff --git a/documentation/internal/sdk-knowledge/web/nextjs-pages-router.md b/documentation/internal/sdk-knowledge/web/nextjs-pages-router.md index 1fd20ba81..59ba2a20c 100644 --- a/documentation/internal/sdk-knowledge/web/nextjs-pages-router.md +++ b/documentation/internal/sdk-knowledge/web/nextjs-pages-router.md @@ -151,6 +151,13 @@ source: `nextjs-sdk#pages-router.ts#OptimizedEntry`; `react-web-sdk#optimized-en histories, blocked events are diagnostic only, and event-stream `optimization` is not sent to Experience/Insights API payloads. source: `nextjs-sdk#pages-router.ts#toClientRootConfig`; `react-web-sdk#provider/OptimizationProvider.tsx#OptimizationProvider`; kb:shared/concepts.md; kb:web/react-web.md +- Browser/client flags use the React Web stateful runtime: `sdk.getFlag()` and `sdk.states.flag()` + auto-attempt flag-view tracking, while `sdk.trackFlagView()` is the explicit/manual path. + source: `react-web-sdk#hooks/useOptimization.ts#useOptimizationContext`; core-sdk#CoreStatefulEventEmitter.ts#getFlag; core-sdk#CoreStatefulEventEmitter.ts#getFlagObservable; core-sdk#CoreStatefulEventEmitter.ts#trackFlagView +- Preview-panel attachment is a browser/client concern; once attached to the live client SDK, its + audience, variant, and inline-variable overrides use the shared preview override behavior and + force live updates while open. + source: preview-panel#attachOptimizationPreviewPanel.ts#attachOptimizationPreviewPanelToSdk; core-sdk#preview-support/PreviewOverrideManager.ts#setVariantOverride; core-sdk#preview-support/applyChangeOverrides.ts#applyChangeOverrides; react-web-sdk#provider/LiveUpdatesProvider.tsx#LiveUpdatesProvider ## Consent & persistence diff --git a/documentation/internal/sdk-knowledge/web/react-web.md b/documentation/internal/sdk-knowledge/web/react-web.md index 0a050e216..e10feb480 100644 --- a/documentation/internal/sdk-knowledge/web/react-web.md +++ b/documentation/internal/sdk-knowledge/web/react-web.md @@ -193,18 +193,15 @@ source: core-sdk#runtime/SnapshotRuntime.ts#SnapshotRuntime; core-sdk#runtime/Sn missing view/click/hover can mean consent policy, a root/per-entry opt-out, missing tracking attributes, or missing profile continuity. source: web-sdk#entry-tracking/EntryInteractionRuntime.ts#reconcileInteraction; web-sdk#entry-tracking/EntryInteractionRuntime.ts#isInteractionAllowed; core-sdk#queues/InsightsQueue.ts#send -- Flags: `getFlag(name)` nonreactive read (defaults to current changes signal, emits flag-view - tracking when consent+profile allow); `states.flag(name)` reactive. - source: core-sdk#CoreStatefulEventEmitter.ts#getFlag; core-sdk#CoreStatefulEventEmitter.ts#getFlagObservable; core-sdk#CoreStateful.ts#CoreStates -- Analytics forwarding: `states.eventStream.current?.messageId` + `.subscribe`; dedupe by - `messageId`; `states.blockedEventStream` for blocked-event diagnostics; - `subscription.unsubscribe()`. - source: api-schemas#experience/event/UniversalEventProperties.ts#UniversalEventProperties; core-sdk#CoreStateful.ts#CoreStates; core-sdk#signals/Observable.ts#Subscription; core-sdk#signals/Observable.ts#Observable -- Browser event forwarding and blocked-event behavior follow shared stateful Core semantics: - current-value observables, not durable history; consent-blocked events are diagnostic only and are - not replayed by later consent; event-stream `optimization` is not sent to Experience/Insights API - payloads. - source: kb:shared/concepts.md +- Flags: `getFlag(name)` nonreactive reads and `states.flag(name)` reactive reads auto-attempt + flag-view tracking; explicit/manual replacement is `trackFlagView()` on the live `sdk`. See + [`../shared/concepts.md`](../shared/concepts.md#custom-flag-views). + source: core-sdk#CoreStatefulEventEmitter.ts#getFlag; core-sdk#CoreStatefulEventEmitter.ts#getFlagObservable; core-sdk#CoreStatefulEventEmitter.ts#trackFlagView; react-web-sdk#hooks/useOptimization.ts#useOptimizationContext +- Analytics forwarding: subscribe to the live `sdk.states.eventStream` for accepted events and + `sdk.states.blockedEventStream` for consent-blocked diagnostics; dedupe accepted events by + `messageId` and clean up subscriptions with `.unsubscribe()`. See + [`../shared/concepts.md`](../shared/concepts.md#stateful-event-forwarding-streams). + source: core-sdk#CoreStateful.ts#CoreStates; core-sdk#events/OptimizationEventStreamEvent.ts#OptimizationEventStreamEvent; core-sdk#events/BlockedEvent.ts#BlockedEvent; core-sdk#signals/Observable.ts#Subscription; react-web-sdk#hooks/useOptimization.ts#useOptimizationContext; kb:shared/concepts.md - `useEventStreamState()` subscribes internally and returns only the latest accepted event payload; it has no `.subscribe()` method. Code that needs accepted and blocked diagnostics or forwarding subscriptions must obtain a guarded live `sdk` from `useOptimizationContext()`, then subscribe to @@ -257,6 +254,10 @@ source: core-sdk#runtime/SnapshotRuntime.ts#SnapshotRuntime; core-sdk#runtime/Sn Web SDK and its preview bridge. `entries`, when supplied, is used instead of fetching through `contentful`. source: react-web-sdk#provider/OptimizationProvider.tsx#OptimizationProvider; preview-panel#attachOptimizationPreviewPanel.ts#attachOptimizationPreviewPanel; preview-panel#attachOptimizationPreviewPanel.ts#attachOptimizationPreviewPanelToSdk; preview-panel#attachOptimizationPreviewPanel.ts#AttachOptimizationPreviewPanelArgs +- Preview overrides force audiences, variants, and inline-variable flag values by mutating + stateful SDK signals from an API baseline; panel-open state forces optimized entries to + live-update. See [`../shared/concepts.md`](../shared/concepts.md#preview-overrides). + source: preview-panel#attachOptimizationPreviewPanel.ts#attachOptimizationPreviewPanelToSdk; core-sdk#preview-support/PreviewOverrideManager.ts#setVariantOverride; core-sdk#preview-support/applyChangeOverrides.ts#applyChangeOverrides; react-web-sdk#provider/LiveUpdatesProvider.tsx#LiveUpdatesProvider - `locale` prop change updates the SDK's Experience/event locale; the app still refetches Contentful and re-emits page events itself. source: react-web-sdk#provider/OptimizationProvider.tsx#OptimizationProvider; core-sdk#CoreStateful.ts#setLocale diff --git a/documentation/internal/sdk-knowledge/web/web.md b/documentation/internal/sdk-knowledge/web/web.md index 260577068..8260215d6 100644 --- a/documentation/internal/sdk-knowledge/web/web.md +++ b/documentation/internal/sdk-knowledge/web/web.md @@ -159,17 +159,6 @@ None (imperative class + Web Components; no React surface). Web Components eleme `tracking.enableElement('views', el, { data, dwellTimeMs })` / `disableElement` / `clearElement` (manual data precedes attributes). Uses RESOLVED entry id. source: web-sdk#entry-tracking/EntryInteractionRuntime.ts#EntryInteractionRuntime; web-sdk#entry-tracking/resolveAutoTrackEntryInteractionOptions.ts#EntryInteractionApi; web-sdk#presentation/OptimizedEntryTrackingAttributes.ts#resolveOptimizedEntryTrackingAttributes -- Flags: `getFlag(name)` one-off; `states.flag(name)` reactive; flag-view emission gated on - consent+profile and deduped. - source: core-sdk#CoreStatefulEventEmitter.ts#getFlag -- Analytics forwarding: `states.eventStream.current?.messageId` + `.subscribe`; dedupe by - `messageId`; every event carries `messageId` + `type`. - source: api-schemas#experience/event/UniversalEventProperties.ts#UniversalEventProperties; api-schemas#experience/event/PageViewEvent.ts#PageViewEvent -- Browser event forwarding and blocked-event behavior follow shared stateful Core semantics: - current-value observables, not durable history; consent-blocked events are diagnostic only and are - not replayed by later consent; event-stream `optimization` is not sent to Experience/Insights API - payloads. - source: kb:shared/concepts.md - Analytics-only handoff: `initializeOptimizationAnalyticsRuntime(config)` creates a narrow Web runtime with `tracking`, `trackCurrentPage`, `flush`, and `destroy`, but no content-resolution surface. It removes the global browser SDK reference if construction registered this analytics @@ -180,6 +169,16 @@ None (imperative class + Web Components; no React surface). Web Components eleme `static` and `public-permutation` analytics handoffs suppress durable continuity persistence the same way content handoffs do. source: web-sdk#analytics.ts#initializeOptimizationAnalyticsRuntime; web-sdk#analytics.ts#hydrateOptimizationAnalyticsHandoff; web-sdk#analytics.ts#warnSkippedInitialPageWithoutProfileContinuity; web-sdk#handoff.ts#shouldPreserveDurableContinuity +- Flags: `getFlag(name)` one-off and `states.flag(name)` reactive reads auto-attempt flag-view + tracking; explicit/manual replacement is `trackFlagView()`. See + [`../shared/concepts.md`](../shared/concepts.md#custom-flag-views). + source: core-sdk#CoreStatefulEventEmitter.ts#getFlag; core-sdk#CoreStatefulEventEmitter.ts#getFlagObservable; core-sdk#CoreStatefulEventEmitter.ts#trackFlagView +- Analytics forwarding: subscribe to `states.eventStream` for accepted events and + `states.blockedEventStream` for consent-blocked diagnostics; dedupe accepted events by + `messageId`. Event-stream payloads carry each event's normal schema plus universal fields such as + `messageId`, `channel`, `context`, and timestamps. See + [`../shared/concepts.md`](../shared/concepts.md#stateful-event-forwarding-streams). + source: core-sdk#CoreStateful.ts#CoreStates; core-sdk#events/OptimizationEventStreamEvent.ts#OptimizationEventStreamEvent; core-sdk#events/BlockedEvent.ts#BlockedEvent; api-schemas#experience/event/UniversalEventProperties.ts#UniversalEventProperties; kb:shared/concepts.md ## Consent & persistence @@ -233,5 +232,9 @@ None (imperative class + Web Components; no React surface). Web Components eleme are supplied, `entries` takes precedence and no Contentful fetch is made. Idempotent — re-invoking reuses the in-flight/completed attachment. source: preview-panel#attachOptimizationPreviewPanel.ts#attachOptimizationPreviewPanel; preview-panel#attachOptimizationPreviewPanel.ts#AttachOptimizationPreviewPanelArgs; preview-panel#lib/entries.ts#PreviewPanelEntries +- Preview overrides force audiences, variants, and inline-variable flag values by mutating + stateful SDK signals from an API baseline; panel-open state forces optimized entries to + live-update. See [`../shared/concepts.md`](../shared/concepts.md#preview-overrides). + source: preview-panel#attachOptimizationPreviewPanel.ts#attachOptimizationPreviewPanelToSdk; core-sdk#preview-support/PreviewOverrideManager.ts#setVariantOverride; core-sdk#preview-support/applyChangeOverrides.ts#applyChangeOverrides; web-sdk#presentation/OptimizedEntryController.ts#resolveShouldLiveUpdate - Validation: `pnpm implementation:run -- web-sdk typecheck`; `pnpm test:e2e:web-sdk`. source: impl:web-sdk#package.json diff --git a/scripts/validate-guide-authoring.ts b/scripts/validate-guide-authoring.ts index cb8c746ac..c1d6795ac 100644 --- a/scripts/validate-guide-authoring.ts +++ b/scripts/validate-guide-authoring.ts @@ -8,7 +8,11 @@ import { headingsOf, isTableDivider, parseTableRow } from './sdk-knowledge/markd const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') const blueprintDir = path.join(rootDir, 'documentation/authoring/blueprints') const templatePath = path.join(blueprintDir, '_template.md') +const migrationBlueprintDir = path.join(rootDir, 'documentation/authoring/migration-blueprints') +const migrationTemplatePath = path.join(migrationBlueprintDir, '_template.md') +const migrationRecipePath = path.join(rootDir, 'documentation/authoring/recipes/migration.md') const requiredFrontmatter = ['sdk', 'archetype', 'kb', 'guide'] as const +const migrationRequiredFrontmatter = ['migration', 'archetype', 'source', 'guide'] as const const sectionMapColumns = [ 'Section', 'Category', @@ -16,6 +20,8 @@ const sectionMapColumns = [ 'Must teach or show', 'Fact sources', ] as const +const migrationRouteColumns = ['Legacy surface', 'Target route', 'Detail owner'] as const +const migrationSectionPlanColumns = ['Section', 'Purpose', 'Must route to', 'Fact sources'] as const const categories = new Set([ 'Required for first integration', 'Common but policy-dependent', @@ -109,7 +115,7 @@ function validateLinks(file: string, lines: string[]): void { }) } -function validateLinkRoles(file: string, lines: string[]): void { +function validateLinkRoles(file: string, lines: string[], allowCodeReferences = false): void { const headings = headingsOf(lines) const rolesHeading = headings.find( (heading) => heading.level === 2 && heading.text === 'Link roles', @@ -126,16 +132,19 @@ function validateLinkRoles(file: string, lines: string[]): void { return } roles.forEach((role, index) => { - if (!/\[[^\]]+\]\([^)]+\)/u.test(role)) { + if (!/\[[^\]]+\]\([^)]+\)/u.test(role) && !(allowCodeReferences && /`[^`]+`/u.test(role))) { addProblem(file, rolesHeading.line + index + 1, 'each Link role must contain a Markdown link') } }) } -function sectionMapTable(lines: string[]): { firstLine: number; lines: string[] } | undefined { +function tableSection( + lines: string[], + headingText: string, +): { firstLine: number; lines: string[] } | undefined { const headings = headingsOf(lines) const sectionHeading = headings.find( - (heading) => heading.level === 2 && heading.text === 'Section map', + (heading) => heading.level === 2 && heading.text === headingText, ) if (sectionHeading === undefined) { return undefined @@ -149,22 +158,24 @@ function sectionMapTable(lines: string[]): { firstLine: number; lines: string[] } } -function sectionMapHeader( +function tableHeader( file: string, table: { firstLine: number; lines: string[] }, + columns: readonly string[], + tableName: string, ): number | undefined { const { lines: tableLines } = table const headerOffset = tableLines.findIndex((line) => { const cells = parseTableRow(line) - return cells?.join('\0') === sectionMapColumns.join('\0') + return cells?.join('\0') === columns.join('\0') }) if (headerOffset === -1) { - addProblem(file, table.firstLine, 'Section map table has the wrong columns') + addProblem(file, table.firstLine, `${tableName} table has the wrong columns`) return undefined } const divider = parseTableRow(tableLines[headerOffset + 1] ?? '') if (divider === undefined || !isTableDivider(divider)) { - addProblem(file, table.firstLine + headerOffset + 1, 'Section map table lacks a divider') + addProblem(file, table.firstLine + headerOffset + 1, `${tableName} table lacks a divider`) return undefined } return headerOffset @@ -205,11 +216,11 @@ function sectionMapRow(file: string, line: number, cells: string[]): SectionRow } function sectionRows(file: string, lines: string[]): SectionRow[] { - const table = sectionMapTable(lines) + const table = tableSection(lines, 'Section map') if (table === undefined) { return [] } - const headerOffset = sectionMapHeader(file, table) + const headerOffset = tableHeader(file, table, sectionMapColumns, 'Section map') if (headerOffset === undefined) { return [] } @@ -284,13 +295,123 @@ function compareRows(blueprint: string, guide: string, expected: SectionRow[]): } } +function sectionsOf(lines: string[]): string[] { + return headingsOf(lines) + .filter((heading) => heading.level === 2) + .map((heading) => heading.text) +} + +function validateSections(file: string, lines: string[], expected: string[], label: string): void { + const sections = sectionsOf(lines) + if (sections.join('\0') !== expected.join('\0')) { + addProblem(file, 1, `${label} headings do not match _template.md: ${sections.join(' -> ')}`) + } +} + +function tableRows( + file: string, + lines: string[], + headingText: string, + columns: readonly string[], +): Array<{ line: number; cells: string[] }> { + const table = tableSection(lines, headingText) + if (table === undefined) { + return [] + } + const headerOffset = tableHeader(file, table, columns, headingText) + if (headerOffset === undefined) { + return [] + } + return table.lines + .slice(headerOffset + tableHeaderRowCount) + .map((tableLine, index) => { + const cells = parseTableRow(tableLine) + const line = table.firstLine + headerOffset + tableHeaderRowCount + index + 1 + if (cells === undefined) { + return undefined + } + if (cells.length !== columns.length) { + addProblem( + file, + line, + `${headingText} row has ${cells.length} cells; expected ${columns.length}`, + ) + return undefined + } + if (cells.some((cell) => cell === '')) { + addProblem(file, line, `${headingText} cells must all be non-empty`) + } + return { line, cells } + }) + .filter((row): row is { line: number; cells: string[] } => row !== undefined) +} + +function validateMigrationTables(file: string, lines: string[]): number { + tableRows(file, lines, 'Migration route', migrationRouteColumns) + const rows = tableRows(file, lines, 'Section plan', migrationSectionPlanColumns) + rows.forEach(({ line, cells }) => { + const facts = cells[migrationSectionPlanColumns.indexOf('Fact sources')] ?? '' + if (!/\[[^\]]+\]\([^)]+\)/u.test(facts)) { + addProblem(file, line, 'Fact sources must contain at least one knowledge link') + } + }) + return rows.length +} + +function validateTarget( + file: string, + frontmatter: Map, + key: string, + label: string, +): string | undefined { + const target = frontmatter.get(key) + if (target === undefined) { + return undefined + } + const resolved = path.resolve(path.dirname(file), target) + if (!existsSync(resolved)) { + addProblem(file, 1, `${label} target does not exist: ${target}`) + return undefined + } + return resolved +} + +function migrationGuideSections(): string[] { + const lines = readFileSync(migrationRecipePath, 'utf8').split(/\r?\n/u) + const headings = headingsOf(lines) + const templateHeading = headings.find( + (heading) => heading.level === 2 && heading.text === 'Template', + ) + return headings + .filter( + (heading) => + heading.level === 2 && templateHeading !== undefined && heading.line > templateHeading.line, + ) + .map((heading) => heading.text) +} + +function validateMigrationGuideSpine(file: string, lines: string[], expected: string[]): void { + const sections = sectionsOf(lines) + if (sections.join('\0') !== expected.join('\0')) { + addProblem( + file, + 1, + `migration guide headings do not match migration recipe: ${sections.join(' -> ')}`, + ) + } +} + const templateLines = readFileSync(templatePath, 'utf8').split(/\r?\n/u) -const templateSections = headingsOf(templateLines) - .filter((heading) => heading.level === 2) - .map((heading) => heading.text) +const templateSections = sectionsOf(templateLines) +const migrationTemplateLines = readFileSync(migrationTemplatePath, 'utf8').split(/\r?\n/u) +const migrationTemplateSections = sectionsOf(migrationTemplateLines) +const expectedMigrationGuideSections = migrationGuideSections() const blueprintFiles = readdirSync(blueprintDir) .filter((name) => name.endsWith('.md') && name !== '_template.md') .sort() +const migrationBlueprintFiles = readdirSync(migrationBlueprintDir) + .filter((name) => name.endsWith('.md') && name !== '_template.md') + .sort() let sectionCount = 0 for (const name of blueprintFiles) { @@ -302,32 +423,44 @@ for (const name of blueprintFiles) { addProblem(file, 1, `missing frontmatter key: ${key}`) } }) - const sections = headingsOf(lines) - .filter((heading) => heading.level === 2) - .map((heading) => heading.text) - if (sections.join('\0') !== templateSections.join('\0')) { - addProblem(file, 1, `blueprint headings do not match _template.md: ${sections.join(' -> ')}`) - } + validateSections(file, lines, templateSections, 'blueprint') validateLinks(file, lines) validateLinkRoles(file, lines) const rows = sectionRows(file, lines) sectionCount += rows.length - const guideTarget = frontmatter.get('guide') - const kbTarget = frontmatter.get('kb') - if (guideTarget !== undefined) { - const guide = path.resolve(path.dirname(file), guideTarget) - if (!existsSync(guide)) { - addProblem(file, 1, `guide target does not exist: ${guideTarget}`) - } else { - const guideLines = readFileSync(guide, 'utf8').split(/\r?\n/u) - validateLinks(guide, guideLines) - validateGuideMarkers(guide, guideLines) - compareRows(file, guide, rows) - } + const guide = validateTarget(file, frontmatter, 'guide', 'guide') + if (guide !== undefined) { + const guideLines = readFileSync(guide, 'utf8').split(/\r?\n/u) + validateLinks(guide, guideLines) + validateGuideMarkers(guide, guideLines) + compareRows(file, guide, rows) } - if (kbTarget !== undefined && !existsSync(path.resolve(path.dirname(file), kbTarget))) { - addProblem(file, 1, `KB target does not exist: ${kbTarget}`) + validateTarget(file, frontmatter, 'kb', 'KB') +} + +let migrationSectionCount = 0 +for (const name of migrationBlueprintFiles) { + const file = path.join(migrationBlueprintDir, name) + const lines = readFileSync(file, 'utf8').split(/\r?\n/u) + const frontmatter = parseFrontmatter(file, lines) + migrationRequiredFrontmatter.forEach((key) => { + if (!frontmatter.has(key)) { + addProblem(file, 1, `missing frontmatter key: ${key}`) + } + }) + validateSections(file, lines, migrationTemplateSections, 'migration blueprint') + validateLinks(file, lines) + validateLinkRoles(file, lines, true) + migrationSectionCount += validateMigrationTables(file, lines) + + validateTarget(file, frontmatter, 'source', 'source') + const guide = validateTarget(file, frontmatter, 'guide', 'guide') + if (guide !== undefined) { + const guideLines = readFileSync(guide, 'utf8').split(/\r?\n/u) + validateLinks(guide, guideLines) + validateGuideMarkers(guide, guideLines) + validateMigrationGuideSpine(guide, guideLines, expectedMigrationGuideSections) } } @@ -342,6 +475,6 @@ if (problems.length > 0) { process.exitCode = 1 } else { process.stdout.write( - `✓ Guide authoring: ${blueprintFiles.length} blueprint(s), ${sectionCount} planned section(s), links and rendered guide structure valid.\n`, + `✓ Guide authoring: ${blueprintFiles.length} integration blueprint(s), ${sectionCount} planned integration section(s), ${migrationBlueprintFiles.length} migration blueprint(s), ${migrationSectionCount} planned migration section(s), links and rendered guide structure valid.\n`, ) }