5604e14e fix[abTests][gen1]: ENG-13175 A/B Variation Hydration Mismatch on First Server Render (#4844)
## Description
When you preview a non-default A/B variation, the page briefly shows the
default variation before switching to the one you asked for. This causes
a layout shift and a React hydration error. Reloading fixes it, so it
only happens on the first visit.
**Root Cause:**
The editor's preview link carries the chosen variation in the URL as
`builder.tests.<contentId>=<variationId>`, but nothing reads it early
enough:
- The server doesn't look at it, so the HTML ships all variations.
- The inlined variants script only checks the cookie. On a first visit
there is no cookie, so it picks a variation at random, usually the
default.
- The SDK then writes the URL's variation into the cookie, and React
hydrates with that value. It no longer matches the DOM, so React
re-renders and you see the switch.
**Fix:**
Both places that choose a variation now check the URL parameter first,
then fall back to the cookie, then to the random assignment as before:
- the inlined SSR script, so the correct variation is on screen at first
paint
- `VariantsProvider`'s browser branch, so hydration agrees with the DOM
The URL value is validated against the content's known variation ids,
and is read at runtime in the browser, so nothing untrusted is written
into the script.
**Link to JIRA ticket (if applicable):**
https://builder-io.atlassian.net/browse/ENG-13175
**Screenshot/Clip**
Bug: https://clips.agent-native.com/r/lqL0Ngm5BL7m
Fix: https://clips.agent-native.com/r/ByaNuJ0mjqwo
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes first-paint A/B selection and cookie writes in a widely used
React path; behavior is constrained by validating URL ids against known
variations.
>
> **Overview**
> Fixes a **flash of the default A/B variation** (and hydration
mismatch) when previewing a variation via `builder.tests.<contentId>` on
first visit.
>
> **Variant selection precedence** is now aligned in two places: the
**inlined SSR variants script** and **`VariantsProvider`’s browser
path** both read `builder.tests.<contentId>` /
`builder_tests_<contentId>` first (validated against known variation
ids), then cookie, then random assignment. A valid URL choice is also
written to the cookie in the inlined script so later logic stays
consistent.
>
> Adds **Jest coverage** for inlined script syntax, URL vs cookie vs
random behavior in jsdom, and browser render matching the URL param when
no cookie is set. Ships as a **patch** to `@builder.io/react` via
changeset.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ab6cac2b9e1fa649445e2fd7929419392fc2b062. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY --> c0ef7b59 fix[smartling][utils]: ENG-13615 plugin sends enum field values from custom components to translation jobs (#4826)
## Description
A customer reported that Smartling translation jobs were full of junk
strings like White, Left, and Bottom. These are dropdown (enum) values
from custom component fields (these are settings, not text anyone should
translate)
**Root Cause:**
When a custom component has a `localized` list field,
`getTranslateableFields` first looks for child values that are
individually marked as localized. If it doesn't find any, it falls back
to grabbing every string inside the list.
That fallback isn't a bug on its own, it's what makes ordinary lists
translatable and there's a test pinning it. The real issue is that the
extractor has no way to tell a **headline** from a **dropdown value**.
It runs on the server against raw content JSON, and a component's
`inputs` schema only exists in the app code. The server never sees enum:
["Left", "Center", "Right"].
**Fix:**
Blocks can now carry a list of input paths that should be
non-translatable:
```
meta.nonTranslatableInputs: [
'textColumns.*.textColumnAlignment',
'textColumns.*.textRowAlignment',
'textColumns.*.backgroundColor',
]
```
`getTranslateableFields` reads that list and skips those leaves. The *
stands in for list indices, so one item's schema covers the whole list.
**Link to JIRA ticket (if applicable):**
https://builder-io.atlassian.net/browse/ENG-13615
**Screenshot/Clip**
https://clips.agent-native.com/r/ATlR8Yi3xWgq
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes core translation extract/apply logic for custom components;
incorrect path matching could omit real copy or mishandle locale
seeding, though behavior without `nonTranslatableInputs` is explicitly
preserved by tests.
>
> **Overview**
> Stops **dropdown/enum values** (e.g. alignment, background color) from
being sent to translation providers when custom components use localized
list fields whose string leaves were previously all extracted.
>
> Blocks can declare **`meta.nonTranslatableInputs`** with wildcard
paths (`textColumns.*.textColumnAlignment`).
**`getTranslateableFields`** normalizes paths (indices → `*`, `#`/`.`
equivalent) and omits those leaves during nested extraction; behavior is
unchanged when the list is empty.
>
> **`applyTranslation`** mirrors the same exclusions: it drops compound
keys for excluded paths, **ignores legacy job payloads** that still
contain translated enums, and **seeds the target locale from source**
when an input has no translatable leaves (so lists do not resolve to
`undefined` and disappear). Broad test coverage was added for
TextColumns-style scenarios and all-excluded inputs.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
cb6ba9b1f5f4670db49d31705d2ca44786bc18bf. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY --> 4194f07c fix[smartling][utils]: ENG-13615 plugin sends enum field values from custom components to translation jobs (#4826)
## Description
A customer reported that Smartling translation jobs were full of junk
strings like White, Left, and Bottom. These are dropdown (enum) values
from custom component fields (these are settings, not text anyone should
translate)
**Root Cause:**
When a custom component has a `localized` list field,
`getTranslateableFields` first looks for child values that are
individually marked as localized. If it doesn't find any, it falls back
to grabbing every string inside the list.
That fallback isn't a bug on its own, it's what makes ordinary lists
translatable and there's a test pinning it. The real issue is that the
extractor has no way to tell a **headline** from a **dropdown value**.
It runs on the server against raw content JSON, and a component's
`inputs` schema only exists in the app code. The server never sees enum:
["Left", "Center", "Right"].
**Fix:**
Blocks can now carry a list of input paths that should be
non-translatable:
```
meta.nonTranslatableInputs: [
'textColumns.*.textColumnAlignment',
'textColumns.*.textRowAlignment',
'textColumns.*.backgroundColor',
]
```
`getTranslateableFields` reads that list and skips those leaves. The *
stands in for list indices, so one item's schema covers the whole list.
**Link to JIRA ticket (if applicable):**
https://builder-io.atlassian.net/browse/ENG-13615
**Screenshot/Clip**
https://clips.agent-native.com/r/ATlR8Yi3xWgq
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes core translation extract/apply logic for custom components;
incorrect path matching could omit real copy or mishandle locale
seeding, though behavior without `nonTranslatableInputs` is explicitly
preserved by tests.
>
> **Overview**
> Stops **dropdown/enum values** (e.g. alignment, background color) from
being sent to translation providers when custom components use localized
list fields whose string leaves were previously all extracted.
>
> Blocks can declare **`meta.nonTranslatableInputs`** with wildcard
paths (`textColumns.*.textColumnAlignment`).
**`getTranslateableFields`** normalizes paths (indices → `*`, `#`/`.`
equivalent) and omits those leaves during nested extraction; behavior is
unchanged when the list is empty.
>
> **`applyTranslation`** mirrors the same exclusions: it drops compound
keys for excluded paths, **ignores legacy job payloads** that still
contain translated enums, and **seeds the target locale from source**
when an input has no translatable leaves (so lists do not resolve to
`undefined` and disappear). Broad test coverage was added for
TextColumns-style scenarios and all-excluded inputs.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
cb6ba9b1f5f4670db49d31705d2ca44786bc18bf. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY --> c0ef7b59 fix[smartling][utils]: ENG-13615 plugin sends enum field values from custom components to translation jobs (#4826)
## Description
A customer reported that Smartling translation jobs were full of junk
strings like White, Left, and Bottom. These are dropdown (enum) values
from custom component fields (these are settings, not text anyone should
translate)
**Root Cause:**
When a custom component has a `localized` list field,
`getTranslateableFields` first looks for child values that are
individually marked as localized. If it doesn't find any, it falls back
to grabbing every string inside the list.
That fallback isn't a bug on its own, it's what makes ordinary lists
translatable and there's a test pinning it. The real issue is that the
extractor has no way to tell a **headline** from a **dropdown value**.
It runs on the server against raw content JSON, and a component's
`inputs` schema only exists in the app code. The server never sees enum:
["Left", "Center", "Right"].
**Fix:**
Blocks can now carry a list of input paths that should be
non-translatable:
```
meta.nonTranslatableInputs: [
'textColumns.*.textColumnAlignment',
'textColumns.*.textRowAlignment',
'textColumns.*.backgroundColor',
]
```
`getTranslateableFields` reads that list and skips those leaves. The *
stands in for list indices, so one item's schema covers the whole list.
**Link to JIRA ticket (if applicable):**
https://builder-io.atlassian.net/browse/ENG-13615
**Screenshot/Clip**
https://clips.agent-native.com/r/ATlR8Yi3xWgq
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes core translation extract/apply logic for custom components;
incorrect path matching could omit real copy or mishandle locale
seeding, though behavior without `nonTranslatableInputs` is explicitly
preserved by tests.
>
> **Overview**
> Stops **dropdown/enum values** (e.g. alignment, background color) from
being sent to translation providers when custom components use localized
list fields whose string leaves were previously all extracted.
>
> Blocks can declare **`meta.nonTranslatableInputs`** with wildcard
paths (`textColumns.*.textColumnAlignment`).
**`getTranslateableFields`** normalizes paths (indices → `*`, `#`/`.`
equivalent) and omits those leaves during nested extraction; behavior is
unchanged when the list is empty.
>
> **`applyTranslation`** mirrors the same exclusions: it drops compound
keys for excluded paths, **ignores legacy job payloads** that still
contain translated enums, and **seeds the target locale from source**
when an input has no translatable leaves (so lists do not resolve to
`undefined` and disappear). Broad test coverage was added for
TextColumns-style scenarios and all-excluded inputs.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
cb6ba9b1f5f4670db49d31705d2ca44786bc18bf. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->