feat(core): support heterogeneous optimized entry types - #413
Conversation
3d0b2d9 to
bb2ae3f
Compare
0fb9ebd to
1f0fcb4
Compare
|
/review |
|
| Source | Requirement / Code Area | Status | Notes |
|---|---|---|---|
| NT-3870 | Support heterogeneous content types in optimized entry resolution so that variant entries can resolve to entries with different content types than the baseline | ✅ Met | The resolver in packages/universal/core-sdk/src/resolvers/OptimizedEntryResolver.ts no longer enforces content-type matching between baseline and variant entries. The removed check at step 11 (formerly requiring variant content type to match baseline) allows heterogeneous content types. A new test case 'resolves a linked variant with a different content type' (line 3893) confirms that when `baselineEntry` is type 'page' and `variantEntry` is type 'hero', resolution correctly returns the variant entry. The concept documentation in documentation/concepts/entry-personalization-and-variant-resolution.md explicitly states 'Selected variant entries can use any Contentful content type' and removes the previous constraint row 'Variant entry content type differs from baseline' from the resolution miss table. |
| NT-3870 | Provide TypeScript utilities for safe content type narrowing when working with optimized entry unions | ✅ Met | The isEntryOfContentType function is implemented in packages/universal/api-schemas/src/contentful/typeGuards.ts and exported from `/api-schemas` entrypoints across all SDK packages (react-web, nextjs, node, react-native). The function checks `sys.contentType.sys.id` to narrow entry union types. Usage examples appear throughout documentation guides (integrating-the-web-sdk-in-a-web-app.md, using-contentful-graphql-data-with-the-optimization-sdks.md) showing pattern like `isEntryOfContentType(entry, 'hero')` followed by type-safe field access. Comprehensive test coverage exists in packages/universal/api-schemas/src/contentful/typeGuards.test.ts. |
| NT-3870 | Expose content type identification through native platform entry adapters | ✅ Met | The CTEntry adapter in packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Contentful/CTEntry.swift now exposes a `contentTypeId` property (lines 2739-2742) that returns `entry.sys?.contentType?.sys.id`. This property enables content type identification on iOS, consistent with the TypeScript implementation. Test coverage is provided in packages/ios/ContentfulOptimization/Tests/ContentfulOptimizationTests/CTEntryTests.swift with test cases 'testContentTypeIdReturnsNestedContentTypeId' and 'testContentTypeIdReturnsNilWhenAbsent'. The iOS runtime documentation in documentation/concepts/ios-sdk-runtime-and-interaction-mechanics.md includes an example (lines 329-339) showing how to use `contentTypeId` with `hasField` for type-specific rendering. |
| ContentEntry.tsx, LiveEntryCard.tsx, useOptimizationResolver.ts, OptimizedEntry.tsx, and other component files | Removed explicit type casts (`as ContentEntry`) from render prop parameters across React implementations (react-web-sdk, nextjs-sdk, react-native-sdk, web-sdk). The resolved entry is now used directly without casting, enabled by the skeleton union generic parameters propagating through the type system. Affected files include implementations/react-web-sdk/src/sections/*.tsx, implementations/nextjs-sdk_app-router/components/*.tsx, implementations/nextjs-sdk_pages-router/components/*.tsx, and packages/react-native-sdk/src/components/OptimizedEntry.tsx. | ⭕ Out of Scope | Removed explicit type casts (`as ContentEntry`) from render prop parameters across React implementations (react-web-sdk, nextjs-sdk, react-native-sdk, web-sdk). The resolved entry is now used directly without casting, enabled by the skeleton union generic parameters propagating through the type system. Affected files include implementations/react-web-sdk/src/sections/*.tsx, implementations/nextjs-sdk_app-router/components/*.tsx, implementations/nextjs-sdk_pages-router/components/*.tsx, and packages/react-native-sdk/src/components/OptimizedEntry.tsx. |
Impact Analysis by BitoCross-Repository Impact Analysis
Code Paths AnalyzedImpact: Flow: Direct Changes (Diff Files): Repository Impact: Cross-Repository Dependencies: Database/Caching Impact: API Contract Violations: Infrastructure Dependencies: Additional Insights: Testing RecommendationsFrontend Impact: Service Integration: Data Serialization: Privacy Compliance: Backward Compatibility: OAuth Functionality: Cross-Service Communication: Reliability Testing: Additional Insights: Analysis based on known dependency patterns and edges. Actual impact may vary. |
There was a problem hiding this comment.
Code Review Agent Run #4cd235
Actionable Suggestions - 4
-
packages/react-native-sdk/src/components/OptimizedEntry.tsx - 1
- Remove unused BivariantCallbacks class · Line 12-20
-
documentation/guides/using-contentful-graphql-data-with-the-optimization-sdks.md - 1
- Missing default case in variant switch · Line 264-281
-
packages/web/frameworks/nextjs-sdk/src/bound-component-types.ts - 1
- Missing type exports · Line 67-88
-
packages/universal/core-sdk/src/CoreStatelessRequest.ts - 1
- Missing type parameters on return type · Line 375-375
Additional Suggestions - 12
-
packages/universal/core-sdk/src/CoreStateless.test.ts - 1
-
Reference equality vs deep equality · Line 317-317The assertion uses `.toBe()` which performs strict reference equality (`Object.is()`). If `fetchOptimizedEntry` returns a new object instance even with semantically identical content, this test will fail. Use `.toEqual()` for deep value comparison instead.
-
-
packages/web/frameworks/react-web-sdk/src/optimized-entry/useOptimizedEntry.ts - 2
-
Missing public type export · Line 2-6The diff adds `EntryFor` to the public API surface via the import on line 4 but does not show it being re-exported from the package's `index.ts`. Verify that `EntryFor` is reachable from the package's public barrel export, otherwise consumers cannot reference this type in their own generic parameters.
-
Unsafe type cast · Line 214-216The type assertion `useOptimizationContext() as { readonly sdk?: OptimizedEntrySdk }` unsafely narrows the context type, discarding type information about the `error`, `isLive`, and `prefetchedManagedEntries` properties. Consider extracting the context first, then narrowing only the `sdk` property: `const context = useOptimizationContext(); const sdk = context.sdk as OptimizedEntrySdk | undefined;` This preserves type safety for the full context while safely narrowing only the property you need.
-
-
packages/react-native-sdk/src/hooks/useOptimizedEntry.ts - 1
-
Unused generic parameters in callback · Line 63-63The `BivariantCallbacks` pattern at line 63 defines an untyped `onEntryResolved` for implementation, while line 27 defines a typed variant in `UseOptimizedEntrySharedParams`. The callback is invoked at line 307 with the untyped `metadata`, making the generic parameters `` on line 27 inert. This creates confusion about what types the callback receives.
-
-
packages/react-native-sdk/src/hooks/useTapTracking.ts - 1
-
Inconsistent generics across tracking hooks · Line 24-28The `UseTapTrackingOptions` interface now accepts a generic `TEntry`, but `useViewportTracking` (same file, same module) still uses the unparameterized `Entry` type directly. Inconsistent generic signatures across sibling tracking hooks create divergence risk: if callers abstract over tracking behavior, they'd need incompatible type signatures. Align `UseViewportTrackingOptions` with the same `` pattern for consistency.
-
-
documentation/internal/sdk-knowledge/native/ios.md - 1
-
CWE-1284: Vague Reference · Line 170-174The phrase 'exposes stable identifiers and fields from the resolver's JSON shape' is ambiguous — CTEntry exposes Contentful.Entry's own surface, not the resolver's JSON. 'Stable identifiers' is undefined terminology that adds no precision.
-
-
packages/web/frameworks/react-web-sdk/src/optimized-entry/OptimizedEntry.tsx - 1
-
Duplicate overload signature · Line 406-406Line 406 declares an overload signature that is identical to the implementation signature at line 407. This duplicate has no effect since the implementation immediately follows. Remove line 406 to eliminate redundant code.
-
-
documentation/internal/sdk-knowledge/web/web.md - 1
-
Inconsistent cross-reference anchor · Line 118-118Line 118 now links to `#entry-resolution`, but line 222 in the same file still links to `#baseline-fallback`. These should be consistent. Either update both to the same anchor or revert line 118 to use `#baseline-fallback`.
-
-
packages/web/frameworks/react-web-sdk/src/hooks/useEntryResolver.ts - 1
-
Potential API confusion · Line 39-39The interface declares `resolveEntryData` with the same type as `resolveOptimizedEntry` (line 18), but the JSDoc describes it as returning entry plus optimization metadata. Verify this is intentional — both methods return identical types, making the distinction unclear.
-
-
documentation/guides/integrating-the-react-web-sdk-in-a-react-app.md - 1
-
Ambiguous capability description · Line 467-470The statement "A selected variant can use any Contentful content type; selection depends on..." uses "can use" which implies capability rather than the intended meaning that variant content types are a separate concern from baseline content types. Reword to "A selected variant's content type is independent of the baseline's content type; selection depends on..." to make the design intent unambiguous.
-
-
packages/web/web-sdk/src/presentation/OptimizedEntryController.ts - 2
-
Generic class backward compatible · Line 219-269The class `OptimizedEntryController` is now generic with default type parameters `
`. This ensures backward compatibility - existing code using `new OptimizedEntryController(options)` will continue to work without changes since defaults match the original non-generic behavior. -
Dead code removal verified · Line 259-318Removed private comparison functions (`areHostAttributesEqual`, `areLoadingPresentationsEqual`, `areSnapshotMetadataEqual`, `areSnapshotValuesEqual`, `areSnapshotsEqual`) are not exported from the module. Verified via diff that no `export` keyword was present on these functions. Safe to remove with no external API impact.
-
Review Details
-
Files reviewed - 83 · Commit Range:
1f0fcb4..1f0fcb4- documentation/authoring/blueprints/ios-swiftui.md
- documentation/authoring/blueprints/nextjs-app-router.md
- documentation/authoring/blueprints/nextjs-pages-router.md
- documentation/authoring/blueprints/node.md
- documentation/authoring/blueprints/react-native.md
- documentation/authoring/blueprints/react-web.md
- documentation/concepts/android-sdk-runtime-and-interaction-mechanics.md
- documentation/concepts/entry-personalization-and-variant-resolution.md
- documentation/concepts/ios-sdk-runtime-and-interaction-mechanics.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/using-contentful-graphql-data-with-the-optimization-sdks.md
- documentation/internal/sdk-knowledge/native/ios.md
- documentation/internal/sdk-knowledge/native/react-native.md
- documentation/internal/sdk-knowledge/shared/concepts.md
- documentation/internal/sdk-knowledge/web/nextjs-app-router.md
- documentation/internal/sdk-knowledge/web/nextjs-pages-router.md
- documentation/internal/sdk-knowledge/web/react-web.md
- documentation/internal/sdk-knowledge/web/web.md
- implementations/nextjs-sdk_app-router/app/(static)/analytics-only/[segment]/page.tsx
- implementations/nextjs-sdk_app-router/app/(static)/selection-handoff/[segment]/page.tsx
- implementations/nextjs-sdk_app-router/components/EntryCard.tsx
- implementations/nextjs-sdk_app-router/components/LiveEntryCard.tsx
- implementations/nextjs-sdk_pages-router/components/EntryCard.tsx
- implementations/nextjs-sdk_pages-router/components/LiveEntryCard.tsx
- implementations/nextjs-sdk_pages-router/pages/selection-handoff/[segment].tsx
- implementations/react-web-sdk/src/sections/ContentEntry.tsx
- implementations/react-web-sdk/src/sections/LiveUpdatesExampleEntry.tsx
- implementations/react-web-sdk/src/sections/NestedContentItem.tsx
- implementations/web-sdk_react/src/optimization/hooks/useOptimizationResolver.ts
- packages/ios/ContentfulOptimization/README.md
- packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Contentful/CTEntry.swift
- packages/ios/ContentfulOptimization/Tests/ContentfulOptimizationTests/CTEntryTests.swift
- packages/node/node-sdk/src/ContentfulOptimization.test.ts
- packages/react-native-sdk/README.md
- packages/react-native-sdk/src/components/OptimizedEntry.tsx
- packages/react-native-sdk/src/hooks/useEntryResolver.ts
- packages/react-native-sdk/src/hooks/useOptimizedEntry.test.tsx
- packages/react-native-sdk/src/hooks/useOptimizedEntry.ts
- packages/react-native-sdk/src/hooks/useTapTracking.ts
- packages/universal/api-schemas/README.md
- packages/universal/api-schemas/src/contentful/typeGuards.test.ts
- packages/universal/api-schemas/src/contentful/typeGuards.ts
- packages/universal/core-sdk/README.md
- packages/universal/core-sdk/src/CoreBase.ts
- packages/universal/core-sdk/src/CoreStateful.test.ts
- packages/universal/core-sdk/src/CoreStateless.test.ts
- packages/universal/core-sdk/src/CoreStatelessRequest.ts
- packages/universal/core-sdk/src/OptimizedEntryMetadata.ts
- packages/universal/core-sdk/src/handoff.ts
- packages/universal/core-sdk/src/resolvers/OptimizedEntryResolver.test.ts
- packages/universal/core-sdk/src/resolvers/OptimizedEntryResolver.ts
- packages/universal/core-sdk/src/runtime/SnapshotRuntime.test.ts
- packages/web/frameworks/nextjs-sdk/README.md
- packages/web/frameworks/nextjs-sdk/src/app-router-client.ts
- packages/web/frameworks/nextjs-sdk/src/app-router-server.tsx
- packages/web/frameworks/nextjs-sdk/src/bound-component-types.ts
- packages/web/frameworks/nextjs-sdk/src/pages-router.ts
- packages/web/frameworks/nextjs-sdk/src/runtime-types.test.ts
- packages/web/frameworks/nextjs-sdk/src/server-entry-renderer.tsx
- packages/web/frameworks/nextjs-sdk/src/server.tsx
- packages/web/frameworks/nextjs-sdk/src/tracking-attributes.ts
- packages/web/frameworks/react-web-sdk/README.md
- packages/web/frameworks/react-web-sdk/src/hooks/useEntryResolver.ts
- packages/web/frameworks/react-web-sdk/src/index.ts
- packages/web/frameworks/react-web-sdk/src/optimized-entry/OptimizedEntry.test.tsx
- packages/web/frameworks/react-web-sdk/src/optimized-entry/OptimizedEntry.tsx
- packages/web/frameworks/react-web-sdk/src/optimized-entry/optimizedEntryUtils.ts
- packages/web/frameworks/react-web-sdk/src/optimized-entry/useOptimizedEntry.ts
- packages/web/web-sdk/README.md
- packages/web/web-sdk/src/presentation/OptimizedEntryController.test.ts
- packages/web/web-sdk/src/presentation/OptimizedEntryController.ts
- packages/web/web-sdk/src/presentation/OptimizedEntryLoadingPresentation.ts
- packages/web/web-sdk/src/presentation/OptimizedEntrySnapshot.ts
- packages/web/web-sdk/src/presentation/OptimizedEntryTrackingAttributes.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 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
| import { useTapTracking } from '../hooks/useTapTracking' | ||
| import { useViewportTracking } from '../hooks/useViewportTracking' | ||
|
|
||
| declare class BivariantCallbacks { | ||
| onEntryResolved(metadata: OptimizedEntryMetadata): void | ||
| onTap(entry: Entry): void | ||
| render(entry: Entry, metadata: OptimizedEntryMetadata): ReactNode | ||
| } | ||
|
|
There was a problem hiding this comment.
The BivariantCallbacks class is declared but not exported or referenced anywhere in the visible code. This appears to be dead code or an incomplete refactoring artifact. Either remove it if unused, or ensure it's properly integrated.
Code suggestion
Check the AI-generated fix before applying
| import { useTapTracking } from '../hooks/useTapTracking' | |
| import { useViewportTracking } from '../hooks/useViewportTracking' | |
| declare class BivariantCallbacks { | |
| onEntryResolved(metadata: OptimizedEntryMetadata): void | |
| onTap(entry: Entry): void | |
| render(entry: Entry, metadata: OptimizedEntryMetadata): ReactNode | |
| } | |
Suggested based on your custom review guideline "Review Posture"
Code Review Run #4cd235
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
There was a problem hiding this comment.
No change needed. BivariantCallbacks is referenced by OptimizedEntryImplementationProps for children, onEntryResolved, and onTap. Its method types intentionally provide bivariance at the erased implementation boundary while the public overloads retain their generic callback types. It is type-only and emits no runtime code; removing it would undo that compatibility mechanism.
| function toVariantEntry(variant: GraphQlVariant): Entry<EntrySkeletonType> { | ||
| switch (variant.__typename) { | ||
| case 'Hero': | ||
| return entryLike(variant, 'hero', { | ||
| headline: variant.headline, | ||
| }) | ||
| case 'CallToAction': | ||
| return entryLike(variant, 'callToAction', { | ||
| label: variant.label, | ||
| }) | ||
| case 'Page': | ||
| return entryLike(variant, 'page', { | ||
| title: variant.title, | ||
| slug: variant.slug, | ||
| heroHeadline: variant.heroHeadline, | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
The toVariantEntry function's switch statement handles 'Hero', 'CallToAction', and 'Page' cases but lacks a default case. If GraphQL data contains a variant with an unexpected __typename value, the function implicitly returns undefined, which could cause downstream errors when the variant is processed.
Code suggestion
Check the AI-generated fix before applying
| function toVariantEntry(variant: GraphQlVariant): Entry<EntrySkeletonType> { | |
| switch (variant.__typename) { | |
| case 'Hero': | |
| return entryLike(variant, 'hero', { | |
| headline: variant.headline, | |
| }) | |
| case 'CallToAction': | |
| return entryLike(variant, 'callToAction', { | |
| label: variant.label, | |
| }) | |
| case 'Page': | |
| return entryLike(variant, 'page', { | |
| title: variant.title, | |
| slug: variant.slug, | |
| heroHeadline: variant.heroHeadline, | |
| }) | |
| } | |
| } | |
| function toVariantEntry(variant: GraphQlVariant): Entry<EntrySkeletonType> { | |
| switch (variant.__typename) { | |
| case 'Hero': | |
| return entryLike(variant, 'hero', { | |
| headline: variant.headline, | |
| }) | |
| case 'CallToAction': | |
| return entryLike(variant, 'callToAction', { | |
| label: variant.label, | |
| }) | |
| case 'Page': | |
| return entryLike(variant, 'page', { | |
| title: variant.title, | |
| slug: variant.slug, | |
| heroHeadline: variant.heroHeadline, | |
| }) | |
| default: | |
| throw new Error(`Unknown variant type: ${variant.__typename}`) | |
| } | |
| } |
Suggested based on your custom review guideline "Review Posture"
Code Review Run #4cd235
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
There was a problem hiding this comment.
No change needed. GraphQlVariant is a closed discriminated union containing exactly Page, Hero, and CallToAction, so the switch is exhaustive for its declared input. Adding the proposed default throw would turn unsupported runtime data into an exception, contrary to the resolver's fail-soft fallback. If this example needed runtime GraphQL validation, that validation would belong at the response boundary rather than as a default branch here.
| export type NextjsBoundOptimizedEntryBaselineProps< | ||
| S extends EntrySkeletonType = EntrySkeletonType, | ||
| M extends ChainModifiers = ChainModifiers, | ||
| L extends LocaleCode = LocaleCode, | ||
| > = Omit<OptimizedEntryBaselineProps<S, M, L>, 'liveUpdates' | 'loadingFallback'> | ||
|
|
||
| export type NextjsBoundOptimizedEntryManagedProps< | ||
| S extends EntrySkeletonType = EntrySkeletonType, | ||
| L extends LocaleCode = LocaleCode, | ||
| > = Omit<OptimizedEntryManagedProps<S, L>, 'liveUpdates' | 'loadingFallback'> | ||
|
|
||
| export type NextjsBoundOptimizedEntryProps< | ||
| S extends EntrySkeletonType = EntrySkeletonType, | ||
| M extends ChainModifiers = ChainModifiers, | ||
| L extends LocaleCode = LocaleCode, | ||
| > = DistributiveOmit<OptimizedEntryProps<S, M, L>, 'liveUpdates' | 'loadingFallback'> | ||
|
|
||
| export type NextjsServerOptimizedEntryProps< | ||
| S extends EntrySkeletonType = EntrySkeletonType, | ||
| M extends ChainModifiers = ChainModifiers, | ||
| L extends LocaleCode = LocaleCode, | ||
| > = NextjsBoundOptimizedEntryProps<S, M, L> |
There was a problem hiding this comment.
Three types are defined in the diff but never exported from the package entry points. NextjsBoundOptimizedEntryBaselineProps (line 67), NextjsBoundOptimizedEntryManagedProps (line 73), and NextjsBoundOptimizedEntryComponent (line 90) are dead code in the public API — they cannot be imported by consumers. Add them to the export type { ... } block in app-router-server.tsx (line 75-85) alongside the existing NextjsBoundOptimizedEntryProps export.
Citations
- Rule Violated: AGENTS.md:1
Code Review Run #4cd235
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
There was a problem hiding this comment.
No change needed. These are internal construction types for the bound component. The consumer-facing NextjsBoundOptimizedEntryProps union is already exported; the baseline/managed branches and NextjsBoundOptimizedEntryComponent support the implementation and emitted component declaration. Exporting each helper separately would add public API without a consumer requirement.
| options: FetchOptimizedEntryOptions = {}, | ||
| ): Promise<FetchOptimizedEntryResult<S, undefined, L>> { | ||
| return await this.core.fetchOptimizedEntry<S, L>(entryId, { | ||
| ): Promise<FetchOptimizedEntryResult> { |
There was a problem hiding this comment.
The implementation overload returns FetchOptimizedEntryResult without type parameters. Since FetchOptimizedEntryResult is an interface requiring three type parameters, this becomes FetchOptimizedEntryResult<unknown, unknown, unknown>, breaking type inference for callers. Compare with CoreBase.fetchOptimizedEntry at line 354 which correctly uses FetchOptimizedEntryResult<EntrySkeletonType, ChainModifiers>.
Code Review Run #4cd235
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
There was a problem hiding this comment.
No change needed. FetchOptimizedEntryResult defaults all three parameters to EntrySkeletonType, undefined, and LocaleCode; it does not become <unknown, unknown, unknown>. Callers see the preceding generic overload, while this is the non-public implementation signature. The current typecheck and compile-time contract tests pass.
✅ Review Settings OverriddenStatus: Guidelines:
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. |
Bito review follow-up: secondary suggestionsI reviewed all twelve secondary notes that do not have individual review threads:
The exact PR head has passing typecheck, unit-test, build, native, and relevant E2E checks. |
Bito's feedback has been reviewed and largely rejected.
Allow optimized Contentful entries to resolve selected variants whose content type differs from the baseline entry. Represent every possible baseline and variant with one skeleton union. Distribute EntryFor over that union so Contentful chain modifiers and locales are preserved while same-type consumers retain precise inference. Propagate modeled entries through Core, Node, Web, React Web, Next.js, and React Native resolution and presentation APIs. Keep long-lived event streams model-agnostic and preserve baseline fallback and empty-variant behavior. Add isEntryOfContentType for narrowing modeled entry unions and expose CTEntry.contentTypeId for native content-type switching. Separate reusable Web snapshot state and equality logic from controller lifecycle concerns. Update reference implementations, SDK knowledge, concepts, blueprints, integration guides, READMEs, and compile-time and runtime coverage. Line-change rundown: | Area | Files | Added | Removed | Net | | --------------------------------------- | ----: | ----: | ------: | ---: | | SDK production source | 27 | 973 | 481 | +492 | | Tests | 11 | 425 | 13 | +412 | | Reference implementations | 11 | 26 | 40 | -14 | | Package READMEs | 7 | 20 | 2 | +18 | | Documentation, guides, and SDK knowledge | 27 | 988 | 478 | +510 | | Total | 83 | 2,432 | 1,014 | +1,418 | [[NT-3870](https://contentful.atlassian.net/browse/NT-3870)]
1f0fcb4 to
82a1f4a
Compare
Summary
nt_variantsentries to resolve to any linked Contentful content type instead of requiring the baseline and variant content types to match.EntryFor<S, M, L>so Contentful chain modifiers and locales are preserved.isEntryOfContentTypefor safe TypeScript narrowing and exposecontentTypeIdthrough the iOSCTEntryadapter.Consumer typing and compatibility
Same-content-type calls infer their entry type without explicit generic arguments.
Known heterogeneous models provide the baseline and all possible variant skeletons through the first generic argument:
The baseline, resolved entry, and optimized-entry metadata use that same distributed entry union. Consumers narrow at the rendering or handling boundary with
isEntryOfContentTypeorsys.contentType.sys.id.Chain modifiers and locale types are retained for every union member.
Open-ended integrations can use
EntrySkeletonTypewhen the complete content model is not known at compile time.Existing generic parameter positions and resolver call shapes remain intact.
Baseline fallback, empty variants, selection ownership, tracking, metadata, and presentation retain their existing contracts.
Coverage
Validation
Line change breakdown
Compared with
main, this branch changes 83 files: 2,427 additions and 1,014 deletions.Type counts cover declarations erased from emitted JavaScript; type annotations embedded in executable implementations count as runtime code. Test files and Markdown/READMEs are classified wholly as test code and documentation, respectively.
[NT-3870]