Skip to content

feat(docs): generate a Fern-ready docs bundle and sync it to contentful-docs [NT-3942] - #442

Merged
Tim Beyer (TimBeyer) merged 13 commits into
mainfrom
feat/fern-docs-sync-nt-3942
Sep 11, 2026
Merged

feat(docs): generate a Fern-ready docs bundle and sync it to contentful-docs [NT-3942]#442
Tim Beyer (TimBeyer) merged 13 commits into
mainfrom
feat/fern-docs-sync-nt-3942

Conversation

@TimBeyer

@TimBeyer Tim Beyer (TimBeyer) commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Closes NT-3942.

Today the Optimization SDK documentation is hand-copied into contentful/contentful-docs: links rewritten, pages restructured, slugs renamed by hand. This replaces that step with a deterministic transform, and fixes the authoring instructions that caused the divergence in the first place.

Why

The manual process has measurably drifted. On the live site right now:

  • 52 internal links point at 12 slugs that do not exist. Worst: integrate-the-node-sdk-in-a-node-app (×15 — the real slug is …into-a-node-app) and integrate-the-optimization-android-sdk-in-a-compose-app (×9 — the nav declares integrate-the-android-sdk-in-a-compose-app).
  • 13 malformed GitHub URLs where a find-replace ate a slash: …/main/implementationsreact-native-sdk/README.md.
  • Deep links are broken by appending the trailing slash after the fragment: …/entry-personalization-and-variant-resolution#single-locale-cda-entry-contract/.
  • Severity was flattened — all three GitHub alert types became <Info>, so the one real WARNING (native SDKs are in beta) lost it.
  • Two pages ship an empty description, and one slug is a typo (builde-a-custom-…).

It is also lossy, which is the part worth flagging: isEmptyVariant appears 9 times and slugField 7 times in these guides, and zero times on the live site. The Node SDK's slug-based fetching and empty-variant handling are documented here but missing in public, even though both landed days before the last manual sync.

What this adds

Three layers, so the first two need no credentials and can be iterated on locally:

Command Does
pnpm docs:fern Builds pages, the nav block, and redirects into a gitignored fern-bundle/
pnpm docs:fern:apply -- --docs-repo <path> Splices a bundle into a local contentful-docs checkout
pnpm fern:check Runs the same pipeline without writing; gates CI

The transform owns structure, links, and metadata, and never prose — the wording in documentation/ stays the source of truth, and a transform that rewrote sentences would make the two copies impossible to diff.

fern:check is what makes the link rot unrepeatable: every cross-document link must resolve to a published page, every #fragment must match a real heading, no page may link into authoring/ or internal/, the MDX must be safe, and a slug cannot move without a recorded redirect. Every line-based pass is fence-aware (reusing headingsOf from the existing knowledge validators), because a Swift #if DEBUG and a JSX <h1> both appear inside fenced blocks here and neither is prose.

The instruction fix

The gerund titles were not an accident — recipes/integration.md mandated the H1 form literally, and the decision, migration, and supplemental recipes stated no title form at all. STYLE_GUIDE.md covered imperative mood for instructions and procedure steps, but nothing about document titles.

So the rule now has one home in STYLE_GUIDE.md (task documents take imperative titles, explanatory documents descriptive noun phrases, neither takes a gerund), all four recipes defer to it, and concepts/AGENTS.md states the counterpart. Then applied: 23 headings and 152 link texts. The published title is now the heading itself, with no second key to drift from it.

Same class of gap, found on the way: concepts used three different names for one closing section with nothing governing it. Normalized to ## Related documentation and the rule written down.

Verification

Applied end to end against a real contentful-docs checkout, then reset:

  • fern check0 errors (3 warnings, all pre-existing in unrelated experience-orchestration/ pages)
  • check-orphan-pages — 490/490 registered · check-redirects — 409 valid · markdownlint — 0 errors
  • Broken internal links: 52 → 0

In this repo: fern:check, guides:check, knowledge:check, eslint, prettier, tsc all clean.

Reviewing this

Six commits, each one concern, meant to be read in order. docs(docs): give guides imperative titles… is the large one but its diff is uniform and mechanical.

Not in this PR, and needing a human

The golden diff (generated bundle vs the 35 live pages) leaves 29 items across 14 pages that need editorial judgement, not code. The ones that matter are 9 where the site has content this repo does not — they would be destroyed by a sync unless back-ported first:

  • <Info> callouts the writers added to build-a-custom-…, choose-a-nextjs-migration-path, and consent-management
  • a forwardedSemanticInteractions code block and a consent bullet list in the forwarding guide

Two judgement calls rather than clear wins: the site dropped a definitional clause from states.selectedOptimizations, and its core-state-management intro reads better than this repo's. Separately, the site renamed ## Related guides and conceptsRelated documentation, which conflicts with what supplemental-recipe.md mandates — I left the recipe's version standing, since that is an editorial call.

Before this can run

  • A FERN_DOCS_SYNC_TOKEN secret scoped to contentful-docs (contents: write, pull-requests: write). A GitHub App installation token is preferable to a long-lived PAT. A PR from a branch in that repo also gets the Fern preview URL; a fork PR cannot, because forks cannot read secrets.
  • Likely an ADR in contentful-docs: its .bito/guidelines say "do not add SDK generation workflows", and CODEOWNERS requires review from both technical-writing and developer-experience.
  • Sidebar order changes. Order now comes from the README children: lists, which are reader-routing order (Choose the right SDK → Node → Web → React → Next.js). The live site is alphabetical. Deliberate, and worth a look before merge.

Decision record

docs/ADRs/0002-generate-the-public-documentation-site-content-from-this-repository.md records this
decision in the practice 4d2e37f3 introduced, including the alternatives rejected along the way and
the three that were tried first and then reversed: an explicit fern.title, deriving slugs from
headings, and holding site metadata in a single manifest.

It also states two consequences that outlive this PR: pages under
fern/docs/pages/personalization/optimization-sdk/ become generated output, and contentful-docs
accepting generated content from another repository is a change to how that repository works, which
is its maintainers' decision to record rather than ours.

Rebase notes

Rebased onto main at 32 commits (2026-08-31). One conflict, in
react-native-sdk-interaction-tracking-mechanics.md: ddca28de replaced the untagged code fence my
first commit was tagging with a table, because the interaction model became start/end only. Main's
version wins and that part of the commit is now moot. Everything else applied clean.

Because most of the 19 overlapping docs auto-merged, I re-swept the whole published set afterward for
content main could have introduced past the merge — gerund titles or link text, Related heading
variants, frontmatter drifted from its heading, missing fern: blocks, non-canonical fences, MDX
hazards. All zero. guides:check still agrees blueprint-to-guide across the 9 blueprints main
changed, and knowledge:check now covers 1511 source pointers (was 1424).

A correction to the numbers above. They previously read 53 links across 13 slugs. The real
figure is 52 across 12: my measurement script mis-associated slugs for the four nav entries that
order their keys page:/slug:/path: instead of page:/path:/slug:, and over-counted by one.
The generated nav emits a fixed key order, which is why the corrected and original parsers agree on
this PR's output but disagree on the hand-maintained file.

A second manual sync landed meanwhile (3eca89b, contentful-docs #214, 2026-08-19) carrying the
Android, iOS, and React Native updates across by hand. It changed no slugs, so the lock and redirect
here are unaffected. It also moved none of the drift metrics: still 52 broken links, 35 gerund
titles or link texts, 16 malformed GitHub URLs, 7 anchors broken by a trailing slash — before and
after. The manual process is neither fixing nor worsening this on its own.

🤖 Generated with Claude Code

@wiz-inc-38d59fb8d7

wiz-inc-38d59fb8d7 Bot commented Aug 18, 2026

Copy link
Copy Markdown

Wiz Scan Summary

Scanner Findings
Vulnerability Finding Vulnerabilities -
Data Finding Sensitive Data -
Secret Finding Secrets -
IaC Misconfiguration IaC Misconfigurations -
SAST Finding SAST Findings 1 Medium
Software Management Finding Software Management Findings -
Total 1 Medium

View scan details in Wiz

To detect these findings earlier in the dev lifecycle, try the Wiz Code extension for VS Code, JetBrains, or Visual Studio.

@bito-code-review

bito-code-review Bot commented Aug 18, 2026

Copy link
Copy Markdown

Functional Validation by Bito

SourceRequirement / Code AreaStatusNotes
NT-3942Adjust the SDK documentation pipeline to produce Fern-compatible output with correctly restructured organization and links.✅ MetThe PR adds a deterministic Fern documentation pipeline in scripts/export-fern-docs.ts, scripts/fern/build.ts, scripts/fern/bundle.ts, scripts/fern/docs.ts, and scripts/fern/transform.ts. It adds Fern metadata, restructuring, navigation generation, slug locking, redirect handling, MDX conversion, and cross-document link and anchor validation across the published documentation in documentation/. The pipeline is exposed through package.json as `docs:fern` and `fern:check`, replacing the manual rewriting process with validated Fern-compatible output.
NT-3942Enable the generated Fern documentation updates to be submitted directly when an SDK release occurs.✅ MetThe release-triggered workflow in .github/workflows/sync-fern-docs.yaml runs on published releases, generates the Fern bundle with `pnpm docs:fern`, applies it to the contentful/contentful-docs checkout through scripts/apply-fern-docs.ts, and pushes or updates a pull request containing the generated documentation. The workflow is release-gated, idempotent, and uses a scoped short-lived credential for direct cross-repository submission.

@bito-code-review

bito-code-review Bot commented Aug 18, 2026

Copy link
Copy Markdown

Impact Analysis by Bito

Cross-Repository Impact Analysis
What Changed Impact of Change Suggested Review Actions
A generated Fern documentation bundle and an apply step were added for synchronizing this repository into a separate contentful-docs checkout. - verify-needed: contentful-docs checkout and its Fern CI/publishing workflow CONSUMES the generated fern-bundle artifacts: The new apply script writes fern/docs/pages/personalization/optimization-sdk, fern/products/personalization.yml, and fern/docs.yml in an external checkout, but the dependency graph lookup could not resolve repository 'optimization' and no consumer call-site was verified. - Verify the target contentful-docs repository name and inspect its Fern validation and publishing workflow.
- Run the apply script in a disposable checkout and confirm that only the generated navigation, redirects, and Optimization SDK pages are modified.
Documentation URLs, navigation labels, and redirect generation are now derived from Fern metadata and an append-only slug lock. - verify-needed: published documentation consumers and bookmarks may depend on existing Optimization SDK URLs: Changing titles and slugs can alter navigation and generated URLs; redirect resolution is intended to preserve old URLs but must be validated against the published site's redirect format. - Compare generated redirects with the currently published URL set.
- Test chained slug changes, dangling redirects, and redirect cycles before merging.
Code Paths Analyzed

Impact:
This change converts the documentation repository into the source of truth for a generated Fern publication bundle. It adds frontmatter and publication metadata across concepts and guides, validates document structure and links, transforms Markdown into MDX, generates navigation and redirects, and introduces local export, validation, and cross-checkout apply commands.

Flow:
Markdown source and group README ordering → scripts/fern/docs.ts discovery/frontmatter parsing → scripts/fern/transform.ts Markdown-to-MDX transformation and safety checks → scripts/fern/bundle.ts navigation and redirect rendering → scripts/fern/build.ts shared pipeline → export-fern-docs.ts bundle output or validate-fern-export.ts validation → apply-fern-docs.ts writes a contentful-docs checkout.

Direct Changes (Diff Files):
• .github/workflows/main-pipeline.yaml [workflow diff] — Integrates documentation/Fern validation into the main pipeline.
• .github/workflows/sync-fern-docs.yaml [workflow diff] — Adds or updates automated Fern documentation synchronization.
• .gitignore [file diff] — Ignores generated Fern bundle output.
• STYLE_GUIDE.md [file diff] — Documents style requirements relevant to generated documentation.
• docs/ADRs/0002-generate-the-public-documentation-site-content-from-this-repository.md [file diff] — Records the documentation publication architecture decision.
• documentation/AGENTS.md [file diff] — Adds or updates authoring constraints used by validators.
• documentation/authoring/recipes/decision.md [file diff] — Updates documentation authoring guidance.
• documentation/authoring/recipes/integration.md [file diff] — Updates documentation authoring guidance.
• documentation/authoring/recipes/migration.md [file diff] — Updates documentation authoring guidance.
• documentation/authoring/recipes/supplemental-recipe.md [file diff] — Updates documentation authoring guidance.
• documentation/concepts/AGENTS.md [file diff] — Adds concept-document authoring constraints.
• documentation/concepts/android-sdk-runtime-and-interaction-mechanics.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/concepts/consent-management-in-the-optimization-sdk-suite.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/concepts/core-state-management.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/concepts/entry-personalization-and-variant-resolution.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/concepts/interaction-tracking-in-web-sdks.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/concepts/ios-sdk-runtime-and-interaction-mechanics.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/concepts/locale-handling-in-the-optimization-sdk-suite.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/concepts/optimization-handoff-and-cache-safe-rendering.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/concepts/profile-synchronization-between-client-and-server.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/fern-slugs.lock.json [file diff] — Tracks current slugs and historical redirects.
• documentation/guides/README.md [file diff] — Defines guide ordering and publication metadata.
• documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md [file diff] — Adds Fern metadata and updates title/link wording.
• documentation/guides/choosing-the-right-sdk.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md [file diff] — Adds Fern metadata and updates title/link wording.
• documentation/guides/integrating-the-node-sdk-in-a-node-app.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/integrating-the-optimization-android-sdk-in-a-compose-app.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/integrating-the-react-native-sdk-in-a-react-native-app.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/integrating-the-react-web-sdk-in-a-react-app.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/integrating-the-web-sdk-in-a-web-app.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/migrating-experience-js-contentful-model-to-optimization.md [file diff] — Adds Fern metadata and updates title/link wording.
• documentation/guides/migrating-experience-js-next-to-nextjs-app-router.md [file diff] — Adds Fern metadata and updates title/link wording.
• documentation/guides/migrating-experience-js-next-to-nextjs-pages-router.md [file diff] — Adds Fern metadata and updates title/link wording.
• documentation/guides/migrating-experience-js-node-ssr-and-esr.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/migrating-experience-js-plugins-and-preview.md [file diff] — Adds Fern metadata and updates title/link wording.
• documentation/guides/migrating-experience-js-react-to-react-web.md [file diff] — Adds Fern metadata and updates title/link wording.
• documentation/guides/migrating-experience-js-to-the-web-sdk.md [file diff] — Adds Fern metadata and updates title/link wording.
• documentation/guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md [file diff] — Adds Fern metadata and publication-ready content.
• documentation/guides/using-contentful-graphql-data-with-the-optimization-sdks.md [file diff] — Adds Fern metadata and publication-ready content.
• package.json [file diff] — Adds documentation export, validation, and apply commands.
• scripts/apply-fern-docs.ts [1-194] — Safely applies generated pages, navigation, and redirects to a contentful-docs checkout.
• scripts/export-fern-docs.ts [1-130] — Builds and writes the local Fern bundle.
• scripts/fern/build.ts [1-144] — Provides the shared discovery, transformation, validation, and rendering pipeline.
• scripts/fern/bundle.ts [1-201] — Renders navigation and append-only slug redirects.
• scripts/fern/docs.ts [1-approximately 350] — Discovers documentation, parses frontmatter, validates metadata, and preserves authored ordering.
• scripts/fern/transform.ts [1-approximately 200] — Transforms Markdown into Fern-compatible MDX and checks unsafe markup.
• scripts/sdk-knowledge/markdown.ts [6-approximately 70] — Adds shared fence masking and heading-anchor generation.
• scripts/validate-fern-export.ts [1-44] — Adds a read-only Fern export validation command.
• scripts/validate-guide-authoring.ts [file diff] — Reuses the shared heading-anchor implementation.

Repository Impact:
Documentation authoring: All published concept and guide documents now require Fern-compatible metadata and stricter frontmatter/title/link conventions.
Build and validation tooling: The new shared pipeline validates slugs, anchors, cross-document links, fenced code languages, and MDX safety.
Release and CI automation: Main-pipeline and sync workflows now govern documentation export and publication artifacts.
Generated output ownership: The apply step intentionally owns only the Optimization SDK navigation section, generated pages, and generated redirects.

Cross-Repository Dependencies:
contentful-docs: The apply script writes generated pages and configuration into a separate documentation checkout.

Database/Caching Impact:
• None

API Contract Violations:
• No removed public code symbols, HTTP routes, schemas, or dependency versions were identified in the diff.
• Published documentation slugs, navigation entries, and redirects are externally visible URL contracts; incorrect lock reconciliation or redirect rendering could create broken links.
• The title and navigation-label renames must remain consistent with generated Fern metadata and internal links.

Infrastructure Dependencies:
• GitHub Actions must provide the expected checkout paths, Node/pnpm environment, and permissions for the sync workflow.
• The apply command requires a valid contentful-docs checkout containing fern/docs/pages/personalization/optimization-sdk, fern/products/personalization.yml, and fern/docs.yml.
• fern-bundle is generated local state and must not be treated as a source-controlled publication artifact.
• No database, runtime service, or application environment variable changes are present.

Additional Insights:
Redirect safety: Slug history is append-only and explicitly detects dangling destinations and cycles; these checks are central to preserving existing published URLs.
MDX safety: Raw HTML-like tags outside fences are rejected unless they map to approved Fern alert components, reducing accidental JSX parsing.
Code-fence handling: The shared fenceMask prevents Swift preprocessor directives, shell comments, and JSX examples inside code blocks from being interpreted as document structure.
Potential parser limitations: The new frontmatter parser intentionally supports a narrow scalar/folded-block syntax; documents using YAML quoting, arrays, or additional frontmatter forms may fail and should be covered by validation tests.

Testing Recommendations

Frontend Impact:
• Run Fern preview or equivalent rendered-site checks for the generated navigation hierarchy, page titles, sidebar labels, and all renamed guide links.
• Verify that generated page paths remain under /personalization/optimization-sdk and that the Overview entry is preserved as writer-owned content.

Service Integration:
• Run pnpm fern:check -- --ref main against the complete documentation tree.
• Run pnpm docs:fern and then pnpm docs:fern:apply -- --docs-repo &lt;disposable-contentful-docs-checkout&gt; --dry-run; verify the reported page and removal counts.
• Execute the sync workflow in a test branch and run the target contentful-docs repository's fern check, orphan-page check, redirect check, and docs build.

Data Serialization:
• Parse generated nav-block.yaml, redirects.yaml, and manifest.json with the same tooling used by contentful-docs.
• Test frontmatter with plain scalars, folded descriptions, quoted values, missing required fern.slug, invalid sections, unsupported fern.title, and mismatched authored title/H1.
• Test slug-lock reconciliation for first publication, one-step rename, chained renames, dangling redirects, duplicate redirects, and cycles.

Privacy Compliance:
• Confirm that generated documentation and manifest files contain no credentials, tokens, environment values, or unintended checkout-local absolute paths.
• Review the sync workflow permissions and ensure the apply step cannot write outside the supplied contentful-docs checkout.

Backward Compatibility:
• Build a redirect fixture from every changed slug and verify old published URLs resolve to current destinations.
• Compare generated navigation and page counts with the previous published site, excluding intentionally renamed labels.
• Verify that existing authoring validation still reports the same anchor semantics after replacing the local slug helper with headingAnchor.

OAuth Functionality:
• None

Cross-Service Communication:
• Validate the complete source-to-contentful-docs flow: documentation Markdown → Fern bundle → apply checkout → Fern build.
• Confirm that the generated redirect base /developers/docs/personalization/optimization-sdk matches the deployed site's actual instance base path.
• Check that the sync workflow's git ref passed to export-fern-docs.ts produces links resolving to the intended repository revision.

Reliability Testing:
• None

Additional Insights:
• Add unit tests for fenceMask and headingsOf covering opening/closing fences, fence delimiter lines, unclosed fences, and headings resembling Swift, shell, or JSX syntax.
• Test transformDoc with GitHub alerts, unsupported raw HTML, inline code containing angle brackets, links with fragments, tables, and canonical versus aliased fence languages.
• Run the repository's typecheck, lint, and existing guide-authoring tests after the new scripts are added.
• Check failure behavior for missing --docs-repo, unknown CLI arguments, missing target checkout files, malformed lock JSON, and write failures.

Analysis based on known dependency patterns and edges. Actual impact may vary.

@bito-code-review bito-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Agent Run #94402c

Actionable Suggestions - 7
  • scripts/apply-fern-docs.ts - 1
    • Empty-string bundle path silently misroutes · Line 52-52
  • .github/workflows/sync-fern-docs.yaml - 1
    • Insufficient permissions declaration · Line 38-39
  • scripts/fern/build.ts - 2
    • Slug validation bypassed when acceptSlugChanges is true · Line 78-92
    • Extract duplicated sorting and logging pattern · Line 125-125
  • documentation/authoring/recipes/supplemental-recipe.md - 1
  • scripts/fern/transform.ts - 2
Additional Suggestions - 3
  • scripts/fern/transform.ts - 1
    • Mdx safety line-number mismatch · Line 364-370
      In `checkMdxSafety`, `index` inside the `forEach` over `withoutCode.split('\n')` is the position in the fence-stripped, code-stripped joined string — not the original `lines` array index. Error messages will report wrong line numbers to authors. Also: add unit tests for this function covering fenced blocks, inline code spans, and allowed/denied tags.
  • scripts/sdk-knowledge/markdown.ts - 1
    • Misleading variable name for array · Line 30-30
      The variable `inFence` holds a `boolean[]` (a fence mask), but the name implies a single `boolean` flag. This naming is consistent across all 5 call sites in this file and `transform.ts`, making the mismatch easy to miss. A descriptive name like `fenceFlags` or `fenceStatus` signals the array type.
  • documentation/guides/building-a-custom-javascript-optimization-adapter.md - 1
    • Duplicate description text · Line 6-8
      The `description` field in the frontmatter (lines 6-8) is identical to the body text at lines 13-14. Remove the redundant frontmatter field to avoid maintenance divergence.
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • scripts/fern/build.ts - 1
    • reconcileLock exception escapes reportFatal · Line 94-94
  • documentation/guides/migrating-experience-js-node-ssr-and-esr.md - 1
  • documentation/concepts/consent-management-in-the-optimization-sdk-suite.md - 1
Review Details
  • Files reviewed - 55 · Commit Range: 5f34a84..a4f3547
    • .github/workflows/main-pipeline.yaml
    • .github/workflows/sync-fern-docs.yaml
    • .gitignore
    • STYLE_GUIDE.md
    • documentation/AGENTS.md
    • documentation/authoring/recipes/decision.md
    • documentation/authoring/recipes/integration.md
    • documentation/authoring/recipes/migration.md
    • documentation/authoring/recipes/supplemental-recipe.md
    • documentation/concepts/AGENTS.md
    • documentation/concepts/android-sdk-runtime-and-interaction-mechanics.md
    • documentation/concepts/consent-management-in-the-optimization-sdk-suite.md
    • documentation/concepts/core-state-management.md
    • documentation/concepts/entry-personalization-and-variant-resolution.md
    • documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md
    • documentation/concepts/interaction-tracking-in-web-sdks.md
    • documentation/concepts/ios-sdk-runtime-and-interaction-mechanics.md
    • documentation/concepts/locale-handling-in-the-optimization-sdk-suite.md
    • documentation/concepts/optimization-handoff-and-cache-safe-rendering.md
    • documentation/concepts/profile-synchronization-between-client-and-server.md
    • documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md
    • documentation/fern-slugs.lock.json
    • documentation/guides/README.md
    • documentation/guides/building-a-custom-javascript-optimization-adapter.md
    • documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md
    • documentation/guides/choosing-the-right-sdk.md
    • documentation/guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md
    • documentation/guides/integrating-the-node-sdk-in-a-node-app.md
    • documentation/guides/integrating-the-optimization-android-sdk-in-a-compose-app.md
    • documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md
    • documentation/guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md
    • documentation/guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md
    • documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md
    • documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md
    • documentation/guides/integrating-the-react-native-sdk-in-a-react-native-app.md
    • documentation/guides/integrating-the-react-web-sdk-in-a-react-app.md
    • documentation/guides/integrating-the-web-sdk-in-a-web-app.md
    • documentation/guides/migrating-experience-js-contentful-model-to-optimization.md
    • documentation/guides/migrating-experience-js-next-to-nextjs-app-router.md
    • documentation/guides/migrating-experience-js-next-to-nextjs-pages-router.md
    • documentation/guides/migrating-experience-js-node-ssr-and-esr.md
    • documentation/guides/migrating-experience-js-plugins-and-preview.md
    • documentation/guides/migrating-experience-js-react-to-react-web.md
    • documentation/guides/migrating-experience-js-to-the-web-sdk.md
    • documentation/guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md
    • documentation/guides/using-contentful-graphql-data-with-the-optimization-sdks.md
    • package.json
    • scripts/apply-fern-docs.ts
    • scripts/export-fern-docs.ts
    • scripts/fern/build.ts
    • scripts/fern/bundle.ts
    • scripts/fern/docs.ts
    • scripts/fern/transform.ts
    • scripts/sdk-knowledge/markdown.ts
    • scripts/validate-fern-export.ts
  • Files skipped - 0
  • Tools
    • GITHUB_ADVISORY (Security Vulnerability) - ✔︎ Successful
    • OWASP (Security Vulnerability) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Default Agent You can customize the agent settings here or contact your Bito workspace admin at jared.jolton@contentful.com.

Documentation & Help

AI Code Review powered by Bito Logo

Comment thread scripts/apply-fern-docs.ts
Comment thread .github/workflows/sync-fern-docs.yaml
Comment thread scripts/fern/build.ts
Comment thread scripts/fern/build.ts
Comment thread documentation/authoring/recipes/supplemental-recipe.md
Comment thread scripts/fern/transform.ts
Comment thread scripts/fern/transform.ts
@bito-code-review

Copy link
Copy Markdown

✅ Review Settings Overridden

Status: Overridden Successfully

Guidelines:

  • Accepted:

    • General : Review Posture, Repo Truth And Boundaries, Domain Invariants

    • Language : typescript= TypeScript Review Evidence

Note: Extra guidelines beyond 3 general purpose guidelines and 1 language specific guideline per language are not processed. Guidelines are fetched from the source branch.

@TimBeyer
Tim Beyer (TimBeyer) force-pushed the feat/fern-docs-sync-nt-3942 branch from a4f3547 to f68566c Compare August 18, 2026 15:23
@bito-code-review

bito-code-review Bot commented Aug 18, 2026

Copy link
Copy Markdown

Code Review Agent Run #7c72f9

Actionable Suggestions - 0
Additional Suggestions - 7
  • documentation/guides/integrating-the-optimization-android-sdk-in-a-compose-app.md - 1
    • CWE-N: Frontmatter field inconsistency across guides · Line 5-5
      The `navTitle` field is inconsistent with all other integration guides in this repository (Views, iOS SwiftUI, React Native, React Web), which omit this field entirely. Fern infers the nav title from the H1 heading by default, so explicit `navTitle` is redundant. Removing it maintains API consistency and reduces maintenance surface.
  • scripts/export-fern-docs.ts - 1
    • Magic number without invariant · Line 23-23
      Line 106 divides by this constant. While the value is correct today (each redirect in `renderRedirects` produces a `source` line and a `destination` line), an accidental future edit to `renderRedirects` that adds or removes a line per entry would silently corrupt the count without any test catching it. A self-documenting constant or an inline assertion would make the invariant explicit.
  • documentation/guides/using-contentful-graphql-data-with-the-optimization-sdks.md - 1
    • Naming mismatch vs peer guides · Line 11-11
      The H1 starts with 'Use', but every other integration guide in this guides section (e.g., 'Integrate the Optimization React Web SDK in a React app') uses 'Integrate' as the heading verb. Update to 'Integrate' for naming consistency across related guides.
  • documentation/guides/migrating-experience-js-to-the-web-sdk.md - 1
    • Duplicate description text · Line 10-13
      The description on lines 12–13 is an exact duplicate of the frontmatter `description` field. Remove the redundant paragraph to eliminate divergence risk when one copy is updated without the other.
  • documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md - 1
    • Duplicate link in related docs · Line 592-593
      The 'Related documentation' section now contains a duplicate entry. The guide `../guides/integrating-the-react-native-sdk-in-a-react-native-app.md` is already referenced at line 18 under 'For step-by-step setup, see'. Remove line 592 to eliminate redundancy and maintenance risk.
  • scripts/fern/bundle.ts - 1
    • Unreachable code for Migration guides · Line 134-149
      The loop body for `"Migration guides"` is unreachable: `docsInSection(docs, 'Migration guides')` always returns `[]` because `loadPublishedDocs` only reads from `guides/` and `concepts/` (no migration directory exists), and `inSection.length === 0` on line 136 causes an immediate `continue` on every iteration for this section.
  • scripts/validate-fern-export.ts - 1
    • Misleading anchor count metric · Line 34-34
      The variable `anchors` counts the total number of anchor strings available on each page (`doc.anchors.size` summed), but the message describes it as 'anchors resolvable' — implying fragment references from other docs were resolved against these anchors. The name and message are misleading: `anchors` is a static set populated during doc parsing (what headings exist), not a count of cross-doc link resolutions. Rename to `totalAnchors` and clarify the message.
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • documentation/guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md - 1
  • documentation/guides/building-a-custom-javascript-optimization-adapter.md - 1
Review Details
  • Files reviewed - 55 · Commit Range: 19e974c..f68566c
    • .github/workflows/main-pipeline.yaml
    • .github/workflows/sync-fern-docs.yaml
    • .gitignore
    • STYLE_GUIDE.md
    • documentation/AGENTS.md
    • documentation/authoring/recipes/decision.md
    • documentation/authoring/recipes/integration.md
    • documentation/authoring/recipes/migration.md
    • documentation/authoring/recipes/supplemental-recipe.md
    • documentation/concepts/AGENTS.md
    • documentation/concepts/android-sdk-runtime-and-interaction-mechanics.md
    • documentation/concepts/consent-management-in-the-optimization-sdk-suite.md
    • documentation/concepts/core-state-management.md
    • documentation/concepts/entry-personalization-and-variant-resolution.md
    • documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md
    • documentation/concepts/interaction-tracking-in-web-sdks.md
    • documentation/concepts/ios-sdk-runtime-and-interaction-mechanics.md
    • documentation/concepts/locale-handling-in-the-optimization-sdk-suite.md
    • documentation/concepts/optimization-handoff-and-cache-safe-rendering.md
    • documentation/concepts/profile-synchronization-between-client-and-server.md
    • documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md
    • documentation/fern-slugs.lock.json
    • documentation/guides/README.md
    • documentation/guides/building-a-custom-javascript-optimization-adapter.md
    • documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md
    • documentation/guides/choosing-the-right-sdk.md
    • documentation/guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md
    • documentation/guides/integrating-the-node-sdk-in-a-node-app.md
    • documentation/guides/integrating-the-optimization-android-sdk-in-a-compose-app.md
    • documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md
    • documentation/guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md
    • documentation/guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md
    • documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md
    • documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md
    • documentation/guides/integrating-the-react-native-sdk-in-a-react-native-app.md
    • documentation/guides/integrating-the-react-web-sdk-in-a-react-app.md
    • documentation/guides/integrating-the-web-sdk-in-a-web-app.md
    • documentation/guides/migrating-experience-js-contentful-model-to-optimization.md
    • documentation/guides/migrating-experience-js-next-to-nextjs-app-router.md
    • documentation/guides/migrating-experience-js-next-to-nextjs-pages-router.md
    • documentation/guides/migrating-experience-js-node-ssr-and-esr.md
    • documentation/guides/migrating-experience-js-plugins-and-preview.md
    • documentation/guides/migrating-experience-js-react-to-react-web.md
    • documentation/guides/migrating-experience-js-to-the-web-sdk.md
    • documentation/guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md
    • documentation/guides/using-contentful-graphql-data-with-the-optimization-sdks.md
    • package.json
    • scripts/apply-fern-docs.ts
    • scripts/export-fern-docs.ts
    • scripts/fern/build.ts
    • scripts/fern/bundle.ts
    • scripts/fern/docs.ts
    • scripts/fern/transform.ts
    • scripts/sdk-knowledge/markdown.ts
    • scripts/validate-fern-export.ts
  • Files skipped - 0
  • Tools
    • GITHUB_ADVISORY (Security Vulnerability) - ✔︎ Successful
    • OWASP (Security Vulnerability) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Default Agent You can customize the agent settings here or contact your Bito workspace admin at jared.jolton@contentful.com.

Documentation & Help

AI Code Review powered by Bito Logo

bito-code-review[bot]
bito-code-review Bot previously approved these changes Aug 26, 2026
@phobetron

Copy link
Copy Markdown
Collaborator

Reviewed the non-threaded Bito output against the current head and repository authoring rules. No code changes are warranted for those suggestions:

  • The Android Compose navTitle intentionally shortens “Jetpack Compose” to “Compose”, which is the documented reason to supply navTitle.
  • REDIRECT_LINES is already a named constant with a comment stating the two-line invariant.
  • The GraphQL document is a supplemental recipe, not an SDK integration guide; “Use” is the correct imperative for its reader task.
  • Fern description metadata is required, while the opening paragraph separately orients the reader. Removing either copy would violate the publishing or guide-authoring contract.
  • Links that appear once during initial routing and again in the closing related-documentation section serve different reader moments; they are not duplicate list entries.
  • Migration guides live under documentation/guides/ and select section: Migration guides in frontmatter, so the migration navigation branch is reachable.
  • The anchor count describes headings available for fragment resolution. Cross-document fragment references are validated separately in the same build, so the status wording does not misrepresent a missing check.
  • inFence is consistently used as the per-line fence predicate array, with its boolean[] type and indexed access making that meaning explicit.
  • Unexpected non-DocError exceptions are deliberately rethrown with their stack rather than reformatted as authored-document diagnostics.
  • The filtered iOS, custom-adapter, Node-migration, and consent-document link observations either match their targets or intentionally repeat routing at the opening and closing navigation points.

The MDX line-number observation is also non-actionable here. Fence lines retain their positions; other intentional transformations already make these transformed-document diagnostics rather than a guaranteed source map. Unsafe markup is still rejected and generated output is unaffected. Exact authored-source locations would require provenance across every transform, not either suggested local rewrite.

The generated impact summaries also contain factual mismatches: there is no scripts/fern/nav.ts, no package source was changed, the workflow explicitly integrates with the separate contentful-docs repository, and alerts render as Info or Warning components rather than a Callout component. The workflow is currently manual-dispatch only, so its release trigger is described in source as a future additive step rather than functionality already present.

All seven inline findings have evidence replies on their originating threads and have been resolved directly.

Tim Beyer (TimBeyer) and others added 6 commits August 31, 2026 16:07
The Compose and React Web integration guides ended with `</content>` and `</invoke>`
tags left behind by an authoring agent. They render as literal text on GitHub and would
break MDX compilation once these guides publish to the documentation site, so the tech
writers were stripping them by hand on every copy.

Also tag two code fences that were unlabelled or used a non-canonical alias, so every
fence in the published set carries a language the site recognises.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The authoring pipeline was producing gerund titles ("Integrating the...") because it was
told to. `recipes/integration.md` mandated the H1 form literally, and the decision,
migration, and supplemental recipes stated no title form at all, so authors defaulted to
the same shape. STYLE_GUIDE.md covered imperative mood for instructions and for
procedure steps, but said nothing about the title of a document.

Give the rule one home in STYLE_GUIDE.md: a task document takes an imperative title
naming the task, an explanatory document takes a descriptive noun phrase, and neither
takes a gerund. All four recipes now defer to it instead of restating or omitting it,
and the concepts guide states the noun-phrase counterpart so the two archetypes cannot
drift into each other.

Two related rules the pipeline was also missing: a document title is its published page
title and sidebar label, so cross-document link text naming it must change with it; and
a concept closes with `## Related documentation` under exactly that name, which had
three spellings across eleven files with nothing governing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tion name

Add the `fern:` frontmatter block each concept needs to publish: slug, sidebar section,
and meta description. The published page title is the document's `#` heading, so there
is no title key here and nothing for the title to drift from.

Update the link text that names a guide, which is now imperative, and rename the closing
section to `## Related documentation` in the four concepts that spelled it `Related docs`
or `Related docs and reference implementations`. The TOC entries and the five in-page
anchors pointing at the old headings move with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Apply the title rule to the 23 guides that carried gerund headings: 23 headings and the
link text that names them across the guide set. The four guides with an authored
frontmatter `title` have it realigned to their heading, which `documentation/AGENTS.md`
already required.

Add the `fern:` block each guide needs to publish: slug, sidebar section, and meta
description, plus `navTitle` on the two guides whose sidebar label is deliberately
shorter than their page title. Frontmatter and heading land together because in the
nineteen guides that had no frontmatter they are adjacent lines in the same diff hunk.

Slugs are recorded rather than derived. Rewording a heading changes the published title,
which is correct; it must never silently move a live URL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replace the hand-copy step into contentful-docs with a deterministic transform. It owns
structure, links, and metadata, and never prose: the wording in `documentation/` stays
the single source of truth, and a transform that rewrote sentences would make the two
copies impossible to diff.

Three layers, so the first two need no credentials and can be iterated on locally:

- `pnpm docs:fern` builds the pages, the navigation block, and the redirects into a
  gitignored `fern-bundle/`.
- `pnpm docs:fern:apply -- --docs-repo <path>` splices that bundle into a local
  contentful-docs checkout, so the output can be checked with that repo's own gates
  before any cross-repo automation is involved. Navigation and redirects are spliced,
  never regenerated, because that file also holds hand-maintained Personalization
  content this repo does not own.
- `pnpm fern:check` runs the same pipeline without writing, and is what makes the link
  rot the manual process accumulated impossible to reintroduce: every cross-document
  link must resolve to a published page, every `#fragment` must match a real heading,
  no page may link into `authoring/` or `internal/`, the MDX must be safe, and a slug
  may not move without a recorded redirect.

Every line-based pass is fence-aware, reusing `headingsOf` from the existing knowledge
validators, because a Swift `#if DEBUG` and a JSX `<h1>` both appear inside fenced
blocks in these guides and neither is prose.

Slugs live in a lock file. A changed slug needs `--update-lock`, which appends a
permanent redirect; old URLs have to keep resolving, so that list accumulates and is
never pruned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Run `pnpm fern:check` in the knowledge-check job, and add
`documentation/concepts/**` to the filter that triggers it. Concepts were absent from
that filter, so a concepts-only change ran no documentation validation at all.

Add `sync-fern-docs.yaml` to open the pull request against contentful-docs. Manual
dispatch only for now, but built so a release trigger is additive rather than a rewrite:
ref-parameterized, idempotent against one fixed branch and one upserted pull request,
and concurrency-guarded. That last part matters because merging the grouped release pull
request creates one GitHub release per component, so `on: release` fires several times
for a single release moment and those firings have to converge on one pull request.

Follows the constraints contentful-docs sets for its own workflows: no `${{ }}`
interpolation inside a `run:` block, and no third-party actions beyond the SHA-pinned
ones this repo already uses. Needs a `FERN_DOCS_SYNC_TOKEN` secret scoped to that repo;
a GitHub App installation token is preferable to a long-lived PAT.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The repository gained an architecture decision record practice in 4d2e37f, and
generating the public documentation site content from this repository is
architecture-significant: it introduces a cross-repository integration, turns pages in
contentful-docs into generated output, and changes who owns the wording on the published
site.

Records the decision, and the alternatives that were considered and rejected along the
way, including the ones that were tried first and then reversed: an explicit
`fern.title`, deriving slugs from headings, holding site metadata in a single manifest,
putting the transform in contentful-docs, and publishing on every documentation merge
rather than on release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@bito-code-review bito-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Agent Run #492cb5

Actionable Suggestions - 3
  • scripts/export-fern-docs.ts - 1
  • scripts/apply-fern-docs.ts - 1
    • Nav splice may remove non-list content · Line 90-90
  • scripts/fern/bundle.ts - 1
    • Redirect cycle causes infinite loop · Line 48-59
Additional Suggestions - 10
  • scripts/export-fern-docs.ts - 1
    • CWE-22: Destructive Path Deletion · Line 75-76
      `--out` is resolved via `path.resolve(rootDir, options.outDir)` and then `rmSync(outDir, { recursive: true, force: true })` deletes it. Passing `--out .` or `--out ..` resolves to `rootDir` or a parent, recursively deleting the whole repo before the bundle is written. Guard that `outDir` is a strict subdirectory of `rootDir` before the destructive call. ([CWE-22](https://cwe.mitre.org/data/definitions/22.html))
  • .github/workflows/sync-fern-docs.yaml - 1
    • Draft input ignored on update · Line 133-135
      The `draft` input is only honored on the initial `gh pr create`. On re-runs — the designed convergence path — `gh pr edit` (line 134) never passes `--draft`/`--no-draft`, so the input is silently ignored and the existing PR keeps its prior draft state. Consider applying the flag on the update branch too.
  • documentation/AGENTS.md - 1
    • Inaccurate publish command · Line 26-27
      `pnpm docs:fern` (`scripts/export-fern-docs.ts`) only writes a local bundle to `fern-bundle/`; it does not publish. Applying the bundle to a `contentful-docs` checkout is the separate `pnpm docs:fern:apply` step (`scripts/apply-fern-docs.ts`, "Layer 2 of the sync"). A contributor following this would believe they published when they only built locally.
  • documentation/concepts/android-sdk-runtime-and-interaction-mechanics.md - 1
    • Doc link text inconsistency · Line 21-21
      The link text "XML Views app" doesn't match the target guide's H1, which is "Integrate the Optimization Android SDK in an Android Views app". The repo's dominant convention for this guide (`core-state-management.md`, `locale-handling-in-the-optimization-sdk-suite.md`, `choosing-the-right-sdk.md`) uses "Android Views app". Consider aligning both occurrences (line 21 and 343) for consistency.
  • documentation/concepts/entry-personalization-and-variant-resolution.md - 2
    • Link label mismatch · Line 710-711
      These two rewritten labels drop the "Optimization" prefix that the other nine links in this list and the target guide titles include. `integrating-the-react-native-sdk-in-a-react-native-app.md` and `integrating-the-node-sdk-in-a-node-app.md` both title themselves "Integrate the Optimization ... SDK". Align the labels for consistency.
    • Link label mismatch · Line 716-716
      The rewritten label says "XML Views app", but the target `integrating-the-optimization-android-sdk-in-a-views-app.md` titles itself "Integrate the Optimization Android SDK in an Android Views app". The modifier no longer matches the page it links to.
  • scripts/sdk-knowledge/markdown.ts - 1
    • Anchor logic duplicated · Line 46-55
      `headingAnchor` duplicates the anchor logic already present as `slug` in `scripts/validate-guide-authoring.ts` (line 78), and the two diverge: `## [Quick Start](quickstart.md)` yields `quick-start` here but `-quick-startquickstartmd` in `slug`, and `_` is stripped here but kept there. Both scripts validate heading anchors for links, so this is a divergence risk. Consider sharing one implementation.
  • documentation/authoring/recipes/decision.md - 1
    • Line exceeds 100-char limit · Line 21-21
      Line 21 is 121 characters, exceeding the repo's `MD013` limit of 100 set in `.markdownlint.yaml`. Reflow the prose so no line exceeds 100 chars (e.g. put the two example titles on their own line).
  • documentation/concepts/consent-management-in-the-optimization-sdk-suite.md - 1
    • Link text hyphenation mismatch · Line 554-554
      The link text here reads "tag management tools", but the target page title is "Forward Optimization SDK context to analytics and tag-management tools" (hyphenated), and the sibling file `interaction-tracking-in-web-sdks.md:535` uses the hyphenated form. Align the text for consistency.
  • documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md - 1
    • Link text/title mismatch · Line 976-976
      The new link text "tag management tools" doesn't match the target guide's H1, which is "Forward Optimization SDK context to analytics and tag-management tools" (hyphenated). Since this PR's intent is to align link text with titles, update the text to "tag-management tools" for consistency.
Review Details
  • Files reviewed - 55 · Commit Range: 10cd407..871b417
    • .github/workflows/main-pipeline.yaml
    • .github/workflows/sync-fern-docs.yaml
    • .gitignore
    • STYLE_GUIDE.md
    • documentation/AGENTS.md
    • documentation/authoring/recipes/decision.md
    • documentation/authoring/recipes/integration.md
    • documentation/authoring/recipes/migration.md
    • documentation/authoring/recipes/supplemental-recipe.md
    • documentation/concepts/AGENTS.md
    • documentation/concepts/android-sdk-runtime-and-interaction-mechanics.md
    • documentation/concepts/consent-management-in-the-optimization-sdk-suite.md
    • documentation/concepts/core-state-management.md
    • documentation/concepts/entry-personalization-and-variant-resolution.md
    • documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md
    • documentation/concepts/interaction-tracking-in-web-sdks.md
    • documentation/concepts/ios-sdk-runtime-and-interaction-mechanics.md
    • documentation/concepts/locale-handling-in-the-optimization-sdk-suite.md
    • documentation/concepts/optimization-handoff-and-cache-safe-rendering.md
    • documentation/concepts/profile-synchronization-between-client-and-server.md
    • documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md
    • documentation/fern-slugs.lock.json
    • documentation/guides/README.md
    • documentation/guides/building-a-custom-javascript-optimization-adapter.md
    • documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md
    • documentation/guides/choosing-the-right-sdk.md
    • documentation/guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md
    • documentation/guides/integrating-the-node-sdk-in-a-node-app.md
    • documentation/guides/integrating-the-optimization-android-sdk-in-a-compose-app.md
    • documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md
    • documentation/guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md
    • documentation/guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md
    • documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md
    • documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md
    • documentation/guides/integrating-the-react-native-sdk-in-a-react-native-app.md
    • documentation/guides/integrating-the-react-web-sdk-in-a-react-app.md
    • documentation/guides/integrating-the-web-sdk-in-a-web-app.md
    • documentation/guides/migrating-experience-js-contentful-model-to-optimization.md
    • documentation/guides/migrating-experience-js-next-to-nextjs-app-router.md
    • documentation/guides/migrating-experience-js-next-to-nextjs-pages-router.md
    • documentation/guides/migrating-experience-js-node-ssr-and-esr.md
    • documentation/guides/migrating-experience-js-plugins-and-preview.md
    • documentation/guides/migrating-experience-js-react-to-react-web.md
    • documentation/guides/migrating-experience-js-to-the-web-sdk.md
    • documentation/guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md
    • documentation/guides/using-contentful-graphql-data-with-the-optimization-sdks.md
    • package.json
    • scripts/apply-fern-docs.ts
    • scripts/export-fern-docs.ts
    • scripts/fern/build.ts
    • scripts/fern/bundle.ts
    • scripts/fern/docs.ts
    • scripts/fern/transform.ts
    • scripts/sdk-knowledge/markdown.ts
    • scripts/validate-fern-export.ts
  • Files skipped - 0
  • Tools
    • OWASP (Security Vulnerability) - ✔︎ Successful
    • GITHUB_ADVISORY (Security Vulnerability) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Default Agent You can customize the agent settings here or contact your Bito workspace admin at jared.jolton@contentful.com.

Documentation & Help

AI Code Review powered by Bito Logo

Comment thread scripts/export-fern-docs.ts Outdated
Comment thread scripts/apply-fern-docs.ts Outdated
Comment thread scripts/fern/bundle.ts
@bito-code-review

bito-code-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

Code Review Agent Run #8b234f

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 871b417..6f05b26
    • docs/ADRs/0002-generate-the-public-documentation-site-content-from-this-repository.md
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Default Agent You can customize the agent settings here or contact your Bito workspace admin at jared.jolton@contentful.com.

Documentation & Help

AI Code Review powered by Bito Logo

@phobetron

Charles Hudson (phobetron) commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

I (Codex) reviewed the two latest automated review runs against the current head (6f05b26). The later run examined only the new ADR and reported no findings. The full run reported 13 items, representing 12 unique claims because the unsafe-output issue was listed twice.

Three findings are valid, realistic, and worth addressing before merge:

  1. Unsafe output-directory deletion: pnpm docs:fern -- --out . or --out .. can pass the repository or its parent to recursive rmSync. The destination should be required to be a strict subdirectory of the repository before any mutation. The proposed guard is incomplete because it still permits arbitrary outside directories.
  2. Navigation splicing can remove non-owned YAML: the live contentful-docs layout is safe today because another navigation list item follows the generated section, but an ordinary same-indent separator comment or a valid trailing tabs:/landing-page: property would be consumed by the splice. The first nonblank line at the section's indentation or less should terminate the owned span, including comments.
  3. Slug restoration can create redirect cycles: a documented A -> B change followed by a later B -> A restoration persists both redirects. The downstream contentful-docs redirect check should normally block the sync PR, so a blocked publication is the expected first consequence rather than an immediate live loop. Removing only the direct reverse edge is insufficient for longer histories such as A -> B -> C -> A; the redirect graph should be validated and current slugs treated as terminal nodes.

The following suggestions are also valid, but lower priority:

  • documentation/AGENTS.md describes pnpm docs:fern as publishing, although it only builds the local bundle; applying it and opening the cross-repository PR are separate layers.
  • The seven flagged title-style link labels should match their destination titles: three XML Views app labels, the React Native and Node labels missing Optimization, and two tag management tools labels missing the title's hyphen.
  • headingAnchor and the local slug helper can disagree for supported headings containing inline links or repeated whitespace, potentially making fern:check and guides:check disagree. No current heading triggers this. The review's underscore example is incorrect, but the underlying divergence is real.
  • documentation/authoring/recipes/decision.md:21 is 121 characters against the checked-in MD013 limit of 100. Markdownlint is not currently run by repository CI, so this is a local/manual lint issue rather than a failing gate.

I do not consider the draft-on-update suggestion valid. The input says "Open the pull request as a draft," making it creation-scoped. Preserving the existing PR state on reruns avoids silently re-drafting a PR after a maintainer marks it ready; gh pr edit also cannot toggle draft state.

All current CI checks are green, but they do not exercise the three failure histories above.

Tim Beyer (TimBeyer) and others added 2 commits September 1, 2026 10:40
Three failure histories from review that the current tests do not reach.

**A caller-supplied output path could delete the working tree.** `--out` resolved against the
repository root and went straight into a recursive delete, so `--out .` or `--out ..` removed the
repository or its parent. Requiring the path to sit inside the repository does not fix this: `--out
documentation` is inside it and would delete the authored docs. Nothing consumed a configurable
location — not the sync workflow, not the apply step, not once in practice — so the option is gone
and the bundle directory is a fixed path this script owns. Cleanup now removes the members a
previous build wrote rather than the directory, so no recursive delete takes a caller's input at
all. To compare two builds, copy the directory aside between runs.

**Navigation splicing could delete content this repo does not own.** The owned span ended at the
next sibling `- ` list item, so a comment banner at the section's indentation, or a trailing
top-level key such as `tabs:` or `landing-page:`, fell inside the replaced range. With the
Optimization SDK section last in the list, that consumed everything to end of file — 127 lines in a
reproduction. The span now ends at the first nonblank line indented no deeper than the section,
whatever that line is.

**A restored slug pointed the live page away from itself.** The recorded history is append-only, so
moving `A` to `B` and later back to `A` kept both edges and emitted both, which is a redirect loop
and, worse, sends the published `A` somewhere else. Published slugs are now terminal: a slug that is
live again is a page, not a redirect source, so its recorded move is skipped. Longer histories
collapse the same way — `A -> B -> C -> A` publishes `B -> A` and `C -> A` — rather than special-
casing the direct reverse edge. A history that never reaches a published slug now fails
`pnpm fern:check` instead of silently emitting nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
**One anchor implementation, and the correct one.** `headingAnchor` collapsed runs of whitespace to a
single hyphen while the validator's own local helper replaced each whitespace character. They
disagreed on 87 headings in the knowledge base and authoring templates. The local helper was right:
dropping the punctuation from `## Render / entry resolution` leaves two adjacent spaces, and 415
authored links spell that anchor `#render--entry-resolution`. `headingAnchor` now matches, keeps its
handling of inline code, links, and emphasis that the local helper lacked, and is the single
implementation both `pnpm fern:check` and `pnpm guides:check` use. The published set was unaffected
either way, so the anchor count is unchanged.

**Link labels that read as titles now match one.** Twelve labels named a document but not by its
title: `XML Views app` for a guide titled `Android Views app`, Node and React Native labels missing
`Optimization`, and `tag management tools` missing the title's hyphen. The style guide already asks
for the exact document title when a label names one; a shortened contextual phrase stays fine, but a
label that looks like a full title should be one.

Also: `documentation/AGENTS.md` described `pnpm docs:fern` as publishing, when it only builds the
local bundle — applying it and opening the cross-repository pull request are separate steps. And one
over-long line in the decision recipe is rewrapped to the checked-in 100-column limit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bito-code-review

bito-code-review Bot commented Sep 1, 2026

Copy link
Copy Markdown

Code Review Agent Run #2c0f1c

Actionable Suggestions - 0
Additional Suggestions - 3
  • scripts/fern/build.ts - 1
    • Redundant O(n²) redirect resolution · Line 105-105
      `resolveRedirects(reconciled)` is now computed twice per build: here at line 105 and again inside `renderRedirects(reconciled)` at line 113 (bundle.ts:142). Since `resolveRedirects` is O(n²) (nested `lock.redirects.find`), this doubles the work on every `docs:fern`/`fern:check` run. Resolve once and pass the entries to the render step.
  • scripts/export-fern-docs.ts - 1
    • Misleading comment · Line 22-23
      The comment claims "Nothing consumes a configurable location: `docs:fern:apply` reads from here", but `apply-fern-docs.ts` still exposes a `--bundle` option (lines 49-52) that overrides the bundle directory. This inaccuracy could mislead a maintainer into removing `--bundle` as dead code. Consider acknowledging the override.
  • scripts/fern/bundle.ts - 1
    • O(n²) redirect walk · Line 104-104
      `lock.redirects.find(...)` is an O(n) linear scan executed inside the nested chain-walk loop, once per hop per distinct `from`. Since `redirects` is append-only and never pruned (per the file header), this is O(n²) overall and grows with the lock. Build a `Map` from `from`→`to` once before the loop and use `nextByFrom.get(current)` for O(1) hops.
Review Details
  • Files reviewed - 17 · Commit Range: 6f05b26..2bddaab
    • scripts/apply-fern-docs.ts
    • scripts/export-fern-docs.ts
    • scripts/fern/build.ts
    • scripts/fern/bundle.ts
    • documentation/AGENTS.md
    • documentation/authoring/recipes/decision.md
    • documentation/concepts/android-sdk-runtime-and-interaction-mechanics.md
    • documentation/concepts/consent-management-in-the-optimization-sdk-suite.md
    • documentation/concepts/entry-personalization-and-variant-resolution.md
    • documentation/concepts/locale-handling-in-the-optimization-sdk-suite.md
    • documentation/concepts/profile-synchronization-between-client-and-server.md
    • documentation/guides/integrating-the-optimization-android-sdk-in-a-compose-app.md
    • documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md
    • documentation/guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md
    • documentation/guides/using-contentful-graphql-data-with-the-optimization-sdks.md
    • scripts/sdk-knowledge/markdown.ts
    • scripts/validate-guide-authoring.ts
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Default Agent You can customize the agent settings here or contact your Bito workspace admin at jared.jolton@contentful.com.

Documentation & Help

AI Code Review powered by Bito Logo

bito-code-review[bot]
bito-code-review Bot previously approved these changes Sep 9, 2026
Comment thread .github/workflows/sync-fern-docs.yaml Fixed
@bito-code-review

bito-code-review Bot commented Sep 11, 2026

Copy link
Copy Markdown

Code Review Agent Run #a0e967

Actionable Suggestions - 0
Additional Suggestions - 1
  • .github/workflows/sync-fern-docs.yaml - 1
    • Fail-open delivery default · Line 90-94
      The `else` branch sets `deliver=true` for any event that isn't `workflow_dispatch`. Today only `release` and `workflow_dispatch` are triggers, so it's correct — but if a future trigger (e.g. `push`, `schedule`) is added, this would silently push to `contentful-docs`. Consider defaulting to `deliver=false` and enabling delivery only for `release`.
Review Details
  • Files reviewed - 3 · Commit Range: 2bddaab..ff88ad1
    • .github/workflows/sync-fern-docs.yaml
    • docs/ADRs/0002-generate-the-public-documentation-site-content-from-this-repository.md
    • documentation/AGENTS.md
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Default Agent You can customize the agent settings here or contact your Bito workspace admin at jared.jolton@contentful.com.

Documentation & Help

AI Code Review powered by Bito Logo

…ed secret

Replaces `secrets.FERN_DOCS_SYNC_TOKEN` with the `cross-repository-write` Vault
preset, which issues a one-hour `contentful-cross-repository` GitHub App token
scoped to contentful-docs by immutable repository ID (`contents: write`,
`pull_requests: write`). The grant is target-owned, so contentful-docs decides
who may request it and nothing here can widen that.

The preset authenticates only for a default-branch push, a pull request, a tag
push, or a published release, so dispatch-only delivery is no longer possible.
The two triggers now do different work:

  * release published — export, then deliver the pull request
  * workflow_dispatch — export and validate, publishing the bundle as an
    artifact so a manual run still shows the exact pages, nav block, and
    redirects a release would deliver

That keeps the manual path credential-free rather than dropping it, which is
where nearly all iteration happens anyway.

Also drops the hand-rolled `contentful-optimization-bot` git identity: the
action configures `contentful-cross-repository[bot]` globally with the App's
real user ID, so commits are attributable to the App. The `draft` input is
removed because only a release delivers now, and a release publishes a
reviewable pull request.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ADR 0002 recorded a GitHub App token as merely preferred over a long-lived one
and named manual dispatch as the escape hatch for out-of-band publishes. Both
are now settled differently: the credential is decided, and the preset issues no
token for `workflow_dispatch`, so that escape hatch does not exist.

Records the long-lived token as a considered-and-discarded alternative, and
replaces the credential consequence with what delivery actually depends on —
infrastructure in three other repositories, and no on-demand publish.

Notes in the authoring contract that publication is release-gated, so an author
knows an edit reaches the site at the next release rather than on merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `cross-repository-write` preset originally issued no token for
`workflow_dispatch`. Narrowing this workflow to fit that was the wrong way
around, because manual publication is a requirement, so the Vault grant was
extended to cover it instead: contentful/cf-vault#2436 adds a
`workflow-dispatch` role per grant and contentful/vault-github-actions#26 maps
the event onto it.

Restores dispatch to a full delivery trigger and drops the export-only dry run
and its bundle artifact, so both triggers now do the same work and differ only
in the Vault role the preset selects.

Pins the action past v1.5.0, which was tagged a minute before that mapping
merged and therefore still rejects a manual run.

The dispatch role's subject is pinned to this repository's default branch, so a
manual run must be launched from `main`. That does not limit what can be
published, since launching from `main` and passing `ref` exports any other tree.
The workflow comment says so, because a run launched from a feature branch fails
Vault authentication rather than doing something visible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The sync commit message and pull request title carried a hardcoded ticket key.
Every future sync would have cited the ticket that built the pipeline rather
than anything about the content being published, so it is removed with nothing
in its place.

Also records why `types: [published]` is not a free choice. The preset requires
`github.event.action == 'published'`, so adding `created` or `released` would
produce runs that fail Vault authentication.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@bito-code-review bito-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Agent Run #609738

Actionable Suggestions - 4
  • scripts/fern/transform.ts - 3
  • scripts/fern/bundle.ts - 1
    • Stale redirect entry breaks chain resolution · Line 102-107
Additional Suggestions - 13
  • scripts/fern/bundle.ts - 1
    • First-edge redirect resolution · Line 104-104
      `lock.redirects.find(...)` returns only the first entry per `from`. Since the lock is append-only and a slug can revisit a prior value (e.g. `A->B->A->C` yields `A->B, B->A, A->C`), the chain loops back to `A` and reports a false cycle, dropping the valid `A->C` redirect. The docstring claims multi-step histories are handled, but that only holds when the revisited slug is live (skipped at line 93). Consider resolving against the full history graph.
  • scripts/apply-fern-docs.ts - 1
    • Partial redirect span handling · Line 116-131
      `spliceRedirects` only replaces the span when both markers exist and `finish > begin`. If the target `docs.yml` has a partial span (e.g. `REDIRECT_BEGIN` present but `REDIRECT_END` missing, from a hand-edit or interrupted write), it falls through to the `redirects:` key insertion and writes a second generated block while leaving the orphaned marker, yielding duplicate/conflicting redirects. Consider filtering out orphaned markers before inserting.
  • scripts/fern/transform.ts - 1
    • Reference links not rewritten · Line 316-316
      The docstring on line 303 says this rewrites "every inline and reference link," but the regex `/\]\(([^)\s]+)\)/gu` only matches inline `[text](url)` links. Reference-style links (`[text][ref]` and `[ref]: url`) are left untouched, so any such links in `documentation/` would not be converted to site paths and would break on the published site. If reference links are used, extend the rewrite; otherwise fix the comment.
  • scripts/validate-fern-export.ts - 1
    • Silent arg fallback false-green · Line 16-22
      `parseRef` silently ignores unknown/misspelled args and a `--ref` with no value, falling back to `DEFAULT_REF` while still reporting success — a false green. The sibling `export-fern-docs.ts` `parseArgs` throws on unknown arguments; make this validator consistent so a typo like `--refx main` fails instead of validating the wrong ref.
  • documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md - 1
    • Copied misleading description · Line 6-8
      The added `description` (lines 6-8) is a verbatim copy of the one in `entry-personalization-and-variant-resolution.md`, which describes Contentful variant resolution — not this document's topic. This misrepresents the page in search/listing surfaces. Rewrite it to describe Node/stateless interaction tracking and Web SDK browser tracking.
  • documentation/AGENTS.md - 1
    • Doc contradicts build code · Line 35-35
      The claim 'There is no `fern.title`' is inaccurate: `scripts/fern/transform.ts` writes `title: ${yamlScalar(doc.fern.title)}` to the published frontmatter (line 107). `fern.title` exists in the build pipeline — it is derived from the `#` heading, not authored. Suggest rewording to 'There is no authored `fern.title`; the build derives `title` from the heading.'
  • documentation/guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md - 1
    • Doc link label mismatch · Line 691-692
      The two Android link labels don't match their target pages' H1 titles. `integrating-the-optimization-android-sdk-in-a-compose-app.md` is titled "...in a Jetpack Compose app" and `...-in-a-views-app.md` is titled "...in an Android Views app". Align the labels so readers aren't misled about the target content.
  • scripts/export-fern-docs.ts - 1
    • CLI arg value swallowed · Line 44-46
      If `--ref` is followed by another flag (e.g. `pnpm docs:fern --ref --update-lock`), the flag token is silently consumed as the ref value, so `ref` becomes `--update-lock` and the flag is never applied. The bundle is then pinned to a bogus ref with no error. Consider rejecting a missing or `--`-prefixed value after `--ref`.
  • STYLE_GUIDE.md - 2
    • Dangling antecedent from insertion · Line 67-69
      The new list-counting rule (67-69) was inserted between the time-bound-words guidance and its exception, so 'This principle' (line 71) now most directly refers to the list-counting rule — but the time-stamped-docs exception does not apply to list counting. Reposition the new rule after line 72, or make the exception's subject explicit.
    • Line length exceeds MD013 · Line 96-104
      Added lines 96, 99, and 104 are 105, 105, and 101 characters, exceeding the repo's configured markdownlint MD013 `line_length: 100` (`.markdownlint.yaml`) and prettier `printWidth: 100`. Since `proseWrap: preserve` won't auto-wrap, these will persist. Rewrap to stay within the documented limit.
  • scripts/fern/build.ts - 1
    • Incomplete lock validation · Line 50-55
      `readLock` validates only the top-level object, but `slugs ?? {}` and `redirects ?? []` pass through any non-nullish value unchanged. A malformed lock (e.g. `slugs` as a string, or `redirects` as a string) flows into `reconcileLock`/`resolveRedirects`, where `{ ...lock.slugs }` spreads a string into char keys and `lock.redirects.map` throws a TypeError. Consider validating the nested shapes so a bad lock fails with a clear message.
  • documentation/fern-slugs.lock.json - 1
    • Spurious redirect entry · Line 38-43
      This redirect's `from` slug (`integrate-the-android-sdk-in-a-compose-app`) never existed in repo history — the compose guide's slug has always been `integrate-the-optimization-android-sdk-in-a-compose-app` (verified at `aaabd691` and `935ecdda`). Per `reconcileLock` in `scripts/fern/bundle.ts`, redirects are only appended when a slug actually changes. This entry publishes a pointless redirect from a URL that never existed; consider removing it.
  • documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md - 1
    • Missing navTitle frontmatter · Line 1-8
      The new `fern` frontmatter omits `navTitle`, while the direct counterpart `integrating-the-optimization-android-sdk-in-a-compose-app.md` includes `navTitle: Integrate the Optimization Android SDK in a Compose app`. These two Android guides are siblings; adding `navTitle` keeps navigation titles consistent.
Review Details
  • Files reviewed - 57 · Commit Range: 10cd407..5401011
    • .github/workflows/main-pipeline.yaml
    • .github/workflows/sync-fern-docs.yaml
    • .gitignore
    • STYLE_GUIDE.md
    • docs/ADRs/0002-generate-the-public-documentation-site-content-from-this-repository.md
    • documentation/AGENTS.md
    • documentation/authoring/recipes/decision.md
    • documentation/authoring/recipes/integration.md
    • documentation/authoring/recipes/migration.md
    • documentation/authoring/recipes/supplemental-recipe.md
    • documentation/concepts/AGENTS.md
    • documentation/concepts/android-sdk-runtime-and-interaction-mechanics.md
    • documentation/concepts/consent-management-in-the-optimization-sdk-suite.md
    • documentation/concepts/core-state-management.md
    • documentation/concepts/entry-personalization-and-variant-resolution.md
    • documentation/concepts/interaction-tracking-in-node-and-stateless-environments.md
    • documentation/concepts/interaction-tracking-in-web-sdks.md
    • documentation/concepts/ios-sdk-runtime-and-interaction-mechanics.md
    • documentation/concepts/locale-handling-in-the-optimization-sdk-suite.md
    • documentation/concepts/optimization-handoff-and-cache-safe-rendering.md
    • documentation/concepts/profile-synchronization-between-client-and-server.md
    • documentation/concepts/react-native-sdk-interaction-tracking-mechanics.md
    • documentation/fern-slugs.lock.json
    • documentation/guides/README.md
    • documentation/guides/building-a-custom-javascript-optimization-adapter.md
    • documentation/guides/choosing-a-nextjs-migration-path-from-experience-js.md
    • documentation/guides/choosing-the-right-sdk.md
    • documentation/guides/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools.md
    • documentation/guides/integrating-the-node-sdk-in-a-node-app.md
    • documentation/guides/integrating-the-optimization-android-sdk-in-a-compose-app.md
    • documentation/guides/integrating-the-optimization-android-sdk-in-a-views-app.md
    • documentation/guides/integrating-the-optimization-ios-sdk-in-a-swiftui-app.md
    • documentation/guides/integrating-the-optimization-ios-sdk-in-a-uikit-app.md
    • documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-app-router-app.md
    • documentation/guides/integrating-the-optimization-sdk-in-a-nextjs-pages-router-app.md
    • documentation/guides/integrating-the-react-native-sdk-in-a-react-native-app.md
    • documentation/guides/integrating-the-react-web-sdk-in-a-react-app.md
    • documentation/guides/integrating-the-web-sdk-in-a-web-app.md
    • documentation/guides/migrating-experience-js-contentful-model-to-optimization.md
    • documentation/guides/migrating-experience-js-next-to-nextjs-app-router.md
    • documentation/guides/migrating-experience-js-next-to-nextjs-pages-router.md
    • documentation/guides/migrating-experience-js-node-ssr-and-esr.md
    • documentation/guides/migrating-experience-js-plugins-and-preview.md
    • documentation/guides/migrating-experience-js-react-to-react-web.md
    • documentation/guides/migrating-experience-js-to-the-web-sdk.md
    • documentation/guides/rendering-personalized-nextjs-routes-with-static-isr-and-edge-handoffs.md
    • documentation/guides/using-contentful-graphql-data-with-the-optimization-sdks.md
    • package.json
    • scripts/apply-fern-docs.ts
    • scripts/export-fern-docs.ts
    • scripts/fern/build.ts
    • scripts/fern/bundle.ts
    • scripts/fern/docs.ts
    • scripts/fern/transform.ts
    • scripts/sdk-knowledge/markdown.ts
    • scripts/validate-fern-export.ts
    • scripts/validate-guide-authoring.ts
  • Files skipped - 0
  • Tools
    • OWASP (Security Vulnerability) - ✔︎ Successful
    • GITHUB_ADVISORY (Security Vulnerability) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Default Agent You can customize the agent settings here or contact your Bito workspace admin at jared.jolton@contentful.com.

Documentation & Help

AI Code Review powered by Bito Logo

Comment thread scripts/fern/transform.ts
Comment on lines +359 to +375
function checkMdxSafety(lines: string[], problems: TransformProblem[]): void {
const inFence = fenceMask(lines)
const prose = lines.map((line, index) => (inFence[index] === true ? '' : line))
const withoutCode = prose.join('\n').replace(/`[^`]*`/gu, '')

withoutCode.split('\n').forEach((line, index) => {
for (const match of line.matchAll(/<\/?([A-Za-z][A-Za-z0-9]*)/gu)) {
const tag = match[1] ?? ''
if (!ALLOWED_COMPONENTS.has(tag)) {
problems.push({
line: index + 1,
message: `raw <${tag}> outside a code fence would be parsed as JSX by MDX`,
})
}
}
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Line numbers drift after multi-line code spans

The checkMdxSafety function joins all prose lines and then removes inline code spans with /[^]*/gu. When a backtick span wraps across a line break (which the comment explicitly says is expected), the regex matches the newline inside the span and removes it. This collapses the line count, so the subsequent split('\n')produces fewer lines than the original input, and theline: index + 1reported in problems no longer matches the actual source line. For example, a span on lines 5-6 followed by

` on line 7 would report the problem at line 6 instead of 7.

Code suggestion
Check the AI-generated fix before applying
Suggested change
function checkMdxSafety(lines: string[], problems: TransformProblem[]): void {
const inFence = fenceMask(lines)
const prose = lines.map((line, index) => (inFence[index] === true ? '' : line))
const withoutCode = prose.join('\n').replace(/`[^`]*`/gu, '')
withoutCode.split('\n').forEach((line, index) => {
for (const match of line.matchAll(/<\/?([A-Za-z][A-Za-z0-9]*)/gu)) {
const tag = match[1] ?? ''
if (!ALLOWED_COMPONENTS.has(tag)) {
problems.push({
line: index + 1,
message: `raw <${tag}> outside a code fence would be parsed as JSX by MDX`,
})
}
}
})
}
function checkMdxSafety(lines: string[], problems: TransformProblem[]): void {
const inFence = fenceMask(lines)
function checkMdxSafety(lines: string[], problems: TransformProblem[]): void {
const inFence = fenceMask(lines)
let inCodeSpan = false
lines.forEach((rawLine, index) => {
if (inFence[index] === true) {
return
}
let line = rawLine
if (inCodeSpan) {
const end = line.indexOf('`')
if (end === -1) {
return // Entire line is inside the code span
}
line = line.slice(end + 1)
inCodeSpan = false
}
// Remove complete inline code spans on this line
line = line.replace(/`[^`]*`/gu, '')
// Check for an unclosed span
const openIndex = line.indexOf('`')
if (openIndex !== -1) {
inCodeSpan = true
line = line.slice(0, openIndex)
}
for (const match of line.matchAll(/<\/?([A-Za-z][A-Za-z0-9]*)/gu)) {
const tag = match[1] ?? ''
if (!ALLOWED_COMPONENTS.has(tag)) {
problems.push({
line: index + 1,
message: `raw <${tag}> outside a code fence would be parsed as JSX by MDX`,
})
}
}
})
}
Suggested based on your custom review guideline "TypeScript Review Evidence"

Code Review Run #609738


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

Comment thread scripts/fern/transform.ts
Comment on lines +255 to +256
const [rawPath = '', fragment] = target.split('#', 2)
const resolved = normalizeRelative(from.relPath, rawPath)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Self-links resolve to GitHub

A self-link like ./#anchor resolves rawPath to the current document's directory (e.g., documentation/guides), which doesn't match the published-doc pattern. The link then falls through to a GitHub URL instead of the current page's site path with the anchor.

Code suggestion
Check the AI-generated fix before applying
Suggested change
const [rawPath = '', fragment] = target.split('#', 2)
const resolved = normalizeRelative(from.relPath, rawPath)
const [rawPath = '', fragment] = target.split('#', 2)
if (rawPath === '' || rawPath === '.' || rawPath === './') {
return { href: sitePath(from.fern.slug, fragment) }
}
const resolved = normalizeRelative(from.relPath, rawPath)
Suggested based on your custom review guideline "Review Posture"

Code Review Run #609738


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

Comment thread scripts/fern/bundle.ts
Comment on lines +102 to +107
for (;;) {
const current = target
const next = lock.redirects.find((entry) => entry.from === current)
if (next === undefined) {
break
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale redirect entry breaks chain resolution

In resolveRedirects, the chain walk uses lock.redirects.find((entry) => entry.from === current), which always picks the first entry for a given from. But reconcileLock can append multiple entries with the same from pointing to different destinations (e.g., slug changes A→B, then B→A, then A→C produce [{A,B},{B,A},{A,C}]). The find would pick A→B first, leading to a false cycle detection and no redirect emitted, even though A→C is the correct latest path. Consider using the most recent entry for each from.

Code suggestion
Check the AI-generated fix before applying
Suggested change
for (;;) {
const current = target
const next = lock.redirects.find((entry) => entry.from === current)
if (next === undefined) {
break
}
for (;;) {
const current = target
const next = [...lock.redirects].reverse().find((entry) => entry.from === current)
if (next === undefined) {
break
}
Suggested based on your custom review guideline "Review Posture"

Code Review Run #609738


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

@TimBeyer
Tim Beyer (TimBeyer) merged commit b9bf0e9 into main Sep 11, 2026
41 checks passed
@TimBeyer
Tim Beyer (TimBeyer) deleted the feat/fern-docs-sync-nt-3942 branch September 11, 2026 16:10
@bito-code-review

Copy link
Copy Markdown

Bito Automatic Review Skipped – PR Already Merged

Bito scheduled an automatic review for this pull request, but the review was skipped because this PR was merged before the review could be run.
No action is needed if you didn't intend to review it. To get a review, you can type /review in a comment and save it

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants