diff --git a/AGENTS.md b/AGENTS.md
index 01f23e10..a0ee91c8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -16,7 +16,7 @@ applicable child `AGENTS.md` from outermost to nearest in the subtree you are ed
- Keep child `AGENTS.md` files focused on local behavior. Do not duplicate root policy unless the
subtree has a local exception.
-## Environment And Tooling
+## Environment and tooling
- Use the Node version from [`.nvmrc`](./.nvmrc) when possible. Repository engine constraints live
in the root [`package.json`](./package.json).
@@ -29,7 +29,7 @@ applicable child `AGENTS.md` from outermost to nearest in the subtree you are ed
- Some implementations have additional runtime prerequisites or process-management expectations. See
the relevant implementation `AGENTS.md` before running local app or E2E flows.
-## Repository Layout
+## Repository layout
- Shared libraries and published SDK packages live under `lib/` and `packages/`.
- Reference implementations live under `implementations/`.
@@ -38,7 +38,7 @@ applicable child `AGENTS.md` from outermost to nearest in the subtree you are ed
- Generated outputs such as `dist/`, `coverage/`, `docs/`, `pkgs/`, `playwright-report/`, and
`test-results/` are not the source of truth.
-## Source Of Truth
+## Source of truth
Prefer editing source files and configuration:
@@ -67,7 +67,7 @@ Do not hand-edit generated or local-only artifacts unless the task is explicitly
- `node_modules/**`
- local `.env` files
-## ESLint Discipline
+## ESLint discipline
- Treat [`eslint.config.ts`](./eslint.config.ts) as an upfront design constraint, not a cleanup step
after coding.
@@ -96,7 +96,7 @@ Do not hand-edit generated or local-only artifacts unless the task is explicitly
- If code keeps fighting the linter, stop and rewrite the approach to match repository patterns
rather than stacking fixes.
-## Validation Policy
+## Validation policy
- Run the smallest meaningful validation that matches the change.
- When linting or formatting is likely needed, prefer the smallest fix-enabled command that matches
@@ -123,7 +123,7 @@ High-signal repo-wide commands:
- `pnpm format:check`
- `pnpm docs:generate`
-## Failure Handling And Recovery
+## Failure handling and recovery
- Do not rerun the same failing command unchanged more than once.
- Before retrying, classify the failure into one of these buckets:
@@ -170,18 +170,21 @@ High-signal repo-wide commands:
- what you already tried
- the smallest next action, user input, or approval needed
-## Docs, Specs, And CI
+## Docs, specs, and CI
- Authored supporting docs live in `documentation/`; generated TypeDoc output lives in `docs/`.
- If the repository later adds any replacement design, architecture, or specification artifacts for
the changed area, keep them aligned in the same change.
- `docs/` is generated by TypeDoc and is gitignored.
- Implementation E2E in `.github/workflows/main-pipeline.yaml` is intentionally path-filtered. If a
- change should alter E2E coverage, update the workflow and keep it aligned with
+ change needs to alter E2E coverage, update the workflow and keep it aligned with
[CONTRIBUTING.md](./CONTRIBUTING.md).
-## README And Markdown Standards
+## README and Markdown standards
+- Follow [`STYLE_GUIDE.md`](./STYLE_GUIDE.md) for Contentful technical writing conventions in
+ human-authored documentation, including READMEs, authored docs, TSDoc/JSDoc prose, and examples.
+ The nearest `AGENTS.md` still owns document structure, commands, and subtree-specific exceptions.
- Treat README files as maintained source-of-truth orientation for humans. Keep them aligned with
package exports, implementation scripts, local `.env.example` files, and authored documentation in
the same change as behavior or workflow updates.
@@ -190,10 +193,10 @@ High-signal repo-wide commands:
header, `Contentful Personalization & Analytics` title, subtype `
`, navigation links, and
pre-release warning.
- `documentation/**/README.md` files are navigation indexes with frontmatter.
- - placeholder and internal-only README files may use a plain Markdown `#` title plus explicit
+ - placeholder and internal-only README files can use a plain Markdown `#` title plus explicit
status or internal-use admonitions.
-- Use title case for Markdown headings, preserving official product, package, API, component, hook,
- and file casing.
+- Use sentence case for Markdown headings, preserving official product, package, API, component,
+ hook, and file casing.
- Lead with reader intent and scope: what to use, when to use it, and what belongs elsewhere. Prefer
concrete implementation guidance over marketing language.
- Keep terminology consistent: "Optimization SDK Suite", "Personalization", "Analytics", "Experience
@@ -230,14 +233,14 @@ High-signal repo-wide commands:
- For Markdown edits, run Prettier on touched files when practical and at least run
`git diff --check` before finishing.
-## Safety Rules
+## Safety rules
- Never overwrite or delete ignored local files just to get a clean run.
- Do not run destructive Contentful scripts unless explicitly asked.
- Do not use broad cleanup commands such as `pm2 delete all` unless explicitly asked.
- Do not assume full cross-platform E2E is required for every change.
-## Preferred Workflow
+## Preferred workflow
1. Read the root `AGENTS.md`.
2. Read each applicable child `AGENTS.md` from outermost to nearest in the subtree you will edit.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4fc5e053..bf14129c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -35,21 +35,21 @@ gotchas.
- [Setup](#setup)
-- [Repository Map](#repository-map)
-- [Common Commands](#common-commands)
-- [Common Workflows](#common-workflows)
- - [Change a Workspace Package](#change-a-workspace-package)
- - [Change an Implementation](#change-an-implementation)
- - [Run E2E for One Implementation](#run-e2e-for-one-implementation)
- - [Implementation Helper Usage](#implementation-helper-usage)
-- [Validation Matrix](#validation-matrix)
-- [Code Style and Local Hooks](#code-style-and-local-hooks)
+- [Repository map](#repository-map)
+- [Common commands](#common-commands)
+- [Common workflows](#common-workflows)
+ - [Change a workspace package](#change-a-workspace-package)
+ - [Change an implementation](#change-an-implementation)
+ - [Run E2E for one implementation](#run-e2e-for-one-implementation)
+ - [Implementation helper usage](#implementation-helper-usage)
+- [Validation matrix](#validation-matrix)
+- [Code style and local hooks](#code-style-and-local-hooks)
- [Documentation](#documentation)
- - [README Depth and Render Targets](#readme-depth-and-render-targets)
-- [Local Troubleshooting](#local-troubleshooting)
-- [Troubleshooting CI Issues](#troubleshooting-ci-issues)
- - [E2E Coverage and Environment](#e2e-coverage-and-environment)
- - [License Check Failure](#license-check-failure)
+ - [README depth and render targets](#readme-depth-and-render-targets)
+- [Local troubleshooting](#local-troubleshooting)
+- [Troubleshooting CI issues](#troubleshooting-ci-issues)
+ - [E2E coverage and environment](#e2e-coverage-and-environment)
+ - [License check failure](#license-check-failure)
@@ -83,7 +83,7 @@ pnpm version:pnpm
`pnpm install` also installs the local Husky hooks used during commit and push.
-## Repository Map
+## Repository map
| Path | Purpose |
| -------------------- | ----------------------------------------------------------------------------------- |
@@ -102,7 +102,7 @@ The most important repository-specific mechanic is this:
- The targeted `pnpm setup:e2e:` wrappers do this refresh for you as part of E2E
setup.
-## Common Commands
+## Common commands
The root [`package.json`](./package.json) contains more scripts than are listed below. These are the
ones most contributors need regularly.
@@ -135,9 +135,9 @@ running the whole repository when the change is narrow.
Before running `pnpm implementation:lint` across the repository, run `pnpm implementation:install`
so the reference implementations have current local package tarballs installed.
-## Common Workflows
+## Common workflows
-### Change a Workspace Package
+### Change a workspace package
1. Read the nearest package or `lib/` `AGENTS.md`.
2. Make your change in the package source, tests, docs, or local harness.
@@ -165,7 +165,7 @@ pnpm setup:e2e:web-sdk
pnpm test:e2e:web-sdk
```
-### Change an Implementation
+### Change an implementation
1. Read the nearest implementation `AGENTS.md`.
2. If your change depends on freshly built local packages, run `pnpm build:pkgs` and reinstall the
@@ -181,7 +181,7 @@ pnpm implementation:run -- web-sdk_react build
pnpm implementation:run -- web-sdk_react implementation:test:e2e:run
```
-### Run E2E for One Implementation
+### Run E2E for one implementation
1. Create a local `.env` from the implementation's `.env.example` if the implementation expects one
and you do not already have it.
@@ -204,7 +204,7 @@ Environment notes:
- Several implementations use PM2-managed processes for local serving; stop only the relevant
implementation process rather than doing broad PM2 cleanup.
-### Implementation Helper Usage
+### Implementation helper usage
`implementation:run` is the shared helper used by the implementation scripts listed above.
@@ -256,28 +256,28 @@ Prefer the root wrapper scripts when they already match what you want to do. For
- `pnpm setup:e2e:`
- `pnpm test:e2e:`
-## Validation Matrix
+## Validation matrix
Use the smallest meaningful validation set for the change.
-| Change type | Usually run | Notes |
-| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
-| Docs-only or markdown-only | `pnpm format:check` | Also run `pnpm docs:generate` if public API docs or linked markdown changed |
-| Package or `lib/` TypeScript change | Targeted `lint`, `typecheck`, `test:unit`, and `build` | Prefer `pnpm --filter ...` when the change is narrow |
-| Built package runtime, export, dependency, or bundle change | Targeted `size:check` or root `pnpm size:check` | Bundle-size checks are currently a contributor-run validation, not a dedicated CI job |
-| Package change used by an implementation | `pnpm build:pkgs`, then reinstall the affected implementation | Use `pnpm setup:e2e:` if E2E is your next step |
-| Implementation code change | `pnpm implementation:lint`, targeted implementation `typecheck`, and implementation-local tests | Some implementations have no meaningful unit tests; E2E matters more there |
-| Shared build or packaging change | Broaden validation to downstream packages and at least one affected implementation | Examples: `lib/build-tools`, package exports, tarball/install flow |
-| User-visible integration or runtime behavior change | Targeted implementation E2E | Choose the implementation that exercises the changed surface |
+| Change type | Usually run | Notes |
+| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
+| Docs-only or markdown-only | `pnpm format:check` | Also run `pnpm docs:generate` if public API docs or linked markdown changed |
+| Package or `lib/` TypeScript change | Targeted `lint`, `typecheck`, `test:unit`, and `build` | Prefer `pnpm --filter ...` when the change is narrow |
+| Built package runtime, export, dependency, or bundle change | Targeted `size:check` or root `pnpm size:check` | Bundle-size checks are contributor-run validation, not a dedicated CI job |
+| Package change used by an implementation | `pnpm build:pkgs`, then reinstall the affected implementation | Use `pnpm setup:e2e:` if E2E is your next step |
+| Implementation code change | `pnpm implementation:lint`, targeted implementation `typecheck`, and implementation-local tests | Some implementations have no meaningful unit tests; E2E matters more there |
+| Shared build or packaging change | Broaden validation to downstream packages and at least one affected implementation | Examples: `lib/build-tools`, package exports, tarball/install flow |
+| User-visible integration or runtime behavior change | Targeted implementation E2E | Choose the implementation that exercises the changed surface |
When in doubt, start targeted and broaden only if the change crosses package or implementation
boundaries.
-## Code Style and Local Hooks
+## Code style and local hooks
This project uses [ESLint](https://eslint.org/) and [Prettier](https://prettier.io/) to enforce
-coding and formatting conventions. It may be useful to enable related editor plugins to have a
-smoother experience when working on Optimization SDKs.
+coding and formatting conventions. You can enable related editor plugins to get the same feedback
+while working on Optimization SDKs.
Please review the following files to familiarize yourself with current configurations:
@@ -326,15 +326,15 @@ keep these artifacts aligned:
`documentation/` contains source markdown that TypeDoc publishes. `docs/` is generated output. Do
not hand-edit generated TypeDoc output.
-### README Depth and Render Targets
+### README depth and render targets
-READMEs are orientation surfaces, not the only place every detail should live. Match depth to the
-README category:
+READMEs are orientation surfaces, not the only place for every detail. Match depth to the README
+category:
- Application-facing package READMEs keep purpose, install, minimal setup, common options, critical
caveats, and links to guides, reference implementations, and generated API reference.
-- Lower-level package READMEs explain the package's role in the SDK stack, who should use it
- directly, common setup options where useful, and where exhaustive API details live.
+- Lower-level package READMEs explain the package's role in the SDK stack, who uses it directly,
+ common setup options where useful, and where exhaustive API details live.
- Reference implementation READMEs stay procedural: what the implementation demonstrates,
prerequisites, setup, run/test commands, environment notes, and related package links.
- Internal and placeholder READMEs stay short, explicit, and status-oriented.
@@ -347,11 +347,10 @@ Package README links must work in GitHub source browsing, generated TypeDoc proj
npmjs README rendering. Use canonical generated-doc URLs for shared header navigation and verify
repo-relative links before relying on package README publish rewriting.
-## Local Troubleshooting
+## Local troubleshooting
-- An implementation is not reflecting your latest package change: run `pnpm build:pkgs`, then
- reinstall the affected implementation with
- `pnpm implementation:run -- implementation:install`.
+- An implementation is not reflecting your package change: run `pnpm build:pkgs`, then reinstall the
+ affected implementation with `pnpm implementation:run -- implementation:install`.
- Playwright reports a missing browser: run `pnpm playwright:install`, or use the targeted
`pnpm setup:e2e:` wrapper.
- Playwright system dependencies are missing on Linux: run `pnpm playwright:install-deps`.
@@ -362,9 +361,9 @@ repo-relative links before relying on package README publish rewriting.
- A local port such as `3000`, `8000`, or `8081` is already in use: stop only the relevant local
process or implementation `serve` flow rather than using broad PM2 cleanup.
-## Troubleshooting CI Issues
+## Troubleshooting CI issues
-### E2E Coverage and Environment
+### E2E coverage and environment
`Main Pipeline` runs implementation E2E jobs when path filters request them for both:
@@ -397,13 +396,13 @@ authoritative filter list.
Skipping an implementation E2E job because its filter did not match is expected behavior, not a CI
coverage defect.
-If a change should trigger an implementation E2E job but does not match the current filters, update
-the path filters in `.github/workflows/main-pipeline.yaml` in the same pull request.
+If a change needs to trigger an implementation E2E job but does not match the current filters,
+update the path filters in `.github/workflows/main-pipeline.yaml` in the same pull request.
E2E setup does not depend on repository secrets. Each implementation creates `.env` from its own
checked-in `.env.example` file in CI, which keeps fork PR behavior aligned with internal PRs.
-### License Check Failure
+### License check failure
Run `license-checker` locally:
diff --git a/README.md b/README.md
index b525f949..9696ff0c 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
> [!WARNING]
>
-> The Optimization SDK Suite is pre-release (alpha). Breaking changes may be published at any time.
+> The Optimization SDK Suite is pre-release (alpha). Breaking changes can be published at any time.
## Introduction
@@ -38,22 +38,22 @@ enables developers and content creators to ship their products faster.
Table of Contents
-- [Choosing a Package](#choosing-a-package)
-- [Published Packages](#published-packages)
-- [Native and Planned SDKs](#native-and-planned-sdks)
-- [Reference Implementations](#reference-implementations)
-- [Repository Layout](#repository-layout)
-- [Get Involved](#get-involved)
+- [Choosing a package](#choosing-a-package)
+- [Published packages](#published-packages)
+- [Native and planned SDKs](#native-and-planned-sdks)
+- [Reference implementations](#reference-implementations)
+- [Repository layout](#repository-layout)
+- [Get involved](#get-involved)
- [License](#license)
-- [Code of Conduct](#code-of-conduct)
+- [Code of conduct](#code-of-conduct)
-## Choosing a Package
+## Choosing a package
If you are deciding which SDK or library belongs in your application, start with
-[Choosing the Right SDK](./documentation/guides/choosing-the-right-sdk.md).
+[Choosing the right SDK](./documentation/guides/choosing-the-right-sdk.md).
For step-by-step implementation docs, start with the [Guides](./documentation/guides/README.md)
index. For behavior explanations, start with the [Concepts](./documentation/concepts/README.md)
@@ -63,7 +63,7 @@ Package README files listed below are package-level guides and API surface summa
[reference documentation](https://contentful.github.io/optimization) remains the source of truth for
exported API signatures.
-## Published Packages
+## Published packages
The published package surface is intentionally layered. The table below is a package inventory and
high-level role summary.
@@ -81,14 +81,14 @@ high-level role summary.
General selection rules:
-- Most application code should start with an environment SDK or framework SDK.
+- We recommend starting application code with an environment SDK or framework SDK.
- `@contentful/optimization-core` is the shared foundation for runtime adapters and SDK layering.
- `@contentful/optimization-api-client` and `@contentful/optimization-api-schemas` are lower-level
building blocks.
-## Native and Planned SDKs
+## Native and planned SDKs
-React Native support is available today through
+React Native support is available through
[`@contentful/optimization-react-native`](./packages/react-native-sdk/README.md).
Native iOS work is also present in this repository as a pre-release Swift Package under
@@ -97,8 +97,7 @@ Native iOS work is also present in this repository as a pre-release Swift Packag
adapter and the [iOS reference app](./implementations/ios-sdk/README.md). Treat this surface as
alpha implementation work rather than a stable public native SDK.
-The following native and framework SDKs are still planned and are not currently published from this
-repository:
+The following native and framework SDKs are planned but are not published from this repository:
- Android Kotlin SDK
- Android Java SDK
@@ -107,7 +106,7 @@ repository:
- Svelte SDK
- Vue SDK
-## Reference Implementations
+## Reference implementations
Reference implementations exist to exercise critical flows end to end and to document common usage
patterns with intentionally minimal application code.
@@ -126,7 +125,7 @@ patterns with intentionally minimal application code.
- [iOS Reference App](./implementations/ios-sdk/README.md) - native app and XCUITest surface for
current iOS bridge and preview-panel scenarios; this is not a published iOS SDK package
-## Repository Layout
+## Repository layout
- `packages/`: published SDKs and supporting libraries
- `implementations/`: reference applications used for examples and E2E coverage
@@ -134,7 +133,7 @@ patterns with intentionally minimal application code.
- `documentation/`: authored supporting documentation published alongside TypeDoc
- `docs/`: generated TypeDoc output; not source of truth
-## Get Involved
+## Get involved
We appreciate any help on our repositories. For more details about how to contribute see our
[CONTRIBUTING](./CONTRIBUTING.md) document.
@@ -143,7 +142,7 @@ We appreciate any help on our repositories. For more details about how to contri
This repository is published under the [MIT](LICENSE) license.
-## Code of Conduct
+## Code of conduct
We want to provide a safe, inclusive, welcoming, and harassment-free space and experience for all
participants, regardless of gender identity and expression, sexual orientation, disability, physical
diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md
new file mode 100644
index 00000000..06a9219a
--- /dev/null
+++ b/STYLE_GUIDE.md
@@ -0,0 +1,413 @@
+# Contentful documentation style guide
+
+Use this guide when writing or editing human-authored documentation in this repository. It adapts
+Contentful's technical writing guidance for agents working on the Optimization SDK Suite, while
+preserving rules that can be useful for future docs.
+
+This guide intentionally excludes only source-guide mechanics that depend on Contentful's internal
+Help Center authoring UI, internal writer peer-review process, or internal ticketing workflow.
+
+## Scope
+
+Follow this guide for:
+
+- Root, package, implementation, and support `README.md` files.
+- Authored Markdown under `documentation/**`.
+- Explanatory prose in TSDoc, JSDoc, examples, and comments when that prose is user-facing or
+ documentation-like.
+- Future FAQs, concepts, guides, troubleshooting pages, reference-implementation notes, release
+ notes, and changelog prose when they are maintained in this repository.
+
+Do not apply this guide blindly to generated TypeDoc output, generated files, code symbols, API
+signatures, package names, UI labels copied from a product, or literal values.
+
+If this guide conflicts with an `AGENTS.md`, follow the nearest applicable `AGENTS.md` for document
+structure, repository terminology, commands, validation, and local exceptions. This guide owns prose
+style.
+
+For a style question not covered here, follow the Google Developer Documentation Style Guide. For
+spelling questions, use Merriam-Webster's Collegiate Dictionary.
+
+## Core principles
+
+- **Meaningfulness** - Keep documentation focused, specific, and to the point.
+- **Comprehensiveness** - Include enough information for the reader to use the product or SDK and
+ achieve the documented goal.
+- **Consistency** - Keep terminology, syntax, and formatting consistent to reduce ambiguity.
+- **Context** - Provide the context the reader needs to understand the task, decision, or concept.
+- **Structure** - Break text into scannable building blocks with descriptive headings, lists,
+ tables, notes, and links where they help.
+- Prefer concrete implementation guidance over marketing language.
+- Do not repeat generated API reference material unless the detail is necessary to avoid an
+ integration mistake.
+
+## Timeless documentation
+
+Write docs so they still read correctly after a feature, SDK, or workflow has been available for a
+long time. Avoid words or phrases that anchor the document to the publication date or assume prior
+or future product updates.
+
+Avoid time-bound words unless the document is explicitly date-stamped:
+
+- new
+- latest
+- old
+- now
+- currently
+- presently
+- at present
+- eventually
+- future
+- in the future
+- soon
+
+When timing matters, use a durable reference such as a package version, release date, API version,
+or documented support boundary.
+
+This principle does not apply to time-stamped documentation intended to describe product updates,
+such as release notes and changelog entries.
+
+## Voice and tone
+
+- Use American English spelling.
+- Use present tense for general behavior that is not tied to a specific time.
+- Use active voice by default so the actor is clear.
+- Use passive voice only when the actor is unknown, unimportant, or the result matters more than the
+ actor.
+- In procedures, it is acceptable to use passive voice for the result of the reader's action, such
+ as "The preview panel is displayed."
+- Address the reader as `you` and `your`.
+- For instructions, use the imperative mood and omit the pronoun: "Install the package", not "You
+ install the package."
+- Avoid first person singular.
+- Use first person plural only when referring to Contentful, the SDK, or the maintainers.
+- In FAQs, first person can be acceptable when phrasing a reader's question.
+- Use singular `they`, `them`, and `their` instead of gendered pronouns.
+
+## Headings
+
+- Use sentence case for headings, preserving official product, package, API, component, hook, file,
+ and UI casing.
+- Make headings short, focused, descriptive, and useful for navigation.
+- Keep headings unique within a document. Avoid reusing identical headings for different purposes
+ across nearby docs when that would confuse navigation or generated anchors.
+- Use subheadings to introduce conceptual blocks.
+- Use a subheading before each flow when a document includes multiple action flows.
+- Do not add a subheading when the document has only one subtopic.
+- Use one `#` heading for the document title, `##` for main sections, and deeper levels only when a
+ section is broken into real subtopics.
+- Do not skip heading levels.
+- Do not put a period at the end of a heading.
+- For overview sections, use a noun phrase that names the feature, concept, or functionality.
+- For task sections, use a task-based verb phrase that starts with the action.
+- Avoid question headings. Prefer the answer or task name.
+- Do not use camel case in prose headings unless the heading includes an official API, component,
+ hook, package, or product name that uses camel case.
+
+## Terminology
+
+Use the repository-standard product names and terms from `AGENTS.md`, including:
+
+- Optimization SDK Suite
+- Personalization
+- Analytics
+- Experience API
+- Insights API
+- reference implementation
+- exact package names, such as `@contentful/optimization-web`
+
+Use Contentful product terms consistently when they apply:
+
+- `app` - An HTML5 application that extends the functionality of the Contentful web app or
+ Contentful apps and can be installed at the organization or space level.
+- `asset` - A media file such as an image, video, audio file, or PDF.
+- `Compose` - A Contentful app that provides a simplified interface for creating and publishing web
+ pages, mainly for content creators.
+- `content` - A collective term for entries.
+- `Contentful apps` - A collective term for apps such as Compose and Launch.
+- `Contentful web app` - See `web app`.
+- `content model` - A structure for content in a space that consists of content types and defines
+ connections between them.
+- `content type` - An entity that serves as a template for an entry and defines its structure. A
+ content type is created on the space level and consists of fields.
+- `editor` or `editor page` - A page in the web app or Contentful apps where a user can make changes
+ to an entity in editing mode and save them.
+- `embargoed assets` - A functionality that protects assets at a space level by making them
+ accessible only to authorized users.
+- `entity` - A single object that can be uniquely identified. Contentful entities include
+ organizations, spaces, environments, content types, assets, entries, tags, locales, users, roles,
+ and teams.
+- `entry` - A single record of content based on a specific content type.
+- `environment` - An entity within a space that keeps a version of space-specific data so changes
+ can be made in isolation from other environments.
+- `environment alias` - An entity that points to a target environment and can be switched to a
+ different target environment.
+- `field` - A building block of a content type with a defined format and customizable settings.
+- `Launch` - A Contentful app that lets content creators group content into releases and publish,
+ schedule publishing, or schedule unpublishing.
+- `locale` - A region-language pair that can be enabled for entries, fields, and assets.
+- `media` - A collective term for assets.
+- `organization` - A top-level company account used for administration. It contains one or multiple
+ spaces.
+- `organization role` - A role that defines a user's access to an organization.
+- `page type` - A custom content type that serves as a template for a page in Compose.
+- `reference` - A field type used to link one or multiple entries inside another entry.
+- `release` - An entity used to group entries and assets for simultaneous publishing.
+- `role` - A set of permissions that enables a user or team to perform tasks related to their job.
+- `space` - A workspace that contains content and media for a project and has its own content model.
+- `space role` - A role that defines a user's access to a space.
+- `tag` - An entity used to mark content and media for more granular governance.
+- `team` - An entity used to group users in the same organization based on business function,
+ geography, or another grouping.
+- `user` - An account used to access the web app or Contentful apps.
+- `web app` or `Contentful web app` - A web application that provides a user interface for
+ interacting with Contentful software.
+- `webhook` - An HTTP callback sent when data in Contentful changes.
+
+Do not introduce glossary terms for unrelated Contentful products unless the document actually
+integrates with those products or the term helps future maintainers understand a cross-product
+relationship.
+
+## Word choice
+
+Prefer clear, direct words:
+
+- Use `use`, not `utilize` or `leverage`.
+- Use `log in to`, not `log into` or `login to`.
+- Use `version` or lowercase `v` for an abbreviated version.
+
+Reduce adjectives and adverbs that do not add concrete information. Either remove them or replace
+them with specifics.
+
+Avoid vague intensifiers unless the sentence gives a specific measure or explanation:
+
+- fast
+- fastly
+- simple
+- simply
+- easy
+- easily
+- efficient
+- efficiently
+- effective
+- effectively
+- quick
+- quickly
+- quite
+- very
+
+## Modal verbs
+
+Use modal verbs consistently:
+
+- Use `can` for an option, permission, or possibility.
+- Use `must` for a requirement.
+- Use `might` for an uncertain outcome.
+- Use `we recommend` for a recommendation.
+
+Avoid these modal verbs in public documentation when one of the terms above is clearer:
+
+- `should` - Use `must`, `can`, or `we recommend`.
+- `could` - Use `can`.
+- `would` - Use present tense or `can`.
+- `may` - Use `can`.
+
+## UI labels and actions
+
+- Spell UI labels exactly as they appear in the UI.
+- Preserve UI capitalization, title case, punctuation, and spelling.
+- Bold UI labels when the reader interacts with them.
+- Use regular text when a UI label is mentioned only for context and no action is applied to it.
+- Use `click` for desktop buttons and controls used with a mouse or trackpad.
+- Use `tap` for mobile controls.
+- Use `enter` for adding text to a field.
+- Use `select` for choosing one or multiple options, choosing list items, marking checkboxes, and
+ checking radio buttons.
+- Use `go to` or `navigate to` for tabs, screens, pages, entities in a list, websites, and apps. Use
+ `go to` for websites and apps.
+- Avoid `hit`, `pick`, `choose`, `open`, and `type` when one of the standard verbs above is more
+ precise.
+
+## Procedures
+
+- Use a procedure for a sequence of actions the reader must perform to complete a task.
+- Introduce the procedure with the goal, prerequisite, or context the reader needs before starting.
+- Use numbered lists for the steps.
+- Start each step with an imperative verb.
+- Keep one primary action per step when possible.
+- Bold UI labels only when the step requires interaction with that UI element.
+- Use `Optional` or `Mandatory` labels only when the distinction changes what the reader must do.
+- State the result of a step when the result helps the reader confirm progress. The standard result
+ phrasing can use passive voice, such as "The entry editor is displayed."
+- Use nested bullets inside a step only for choices, options, or a hierarchy that belongs to that
+ step.
+
+## Lists
+
+- Use numbered lists only for ordered steps.
+- Use bulleted lists for related items, options, capabilities, unordered requirements, or sets where
+ order does not matter.
+- Do not use a numbered list for a set of related items.
+- Do not use a bulleted list for a sequence of steps.
+- Precede a list with an opening sentence that ends with a colon when the list completes or expands
+ that sentence.
+- Keep list items parallel.
+- Start each list item with a capital letter.
+- End complete-sentence list items with a period.
+- Use fragments without periods only when every item in the list is a short fragment.
+- For named option lists, use `- **Name** - Description.` Capitalize the name, bold it, add a
+ hyphen, start the description with a capital letter, and end the item with a period.
+- In numbered steps, start each item with a capital letter and end it with a period.
+- If a step begins with `Optional` or `Mandatory`, bold that label and follow it with a colon.
+- Use a bulleted list inside a numbered step for options that belong to that step.
+- Use multiple-level bulleted lists only for true hierarchies.
+- Avoid deep nesting when a table or separate section would be easier to scan.
+
+## Tables
+
+Use a table when similar items have characteristics that fit shared columns. Do not use a table when
+a short list or paragraph is easier to scan.
+
+- Precede a table with an opening sentence that ends with a colon when the table completes or
+ expands that sentence.
+- Include a header row.
+- Keep column names short.
+- Bold item names in the first column when that helps the reader scan the table.
+- Apply the same punctuation, note, list, and code-formatting rules inside table cells that apply in
+ the rest of the document.
+- Use complete sentences in cells only when the detail needs sentence form.
+- End complete sentences in table cells with periods.
+- Do not put periods in table headings or short table-cell fragments.
+
+## Notes and admonitions
+
+Use GitHub admonitions for notes, warnings, and important information:
+
+```md
+> [!NOTE]
+>
+> Use a note for helpful context that is not required to complete the task.
+```
+
+- Use notes to highlight information that is not structurally part of the surrounding prose but is
+ important for the reader to notice.
+- Use `IMPORTANT` when the reader must act on the information to complete the documented goal.
+- Use `WARNING` or `CAUTION` for destructive, unsafe, pre-release-sensitive, or otherwise risky
+ flows.
+- Use one idea per admonition.
+- If an admonition must contain multiple ideas, separate them into separate paragraphs.
+- Do not use block quotes only for visual emphasis.
+
+## Links
+
+- Add links when the reader needs more context to understand the document or when another document
+ owns deeper implementation details.
+- Link the exact phrase that needs context or the exact document title.
+- Avoid vague link text such as `here`, `this page`, or raw URLs.
+- Link to source-of-truth repository files, generated reference docs, package READMEs,
+ implementation READMEs, or authored docs as required by the nearest `AGENTS.md`.
+- Verify relative links when adding, moving, or renaming documents.
+- Do not use links as a replacement for essential context. The reader must understand the current
+ document's purpose without following every link.
+
+## Code, commands, and examples
+
+- Use fenced code blocks with language tags.
+- Use inline code for package names, commands, file paths, environment variables, code identifiers,
+ API names, literal values, and placeholders.
+- Prefer `pnpm` commands and repository wrapper scripts.
+- Keep examples minimal but complete enough to run or adapt.
+- Keep code examples aligned with package exports and current SDK behavior.
+- Do not mix API reference detail into guides or READMEs when generated TypeDoc owns that detail.
+- Do not use npm, Yarn, or undocumented global-tool commands unless the surrounding repo guidance
+ explicitly requires them.
+
+## Quotation marks
+
+- Use double quotation marks for direct quotations.
+- Use double quotation marks around proper names of entities or records in Contentful when the
+ context needs quotation marks for clarity.
+- Use single quotation marks only for a quotation nested inside another quotation.
+- Do not use quotation marks around UI labels only to distinguish them from prose. Use bold
+ formatting for actionable UI labels.
+
+## Periods
+
+Use a period:
+
+- At the end of a complete sentence, unless it is a question.
+- At the end of a complete-sentence list item.
+- At the end of a complete sentence in a table cell.
+- At the end of table-cell content that mixes fragments and complete sentences.
+- As a decimal point.
+
+Do not use a period:
+
+- In headings.
+- In UI labels.
+- In table headings.
+- In table-cell fragments that are not complete sentences.
+- In short list fragments when every item in the list is a fragment.
+
+## Hyphens and dashes
+
+- Use a hyphen for compound adjectives before a noun when the hyphen improves clarity.
+- Do not hyphenate a compound adjective when it appears after the noun.
+- The source Contentful guide allows a spaced em dash for a sentence break. In this repository,
+ prefer commas, parentheses, colons, or sentence breaks unless the surrounding document already
+ uses em dashes intentionally.
+- Do not use an en dash.
+- For date or number ranges, use `to` when that is clearer than a hyphen.
+
+## Commas
+
+- Use the serial comma before the final `and` or `or` in a series of three or more items.
+- For comma cases not covered here, follow the Google Developer Documentation Style Guide.
+
+## Alternatives
+
+- Use `and/or` only when both separate and combined options are valid.
+- Do not use a slash to separate alternatives unless the term is an established technical form.
+- Use `and` or `or` when only one meaning applies.
+- Do not put optional plurals in parentheses, such as `role(s)`.
+- Use singular or plural consistently.
+- If both singular and plural matter, use `one or multiple`.
+
+## Contractions
+
+- Use negative contractions such as `don't`, `can't`, and `aren't` when they improve readability.
+ They are harder to misread than `do not`, `cannot`, or `are not`.
+- Avoid noun-plus-verb contractions such as `you're`, `we'll`, and `they're` in formal docs.
+- `It's` and `it is` are both acceptable when they read naturally.
+- Do not confuse possessive `its` with the noun-plus-verb phrase `it's`.
+
+## Fractions and decimals
+
+- Spell out fractions fully when they are concise.
+- If a fraction cannot be written concisely, use a decimal number.
+- Limit decimals to three places or fewer.
+- Write decimals only when the number after the decimal point is greater than zero.
+
+## Numbers
+
+- Spell out zero through nine unless referring to a numbered step, version, dimension, or literal
+ value where digits are clearer.
+- Use digits for 10 and higher.
+- Use digits for measurements, versions, command arguments, option values, ports, HTTP status codes,
+ IDs, and other technical literals.
+
+## Capitalization
+
+- Use sentence-style capitalization in prose.
+- Capitalize the first letter of a sentence.
+- Capitalize official product names, company names, API names, package names, and proper nouns
+ exactly as they are officially written.
+- Preserve product casing such as `iPad` or `iA Writer` when a product name begins with a lowercase
+ letter.
+- Capitalize UI labels exactly as they appear in the UI.
+- Capitalize the first word of each list item.
+- Capitalize the first word of a named list-item description.
+- Capitalize the first word in every table cell.
+- Do not capitalize general Contentful concepts such as spaces, content types, entries,
+ environments, assets, locales, roles, tags, web publishing tools, or space templates unless they
+ begin a sentence or appear in a UI label.
diff --git a/documentation/AGENTS.md b/documentation/AGENTS.md
index 84548fcc..f842da26 100644
--- a/documentation/AGENTS.md
+++ b/documentation/AGENTS.md
@@ -4,7 +4,10 @@ Read the repository root `AGENTS.md` first.
These instructions apply to authored documentation under `documentation/`.
-## Documentation Categories
+For prose style, follow [`../STYLE_GUIDE.md`](../STYLE_GUIDE.md). This file adds documentation
+structure, cross-linking, and validation rules.
+
+## Documentation categories
- Put step-by-step implementation material in `guides/`.
- Put "how it works" explanations in `concepts/`.
@@ -14,10 +17,10 @@ These instructions apply to authored documentation under `documentation/`.
guide or concept that matches the reader goal and update that document instead of creating a new
one. Create a new guide only when the material has no existing guide home.
- Do not move exhaustive API reference material from READMEs into authored docs when generated
- TypeDoc already owns the detail. Authored docs should explain integration flow, decisions, and
+ TypeDoc already owns the detail. Authored docs must explain integration flow, decisions, and
mechanics that generated reference docs cannot.
-## Directory README Files
+## Directory README files
- Keep directory `README.md` frontmatter `children` aligned with the visible list order in the same
file.
@@ -27,14 +30,13 @@ These instructions apply to authored documentation under `documentation/`.
a short "start here" paragraph and grouped lists of child documents with one-sentence
descriptions.
- Use the observed index headings for consistency: `## Sections` at the documentation root,
- `## Start Here` and `## Integration Guides` under `guides/`, and `## Available Concepts` under
+ `## Start here` and `## Integration guides` under `guides/`, and `## Available concepts` under
`concepts/`.
- Preserve frontmatter `title` values that match the visible `#` heading.
-## Heading and Writing Style
+## Heading and writing style
-- Use title case for headings, but keep minor words lowercase unless they are the first or last
- word: `a`, `an`, `and`, `for`, `from`, `in`, `on`, `or`, `the`, `to`, `via`, `with`.
+- Use sentence case for headings.
- Preserve official product, package, API, component, and hook casing.
- Lead with what the reader is trying to implement.
- Separate SDK responsibilities from application responsibilities.
@@ -42,7 +44,7 @@ These instructions apply to authored documentation under `documentation/`.
identity policy, routing, and rendering.
- Prefer concrete implementation guidance over marketing language.
-## Cross-Linking
+## Cross-linking
- Link from guides to concepts when the reader needs deeper mechanics.
- Link only to source-of-truth files, package READMEs, implementation READMEs, or relevant source
diff --git a/documentation/concepts/README.md b/documentation/concepts/README.md
index e1df2747..1fbcafcc 100644
--- a/documentation/concepts/README.md
+++ b/documentation/concepts/README.md
@@ -10,7 +10,7 @@ Start here when you need to understand how SDK features work, why they behave a
the implementation makes runtime decisions. Concepts complement package README files and guides;
they are not the first stop for installation or setup commands.
-## Available Concepts
+## Available concepts
- [React Native SDK Interaction Tracking Mechanics](./react-native-sdk-interaction-tracking-mechanics.md) -
explains how the React Native SDK observes, gates, and emits tracking events, covering event
diff --git a/documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md b/documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md
index 1178d132..7db93466 100644
--- a/documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md
+++ b/documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md
@@ -1,4 +1,4 @@
-# React Native SDK Interaction Tracking Mechanics
+# React Native SDK interaction tracking mechanics
Use this concept document to understand exactly _what_ the `@contentful/optimization-react-native`
SDK is tracking, _when_ each event fires, and _how_ it leaves the device. Every number, state
@@ -6,11 +6,11 @@ transition, and gate is grounded in SDK source so you can reason about tracking
running a live experiment.
The companion
-[Integrating the Optimization React Native SDK in a React Native App](../guides/integrating-the-react-native-sdk-in-a-react-native-app.md)
+[Integrating the Optimization React Native SDK in a React Native app](../guides/integrating-the-react-native-sdk-in-a-react-native-app.md)
walks through setup, consent, and screen wiring at a tutorial level. Read that first for "how do I
plug the SDK in?" — come back here for "why isn't my entry view firing?"
-## What You Get Out Of The Box
+## What you get out of the box
If you drop `OptimizationRoot` at the top, wrap `NavigationContainer` in
`OptimizationNavigationContainer`, and wrap Contentful entries in ``, you get:
@@ -35,52 +35,55 @@ Things you still have to enable yourself:
Table of Contents
-- [What You Get Out Of The Box](#what-you-get-out-of-the-box)
-- [1. Events The SDK Emits](#1-events-the-sdk-emits)
- - [Automatic Events](#automatic-events)
- - [Manual Events](#manual-events)
- - [Wire Type Mapping](#wire-type-mapping)
-- [2. How Events Flow From The Device](#2-how-events-flow-from-the-device)
- - [The Two APIs](#the-two-apis)
- - [Queueing, Flushing, And Offline](#queueing-flushing-and-offline)
- - [Persistence Via AsyncStorage](#persistence-via-asyncstorage)
-- [3. Consent Gating](#3-consent-gating)
- - ["Why Is Nothing Tracking?"](#why-is-nothing-tracking)
-- [4. Entry View Tracking Mechanics](#4-entry-view-tracking-mechanics)
- - [Default Thresholds](#default-thresholds)
- - [The Visibility State Machine](#the-visibility-state-machine)
- - [Initial, Periodic, And Final Events](#initial-periodic-and-final-events)
- - [App Backgrounding And Cleanup](#app-backgrounding-and-cleanup)
-- [5. Scroll Context And Viewport Resolution](#5-scroll-context-and-viewport-resolution)
+- [What you get out of the box](#what-you-get-out-of-the-box)
+- [1. Events the SDK emits](#1-events-the-sdk-emits)
+ - [Automatic events](#automatic-events)
+ - [Manual events](#manual-events)
+ - [Wire type mapping](#wire-type-mapping)
+- [2. How events flow from the device](#2-how-events-flow-from-the-device)
+ - [The two APIs](#the-two-apis)
+ - [Queueing, flushing, and offline](#queueing-flushing-and-offline)
+ - [Persistence via AsyncStorage](#persistence-via-asyncstorage)
+- [3. Consent gating](#3-consent-gating)
+ - ["Why is nothing tracking?"](#why-is-nothing-tracking)
+- [4. Entry view tracking mechanics](#4-entry-view-tracking-mechanics)
+ - [Default thresholds](#default-thresholds)
+ - [The visibility state machine](#the-visibility-state-machine)
+ - [Initial, periodic, and final events](#initial-periodic-and-final-events)
+ - [App backgrounding and cleanup](#app-backgrounding-and-cleanup)
+- [5. Scroll context and viewport resolution](#5-scroll-context-and-viewport-resolution)
- [Inside OptimizationScrollProvider](#inside-optimizationscrollprovider)
- [Outside OptimizationScrollProvider](#outside-optimizationscrollprovider)
-- [6. Tap Tracking Semantics](#6-tap-tracking-semantics)
-- [7. Screen Tracking Paths](#7-screen-tracking-paths)
+- [6. Tap tracking semantics](#6-tap-tracking-semantics)
+- [7. Screen tracking paths](#7-screen-tracking-paths)
- [OptimizationNavigationContainer](#optimizationnavigationcontainer)
- [useScreenTracking](#usescreentracking)
- [useScreenTrackingCallback](#usescreentrackingcallback)
-- [8. The Configuration Surface](#8-the-configuration-surface)
- - [OptimizationRoot Props](#optimizationroot-props)
- - [OptimizedEntry Props](#optimizedentry-props)
- - [SDK Init Config](#sdk-init-config)
- - [Resolution Order](#resolution-order)
-- [9. Manual Tracking API](#9-manual-tracking-api)
-- [10. Putting It Together](#10-putting-it-together)
+- [8. The configuration surface](#8-the-configuration-surface)
+ - [OptimizationRoot props](#optimizationroot-props)
+ - [OptimizedEntry props](#optimizedentry-props)
+ - [SDK init config](#sdk-init-config)
+ - [Resolution order](#resolution-order)
+- [9. Manual tracking API](#9-manual-tracking-api)
+ - [Payload shapes](#payload-shapes)
+ - [When to reach for manual tracking](#when-to-reach-for-manual-tracking)
+- [10. Putting it together](#10-putting-it-together)
+- [Reference](#reference)
-## 1. Events The SDK Emits
+## 1. Events the SDK emits
"Tracking" in the React Native SDK is a small, fixed set of event types. Some are fired by the SDK
as a side effect of component rendering and user behavior; others are explicit method calls you make
from application code.
-### Automatic Events
+### Automatic events
These are emitted by the SDK without an application-level call, as long as consent allows and the
relevant provider/component is mounted.
-| Event | When It Fires | Required Wiring |
+| Event | When it fires | Required wiring |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Screen view** | Each time the active navigation route changes. | `` wrapping `NavigationContainer` (or `useScreenTracking` on each screen). |
| **Entry view (initial)** | When a wrapped entry has accumulated enough visible time (default 2000 ms at ≥ 80% visibility). | `` with view tracking enabled (the default). |
@@ -89,7 +92,7 @@ relevant provider/component is mounted.
| **Entry tap** | On touch end, when the touch moved less than 10 points from touch start, on a wrapped entry. | `` with tap tracking enabled (off by default; opt in via `trackTaps` or `onTap`). |
| **Flag view** | Internally emitted whenever `useFlag` / `getFlag` is called. Not strictly an interaction; worth knowing. | Any `getFlag(...)` call. |
-### Manual Events
+### Manual events
Call these on the SDK instance from `useOptimization()`. Use them for screens or components that
don't fit the `OptimizedEntry` pattern, or for business events unrelated to a Contentful entry.
@@ -112,7 +115,7 @@ await optimization.trackView({ componentId: 'entry-123', experienceId: 'exp-456'
At the wire level, "automatic" and "manual" events funnel through the same emission pipeline.
-### Wire Type Mapping
+### Wire type mapping
The on-the-wire event types used by the Insights API do not always match the public method name. In
particular:
@@ -126,9 +129,9 @@ particular:
These wire types are defined in `packages/universal/core-sdk/src/CoreStatefulEventEmitter.ts:45-50`
and are shared across all SDKs (web, iOS, RN).
-## 2. How Events Flow From The Device
+## 2. How events flow from the device
-### The Two APIs
+### The two APIs
The SDK talks to two HTTP endpoints, both defaulting to Ninetailed hosts:
@@ -143,7 +146,7 @@ A single user action can touch either or both APIs. `trackView({ sticky: true })
Experience first (sticky views become part of the profile) then through Insights. Plain `trackView`
only hits Insights; `identify` only touches Experience.
-### Queueing, Flushing, And Offline
+### Queueing, flushing, and offline
Both APIs are fronted by an in-memory queue in the core SDK. Events are enqueued, never sent
synchronously. Insights events are batched and POSTed; Experience events are per-request but the
@@ -158,7 +161,7 @@ The React Native SDK layers RN-specific behavior on top:
you keep tracking but lose offline durability. _Source:
`packages/react-native-sdk/src/handlers/createOnlineChangeListener.ts:74-112`._
2. **Background flushing.** On `AppState` transition to `background` or `inactive`, the SDK calls
- `flush()` to drain the queue before the OS may suspend the process. _Source:
+ `flush()` to drain the queue before the OS might suspend the process. _Source:
`packages/react-native-sdk/src/handlers/createAppStateChangeListener.ts:38-54`._
3. **Final view event on background.** If an entry is mid-visibility-cycle when the app backgrounds,
`useViewportTracking` pauses, emits a final view event if at least one event already fired, and
@@ -169,7 +172,7 @@ The offline queue has a cap (`queuePolicy.offlineMaxEvents`) and a drop callback
[README](../../packages/react-native-sdk/README.md#common-configuration) for the common queue
configuration entry point.
-### Persistence Via AsyncStorage
+### Persistence via AsyncStorage
`AsyncStorageStore` persists the following across launches so tracking decisions and variant
assignments survive a cold start:
@@ -191,7 +194,7 @@ Why this matters for tracking: selected optimizations persist, so a user placed
continues to see it on the next launch and view/tap events carry the correct `experienceId` /
`variantIndex` without re-round-tripping Experience first.
-## 3. Consent Gating
+## 3. Consent gating
The SDK gates event emission behind a three-valued consent state: `true`, `false`, or `undefined`
(unset). This is the most common cause of "tracking isn't working" during integration — without
@@ -221,7 +224,7 @@ When consent flips:
- **`consent(false)`** — the allow-list gate re-engages. In-flight events that already cleared the
guard continue to flush.
-### "Why Is Nothing Tracking?"
+### "Why is nothing tracking?"
Four checks, in order of likelihood:
@@ -232,12 +235,12 @@ Four checks, in order of likelihood:
4. **No scroll context.** An entry below the fold without `` will never
pass the visibility threshold — `scrollY` is assumed `0`.
-## 4. Entry View Tracking Mechanics
+## 4. Entry view tracking mechanics
This section describes the internals of `useViewportTracking`, the hook `` uses
under the hood.
-### Default Thresholds
+### Default thresholds
All defaults live as module constants in
`packages/react-native-sdk/src/hooks/useViewportTracking.ts:72-75`:
@@ -255,7 +258,7 @@ Tap tracking has one additional threshold in
| ------------------------ | ----- | ------------------------------------------------------------------------------------------------------------------------------- |
| `TAP_DISTANCE_THRESHOLD` | `10` | Maximum pixel distance between `touchStart` and `touchEnd`. Beyond this, the gesture is classified as a scroll/drag, not a tap. |
-### The Visibility State Machine
+### The visibility state machine
Each mounted `` runs a small state machine keyed on a "visibility cycle" — a cycle
starts when the entry goes not-visible → visible, and ends when it transitions back or unmounts.
@@ -281,7 +284,7 @@ measured `{y, height}` and the current viewport `{scrollY, viewportHeight}` to d
- **visible → not-visible** — `onVisibilityEnd` clears the fire timer, pauses accumulation, emits a
**final** event if `attempts > 0`, and resets the cycle.
-### Initial, Periodic, And Final Events
+### Initial, periodic, and final events
Within a cycle, events fire based on accumulated visible time. The schedule mirrors the Web SDK's
`ElementViewObserver`:
@@ -316,7 +319,7 @@ A few consequences:
- **Each event also carries `viewId`** — the UUID for the cycle. All events in one cycle share a
`viewId`; a new cycle gets a fresh one. Use `viewId` downstream to correlate.
-### App Backgrounding And Cleanup
+### App backgrounding and cleanup
Two additional transitions matter:
@@ -335,7 +338,7 @@ Combined, these guarantees mean that as long as the initial event fired, a final
matching `viewId` and the true total duration) will always follow — whether visibility ends
naturally, the user backgrounds the app, or the component unmounts.
-## 5. Scroll Context And Viewport Resolution
+## 5. Scroll context and viewport resolution
`useViewportTracking` needs the entry's position (`{y, height}` from `onLayout`) and the viewport
(`{scrollY, viewportHeight}`). Where the viewport comes from depends on whether the entry sits
@@ -371,13 +374,13 @@ With no scroll context, the hook falls back to screen dimensions — `scrollY =
This is correct for full-screen non-scrollable layouts, hero/banner content always on screen, and
modal content. It is _wrong_ for anything below the fold in a `ScrollView` — wrap those.
-## 6. Tap Tracking Semantics
+## 6. Tap tracking semantics
Tap tracking is implemented by `useTapTracking`. Behavior:
1. The wrapping `View` gets `onTouchStart` / `onTouchEnd` (not `onPress`). Raw touch events mean
- **taps are captured even when a child `Pressable` also handles the press** — if the SDK used a
- `Pressable` wrapper the child's `onPress` would win.
+ **taps are captured even when a child `Pressable` also handles the press**. A `Pressable` wrapper
+ gives the child's `onPress` precedence.
2. `onTouchStart` records `{ pageX, pageY }`.
3. `onTouchEnd` computes Euclidean distance from start to end. Under `TAP_DISTANCE_THRESHOLD` (10
points) → tap; over → scroll/drag, ignored.
@@ -391,7 +394,7 @@ Tap tracking is **off by default**. Enable via
``, ``, or
implicitly by passing `onTap`.
-## 7. Screen Tracking Paths
+## 7. Screen tracking paths
Screen tracking emits `screen` events, which are allowed before consent and feed into route-based
profile attribution. The SDK gives you three paths.
@@ -456,13 +459,13 @@ trackScreen('Deep Linked Article', { slug, source: 'email' })
_Source: `packages/react-native-sdk/src/hooks/useScreenTracking.ts:55-71`._
-## 8. The Configuration Surface
+## 8. The configuration surface
All interaction-tracking behavior is controlled at one of three layers: SDK init config,
`OptimizationRoot` props, or per-component `` props. Lower layers override higher
ones.
-### OptimizationRoot Props
+### OptimizationRoot props
| Prop | Type | Default | Controls |
| ----------------------- | ---------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------- |
@@ -475,7 +478,7 @@ ones.
The "`{ views: true, taps: false }`" default lives in
`packages/react-native-sdk/src/context/InteractionTrackingContext.tsx:38-44`.
-### OptimizedEntry Props
+### OptimizedEntry props
| Prop | Type | Default | Controls |
| ------------------------------ | ---------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------ |
@@ -493,7 +496,7 @@ Each default is defined alongside the corresponding prop in
`packages/react-native-sdk/src/components/OptimizedEntry.tsx:64-141` and confirmed in
`useViewportTracking.ts:72-75`.
-### SDK Init Config
+### SDK init config
Beyond the layer above, the full `CoreStatefulConfig` is accepted as `OptimizationRoot` props (since
`OptimizationRootProps extends CoreStatefulConfig`). The ones that directly shape tracking:
@@ -514,7 +517,7 @@ Beyond the layer above, the full `CoreStatefulConfig` is accepted as `Optimizati
The full configuration reference lives in the
[React Native SDK README](../../packages/react-native-sdk/README.md#common-configuration).
-### Resolution Order
+### Resolution order
**View tracking enabled?**
@@ -540,7 +543,7 @@ _Source: `packages/react-native-sdk/src/components/OptimizedEntry.tsx:143-151, 2
_Source: `packages/react-native-sdk/src/components/OptimizedEntry.tsx:229-231`._
-## 9. Manual Tracking API
+## 9. Manual tracking API
For content that doesn't fit `` — custom screens, server-rendered fragments,
non-Contentful components — call tracking methods directly on the SDK instance. These hit the same
@@ -559,7 +562,7 @@ useEffect(() => {
}, [contentfulId, experienceId, optimization])
```
-### Payload Shapes
+### Payload shapes
```ts
// trackView — Source: CoreStatefulEventEmitter.ts:215-237
@@ -581,11 +584,11 @@ optimization.trackClick({
})
```
-### When To Reach For Manual Tracking
+### When to reach for manual tracking
- **Screen-wide entry views without viewport-visibility semantics** — `trackView` from `useEffect`
on mount.
-- **Non-Contentful UI that should count as a component click** — `trackClick` from a `Pressable`'s
+- **Non-Contentful UI that counts as a component click** — `trackClick` from a `Pressable`'s
`onPress`.
- **Business events unrelated to a Contentful entry** — `track('Added To Cart', { sku })`.
@@ -593,7 +596,7 @@ For anything backed by a Contentful entry, prefer `` — it hand
initial/periodic/final sequencing, final-on-unmount, final-on-background, and `viewId` correlation
for you.
-## 10. Putting It Together
+## 10. Putting it together
A fully-instrumented list screen combines every mechanism in this guide:
@@ -657,6 +660,6 @@ For the broader integration walkthrough, read the
- **Event emission pipeline:** `packages/universal/core-sdk/src/CoreStatefulEventEmitter.ts`
- **Reference implementation:**
[`implementations/react-native-sdk`](../../implementations/react-native-sdk/README.md) exercises
- the latest React Native SDK API surface in this monorepo.
+ the React Native SDK API surface in this monorepo.
- **Integration guide:**
- [Integrating the Optimization React Native SDK in a React Native App](../guides/integrating-the-react-native-sdk-in-a-react-native-app.md).
+ [Integrating the Optimization React Native SDK in a React Native app](../guides/integrating-the-react-native-sdk-in-a-react-native-app.md).
diff --git a/documentation/drafts/integrating-the-ios-sdk-fundamentals.md b/documentation/drafts/integrating-the-ios-sdk-fundamentals.md
index 0c4efe35..53647631 100644
--- a/documentation/drafts/integrating-the-ios-sdk-fundamentals.md
+++ b/documentation/drafts/integrating-the-ios-sdk-fundamentals.md
@@ -1,4 +1,4 @@
-# iOS SDK Fundamentals
+# iOS SDK fundamentals
This document is the shared reference for the Contentful Optimization iOS SDK. It describes what the
SDK is, how it is architected, and the concepts that apply regardless of whether your app is built
@@ -6,10 +6,10 @@ with SwiftUI or UIKit.
Read this first, then move on to the UI-framework-specific guide:
-- [Integrating the Optimization iOS SDK in a SwiftUI App](./integrating-the-ios-sdk-in-a-swiftui-app.md)
-- [Integrating the Optimization iOS SDK in a UIKit App](./integrating-the-ios-sdk-in-a-uikit-app.md)
+- [Integrating the Optimization iOS SDK in a SwiftUI app](./integrating-the-ios-sdk-in-a-swiftui-app.md)
+- [Integrating the Optimization iOS SDK in a UIKit app](./integrating-the-ios-sdk-in-a-uikit-app.md)
-## What The SDK Is
+## What the SDK is
The iOS SDK (`ContentfulOptimization`, distributed via Swift Package Manager from
[`packages/ios`](../../packages/ios)) is a native Swift layer that lets iOS apps render personalized
@@ -23,7 +23,7 @@ never interact with the JS layer directly; every public API is Swift.
See [`packages/ios/CODE_MAP.md`](../../packages/ios/CODE_MAP.md) for the full architecture diagram.
-## Reference App
+## Reference app
A working demo of both integration styles lives at
[Colorful-Team-Org/OptimizationiOSSDKDemo](https://github.com/Colorful-Team-Org/OptimizationiOSSDKDemo)
@@ -56,7 +56,7 @@ Minimum platforms: iOS 15 / macOS 12.
> demo's `./scripts/setup.sh` handles this). Consumers of a released package get the bundle
> prebuilt.
-## Core Types
+## Core types
The SDK's public surface is small. Most integrations use five types:
@@ -159,7 +159,7 @@ Both demo apps (SwiftUI and UIKit) use this shortcut.
Consent state is exposed reactively as `client.state.consent` (see below).
-## Reactive State
+## Reactive state
`OptimizationClient` is an `ObservableObject`. Several properties are `@Published` and update as the
JS bridge pushes signals:
@@ -177,7 +177,7 @@ analytics/personalization events emitted by the JS bridge. Useful for debug over
In SwiftUI, consume these with `@EnvironmentObject` + property wrappers; in UIKit, subscribe via
Combine (`client.$selectedPersonalizations.sink { ... }`).
-## Personalizing Contentful Entries
+## Personalizing Contentful entries
Fetch entries from Contentful as `[String: Any]` dictionaries (e.g. via `URLSession` or any
Contentful client that returns JSON-shaped output) and include linked optimization references by
@@ -201,7 +201,7 @@ In SwiftUI, `OptimizedEntry` wraps this call for you and also handles variant lo
tracking, and tap tracking. In UIKit, you call `personalizeEntry` yourself — typically in a cell
configuration method — and attach tracking manually.
-## Tracking Model
+## Tracking model
The iOS SDK tracks three kinds of events, each with a corresponding API:
@@ -214,7 +214,7 @@ The iOS SDK tracks three kinds of events, each with a corresponding API:
You will also see `identify(userId:traits:)` and `page(properties:)`. On mobile, screen events are
usually preferred over page events.
-### Entry View Tracking Thresholds
+### Entry view tracking thresholds
For entry views, the SDK fires an event when the entry has been at least **80% visible for 2
seconds**, then emits periodic duration updates every 5 seconds while it remains visible, and a
@@ -222,7 +222,7 @@ final event when it disappears. Both thresholds are configurable per-entry in Sw
`OptimizedEntry(..., viewTimeMs:, threshold:, viewDurationUpdateIntervalMs:)`. In UIKit, you compute
duration yourself and send a `TrackViewPayload`.
-## Live Updates
+## Live updates
`OptimizedEntry` in SwiftUI (and any UIKit code that reads `client.selectedPersonalizations`) can
either **lock to the first variant it resolves** or **update live** when the selected
@@ -238,7 +238,7 @@ Three layers control the behavior, from broadest to narrowest:
The resolution priority is:
-| Preview Panel | Global | Per-Entry | Result |
+| Preview panel | Global | Per-entry | Result |
| ------------- | ------- | --------- | ------ |
| Open | any | any | Live |
| Closed | `true` | `nil` | Live |
@@ -249,7 +249,7 @@ The resolution priority is:
When the preview panel closes, SwiftUI's `OptimizedEntry` snapshots the current variants so that any
overrides applied during the preview session become the new "locked" baseline.
-## Preview Panel
+## Preview panel
The preview panel is an in-app developer tool that lets authors and engineers override audience
membership and variant selections locally without touching production state. It is shipped as part
@@ -277,7 +277,7 @@ let contentfulClient = ContentfulHTTPPreviewClient(
While the panel is open, `client.isPreviewPanelOpen` is `true` and all `OptimizedEntry` components
switch to live update mode.
-## Offline Behavior
+## Offline behavior
The SDK monitors network reachability via `NWPathMonitor`. When offline:
@@ -288,12 +288,12 @@ The SDK monitors network reachability via `NWPathMonitor`. When offline:
No configuration is required. This behavior is identical across SwiftUI and UIKit integrations.
-## Where To Go Next
+## Where to go next
- Building a SwiftUI app? Continue to
- [Integrating the Optimization iOS SDK in a SwiftUI App](./integrating-the-ios-sdk-in-a-swiftui-app.md).
+ [Integrating the Optimization iOS SDK in a SwiftUI app](./integrating-the-ios-sdk-in-a-swiftui-app.md).
- Building a UIKit app? Continue to
- [Integrating the Optimization iOS SDK in a UIKit App](./integrating-the-ios-sdk-in-a-uikit-app.md).
+ [Integrating the Optimization iOS SDK in a UIKit app](./integrating-the-ios-sdk-in-a-uikit-app.md).
- Mixing both UI frameworks in one app? The SwiftUI views work inside `UIHostingController`, and
`OptimizationClient` is the shared underlying type — pass the same instance into both halves of
your app.
diff --git a/documentation/drafts/integrating-the-ios-sdk-in-a-swiftui-app.md b/documentation/drafts/integrating-the-ios-sdk-in-a-swiftui-app.md
index 93435ebc..eee33918 100644
--- a/documentation/drafts/integrating-the-ios-sdk-in-a-swiftui-app.md
+++ b/documentation/drafts/integrating-the-ios-sdk-in-a-swiftui-app.md
@@ -1,17 +1,42 @@
-# Integrating the Optimization iOS SDK in a SwiftUI App
+# Integrating the Optimization iOS SDK in a SwiftUI app
Use this guide when you want to add personalization and analytics to a SwiftUI application using the
Contentful Optimization iOS SDK.
This guide assumes familiarity with the shared concepts covered in
-[iOS SDK Fundamentals](./integrating-the-ios-sdk-fundamentals.md) — installation, configuration,
+[iOS SDK fundamentals](./integrating-the-ios-sdk-fundamentals.md) — installation, configuration,
consent, reactive state, the tracking model, live updates, and the preview panel. Read that first if
you have not already.
Use the UIKit guide instead if your app is UIKit-based:
-[Integrating the Optimization iOS SDK in a UIKit App](./integrating-the-ios-sdk-in-a-uikit-app.md).
+[Integrating the Optimization iOS SDK in a UIKit app](./integrating-the-ios-sdk-in-a-uikit-app.md).
-## Scope And Capabilities
+
+ Table of Contents
+
+
+- [Scope and capabilities](#scope-and-capabilities)
+- [Reference app](#reference-app)
+- [The integration flow](#the-integration-flow)
+- [1. Initialize with OptimizationRoot](#1-initialize-with-optimizationroot)
+- [2. Handle consent](#2-handle-consent)
+- [3. Personalize entries with OptimizedEntry](#3-personalize-entries-with-optimizedentry)
+ - [Basic usage](#basic-usage)
+ - [Render prop signature](#render-prop-signature)
+ - [OptimizationScrollView for scrollable content](#optimizationscrollview-for-scrollable-content)
+ - [Tuning visibility thresholds](#tuning-visibility-thresholds)
+- [4. Track entry interactions](#4-track-entry-interactions)
+ - [Global defaults on OptimizationRoot](#global-defaults-on-optimizationroot)
+ - [Per-entry overrides](#per-entry-overrides)
+- [5. Enable or disable live updates](#5-enable-or-disable-live-updates)
+- [6. Track screen views](#6-track-screen-views)
+- [7. Preview panel](#7-preview-panel)
+- [A complete example](#a-complete-example)
+
+
+
+
+## Scope and capabilities
The SwiftUI integration uses the SDK's SwiftUI-native API surface:
@@ -23,7 +48,7 @@ The SwiftUI integration uses the SDK's SwiftUI-native API surface:
- `.trackScreen(name:)` emits a screen event when a view appears.
- `PreviewPanelOverlay` renders a developer-only FAB that opens the preview panel sheet.
-## Reference App
+## Reference app
See the SwiftUI demo at
[Colorful-Team-Org/OptimizationiOSSDKDemo — SwiftUIDemo](https://github.com/Colorful-Team-Org/OptimizationiOSSDKDemo)
@@ -32,31 +57,7 @@ See the SwiftUI demo at
exercises every pattern in this guide end-to-end against real Contentful content and is worth
reading alongside this document.
-
- Table of Contents
-
-
-- [Scope And Capabilities](#scope-and-capabilities)
-- [The Integration Flow](#the-integration-flow)
-- [1. Initialize With OptimizationRoot](#1-initialize-with-optimizationroot)
-- [2. Handle Consent](#2-handle-consent)
-- [3. Personalize Entries With OptimizedEntry](#3-personalize-entries-with-optimizedentry)
- - [Basic Usage](#basic-usage)
- - [Render Prop Signature](#render-prop-signature)
- - [OptimizationScrollView For Scrollable Content](#optimizationscrollview-for-scrollable-content)
- - [Tuning Visibility Thresholds](#tuning-visibility-thresholds)
-- [4. Track Entry Interactions](#4-track-entry-interactions)
- - [Global Defaults On OptimizationRoot](#global-defaults-on-optimizationroot)
- - [Per-Entry Overrides](#per-entry-overrides)
-- [5. Enable Or Disable Live Updates](#5-enable-or-disable-live-updates)
-- [6. Track Screen Views](#6-track-screen-views)
-- [7. Preview Panel](#7-preview-panel)
-- [A Complete Example](#a-complete-example)
-
-
-
-
-## The Integration Flow
+## The integration flow
A typical SwiftUI integration is:
@@ -70,7 +71,7 @@ A typical SwiftUI integration is:
7. Mark each screen with `.trackScreen(name:)`.
8. Gate `PreviewPanelOverlay` on a debug flag.
-## 1. Initialize With OptimizationRoot
+## 1. Initialize with OptimizationRoot
`OptimizationRoot` owns the `OptimizationClient` instance, initializes it in a `.task {}` block, and
shows a `ProgressView` until `isInitialized` flips to `true`. All SwiftUI views in the tree can then
@@ -121,7 +122,7 @@ struct SomeView: View {
}
```
-## 2. Handle Consent
+## 2. Handle consent
See [Consent](./integrating-the-ios-sdk-fundamentals.md#consent) in the fundamentals guide for the
consent model. In SwiftUI, a minimal banner looks like:
@@ -162,7 +163,7 @@ struct ConsentGate: View {
For demos, pre-grant consent with `StorageDefaults(consent: true)` on the config you pass to
`OptimizationRoot` and skip the banner entirely.
-## 3. Personalize Entries With OptimizedEntry
+## 3. Personalize entries with OptimizedEntry
`OptimizedEntry` is the SwiftUI view you render each Contentful entry through. It:
@@ -172,7 +173,7 @@ For demos, pre-grant consent with `StorageDefaults(consent: true)` on the config
- attaches view tracking (visibility + time-based) and tap tracking (gesture-based)
- locks to the first resolved variant unless live updates are on
-### Basic Usage
+### Basic usage
```swift
import ContentfulOptimization
@@ -193,7 +194,7 @@ The render closure receives `[String: Any]` — the resolved entry dictionary. P
`entry["fields"] as? [String: Any]`. The demo app's `CTAHeader` and `BlogPostCardContent` views are
good references for destructuring.
-### Render Prop Signature
+### Render prop signature
```swift
OptimizedEntry(
@@ -213,7 +214,7 @@ OptimizedEntry(
All tracking and live-update flags are `Optional` — `nil` means "inherit from
`OptimizationRoot`".
-### OptimizationScrollView For Scrollable Content
+### OptimizationScrollView for scrollable content
Inside a plain `ScrollView`, `OptimizedEntry` falls back to "always visible" because it cannot read
scroll position. Wrap the scroll region with `OptimizationScrollView` so view tracking reflects the
@@ -235,7 +236,7 @@ OptimizationScrollView {
For full-screen content (heroes, modal cards, single-screen layouts), a plain container is fine —
the entry is treated as always on screen.
-### Tuning Visibility Thresholds
+### Tuning visibility thresholds
The 80% / 2 seconds / 5 second update defaults are good for feed-style content. Override per entry
when a specific component needs different behavior:
@@ -250,9 +251,9 @@ OptimizedEntry(
}
```
-## 4. Track Entry Interactions
+## 4. Track entry interactions
-### Global Defaults On OptimizationRoot
+### Global defaults on OptimizationRoot
```swift
OptimizationRoot(
@@ -267,7 +268,7 @@ OptimizationRoot(
The SDK defaults are `trackViews: true, trackTaps: false`. Views are safe to turn on everywhere;
taps are opt-in because they are more application-specific.
-### Per-Entry Overrides
+### Per-entry overrides
```swift
// Opt a specific entry out of view tracking
@@ -290,7 +291,7 @@ OptimizedEntry(entry: cta, onTap: { resolved in
Passing `trackTaps: false` always wins — even if `onTap` is provided.
-## 5. Enable Or Disable Live Updates
+## 5. Enable or disable live updates
See [Live Updates](./integrating-the-ios-sdk-fundamentals.md#live-updates) in the fundamentals for
the resolution rules. In SwiftUI:
@@ -316,7 +317,7 @@ OptimizedEntry(entry: card) { resolved in
While the preview panel is open, every `OptimizedEntry` in the tree switches to live mode regardless
of these flags.
-## 6. Track Screen Views
+## 6. Track screen views
Attach `.trackScreen(name:)` to any view — typically the root view of a screen:
@@ -351,7 +352,7 @@ struct DetailsScreen: View {
}
```
-## 7. Preview Panel
+## 7. Preview panel
Wrap your content in `PreviewPanelOverlay`, gated on a debug flag, to expose the developer FAB:
@@ -387,7 +388,7 @@ Tapping the FAB presents the panel as a sheet. While it is open, `client.isPrevi
The `contentfulClient` parameter is optional — without it the panel shows audiences and experiences
by ID. Passing it enables rich names, variant labels, and traffic percentages.
-## A Complete Example
+## A complete example
The SwiftUI demo's app entry point ties all of this together — `OptimizationRoot` with pre-granted
consent and live updates enabled, `PreviewPanelOverlay` wrapping a `NavigationStack`, and a home
diff --git a/documentation/drafts/integrating-the-ios-sdk-in-a-uikit-app.md b/documentation/drafts/integrating-the-ios-sdk-in-a-uikit-app.md
index 00e44615..33cee2e6 100644
--- a/documentation/drafts/integrating-the-ios-sdk-in-a-uikit-app.md
+++ b/documentation/drafts/integrating-the-ios-sdk-in-a-uikit-app.md
@@ -1,17 +1,40 @@
-# Integrating the Optimization iOS SDK in a UIKit App
+# Integrating the Optimization iOS SDK in a UIKit app
Use this guide when you want to add personalization and analytics to a UIKit application using the
Contentful Optimization iOS SDK.
This guide assumes familiarity with the shared concepts covered in
-[iOS SDK Fundamentals](./integrating-the-ios-sdk-fundamentals.md) — installation, configuration,
+[iOS SDK fundamentals](./integrating-the-ios-sdk-fundamentals.md) — installation, configuration,
consent, reactive state, the tracking model, live updates, and the preview panel. Read that first if
you have not already.
Use the SwiftUI guide instead if your app is SwiftUI-based:
-[Integrating the Optimization iOS SDK in a SwiftUI App](./integrating-the-ios-sdk-in-a-swiftui-app.md).
+[Integrating the Optimization iOS SDK in a SwiftUI app](./integrating-the-ios-sdk-in-a-swiftui-app.md).
-## Scope And Capabilities
+
+ Table of Contents
+
+
+- [Scope and capabilities](#scope-and-capabilities)
+- [Reference app](#reference-app)
+- [The integration flow](#the-integration-flow)
+- [1. Initialize in SceneDelegate](#1-initialize-in-scenedelegate)
+- [2. Handle consent](#2-handle-consent)
+- [3. Personalize entries](#3-personalize-entries)
+ - [Calling personalizeEntry](#calling-personalizeentry)
+ - [Reloading on selectedPersonalizations changes](#reloading-on-selectedpersonalizations-changes)
+ - [Live updates vs locked variants](#live-updates-vs-locked-variants)
+- [4. Track entry interactions](#4-track-entry-interactions)
+ - [Click tracking](#click-tracking)
+ - [View tracking](#view-tracking)
+- [5. Track screen views](#5-track-screen-views)
+- [6. Preview panel](#6-preview-panel)
+- [A complete example](#a-complete-example)
+
+
+
+
+## Scope and capabilities
The UIKit integration is more explicit than the SwiftUI one: the SDK does not ship UIKit-native
views equivalent to `OptimizedEntry` or `OptimizationScrollView`. Instead, you work with
@@ -33,7 +56,7 @@ UIKit apps typically use:
The preview panel's UI is itself SwiftUI, but `PreviewPanelViewController` wraps it in a
`UIHostingController` so it drops cleanly into a UIKit navigation stack.
-## Reference App
+## Reference app
See the UIKit demo at
[Colorful-Team-Org/OptimizationiOSSDKDemo — UIKitDemo](https://github.com/Colorful-Team-Org/OptimizationiOSSDKDemo)
@@ -41,29 +64,7 @@ See the UIKit demo at
[`../../../optimization-ios-demo/UIKitDemo`](../../../optimization-ios-demo/UIKitDemo)). It is
functionally identical to the SwiftUI demo so you can compare side-by-side.
-
- Table of Contents
-
-
-- [Scope And Capabilities](#scope-and-capabilities)
-- [The Integration Flow](#the-integration-flow)
-- [1. Initialize In SceneDelegate](#1-initialize-in-scenedelegate)
-- [2. Handle Consent](#2-handle-consent)
-- [3. Personalize Entries](#3-personalize-entries)
- - [Calling personalizeEntry](#calling-personalizeentry)
- - [Reloading On selectedPersonalizations Changes](#reloading-on-selectedpersonalizations-changes)
- - [Live Updates vs Locked Variants](#live-updates-vs-locked-variants)
-- [4. Track Entry Interactions](#4-track-entry-interactions)
- - [Click Tracking](#click-tracking)
- - [View Tracking](#view-tracking)
-- [5. Track Screen Views](#5-track-screen-views)
-- [6. Preview Panel](#6-preview-panel)
-- [A Complete Example](#a-complete-example)
-
-
-
-
-## The Integration Flow
+## The integration flow
A typical UIKit integration is:
@@ -80,10 +81,10 @@ A typical UIKit integration is:
9. Mount the preview panel behind a debug flag with
`PreviewPanelViewController.addFloatingButton(...)`.
-## 1. Initialize In SceneDelegate
+## 1. Initialize in SceneDelegate
Own the `OptimizationClient` from `SceneDelegate` so its lifetime matches the scene and its instance
-is easy to pass into view controllers.
+is straightforward to pass into view controllers.
```swift
import ContentfulOptimization
@@ -131,7 +132,7 @@ Pass `client` into each view controller's initializer. This gives every screen a
singleton instance for calling `personalizeEntry`, tracking events, and observing
`selectedPersonalizations`.
-## 2. Handle Consent
+## 2. Handle consent
See [Consent](./integrating-the-ios-sdk-fundamentals.md#consent) in the fundamentals for the consent
model. In UIKit, typical patterns are:
@@ -160,7 +161,7 @@ client.$state
.store(in: &cancellables)
```
-## 3. Personalize Entries
+## 3. Personalize entries
### Calling personalizeEntry
@@ -196,7 +197,7 @@ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> U
Use `personalization != nil` to decide whether a user saw a personalized variant — useful when
composing tracking payloads.
-### Reloading On selectedPersonalizations Changes
+### Reloading on selectedPersonalizations changes
When `client.selectedPersonalizations` changes (for example, after the user's audience qualification
shifts), re-resolve and redraw affected cells. Observe the property via Combine:
@@ -212,7 +213,7 @@ client.$selectedPersonalizations
.store(in: &cancellables)
```
-### Live Updates vs Locked Variants
+### Live updates vs locked variants
UIKit does not have an automatic "lock to first variant" mechanism — you decide when to re-resolve
based on whether you want to stay locked or update live. Two common patterns:
@@ -226,9 +227,9 @@ based on whether you want to stay locked or update live. Two common patterns:
A common compromise is to live-update while the preview panel is open (for developer feedback) and
lock in production. You can check `client.isPreviewPanelOpen` to decide.
-## 4. Track Entry Interactions
+## 4. Track entry interactions
-### Click Tracking
+### Click tracking
Wire up a tap action on the control and call `client.trackClick(_:)` with a `TrackClickPayload`:
@@ -254,7 +255,7 @@ ctaView.onButtonTap = { [weak self] in
| `experienceId` | `String?` | The ID of the matching experience, if any. |
| `variantIndex` | `Int` | `0` for baseline; `1+` for a personalized variant. |
-### View Tracking
+### View tracking
UIKit does not have a visibility modifier, so you detect visibility yourself (e.g. via
`collectionView(_:willDisplay:forItemAt:)` / `didEndDisplaying` or by observing cell visibility
@@ -282,7 +283,7 @@ per display with a short configurable duration. The SwiftUI `OptimizedEntry` use
threshold-based algorithm described in the fundamentals; UIKit apps that want parity can port that
logic or read `ViewTrackingController` in the SDK source as a reference.
-## 5. Track Screen Views
+## 5. Track screen views
Call `client.screen(name:)` from `viewDidAppear(_:)`:
@@ -307,7 +308,7 @@ Task {
}
```
-## 6. Preview Panel
+## 6. Preview panel
Attach the floating action button in the scene delegate (or from a root view controller's
`viewDidLoad`), gated on a debug flag:
@@ -343,7 +344,7 @@ experiences by ID. Passing `ContentfulHTTPPreviewClient` enables rich names, var
traffic percentages. You can also implement `PreviewContentfulClient` directly if you already have a
Contentful client you want to reuse.
-## A Complete Example
+## A complete example
The UIKit demo's scene delegate and home view controller together show the full pattern — SDK init
in `scene(_:willConnectTo:options:)`, a `UITableView` that calls `personalizeEntry` in
diff --git a/documentation/guides/AGENTS.md b/documentation/guides/AGENTS.md
index 41839957..97c39914 100644
--- a/documentation/guides/AGENTS.md
+++ b/documentation/guides/AGENTS.md
@@ -6,27 +6,27 @@ These instructions apply to public integration guides under `documentation/guide
## Directory README
-- Keep `Choosing the Right SDK` under `## Start Here`.
-- List implementation guides under `## Integration Guides` in platform/layer order:
+- Keep `Choosing the right SDK` under `## Start here`.
+- List implementation guides under `## Integration guides` in platform/layer order:
1. Node
2. Web
3. React Web
4. React Native
-## Integration Guide Structure
+## Integration guide structure
Use this structure for implementation guides:
-1. H1: `# Integrating the Optimization SDK in a App`
+1. H1: `# Integrating the Optimization SDK in a app`
2. A short introduction that starts with the reader's implementation goal, usually
`Use this guide when...`
3. A collapsible table of contents immediately after the introduction and before the first `##`
section.
-4. `## Scope and Capabilities`
-5. `## The Integration Flow`
+4. `## Scope and capabilities`
+5. `## The integration flow`
6. Numbered `##` sections for core implementation steps only.
7. Unnumbered `##` sections for optional or informative material.
-8. `## Reference Implementations to Compare Against`
+8. `## Reference implementations to compare against`
Do not place a table of contents after a section it outlines. Do not turn an implementation guide
into a deep mechanics reference. Link to a concept document when the reader needs lower-level
@@ -37,7 +37,7 @@ runtime. Keep package setup choices, common configuration, and workflow sequenci
keep method-by-method API details in TypeDoc. Create a new guide only when the extracted material
has a distinct implementation goal that is not covered by an existing guide.
-## Table of Contents
+## Table of contents
Every guide must include this TOC wrapper:
@@ -46,8 +46,6 @@ Every guide must include this TOC wrapper:
Table of Contents
-...
-
```
@@ -56,7 +54,7 @@ Every guide must include this TOC wrapper:
- Keep TOC entries and anchors synchronized with the headings after any heading edit.
- Include both `##` and relevant `###` headings in the TOC.
-## Numbering Rules
+## Numbering rules
- Number only core implementation steps.
- Do not number optional or informational sections such as live updates, preview panels, caching
@@ -64,13 +62,13 @@ Every guide must include this TOC wrapper:
- If a section is optional, say so in prose near the integration flow instead of making it part of
the numbered flow.
-## Reference Implementation Policy
+## Reference implementation policy
- Only link to or mention reference implementations that are co-located in this monorepo.
- Do not link to or mention external demo applications.
- Mention relevant monorepo reference implementations briefly near the top of an integration guide,
- usually in `The Integration Flow`.
-- Expand reference links in `Reference Implementations to Compare Against`.
+ usually in `The integration flow`.
+- Expand reference links in `Reference implementations to compare against`.
- Prefer links to implementation READMEs and relevant source files over vague references.
## Validation
diff --git a/documentation/guides/README.md b/documentation/guides/README.md
index 4570f100..0d381752 100644
--- a/documentation/guides/README.md
+++ b/documentation/guides/README.md
@@ -14,22 +14,22 @@ Start here when you need package selection guidance or step-by-step SDK implemen
If you only need package-level API summaries, use the linked package README from the root package
inventory instead.
-## Start Here
+## Start here
-- [Choosing the Right SDK](./choosing-the-right-sdk.md) - pick the narrowest published package layer
+- [Choosing the right SDK](./choosing-the-right-sdk.md) - pick the narrowest published package layer
for a browser, React, Node, or React Native application
-## Integration Guides
+## Integration guides
-- [Integrating the Optimization Node SDK in a Node App](./integrating-the-node-sdk-in-a-node-app.md) -
+- [Integrating the Optimization Node SDK in a Node app](./integrating-the-node-sdk-in-a-node-app.md) -
step-by-step server-side integration guidance using Express-style examples and the Node reference
implementations
-- [Integrating the Optimization Web SDK in a Web App](./integrating-the-web-sdk-in-a-web-app.md) -
+- [Integrating the Optimization Web SDK in a web app](./integrating-the-web-sdk-in-a-web-app.md) -
step-by-step browser-side integration guidance covering singleton SDK setup, consent, page events,
entry resolution, merge tags, flags, tracking, and hybrid SSR cookie continuity
-- [Integrating the Optimization React Web SDK in a React App](./integrating-the-react-web-sdk-in-a-react-app.md) -
+- [Integrating the Optimization React Web SDK in a React app](./integrating-the-react-web-sdk-in-a-react-app.md) -
step-by-step client-side integration guidance covering providers, consent, entry personalization,
interaction tracking, live updates, router adapters, and preview panel setup
-- [Integrating the Optimization React Native SDK in a React Native App](./integrating-the-react-native-sdk-in-a-react-native-app.md) -
+- [Integrating the Optimization React Native SDK in a React Native app](./integrating-the-react-native-sdk-in-a-react-native-app.md) -
step-by-step React Native / Expo integration guidance covering setup, consent, personalization and
interaction tracking, screen tracking, live updates, and the in-app preview panel
diff --git a/documentation/guides/choosing-the-right-sdk.md b/documentation/guides/choosing-the-right-sdk.md
index 10b0dd14..15eeecc4 100644
--- a/documentation/guides/choosing-the-right-sdk.md
+++ b/documentation/guides/choosing-the-right-sdk.md
@@ -1,4 +1,4 @@
-# Choosing the Right SDK
+# Choosing the right SDK
Use this guide to choose the narrowest package layer that matches the runtime you are building for.
@@ -6,23 +6,23 @@ Use this guide to choose the narrowest package layer that matches the runtime yo
Table of Contents
-- [Runtime-First Selection](#runtime-first-selection)
- - [Browser Applications](#browser-applications)
- - [React Applications on the Web](#react-applications-on-the-web)
- - [Node Servers and Server-Side Rendering](#node-servers-and-server-side-rendering)
- - [React Native Applications](#react-native-applications)
-- [Lower-Level Building Blocks](#lower-level-building-blocks)
+- [Runtime-first selection](#runtime-first-selection)
+ - [Browser applications](#browser-applications)
+ - [React applications on the web](#react-applications-on-the-web)
+ - [Node servers and server-side rendering](#node-servers-and-server-side-rendering)
+ - [React Native applications](#react-native-applications)
+- [Lower-level building blocks](#lower-level-building-blocks)
- [`@contentful/optimization-core`](#contentfuloptimization-core)
- [`@contentful/optimization-api-client`](#contentfuloptimization-api-client)
- [`@contentful/optimization-api-schemas`](#contentfuloptimization-api-schemas)
-- [Common Package Combinations](#common-package-combinations)
+- [Common package combinations](#common-package-combinations)
-## Runtime-First Selection
+## Runtime-first selection
-### Browser Applications
+### Browser applications
Choose `@contentful/optimization-web` when the application runs in the browser and needs stateful,
client-side optimization behavior such as consent handling, event delivery, and automatic entry
@@ -31,7 +31,7 @@ interaction tracking.
Add `@contentful/optimization-web-preview-panel` when the same browser runtime also needs local
preview tooling for optimization overrides.
-### React Applications on the Web
+### React applications on the web
Choose `@contentful/optimization-react-web` when the application is already built with React and
benefits from provider composition, hooks, `OptimizedEntry`, and router-specific automatic page
@@ -40,21 +40,21 @@ tracking.
This package sits on top of `@contentful/optimization-web`, so React applications generally use the
React layer as their application-facing entry point and rely on the Web SDK transitively.
-### Node Servers and Server-Side Rendering
+### Node servers and server-side rendering
Choose `@contentful/optimization-node` when optimization decisions are resolved in a stateless Node
environment such as a server, an SSR layer, or a server-side function.
The Node SDK intentionally avoids runtime-managed state. Consent, identity persistence, and other
-long-lived user concerns should remain in the host application or an upstream platform layer.
+long-lived user concerns must remain in the host application or an upstream platform layer.
-### React Native Applications
+### React Native applications
Choose `@contentful/optimization-react-native` for React Native applications that need stateful
optimization behavior on mobile, including offline-aware event handling and React Native-specific
tracking utilities.
-## Lower-Level Building Blocks
+## Lower-level building blocks
Choose one of the lower layers only when the environment SDKs are too opinionated for the use case
or when you are building a new SDK layer inside this repository.
@@ -77,7 +77,7 @@ level state, tracking, or entry-resolution behavior exposed by the SDK layers.
Use the schema package when you only need runtime validation and inferred TypeScript types for the
Optimization APIs and Contentful entry-shape helpers.
-## Common Package Combinations
+## Common package combinations
- Browser application with author preview: `@contentful/optimization-web` and
`@contentful/optimization-web-preview-panel`
diff --git a/documentation/guides/integrating-the-node-sdk-in-a-node-app.md b/documentation/guides/integrating-the-node-sdk-in-a-node-app.md
index 929ac4cb..ab64cd3c 100644
--- a/documentation/guides/integrating-the-node-sdk-in-a-node-app.md
+++ b/documentation/guides/integrating-the-node-sdk-in-a-node-app.md
@@ -1,4 +1,4 @@
-# Integrating the Optimization Node SDK in a Node App
+# Integrating the Optimization Node SDK in a Node app
Use this guide when you want to implement server-side personalization in a Node runtime such as
Express, a custom SSR server, or a server-side function.
@@ -9,27 +9,27 @@ The examples below use Express, but the same flow applies to any Node request ha
Table of Contents
-- [Scope and Capabilities](#scope-and-capabilities)
-- [The Integration Flow](#the-integration-flow)
-- [1. Install and Initialize the SDK](#1-install-and-initialize-the-sdk)
-- [2. Turn the Express Request into SDK Event Context](#2-turn-the-express-request-into-sdk-event-context)
-- [3. Handle Consent in Your Application Layer](#3-handle-consent-in-your-application-layer)
-- [4. Decide How You Will Persist the Profile ID](#4-decide-how-you-will-persist-the-profile-id)
-- [5. Call `page()` and `identify()` at the Right Time](#5-call-page-and-identify-at-the-right-time)
- - [Which Order Should `page()` and `identify()` Use?](#which-order-should-page-and-identify-use)
-- [6. Resolve Contentful Entries with `selectedOptimizations`](#6-resolve-contentful-entries-with-selectedoptimizations)
-- [7. Resolve Merge Tags and Custom Flags](#7-resolve-merge-tags-and-custom-flags)
- - [Merge Tags](#merge-tags)
- - [Custom Flags](#custom-flags)
-- [8. Emit Follow-Up Server Events When They Matter](#8-emit-follow-up-server-events-when-they-matter)
-- [Caching and Cache Safety](#caching-and-cache-safety)
-- [Know When the Web SDK Should Join the Architecture](#know-when-the-web-sdk-should-join-the-architecture)
-- [Reference Implementations to Compare Against](#reference-implementations-to-compare-against)
+- [Scope and capabilities](#scope-and-capabilities)
+- [The integration flow](#the-integration-flow)
+- [1. Install and initialize the SDK](#1-install-and-initialize-the-sdk)
+- [2. Turn the Express request into SDK event context](#2-turn-the-express-request-into-sdk-event-context)
+- [3. Handle consent in your application layer](#3-handle-consent-in-your-application-layer)
+- [4. Decide how you will persist the profile ID](#4-decide-how-you-will-persist-the-profile-id)
+- [5. Call `page()` and `identify()` at the right time](#5-call-page-and-identify-at-the-right-time)
+ - [`page()` and `identify()` call order](#page-and-identify-call-order)
+- [6. Resolve Contentful entries with `selectedOptimizations`](#6-resolve-contentful-entries-with-selectedoptimizations)
+- [7. Resolve merge tags and custom flags](#7-resolve-merge-tags-and-custom-flags)
+ - [Merge tags](#merge-tags)
+ - [Custom flags](#custom-flags)
+- [8. Emit follow-up server events when they matter](#8-emit-follow-up-server-events-when-they-matter)
+- [Caching and cache safety](#caching-and-cache-safety)
+- [Know when the Web SDK belongs in the architecture](#know-when-the-web-sdk-belongs-in-the-architecture)
+- [Reference implementations to compare against](#reference-implementations-to-compare-against)
-## Scope and Capabilities
+## Scope and capabilities
The Node SDK is the server-side package for Node-based applications in the Optimization SDK Suite.
It lets consumers:
@@ -43,13 +43,13 @@ It lets consumers:
The Node SDK is intentionally stateless. It does not manage consent, cookies, sessions, or
long-lived profile state for you. Your application decides how profile IDs are persisted and when
-events should be sent.
+events are sent.
It also does not replace your Contentful delivery client. Your app still fetches entries from
Contentful. The Node SDK helps you choose the right variant for the current profile after that
content has been fetched.
-## The Integration Flow
+## The integration flow
In practice, most Node integrations follow this high-level sequence:
@@ -67,7 +67,7 @@ The two Node reference implementations in this repository show that pattern in w
- [Node SSR Only](../../implementations/node-sdk/README.md)
- [Node SSR + Web SDK Vanilla](../../implementations/node-sdk+web-sdk/README.md)
-## 1. Install and Initialize the SDK
+## 1. Install and initialize the SDK
Install the package in your Node app:
@@ -116,10 +116,10 @@ Notes:
- On modern Node runtimes, the built-in `fetch` implementation is usually enough. If your runtime
does not expose a standard Fetch API, provide `fetchOptions.fetchMethod`.
-## 2. Turn the Express Request into SDK Event Context
+## 2. Turn the Express request into SDK event context
The SDK can accept request-scoped event context such as locale, user agent, and page information.
-That context should be built fresh for every incoming request.
+That context must be built fresh for every incoming request.
The reference implementations do this by translating the Express request into
`UniversalEventBuilderArgs`:
@@ -180,12 +180,12 @@ stable, request-specific description of the current page or route.
locale values are intentionally separate, even if your app derives them from the same request
header.
-## 3. Handle Consent in Your Application Layer
+## 3. Handle consent in your application layer
The Node SDK does not expose a server-side `consent()` state the way stateful SDKs do. In a Node
app, consent belongs in your application layer.
-That usually means your app should:
+That usually means your app needs to:
- store the user's consent decision in its own cookie, session, or user-preference store
- decide which high-level SDK methods are allowed before consent, after consent, and after consent
@@ -221,7 +221,7 @@ function clearOptimizationIdentity(res: Response): void {
The exact consent policy belongs to the application, not the SDK. The important part is that the
server makes that decision before it persists identifiers or emits events on the user's behalf.
-## 4. Decide How You Will Persist the Profile ID
+## 4. Decide how you will persist the profile ID
Because the Node SDK is stateless, it will not remember a visitor between requests on its own. Your
app needs to persist the returned `profile.id` somewhere and pass it back into later SDK calls when
@@ -264,7 +264,7 @@ If your app will also run `@contentful/optimization-web` in the browser, avoid m
as `HttpOnly`, because browser-side code needs to read it. If your app is server-only, a session
store is equally valid. If consent is revoked, clear the same cookie or session value.
-## 5. Call `page()` and `identify()` at the Right Time
+## 5. Call `page()` and `identify()` at the right time
The Node SDK returns optimization data from `page()`, `identify()`, `screen()`, `track()`, and
sticky `trackView()` calls. In a typical SSR route, `page()` is the most important entry point.
@@ -345,27 +345,27 @@ That route lets a consumer accomplish two things:
The returned `OptimizationData` usually gives you the three values you care about most:
-- `profile`: the current profile, including the profile ID you should persist
+- `profile`: the current profile, including the profile ID to persist
- `changes`: Custom Flag inputs
- `selectedOptimizations`: the variant choices to use when resolving Contentful entries
-### Which Order Should `page()` and `identify()` Use?
+### `page()` and `identify()` call order
Both patterns appear in the reference implementations because they answer slightly different
questions:
-- call `identify()` and then `page()` when the current page view should be attributed to the known
+- call `identify()` and then `page()` when the current page view must be attributed to the known
user identity
-- call `page()` and then `identify()` when the request arrived anonymous but the response should
- still render with data returned from the identify step
+- call `page()` and then `identify()` when the request arrived anonymous but the response must still
+ render with data returned from the identify step
The important rule is simpler than the ordering nuance: always render from the most relevant
response object for the user state you want on that response.
-## 6. Resolve Contentful Entries with `selectedOptimizations`
+## 6. Resolve Contentful entries with `selectedOptimizations`
-Once you have optimization data, fetch the baseline Contentful entry the same way you normally
-would, then hand it to `resolveOptimizedEntry()`.
+Once you have optimization data, fetch the baseline Contentful entry the same way your application
+normally does, then hand it to `resolveOptimizedEntry()`.
In the example below, replace `ArticleSkeleton` with the generated Contentful skeleton type your app
already uses.
@@ -429,11 +429,11 @@ This is the main server-side personalization loop:
If your optimized entries contain linked entries or merge tags, fetch with an `include` depth that
matches your content model. The SSR reference implementation uses `include: 10` for that reason.
-## 7. Resolve Merge Tags and Custom Flags
+## 7. Resolve merge tags and custom flags
The Node SDK also exposes helpers for profile-aware merge tags and Custom Flags.
-### Merge Tags
+### Merge tags
If a Rich Text field contains merge-tag entries, resolve them against the current profile while
rendering the field:
@@ -456,7 +456,7 @@ const html = documentToHtmlString(richTextField, {
That is the pattern used in the SSR-only reference implementation.
-### Custom Flags
+### Custom flags
Use `getFlag()` when the response includes Custom Flag changes:
@@ -464,7 +464,7 @@ Use `getFlag()` when the response includes Custom Flag changes:
const showNewNavigation = optimization.getFlag('new-navigation', pageResponse?.changes) === true
```
-In the Node SDK, `getFlag()` does not auto-track flag views. If a flag exposure should also be
+In the Node SDK, `getFlag()` does not auto-track flag views. If a flag exposure also needs to be
captured as an Insights event, call `trackFlagView()` explicitly:
```ts
@@ -477,7 +477,7 @@ if (pageResponse?.profile) {
}
```
-## 8. Emit Follow-Up Server Events When They Matter
+## 8. Emit follow-up server events when they matter
The Node SDK can send more than page views. Common server-side cases are:
@@ -490,8 +490,8 @@ The Node SDK can send more than page views. Common server-side cases are:
Gate these calls with the same consent policy your app applies to `page()` and `identify()`.
In stateless Node usage, Insights-backed calls need a profile. `trackClick()`, `trackHover()`,
-`trackFlagView()`, and non-sticky `trackView()` should use a persisted or freshly returned profile.
-Sticky `trackView()` may omit `profile`, because it can reuse the paired Experience response
+`trackFlagView()`, and non-sticky `trackView()` must use a persisted or freshly returned profile.
+Sticky `trackView()` can omit `profile`, because it can reuse the paired Experience response
profile.
Example custom event:
@@ -535,7 +535,7 @@ if (selectedOptimization?.sticky) {
}
```
-## Caching and Cache Safety
+## Caching and cache safety
The Node SDK sits on one side of an important cache boundary:
@@ -563,8 +563,8 @@ Unsafe patterns:
- sharing merge-tag-rendered Rich Text across users or requests
The request-scoped SDK methods are not cacheable reads. They emit Experience or Insights events and
-may return updated profile state for the current visitor. Call them per request when personalization
-is needed.
+might return updated profile state for the current visitor. Call them per request when
+personalization is needed.
If you want to cache variant resolution itself, key that cache by both:
@@ -577,11 +577,11 @@ Do not key personalized caches only by URL or entry ID.
| -------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------- |
| Raw `contentful.js` entry or query response | Yes | Key by entry or query, locale, include depth, environment, host, and delivery mode |
| `resolveOptimizedEntry(entry, selectedOptimizations)` result | Conditionally | Safe only if keyed by the baseline entry version plus a `selectedOptimizations` fingerprint |
-| Merge-tag-rendered Rich Text | No | Depends on the current request `profile` |
+| Merge-tag-rendered rich text | No | Depends on the current request `profile` |
| SSR HTML with personalized content | Usually no | Safe only when the cache varies on all personalization inputs |
-| `page()`, `identify()`, `screen()`, `track()`, and `trackView()` responses | No | These methods perform side effects and should not be memoized |
+| `page()`, `identify()`, `screen()`, `track()`, and `trackView()` responses | No | These methods perform side effects and must not be memoized |
-## Know When the Web SDK Should Join the Architecture
+## Know when the Web SDK belongs in the architecture
Use the Node SDK by itself when the server is responsible for choosing the variant and rendering the
response.
@@ -599,7 +599,7 @@ The hybrid reference implementation shows exactly that setup:
- [Server integration](../../implementations/node-sdk+web-sdk/src/app.ts)
- [Browser integration](../../implementations/node-sdk+web-sdk/src/index.ejs)
-## Reference Implementations to Compare Against
+## Reference implementations to compare against
Use these files when you want working repository examples instead of guide snippets:
diff --git a/documentation/guides/integrating-the-react-native-sdk-in-a-react-native-app.md b/documentation/guides/integrating-the-react-native-sdk-in-a-react-native-app.md
index 1896421e..55895bf3 100644
--- a/documentation/guides/integrating-the-react-native-sdk-in-a-react-native-app.md
+++ b/documentation/guides/integrating-the-react-native-sdk-in-a-react-native-app.md
@@ -1,4 +1,4 @@
-# Integrating the Optimization React Native SDK in a React Native App
+# Integrating the Optimization React Native SDK in a React Native app
Use this guide when you want to add personalization, analytics, screen tracking, and a preview panel
to a React Native (or Expo) application using `@contentful/optimization-react-native`.
@@ -7,45 +7,45 @@ to a React Native (or Expo) application using `@contentful/optimization-react-na
Table of Contents
-- [Scope and Capabilities](#scope-and-capabilities)
-- [The Integration Flow](#the-integration-flow)
-- [1. Install and Initialize with Minimal Configuration](#1-install-and-initialize-with-minimal-configuration)
- - [Install Peer Dependencies](#install-peer-dependencies)
- - [The Minimum Setup](#the-minimum-setup)
- - [Access the SDK Instance with Hooks](#access-the-sdk-instance-with-hooks)
-- [2. Handle Consent](#2-handle-consent)
- - [Defaulting Consent to `true`](#defaulting-consent-to-true)
- - [Gating Consent on a Banner](#gating-consent-on-a-banner)
- - [Reading and Reacting to Consent State](#reading-and-reacting-to-consent-state)
- - [Revoking Consent](#revoking-consent)
-- [3. Personalize Entries with OptimizedEntry](#3-personalize-entries-with-optimizedentry)
- - [Fetch the Entry with `include: 10`](#fetch-the-entry-with-include-10)
- - [Render the Variant with a Render Prop](#render-the-variant-with-a-render-prop)
- - [Pass-Through for Non-Optimized Entries](#pass-through-for-non-optimized-entries)
-- [4. Interaction Tracking with OptimizedEntry](#4-interaction-tracking-with-optimizedentry)
- - [Global Defaults via OptimizationRoot](#global-defaults-via-optimizationroot)
- - [Per-Component Overrides](#per-component-overrides)
- - [Custom Visibility and Time Thresholds](#custom-visibility-and-time-thresholds)
- - [Use OptimizationScrollProvider for Scrollable Screens](#use-optimizationscrollprovider-for-scrollable-screens)
-- [5. Screen Tracking](#5-screen-tracking)
- - [Automatic Tracking with OptimizationNavigationContainer](#automatic-tracking-with-optimizationnavigationcontainer)
- - [Per-Screen Tracking with `useScreenTracking`](#per-screen-tracking-with-usescreentracking)
- - [Dynamic Names with `useScreenTrackingCallback`](#dynamic-names-with-usescreentrackingcallback)
-- [Live Updates](#live-updates)
- - [Default Behavior](#default-behavior)
- - [Global Live Updates](#global-live-updates)
- - [Per-Component Live Updates](#per-component-live-updates)
- - [Resolution Priority](#resolution-priority)
-- [Preview Panel](#preview-panel)
- - [Enabling the Preview Panel](#enabling-the-preview-panel)
- - [Customizing the Floating Action Button](#customizing-the-floating-action-button)
- - [Preview Panel and Live Updates](#preview-panel-and-live-updates)
-- [Reference Implementations to Compare Against](#reference-implementations-to-compare-against)
+- [Scope and capabilities](#scope-and-capabilities)
+- [The integration flow](#the-integration-flow)
+- [1. Install and initialize with minimal configuration](#1-install-and-initialize-with-minimal-configuration)
+ - [Install peer dependencies](#install-peer-dependencies)
+ - [The minimum setup](#the-minimum-setup)
+ - [Access the SDK instance with hooks](#access-the-sdk-instance-with-hooks)
+- [2. Handle consent](#2-handle-consent)
+ - [Defaulting consent to `true`](#defaulting-consent-to-true)
+ - [Gating consent on a banner](#gating-consent-on-a-banner)
+ - [Reading and reacting to consent state](#reading-and-reacting-to-consent-state)
+ - [Revoking consent](#revoking-consent)
+- [3. Personalize entries with OptimizedEntry](#3-personalize-entries-with-optimizedentry)
+ - [Fetch the entry with `include: 10`](#fetch-the-entry-with-include-10)
+ - [Render the variant with a render prop](#render-the-variant-with-a-render-prop)
+ - [Pass-through for non-optimized entries](#pass-through-for-non-optimized-entries)
+- [4. Interaction tracking with OptimizedEntry](#4-interaction-tracking-with-optimizedentry)
+ - [Global defaults via OptimizationRoot](#global-defaults-via-optimizationroot)
+ - [Per-component overrides](#per-component-overrides)
+ - [Custom visibility and time thresholds](#custom-visibility-and-time-thresholds)
+ - [Use OptimizationScrollProvider for scrollable screens](#use-optimizationscrollprovider-for-scrollable-screens)
+- [5. Screen tracking](#5-screen-tracking)
+ - [Automatic tracking with OptimizationNavigationContainer](#automatic-tracking-with-optimizationnavigationcontainer)
+ - [Per-screen tracking with `useScreenTracking`](#per-screen-tracking-with-usescreentracking)
+ - [Dynamic names with `useScreenTrackingCallback`](#dynamic-names-with-usescreentrackingcallback)
+- [Live updates](#live-updates)
+ - [Default behavior](#default-behavior)
+ - [Global live updates](#global-live-updates)
+ - [Per-component live updates](#per-component-live-updates)
+ - [Resolution priority](#resolution-priority)
+- [Preview panel](#preview-panel)
+ - [Enabling the preview panel](#enabling-the-preview-panel)
+ - [Customizing the floating action button](#customizing-the-floating-action-button)
+ - [Preview panel and live updates](#preview-panel-and-live-updates)
+- [Reference implementations to compare against](#reference-implementations-to-compare-against)
-## Scope and Capabilities
+## Scope and capabilities
The React Native SDK builds on the Optimization Core Library and adds React Native-specific
providers, hooks, and components. It lets consumers:
@@ -62,30 +62,30 @@ The React Native SDK does not replace your Contentful delivery client. Your appl
fetches Contentful entries, decides how consent works, decides when a user becomes known, and
controls where personalized content renders.
-## The Integration Flow
+## The integration flow
Most React Native integrations follow this sequence:
1. Install the SDK and its required peer dependencies.
2. Wrap the app in `OptimizationRoot` with the minimum config (`clientId`).
-3. Decide how consent should behave (default-on for trusted contexts, or gated by a UI prompt).
+3. Decide how consent behaves (default-on for trusted contexts, or gated by a UI prompt).
4. Wrap each personalizable Contentful entry in ``.
5. Enable view and/or tap tracking for the entries you care about.
6. Wrap scrollable screens in `` so viewport tracking is accurate.
7. Add screen tracking either automatically with `OptimizationNavigationContainer` or per screen
with `useScreenTracking`.
-Optional additions include live updates when entries should continuously react to optimization state
-changes, and the preview panel when the application needs authoring or preview overrides.
+Optional additions include live updates when entries need to continuously react to optimization
+state changes, and the preview panel when the application needs authoring or preview overrides.
The React Native reference implementation in this repository shows those patterns in a working
application:
- [`implementations/react-native-sdk`](../../implementations/react-native-sdk/README.md)
-## 1. Install and Initialize with Minimal Configuration
+## 1. Install and initialize with minimal configuration
-### Install Peer Dependencies
+### Install peer dependencies
```sh
pnpm add @contentful/optimization-react-native @react-native-async-storage/async-storage
@@ -109,7 +109,7 @@ them automatically when connectivity returns.
> [README](../../implementations/react-native-sdk/README.md) documents the monorepo setup and E2E
> commands.
-### The Minimum Setup
+### The minimum setup
Wrap your app's root in ``. This is the recommended entry point — it composes the
`OptimizationProvider`, `LiveUpdatesProvider`, and `InteractionTrackingProvider` for you and manages
@@ -170,7 +170,7 @@ The full configuration reference (API endpoints, fetch retries, queue policy, ev
overrides) is documented in the
[React Native SDK README](../../packages/react-native-sdk/README.md#common-configuration).
-### Access the SDK Instance with Hooks
+### Access the SDK instance with hooks
Inside the provider tree, use `useOptimization()` to interact with the SDK directly:
@@ -192,7 +192,7 @@ function MyComponent() {
guaranteed to return a ready SDK instance — `OptimizationProvider` does not render its children
until the SDK has finished initializing.
-## 2. Handle Consent
+## 2. Handle consent
The SDK gates non-essential event types behind a consent state. By default, only `identify` and
`screen` events are allowed before consent is explicitly set. All other event types (including entry
@@ -201,7 +201,7 @@ view/tap tracking) are blocked until the user accepts or rejects consent.
You can change which event types are permitted before consent via the `allowedEventTypes` config
option.
-### Defaulting Consent to `true`
+### Defaulting consent to `true`
If your app already collects consent at install time (e.g. through a prior onboarding flow) or if
you don't need a runtime consent prompt, set `defaults.consent: true` so events flow immediately:
@@ -217,7 +217,7 @@ implementation shows an equivalent trusted-context shortcut in
[`App.tsx`](../../implementations/react-native-sdk/App.tsx) by calling `sdk.consent(true)` after the
provider initializes.
-### Gating Consent on a Banner
+### Gating consent on a banner
For apps that need an explicit prompt, leave `defaults.consent` unset and call `consent()` from your
banner UI:
@@ -243,7 +243,7 @@ When consent is accepted (`true`), all event types are permitted. When consent i
(`false`), non-allowed event types are blocked and `` view/tap tracking will be
silently dropped at the SDK boundary. Consent state persists across app launches via AsyncStorage.
-### Reading and Reacting to Consent State
+### Reading and reacting to consent state
Subscribe to the SDK's consent observable when you need to render UI conditionally:
@@ -268,7 +268,7 @@ function ConsentStatus() {
A common pattern is to gate personalized content rendering on consent and fall back to the baseline
entry while consent is missing or rejected.
-### Revoking Consent
+### Revoking consent
To revoke consent after it was previously accepted, just call `consent(false)`:
@@ -276,7 +276,7 @@ To revoke consent after it was previously accepted, just call `consent(false)`:
optimization.consent(false)
```
-## 3. Personalize Entries with OptimizedEntry
+## 3. Personalize entries with OptimizedEntry
`` is the unified component for resolving optimized variants and tracking
interactions on Contentful entries. It:
@@ -288,7 +288,7 @@ interactions on Contentful entries. It:
- Emits view tracking when the entry crosses the visibility/time threshold.
- Emits tap tracking when enabled.
-### Fetch the Entry with `include: 10`
+### Fetch the entry with `include: 10`
For variant data to resolve, the entry must be fetched with linked optimization references included.
Use `include: 10` on Contentful's Delivery API call:
@@ -300,7 +300,7 @@ const cta = await contentfulClient.getEntry(CTA_ENTRY_ID, { include: 10 })
The in-tree reference implementation centralizes this pattern in
[`utils/sdkHelpers.ts`](../../implementations/react-native-sdk/utils/sdkHelpers.ts).
-### Render the Variant with a Render Prop
+### Render the variant with a render prop
Pass the baseline entry to `` and render with a render prop that receives the
resolved entry:
@@ -322,7 +322,7 @@ profile) or the baseline entry (when no variant qualifies). Either way, `resolve
the same shape as the baseline — so the renderer downstream doesn't need to know whether it's seeing
a variant or not.
-### Pass-Through for Non-Optimized Entries
+### Pass-through for non-optimized entries
When you only want to track an entry (no variant resolution), pass static children instead of a
render prop:
@@ -338,7 +338,7 @@ This is the same tracking pattern used by
entries are wrapped so the SDK can track views/taps, while non-optimized content passes through
unchanged.
-## 4. Interaction Tracking with OptimizedEntry
+## 4. Interaction tracking with OptimizedEntry
`` tracks two interactions: **views** (the entry was at least N% visible for at
least M ms) and **taps** (the user tapped the entry). View tracking is enabled by default; tap
@@ -347,7 +347,7 @@ tracking is opt-in.
For the deeper event timing, threshold, consent-gating, and viewport-state details, see
[React Native SDK Interaction Tracking Mechanics](../concepts/react-native-sdk-interaction-tracking-mechanics.md).
-### Global Defaults via OptimizationRoot
+### Global defaults via OptimizationRoot
Set a global default for all `` components via `trackEntryInteraction` on the
root:
@@ -360,7 +360,7 @@ root:
The default is `{ views: true, taps: false }`.
-### Per-Component Overrides
+### Per-component overrides
Override the global setting on individual entries with `trackViews` and `trackTaps`:
@@ -392,7 +392,7 @@ implicitly enables tap tracking unless `trackTaps={false}` is explicit:
```
-### Custom Visibility and Time Thresholds
+### Custom visibility and time thresholds
By default, view tracking fires when the entry is **80% visible for 2000 ms**. Customize per-entry:
@@ -409,7 +409,7 @@ By default, view tracking fires when the entry is **80% visible for 2000 ms**. C
After the initial view event, the SDK emits periodic view-duration update events every 5000 ms by
default; configure with `viewDurationUpdateIntervalMs`.
-### Use OptimizationScrollProvider for Scrollable Screens
+### Use OptimizationScrollProvider for scrollable screens
Inside a scrolling container, viewport-based view tracking needs to know the actual scroll position.
Wrap the scrollable screen in ``:
@@ -435,12 +435,12 @@ Without `OptimizationScrollProvider`, the SDK assumes scroll position is always
equals the screen. That's fine for a single full-screen component, but for content that appears
below the fold, wrap the screen so tracking fires when the user scrolls the entry into view.
-## 5. Screen Tracking
+## 5. Screen tracking
Screen tracking emits a `screen` event each time the user navigates to a new screen. The SDK uses
these events to update profile attribution and route-aware properties.
-### Automatic Tracking with OptimizationNavigationContainer
+### Automatic tracking with OptimizationNavigationContainer
If you use React Navigation, the easiest setup is ``, which wraps
`` and emits a `screen` event on every active route change:
@@ -487,7 +487,7 @@ Available props:
| `onReady` | No | — | Called after the initial screen tracking on container ready |
| `includeParams` | No | `false` | Whether to include route params in the screen event properties |
-### Per-Screen Tracking with `useScreenTracking`
+### Per-screen tracking with `useScreenTracking`
If you don't use React Navigation, or if you want fine-grained control, call `useScreenTracking`
inside each screen component:
@@ -521,7 +521,7 @@ function DetailsScreen() {
}
```
-### Dynamic Names with `useScreenTrackingCallback`
+### Dynamic names with `useScreenTrackingCallback`
When the screen name isn't known at render time (e.g. derived from navigation state or a deep-link),
use `useScreenTrackingCallback` to get a stable callback you can fire on demand:
@@ -540,16 +540,16 @@ function DynamicScreen({ screenName }: { screenName: string }) {
}
```
-## Live Updates
+## Live updates
-### Default Behavior
+### Default behavior
By default, `` **locks to the first variant it receives** for the lifetime of the
component. If a user later qualifies for a different variant mid-session (e.g. after `identify()`),
they continue to see the original variant until the component unmounts. This prevents UI flashing
when audience membership changes while the user is viewing content.
-### Global Live Updates
+### Global live updates
Set `liveUpdates` on `OptimizationRoot` to enable real-time updates for every ``
in the app:
@@ -560,7 +560,7 @@ in the app:
```
-### Per-Component Live Updates
+### Per-component live updates
Override the global setting on individual entries:
@@ -585,7 +585,7 @@ Override the global setting on individual entries:
;{(resolved) => }
```
-### Resolution Priority
+### Resolution priority
The effective live-updates state for a given `` is resolved in this order (highest
to lowest priority):
@@ -595,7 +595,7 @@ to lowest priority):
3. **`OptimizationRoot` `liveUpdates` prop** — global setting.
4. **Default** — locked to first variant (`false`).
-| Preview Panel | Global Setting | Component Prop | Result |
+| Preview panel | Global setting | Component prop | Result |
| ------------- | -------------- | -------------- | ---------------- |
| Open | any | any | Live updates ON |
| Closed | `true` | `undefined` | Live updates ON |
@@ -615,13 +615,13 @@ function StatusBadge() {
}
```
-## Preview Panel
+## Preview panel
The preview panel is an in-app developer surface that lets you browse audiences, override variant
selection, and inspect the current profile — all without modifying real user data. It's the React
Native counterpart to the Web preview panel.
-### Enabling the Preview Panel
+### Enabling the preview panel
Pass a `previewPanel` config to `OptimizationRoot`. You must also pass an initialized Contentful
client so the panel can fetch audience and experience entries:
@@ -656,7 +656,7 @@ drawer.
For real apps, gate on `__DEV__` (or another build flag) so the FAB doesn't appear in production.
-### Customizing the Floating Action Button
+### Customizing the floating action button
Use `fabPosition` and `showHeader` to fine-tune placement and chrome:
@@ -675,7 +675,7 @@ Use `fabPosition` and `showHeader` to fine-tune placement and chrome:
```
-### Preview Panel and Live Updates
+### Preview panel and live updates
When the preview panel is open, **all** `` components automatically enable live
updates, regardless of their `liveUpdates` prop or the global setting. This is what makes "override
@@ -692,7 +692,7 @@ function DebugBadge() {
}
```
-## Reference Implementations to Compare Against
+## Reference implementations to compare against
- [`implementations/react-native-sdk`](../../implementations/react-native-sdk/README.md): the
in-tree React Native reference implementation that is built and tested alongside the SDK itself
diff --git a/documentation/guides/integrating-the-react-web-sdk-in-a-react-app.md b/documentation/guides/integrating-the-react-web-sdk-in-a-react-app.md
index f1362bf5..3911a949 100644
--- a/documentation/guides/integrating-the-react-web-sdk-in-a-react-app.md
+++ b/documentation/guides/integrating-the-react-web-sdk-in-a-react-app.md
@@ -1,4 +1,4 @@
-# Integrating the Optimization React Web SDK in a React App
+# Integrating the Optimization React Web SDK in a React app
Use this guide when you want to add personalization and analytics to a React web application using
`@contentful/optimization-react-web`.
@@ -14,47 +14,47 @@ to own the browser SDK integration without React abstractions.
Table of Contents
-- [Scope and Capabilities](#scope-and-capabilities)
-- [The Integration Flow](#the-integration-flow)
-- [1. Install and Initialize with OptimizationRoot](#1-install-and-initialize-with-optimizationroot)
- - [Access the SDK Instance with Hooks](#access-the-sdk-instance-with-hooks)
- - [Provide a Pre-Built SDK Instance When Needed](#provide-a-pre-built-sdk-instance-when-needed)
-- [2. Handle Consent in React](#2-handle-consent-in-react)
- - [Reading Consent State](#reading-consent-state)
- - [Updating Consent](#updating-consent)
- - [Revoking Consent](#revoking-consent)
- - [Consent-Gated Rendering](#consent-gated-rendering)
-- [3. Personalize Entries with OptimizedEntry](#3-personalize-entries-with-optimizedentry)
- - [Basic Usage](#basic-usage)
- - [Loading Fallback](#loading-fallback)
- - [Direct ReactNode Children](#direct-reactnode-children)
- - [Wrapper Element](#wrapper-element)
- - [Nested Composition](#nested-composition)
- - [Imperative Hook Alternative](#imperative-hook-alternative)
-- [4. Track Entry Interactions from React](#4-track-entry-interactions-from-react)
- - [Automatic Tracking via OptimizedEntry](#automatic-tracking-via-optimizedentry)
- - [Manual Tracking via the Tracking API](#manual-tracking-via-the-tracking-api)
-- [5. Emit Page Events with Supported Router Adapters](#5-emit-page-events-with-supported-router-adapters)
- - [React Router](#react-router)
- - [Next.js Pages Router](#nextjs-pages-router)
- - [Next.js App Router](#nextjs-app-router)
- - [TanStack Router](#tanstack-router)
- - [Page Payload Enrichment](#page-payload-enrichment)
-- [Live Updates](#live-updates)
- - [Global Live Updates](#global-live-updates)
- - [Per-Component Live Updates](#per-component-live-updates)
- - [Preview Panel Override](#preview-panel-override)
- - [Resolution Priority](#resolution-priority)
-- [Preview Panel](#preview-panel)
- - [Attaching the Preview Panel](#attaching-the-preview-panel)
- - [Content Security Policy Support](#content-security-policy-support)
- - [Preview Panel and Live Updates](#preview-panel-and-live-updates)
-- [Reference Implementations to Compare Against](#reference-implementations-to-compare-against)
+- [Scope and capabilities](#scope-and-capabilities)
+- [The integration flow](#the-integration-flow)
+- [1. Install and initialize with OptimizationRoot](#1-install-and-initialize-with-optimizationroot)
+ - [Access the SDK instance with hooks](#access-the-sdk-instance-with-hooks)
+ - [Provide a pre-built SDK instance when needed](#provide-a-pre-built-sdk-instance-when-needed)
+- [2. Handle consent in React](#2-handle-consent-in-react)
+ - [Reading consent state](#reading-consent-state)
+ - [Updating consent](#updating-consent)
+ - [Revoking consent](#revoking-consent)
+ - [Consent-gated rendering](#consent-gated-rendering)
+- [3. Personalize entries with OptimizedEntry](#3-personalize-entries-with-optimizedentry)
+ - [Basic usage](#basic-usage)
+ - [Loading fallback](#loading-fallback)
+ - [Direct ReactNode children](#direct-reactnode-children)
+ - [Wrapper element](#wrapper-element)
+ - [Nested composition](#nested-composition)
+ - [Imperative hook alternative](#imperative-hook-alternative)
+- [4. Track entry interactions from React](#4-track-entry-interactions-from-react)
+ - [Automatic tracking via OptimizedEntry](#automatic-tracking-via-optimizedentry)
+ - [Manual tracking via the tracking API](#manual-tracking-via-the-tracking-api)
+- [5. Emit page events with supported router adapters](#5-emit-page-events-with-supported-router-adapters)
+ - [React router](#react-router)
+ - [Next.js pages router](#nextjs-pages-router)
+ - [Next.js app router](#nextjs-app-router)
+ - [TanStack router](#tanstack-router)
+ - [Page payload enrichment](#page-payload-enrichment)
+- [Live updates](#live-updates)
+ - [Global live updates](#global-live-updates)
+ - [Per-component live updates](#per-component-live-updates)
+ - [Preview panel override](#preview-panel-override)
+ - [Resolution priority](#resolution-priority)
+- [Preview panel](#preview-panel)
+ - [Attaching the preview panel](#attaching-the-preview-panel)
+ - [Content security policy support](#content-security-policy-support)
+ - [Preview panel and live updates](#preview-panel-and-live-updates)
+- [Reference implementations to compare against](#reference-implementations-to-compare-against)
-## Scope and Capabilities
+## Scope and capabilities
The React Web SDK is the React-specific package in the Optimization SDK Suite. It lets consumers:
@@ -70,7 +70,7 @@ The React Web SDK is still browser-side and stateful because it sits on top of
`@contentful/optimization-web`. Your application still fetches Contentful entries, decides how
consent works, decides when a user becomes known, and controls where personalized content renders.
-## The Integration Flow
+## The integration flow
In practice, most React integrations follow this high-level sequence:
@@ -81,16 +81,16 @@ In practice, most React integrations follow this high-level sequence:
5. Enable automatic or manual entry interaction tracking where needed.
6. Add a router adapter so `page()` events follow client-side navigation.
-Optional additions include live updates when entries should continuously react to optimization state
-changes, and the preview panel when the application needs authoring or preview overrides.
+Optional additions include live updates when entries need to continuously react to optimization
+state changes, and the preview panel when the application needs authoring or preview overrides.
The React-focused reference implementations in this repository show those patterns in working
applications:
-- [React Web SDK Reference](../../implementations/react-web-sdk/README.md)
+- [React Web SDK reference](../../implementations/react-web-sdk/README.md)
- [Custom React Adapter Over Web SDK](../../implementations/web-sdk_react/README.md)
-## 1. Install and Initialize with OptimizationRoot
+## 1. Install and initialize with OptimizationRoot
Install the React Web SDK:
@@ -150,7 +150,7 @@ A more complete initialization with explicit API endpoints and interaction track
```
-### Access the SDK Instance with Hooks
+### Access the SDK instance with hooks
Inside the provider tree, use hooks to interact with the SDK:
@@ -177,7 +177,7 @@ function ConditionalComponent() {
}
```
-### Provide a Pre-Built SDK Instance When Needed
+### Provide a pre-built SDK instance when needed
If you need to manage the SDK instance outside of React, pass it directly via the `sdk` prop on
`OptimizationProvider` instead of using config props:
@@ -202,13 +202,13 @@ function App() {
When using the `sdk` prop, the provider does not own the instance lifecycle, so it will not call
`destroy()` on unmount.
-## 2. Handle Consent in React
+## 2. Handle consent in React
The SDK gates certain event types behind a consent state. By default, only `identify` and `page`
events are allowed before consent is explicitly set. All other event types are blocked until the
user accepts or rejects consent.
-### Reading Consent State
+### Reading consent state
Subscribe to consent state changes using the SDK's `states` observable:
@@ -234,7 +234,7 @@ function ConsentStatus() {
}
```
-### Updating Consent
+### Updating consent
Call `consent()` to accept or reject:
@@ -258,7 +258,7 @@ When consent is accepted (`true`), all event types are permitted and any auto-en
interaction trackers are started. When consent is rejected (`false`), auto-enabled interaction
trackers are disabled and non-allowed event types are blocked.
-### Revoking Consent
+### Revoking consent
To revoke consent after it was previously accepted:
@@ -274,7 +274,7 @@ function RevokeConsent() {
}
```
-### Consent-Gated Rendering
+### Consent-gated rendering
A common pattern is to gate personalization features on consent:
@@ -305,12 +305,12 @@ function PersonalizedSection({ entry }) {
}
```
-## 3. Personalize Entries with OptimizedEntry
+## 3. Personalize entries with OptimizedEntry
`OptimizedEntry` resolves a baseline Contentful entry to an optimized variant using the current
optimization state and renders the result.
-### Basic Usage
+### Basic usage
Pass a baseline entry fetched from Contentful (with `include: 10` to resolve linked optimization
data) and a render prop that receives the resolved entry:
@@ -335,13 +335,13 @@ function HeroSection({ baselineEntry }) {
The component automatically determines readiness:
- entries with optimization references (`nt_experiences`) render when `canOptimize` is `true`
-- entries without optimization references render as soon as the SDK is initialized
+- entries without optimization references render after the SDK is initialized
By default, `OptimizedEntry` locks to the first non-`undefined` optimization state it receives. This
means that once an entry resolves to a variant, it stays locked to that variant for the lifetime of
the component (unless live updates are enabled).
-### Loading Fallback
+### Loading fallback
When `loadingFallback` is provided, it renders while optimization state is unresolved:
@@ -358,7 +358,7 @@ When no `loadingFallback` is provided, a default loading UI wraps the baseline c
optimization readiness is available. Entries without optimization references skip loading and render
directly.
-### Direct ReactNode Children
+### Direct ReactNode children
`OptimizedEntry` also accepts direct `ReactNode` children when you do not need to read the resolved
entry:
@@ -372,7 +372,7 @@ entry:
In this case, the component still resolves the entry and emits tracking data attributes on the
wrapper, but the child content does not change based on the resolved variant.
-### Wrapper Element
+### Wrapper element
The wrapper element defaults to `div` with `display: contents` to remain layout-neutral. Use the
`as` prop to change it:
@@ -383,7 +383,7 @@ The wrapper element defaults to `div` with `display: contents` to remain layout-
```
-### Nested Composition
+### Nested composition
Nested optimized entries are supported through explicit composition:
@@ -402,7 +402,7 @@ Nested optimized entries are supported through explicit composition:
Nesting guard: nested wrappers with the same baseline entry ID as an ancestor are invalid and are
blocked at runtime. Nested wrappers with different baseline entry IDs remain supported.
-### Imperative Hook Alternative
+### Imperative hook alternative
When you need more control, use `useOptimizedEntry` directly:
@@ -440,12 +440,12 @@ function CustomEntry({ baselineEntry }) {
| `resolvedData` | `ResolvedData` | Full resolution result including entry and optimization |
| `selectedOptimizations` | `SelectedOptimizationArray \| undefined` | The locked (or live) selected optimizations snapshot |
-## 4. Track Entry Interactions from React
+## 4. Track entry interactions from React
`OptimizedEntry` emits data attributes on its wrapper element that the Web SDK uses for automatic
entry interaction tracking (views, clicks, and hovers).
-### Automatic Tracking via OptimizedEntry
+### Automatic tracking via OptimizedEntry
When resolved content renders, the wrapper element receives:
@@ -473,7 +473,7 @@ and tracked for entry views, clicks, and hovers without additional code.
When `loadingFallback` is shown, resolved-content tracking attributes are not emitted, so loading
states are not tracked.
-### Manual Tracking via the Tracking API
+### Manual tracking via the tracking API
For entries that are not rendered through `OptimizedEntry`, use the `interactionTracking` API from
`useOptimization()`:
@@ -524,7 +524,7 @@ Available interaction tracking methods:
Supported `interaction` values: `'views'`, `'clicks'`, `'hovers'`.
-## 5. Emit Page Events with Supported Router Adapters
+## 5. Emit page events with supported router adapters
The React Web SDK ships router-specific auto page trackers as isolated subpath exports. Each adapter
automatically emits `page()` events when the route changes, so you do not need to call `page()`
@@ -532,7 +532,7 @@ manually.
Mount the appropriate adapter once inside your provider tree.
-### React Router
+### React router
Requires `react-router-dom` >= 6.4 with a data router (`createBrowserRouter` + `RouterProvider`).
The adapter depends on `useMatches()`, so it does not work with a plain `BrowserRouter`.
@@ -569,7 +569,7 @@ function App() {
Emits on first render and on `pathname + search + hash` changes.
-### Next.js Pages Router
+### Next.js pages router
```tsx
import type { AppProps } from 'next/app'
@@ -589,7 +589,7 @@ export default function App({ Component, pageProps }: AppProps) {
Mount once in `pages/_app.tsx`. The adapter waits for `router.isReady`, emits on the first eligible
render and on route changes, and suppresses duplicate consecutive `router.asPath` values.
-### Next.js App Router
+### Next.js app router
```tsx
'use client'
@@ -611,7 +611,7 @@ Mount in a `'use client'` component inside your App Router provider tree, typica
`providers.tsx` wrapper used by `app/layout.tsx`. Emits on first render and on `pathname + search`
changes.
-### TanStack Router
+### TanStack router
```tsx
import { Outlet } from '@tanstack/react-router'
@@ -631,7 +631,7 @@ function RootLayout() {
Mount inside the TanStack router tree and inside the optimization provider tree, typically in your
root route component. Emits on first render and on TanStack Router `location.href` changes.
-### Page Payload Enrichment
+### Page payload enrichment
All router adapters support static and dynamic page payload enrichment:
@@ -659,19 +659,19 @@ All router adapters support static and dynamic page payload enrichment:
The `context` shape varies by adapter:
-| Adapter | Context Fields |
+| Adapter | Context fields |
| --------------- | -------------------------------------------------------------------------------- |
| React Router | `hash`, `location`, `matches`, `pathname`, `routeKey`, `search`, `url` |
| Next.js Pages | `asPath`, `pathname`, `query`, `routeKey`, `router` |
| Next.js App | `pathname`, `routeKey`, `router`, `search`, `searchParams`, `url` |
| TanStack Router | `hash`, `location`, `matches`, `pathname`, `routeKey`, `router`, `search`, `url` |
-## Live Updates
+## Live updates
Live updates control whether `OptimizedEntry` (and `useOptimizedEntry`) continuously reacts to
optimization state changes or locks to the first resolved state.
-### Global Live Updates
+### Global live updates
Set `liveUpdates` on `OptimizationRoot` to enable live updates for all `OptimizedEntry` components
that do not specify their own `liveUpdates` prop:
@@ -691,7 +691,7 @@ function App() {
}
```
-### Per-Component Live Updates
+### Per-component live updates
Override the global setting on individual `OptimizedEntry` components:
@@ -709,13 +709,13 @@ Override the global setting on individual `OptimizedEntry` components:
```
-### Preview Panel Override
+### Preview panel override
When the preview panel is open, live updates are forced on for all `OptimizedEntry` components
regardless of their `liveUpdates` prop or the global setting. This allows the preview panel to apply
variant overrides in real time.
-### Resolution Priority
+### Resolution priority
The effective live updates state is resolved in this order:
@@ -739,13 +739,13 @@ function LiveUpdatesStatus() {
}
```
-## Preview Panel
+## Preview panel
The preview panel is a Web Component-based micro-frontend that lets content authors override
optimization variant selections locally without modifying production state. It is distributed as a
separate package.
-### Attaching the Preview Panel
+### Attaching the preview panel
Install the preview panel package:
@@ -799,7 +799,7 @@ The preview panel is intentionally tightly coupled to Web SDK internals. It uses
preview bridges and state interceptors to read and mutate internal state for local preview
overrides. This coupling is deliberate and required for preview behavior parity.
-### Content Security Policy Support
+### Content security policy support
In environments with strict CSP policies, pass a nonce:
@@ -817,7 +817,7 @@ Alternatively, set the nonce on `window` before attaching:
window.litNonce = nonce
```
-### Preview Panel and Live Updates
+### Preview panel and live updates
When the preview panel is open, the `LiveUpdatesProvider` detects the `previewPanelOpen` state from
the SDK and forces live updates on for all `OptimizedEntry` components. This allows variant
@@ -835,7 +835,7 @@ function DebugPanel() {
}
```
-## Reference Implementations to Compare Against
+## Reference implementations to compare against
Two reference implementations demonstrate these patterns in working applications:
diff --git a/documentation/guides/integrating-the-web-sdk-in-a-web-app.md b/documentation/guides/integrating-the-web-sdk-in-a-web-app.md
index 206586a3..4abb0624 100644
--- a/documentation/guides/integrating-the-web-sdk-in-a-web-app.md
+++ b/documentation/guides/integrating-the-web-sdk-in-a-web-app.md
@@ -1,4 +1,4 @@
-# Integrating the Optimization Web SDK in a Web App
+# Integrating the Optimization Web SDK in a web app
Use this guide when you want to implement client-side personalization and analytics in a browser
application such as a static site, multi-page app, SPA, or custom frontend runtime using
@@ -12,29 +12,29 @@ providers, hooks, and router adapters, use the React Web guide instead.
Table of Contents
-- [Scope and Capabilities](#scope-and-capabilities)
-- [The Integration Flow](#the-integration-flow)
-- [1. Install and Initialize the SDK](#1-install-and-initialize-the-sdk)
-- [2. Handle Consent in the UI Layer](#2-handle-consent-in-the-ui-layer)
-- [3. Emit `page()` on First Load and Route Changes](#3-emit-page-on-first-load-and-route-changes)
-- [4. Resolve Contentful Entries with `selectedOptimizations`](#4-resolve-contentful-entries-with-selectedoptimizations)
-- [5. Resolve Merge Tags and Custom Flags](#5-resolve-merge-tags-and-custom-flags)
- - [Merge Tags](#merge-tags)
- - [Custom Flags](#custom-flags)
-- [6. Identify Known Users and Reset When Identity Changes](#6-identify-known-users-and-reset-when-identity-changes)
-- [7. Track Entry Interactions and Follow-Up Events](#7-track-entry-interactions-and-follow-up-events)
- - [Automatic Entry Tracking](#automatic-entry-tracking)
- - [Manual Element Observation](#manual-element-observation)
- - [Browser Tracking Mechanics](#browser-tracking-mechanics)
- - [Custom Browser Events](#custom-browser-events)
-- [8. Subscribe to `states` for Rerenders and UI Feedback](#8-subscribe-to-states-for-rerenders-and-ui-feedback)
-- [Share the Anonymous ID Cookie in Hybrid SSR + Browser Apps](#share-the-anonymous-id-cookie-in-hybrid-ssr-browser-apps)
-- [Reference Implementations to Compare Against](#reference-implementations-to-compare-against)
+- [Scope and capabilities](#scope-and-capabilities)
+- [The integration flow](#the-integration-flow)
+- [1. Install and initialize the SDK](#1-install-and-initialize-the-sdk)
+- [2. Handle consent in the UI layer](#2-handle-consent-in-the-ui-layer)
+- [3. Emit `page()` on first load and route changes](#3-emit-page-on-first-load-and-route-changes)
+- [4. Resolve Contentful entries with `selectedOptimizations`](#4-resolve-contentful-entries-with-selectedoptimizations)
+- [5. Resolve merge tags and custom flags](#5-resolve-merge-tags-and-custom-flags)
+ - [Merge tags](#merge-tags)
+ - [Custom flags](#custom-flags)
+- [6. Identify known users and reset when identity changes](#6-identify-known-users-and-reset-when-identity-changes)
+- [7. Track entry interactions and follow-up events](#7-track-entry-interactions-and-follow-up-events)
+ - [Automatic entry tracking](#automatic-entry-tracking)
+ - [Manual element observation](#manual-element-observation)
+ - [Browser tracking mechanics](#browser-tracking-mechanics)
+ - [Custom browser events](#custom-browser-events)
+- [8. Subscribe to `states` for rerenders and UI feedback](#8-subscribe-to-states-for-rerenders-and-ui-feedback)
+- [Share the anonymous ID cookie in hybrid SSR + browser apps](#share-the-anonymous-id-cookie-in-hybrid-ssr-browser-apps)
+- [Reference implementations to compare against](#reference-implementations-to-compare-against)
-## Scope and Capabilities
+## Scope and capabilities
The Web SDK is the browser-side package in the Optimization SDK Suite. It lets consumers:
@@ -56,7 +56,7 @@ The Web SDK also does not replace your Contentful delivery client. Your applicat
entries from Contentful, renders the DOM, decides how consent works, and decides when user identity
becomes known.
-## The Integration Flow
+## The integration flow
In practice, most Web SDK integrations follow this high-level sequence:
@@ -65,7 +65,7 @@ In practice, most Web SDK integrations follow this high-level sequence:
3. Emit `page()` on the first load and again whenever the active route changes.
4. Fetch baseline Contentful entries and resolve variants with `resolveOptimizedEntry()`.
5. Render flags and merge tags from current SDK state.
-6. Call `identify()` when the user becomes known, and `reset()` when identity should be discarded.
+6. Call `identify()` when the user becomes known, and `reset()` when identity must be discarded.
7. Enable automatic or manual entry tracking and send follow-up business events with `track()`,
`trackView()`, `trackClick()`, or `trackHover()`.
8. Subscribe to `states` so the UI rerenders when profile or optimization state changes.
@@ -77,7 +77,7 @@ applications:
- [Node SSR + Web SDK Vanilla](../../implementations/node-sdk+web-sdk/README.md)
- [Web SDK React](../../implementations/web-sdk_react/README.md)
-## 1. Install and Initialize the SDK
+## 1. Install and initialize the SDK
Install the package in your web application:
@@ -136,7 +136,7 @@ Notes:
- If you are not bundling JavaScript at all, the package README also shows direct UMD usage in a
plain HTML page.
-## 2. Handle Consent in the UI Layer
+## 2. Handle consent in the UI layer
The Web SDK exposes a browser-side `consent()` method, but your application still owns the consent
policy and user experience.
@@ -174,7 +174,7 @@ Important behavior:
If your policy requires a stricter pre-consent posture, configure `allowedEventTypes: []` during
initialization instead of relying on the default `['identify', 'page']`.
-## 3. Emit `page()` on First Load and Route Changes
+## 3. Emit `page()` on first load and route changes
In a traditional multi-page site, calling `page()` after initialization is usually enough because
the Web SDK can derive browser page properties such as URL, referrer, title, query parameters, and
@@ -220,13 +220,13 @@ router.onRouteChange(() => {
```
Replace `router.onRouteChange(...)` with whatever hook your framework exposes. The important rule is
-that the browser should emit a new `page()` event whenever the user lands on a different route-like
+that the browser emits a new `page()` event whenever the user lands on a different route-like
experience.
-## 4. Resolve Contentful Entries with `selectedOptimizations`
+## 4. Resolve Contentful entries with `selectedOptimizations`
-Once the page has been evaluated, fetch baseline Contentful entries the same way you normally would,
-then resolve each entry with `resolveOptimizedEntry()`.
+Once the page has been evaluated, fetch baseline Contentful entries the same way your application
+normally does, then resolve each entry with `resolveOptimizedEntry()`.
```ts
async function renderEntry(entryId: string, element: HTMLElement): Promise {
@@ -311,11 +311,11 @@ optimization.states.selectedOptimizations.subscribe((selectedOptimizations) => {
> view-model state. Otherwise, a rerender can accidentally try to resolve a previously selected
> variant as though it were the baseline entry.
-## 5. Resolve Merge Tags and Custom Flags
+## 5. Resolve merge tags and custom flags
The Web SDK also exposes helpers for profile-aware merge tags and Custom Flags.
-### Merge Tags
+### Merge tags
If a Rich Text field contains merge-tag entries, resolve them against current SDK state while
rendering the field:
@@ -339,7 +339,7 @@ const html = documentToHtmlString(article.fields.body, {
That is the same basic pattern used in the reference implementations, even when the final Rich Text
renderer differs.
-### Custom Flags
+### Custom flags
Use `getFlag()` when the current optimization response contains Custom Flag changes:
@@ -358,7 +358,7 @@ optimization.states.flag('new-navigation').subscribe((value) => {
Unlike the stateless Node SDK, the stateful Web SDK automatically emits flag-view tracking when you
read a flag via `getFlag()` or `states.flag(name)`.
-## 6. Identify Known Users and Reset When Identity Changes
+## 6. Identify known users and reset when identity changes
Call `identify()` when the browser session becomes associated with a known user, such as after a
sign-in, account lookup, or persisted auth refresh:
@@ -378,7 +378,7 @@ async function handleLogin(user: { id: string; plan: string }): Promise {
That lets the browser stitch the current anonymous profile to a known identity and update profile
state for later entry resolution, flags, and event attribution.
-When the app should discard the current browser identity, call `reset()`:
+To discard the current browser identity, call `reset()`:
```ts
async function handleLogout(): Promise {
@@ -393,17 +393,17 @@ That is the same shape used in the vanilla reference implementation. `reset()` c
ID cookie, cached profile data, cached flag changes, selected optimizations, and entry-tracking
runtime state. It does not clear consent.
-## 7. Track Entry Interactions and Follow-Up Events
+## 7. Track entry interactions and follow-up events
The Web SDK can emit more than page and identify events. Common browser-side cases are:
- automatic entry `view`, `click`, and `hover` tracking from the DOM
- manual `trackView()` calls for UI regions that are not directly tied to a Contentful entry
- `track()` calls for business events such as quote requests or sign-up milestones
-- `trackClick()` and `trackHover()` calls when the app has custom interaction logic that should not
+- `trackClick()` and `trackHover()` calls when the app has custom interaction logic that must not
rely on DOM auto-detection
-### Automatic Entry Tracking
+### Automatic entry tracking
If you enable `autoTrackEntryInteraction`, add the standard `data-ctfl-*` attributes to the rendered
element that contains the resolved entry content:
@@ -424,7 +424,7 @@ For click tracking, prefer semantic clickable elements such as `