Skip to content

feat: set defaultValue data for constantValues#1077

Merged
asanehisa merged 19 commits intomainfrom
default-data
Mar 6, 2026
Merged

feat: set defaultValue data for constantValues#1077
asanehisa merged 19 commits intomainfrom
default-data

Conversation

@asanehisa
Copy link
Contributor

@asanehisa asanehisa commented Mar 4, 2026

Adds new defaultValue on constantValue objects. No migrations, so only newly dragged on components or new layouts will have a defaultValue that is a fallback if no locale specific value. However if something is like:

constantValue: {
    fr: "", 
    defaultValue: "Hello",
    hasLocalizedValue: true
}

for a french page, it'll use "", not the defaultValue.

Confirmed with aaron we don't want the fallback to just be the "en" value as if a user changes in their en pageset, the default for the non-en pages shouldn't change.

live editor: https://www.yext.com/s/4412098/yextsites/162218/pagesets

Now you start without the locale warnings too! So maybe they'll be more helpful rather than constant (the red box on the locale picker)

@asanehisa asanehisa added the create-dev-release Triggers dev release workflow label Mar 4, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

Warning: Component files have been updated but no migrations have been added. See https://github.com/yext/visual-editor/blob/main/packages/visual-editor/src/components/migrations/README.md for more information.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 4, 2026

commit: 0fe85bd

@asanehisa asanehisa marked this pull request as ready for review March 5, 2026 15:02
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR standardizes default translatable data shapes by replacing per-locale objects (e.g., { en: "...", hasLocalizedValue: "true" }) with { defaultValue: "..." } across many components and defaultProps. It adds localized-asset helpers (isLocalizedAssetImage, resolveLocalizedAssetImage) and updates image types to make hasLocalizedValue optional and support defaultValue. Resolver utilities (resolveComponentData, resolveYextEntityField, plainText, meta-title validation) now prefer locale values and fall back to defaultValue. Several components (images, CTAs, headers, footers, page sections, editor inputs) and tests were updated to align with these shapes.

Sequence Diagram(s)

sequenceDiagram
  participant Component as Visual Component
  participant i18n as i18n (locale)
  participant Resolver as resolveComponentData / resolveYextEntityField
  participant ImageUtil as types/images.ts

  Component->>i18n: request current locale
  i18n-->>Component: locale (e.g., "fr")
  Component->>Resolver: resolve translatable field (value, locale)
  Resolver->>Resolver: inspect value shape (string | localized object | defaultValue)
  alt value is localized asset image
    Resolver->>ImageUtil: isLocalizedAssetImage(value)?
    ImageUtil-->>Resolver: true
    Resolver->>ImageUtil: resolveLocalizedAssetImage(value, locale)
    ImageUtil-->>Resolver: AssetImage (locale or defaultValue)
  else value is translatable string/rich text
    Resolver-->>Resolver: localized = value[locale] ?? value.defaultValue
  end
  Resolver-->>Component: resolved display value (string / RTF / AssetImage)
  Component->>Component: render using resolved value
Loading

Possibly related PRs

Suggested reviewers

  • benlife5
  • jwartofsky-yext
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding defaultValue to constantValue objects across the codebase.
Description check ✅ Passed The description is directly related to the changeset, explaining the defaultValue feature, its fallback behavior, locale handling, and the removal of initial locale warnings.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch default-data

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
packages/visual-editor/src/utils/plainText.test.ts (1)

145-168: Add a regression test for explicit empty locale values.

Given the fallback rules, an explicit locale value of "" should win over defaultValue; adding this test will lock in that behavior.

✅ Suggested test case
 describe("getLocalizedPlainText", () => {
@@
   it("falls back to rich text defaultValue when locale is missing", () => {
@@
     expect(getLocalizedPlainText(value, "de")).toBe("Hello there");
   });
+
+  it("prefers explicit empty locale value over defaultValue", () => {
+    const value: TranslatableString = {
+      hasLocalizedValue: "true",
+      fr: "",
+      defaultValue: "Hello",
+    };
+
+    expect(getLocalizedPlainText(value, "fr")).toBe("");
+  });
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/src/utils/plainText.test.ts` around lines 145 - 168,
Add regression tests to ensure an explicit empty locale value wins over
defaultValue: in packages/visual-editor/src/utils/plainText.test.ts add a test
for getLocalizedPlainText where a TranslatableString has the requested locale
present but set to "" (e.g., { defaultValue: "Hello", de: "" }) and assert it
returns "" not "Hello"; also add a similar test for TranslatableRichText where
the requested locale's rich text is present but empty (e.g., { defaultValue: {
html: "<p>Hello</p>" }, de: { html: "" } }) and assert getLocalizedPlainText
returns an empty string; reference getLocalizedPlainText to locate where to add
these cases.
packages/visual-editor/src/utils/resolveYextEntityField.test.ts (1)

219-261: Add a regression case for “locale value equals defaultValue”.

Nice coverage additions. One missing edge case: when fr exists and has the same string as defaultValue, locale should still be the field that gets resolved.

✅ Suggested test case
+  it("resolves locale value even when it matches defaultValue text", () => {
+    assert.deepEqual(
+      resolveYextEntityField(
+        document,
+        {
+          field: "",
+          constantValue: {
+            fr: "Welcome to [[name]]!",
+            defaultValue: "Welcome to [[name]]!",
+          },
+          constantValueEnabled: true,
+        },
+        "fr"
+      ),
+      {
+        fr: "Welcome to Yext!",
+        defaultValue: "Welcome to [[name]]!",
+      }
+    );
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/src/utils/resolveYextEntityField.test.ts` around lines
219 - 261, Add a regression test to resolveYextEntityField.test.ts that covers
the edge case where the locale (e.g., "fr") has a value identical to the
defaultValue string: call resolveYextEntityField with a
constantValue.defaultValue containing placeholders (like "Welcome to [[name]]!")
and a document that defines the locale field "fr" with the identical string,
then assert that the function resolves using the locale value (i.e., performs
placeholder substitution from the entity, not treating it as a fallback) — this
ensures resolveYextEntityField correctly prefers the locale-specific value even
when it equals defaultValue.
packages/visual-editor/src/components/header/PrimaryHeaderSlot.tsx (1)

431-432: Minor: Inconsistent capitalization with HeroSection.

The CTA labels here use "Call to Action" (lowercase 't') while HeroSection.tsx uses "Call To Action" (uppercase 'T'). If this is intentional for different contexts, consider adding a comment; otherwise, consider aligning the capitalization for consistency across components.

Also applies to: 458-459

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/src/components/header/PrimaryHeaderSlot.tsx` around
lines 431 - 432, Inconsistent CTA label capitalization between PrimaryHeaderSlot
(label defaultValue at the entries near the shown diff and the similar entries
around lines 458-459) and HeroSection.tsx; update the label defaultValue(s) in
PrimaryHeaderSlot to match HeroSection.tsx's "Call To Action" (or vice versa) so
both components use identical capitalization, and if the difference is
intentional add a brief inline comment next to the label definitions explaining
the reason.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/visual-editor/src/docs/ai/components.d.ts`:
- Around line 1124-1127: The LocalizedAssetImage type lacks locale index
signatures expected by resolveLocalizedAssetImage and inconsistent with
LocalizedValues; update the LocalizedAssetImage type to include a locale index
signature (e.g., [locale: string]: AssetImageType | undefined) while keeping
hasLocalizedValue?: "true" and defaultValue?: AssetImageType so runtime locale
overrides are supported and typings match resolveLocalizedAssetImage and
LocalizedValues.

In
`@packages/visual-editor/src/internal/puck/constant-value-fields/EnhancedCallToAction.tsx`:
- Around line 83-92: The branch in EnhancedCallToAction.tsx is destructively
clearing updatedValue.label whenever newValue === "presetImage", which wipes
user-entered CTA text and then gets preserved as an empty label when switching
back; instead, modify the logic around the updatedValue and label handling so
that when newValue === "presetImage" you do not overwrite an existing
updatedValue.label (only set a label default if none exists), and ensure the
other branches (getDirections, textAndLink) continue to only set a label if
updatedValue.label is falsy—locate and update the conditional that checks
newValue and touches updatedValue.label to preserve existing label content.

In `@packages/visual-editor/src/internal/puck/constant-value-fields/TextList.tsx`:
- Around line 149-152: When creating a new translatable item in TextList (the
newItems array built from localItems in TextList.tsx), include the new fallback
field by adding a defaultValue property on the newly created entry (alongside
[locale] and hasLocalizedValue). Update the object literal used to construct the
new item (the element pushed into newItems) to include defaultValue with the
appropriate initial value so newly added entries won't miss the fallback field.

In `@packages/visual-editor/src/utils/resolveYextEntityField.ts`:
- Around line 115-126: The fallback detection is wrong because comparing
data.defaultValue === localizedValue can't determine whether localizedValue came
from data[locale] or data.defaultValue; update resolveYextEntityField to detect
the source by checking property existence (e.g., use
Object.prototype.hasOwnProperty.call(data, locale) or similar) before deciding
to replace data.defaultValue or data[locale]; apply the same pattern to the
other TranslatableString handling blocks referenced (around the checks at lines
~140-149 and ~160-163) and continue using
resolveEmbeddedFieldsInString(streamDocument, locale) result to set the correct
property (locale key vs defaultValue).

---

Nitpick comments:
In `@packages/visual-editor/src/components/header/PrimaryHeaderSlot.tsx`:
- Around line 431-432: Inconsistent CTA label capitalization between
PrimaryHeaderSlot (label defaultValue at the entries near the shown diff and the
similar entries around lines 458-459) and HeroSection.tsx; update the label
defaultValue(s) in PrimaryHeaderSlot to match HeroSection.tsx's "Call To Action"
(or vice versa) so both components use identical capitalization, and if the
difference is intentional add a brief inline comment next to the label
definitions explaining the reason.

In `@packages/visual-editor/src/utils/plainText.test.ts`:
- Around line 145-168: Add regression tests to ensure an explicit empty locale
value wins over defaultValue: in
packages/visual-editor/src/utils/plainText.test.ts add a test for
getLocalizedPlainText where a TranslatableString has the requested locale
present but set to "" (e.g., { defaultValue: "Hello", de: "" }) and assert it
returns "" not "Hello"; also add a similar test for TranslatableRichText where
the requested locale's rich text is present but empty (e.g., { defaultValue: {
html: "<p>Hello</p>" }, de: { html: "" } }) and assert getLocalizedPlainText
returns an empty string; reference getLocalizedPlainText to locate where to add
these cases.

In `@packages/visual-editor/src/utils/resolveYextEntityField.test.ts`:
- Around line 219-261: Add a regression test to resolveYextEntityField.test.ts
that covers the edge case where the locale (e.g., "fr") has a value identical to
the defaultValue string: call resolveYextEntityField with a
constantValue.defaultValue containing placeholders (like "Welcome to [[name]]!")
and a document that defines the locale field "fr" with the identical string,
then assert that the function resolves using the locale value (i.e., performs
placeholder substitution from the entity, not treating it as a fallback) — this
ensures resolveYextEntityField correctly prefers the locale-specific value even
when it equals defaultValue.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c9e16b5b-ab89-4371-a6b7-853422f36285

📥 Commits

Reviewing files that changed from the base of the PR and between 21bf156 and 8fd3018.

⛔ Files ignored due to path filters (16)
  • packages/visual-editor/src/components/testing/screenshots/Locator/[desktop] version 64 static image.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[desktop] version 53 carousel variant with carouselImageCount 1.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[desktop] version 53 carousel variant with carouselImageCount 2.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[desktop] version 53 carousel variant with carouselImageCount 3.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[desktop] version 53 gallery variant with 1 image.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[desktop] version 53 gallery variant with mixed locale images.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[mobile] version 53 carousel variant with carouselImageCount 1.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[mobile] version 53 carousel variant with carouselImageCount 2.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[mobile] version 53 carousel variant with carouselImageCount 3.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[mobile] version 53 gallery variant with 1 image.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[mobile] version 53 gallery variant with mixed locale images.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[tablet] version 53 carousel variant with carouselImageCount 1.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[tablet] version 53 carousel variant with carouselImageCount 2.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[tablet] version 53 carousel variant with carouselImageCount 3.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[tablet] version 53 gallery variant with 1 image.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
  • packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[tablet] version 53 gallery variant with mixed locale images.png is excluded by !**/*.png, !packages/visual-editor/src/components/testing/screenshots/**
📒 Files selected for processing (63)
  • packages/visual-editor/src/components/Locator.tsx
  • packages/visual-editor/src/components/atoms/image.tsx
  • packages/visual-editor/src/components/contentBlocks/BodyText.tsx
  • packages/visual-editor/src/components/contentBlocks/CtaWrapper.tsx
  • packages/visual-editor/src/components/contentBlocks/HeadingText.tsx
  • packages/visual-editor/src/components/contentBlocks/MapboxStaticMap.tsx
  • packages/visual-editor/src/components/contentBlocks/Phone.tsx
  • packages/visual-editor/src/components/contentBlocks/Text.tsx
  • packages/visual-editor/src/components/contentBlocks/image/HeroImage.tsx
  • packages/visual-editor/src/components/contentBlocks/image/Image.tsx
  • packages/visual-editor/src/components/directory/Directory.tsx
  • packages/visual-editor/src/components/footer/CopyrightMessageSlot.tsx
  • packages/visual-editor/src/components/footer/ExpandedFooter.tsx
  • packages/visual-editor/src/components/footer/FooterExpandedLinkSectionSlot.tsx
  • packages/visual-editor/src/components/footer/FooterExpandedLinksWrapper.tsx
  • packages/visual-editor/src/components/footer/FooterLogoSlot.tsx
  • packages/visual-editor/src/components/footer/SecondaryFooterSlot.tsx
  • packages/visual-editor/src/components/header/HeaderLinks.tsx
  • packages/visual-editor/src/components/header/PrimaryHeaderSlot.tsx
  • packages/visual-editor/src/components/pageSections/AboutSection/AboutSection.tsx
  • packages/visual-editor/src/components/pageSections/AboutSection/AboutSectionDetailsColumn.tsx
  • packages/visual-editor/src/components/pageSections/Banner.tsx
  • packages/visual-editor/src/components/pageSections/Breadcrumbs.tsx
  • packages/visual-editor/src/components/pageSections/CoreInfoSection.tsx
  • packages/visual-editor/src/components/pageSections/EventSection/EventCard.tsx
  • packages/visual-editor/src/components/pageSections/EventSection/EventSection.tsx
  • packages/visual-editor/src/components/pageSections/FAQsSection/FAQCard.tsx
  • packages/visual-editor/src/components/pageSections/FAQsSection/FAQsSection.tsx
  • packages/visual-editor/src/components/pageSections/HeroSection.tsx
  • packages/visual-editor/src/components/pageSections/InsightSection/InsightCard.tsx
  • packages/visual-editor/src/components/pageSections/InsightSection/InsightSection.tsx
  • packages/visual-editor/src/components/pageSections/NearbyLocations/NearbyLocations.tsx
  • packages/visual-editor/src/components/pageSections/PhotoGallerySection/PhotoGallerySection.tsx
  • packages/visual-editor/src/components/pageSections/PhotoGallerySection/PhotoGalleryWrapper.tsx
  • packages/visual-editor/src/components/pageSections/ProductSection/ProductCard.tsx
  • packages/visual-editor/src/components/pageSections/ProductSection/ProductSection.tsx
  • packages/visual-editor/src/components/pageSections/ProfessionalHeroSection.tsx
  • packages/visual-editor/src/components/pageSections/PromoSection/ImmersivePromo.tsx
  • packages/visual-editor/src/components/pageSections/PromoSection/PromoSection.tsx
  • packages/visual-editor/src/components/pageSections/PromoSection/SpotlightPromo.tsx
  • packages/visual-editor/src/components/pageSections/TeamSection/TeamCard.tsx
  • packages/visual-editor/src/components/pageSections/TeamSection/TeamSection.tsx
  • packages/visual-editor/src/components/pageSections/TestimonialSection/TestimonialCard.tsx
  • packages/visual-editor/src/components/pageSections/TestimonialSection/TestimonialSection.tsx
  • packages/visual-editor/src/components/pageSections/VideoSection.tsx
  • packages/visual-editor/src/components/pageSections/heroVariants/ImmersiveHero.tsx
  • packages/visual-editor/src/components/pageSections/heroVariants/SpotlightHero.tsx
  • packages/visual-editor/src/docs/ai/components.d.ts
  • packages/visual-editor/src/editor/YextEntityFieldSelector.tsx
  • packages/visual-editor/src/internal/puck/components/meta-title/metaTitleValidation.test.ts
  • packages/visual-editor/src/internal/puck/components/meta-title/metaTitleValidation.ts
  • packages/visual-editor/src/internal/puck/constant-value-fields/EnhancedCallToAction.tsx
  • packages/visual-editor/src/internal/puck/constant-value-fields/Image.tsx
  • packages/visual-editor/src/internal/puck/constant-value-fields/TextList.tsx
  • packages/visual-editor/src/types/images.ts
  • packages/visual-editor/src/types/types.ts
  • packages/visual-editor/src/utils/plainText.test.ts
  • packages/visual-editor/src/utils/plainText.ts
  • packages/visual-editor/src/utils/resolveComponentData.test.tsx
  • packages/visual-editor/src/utils/resolveComponentData.tsx
  • packages/visual-editor/src/utils/resolveYextEntityField.test.ts
  • packages/visual-editor/src/utils/resolveYextEntityField.ts
  • packages/visual-editor/src/vite-plugin/defaultLayoutData.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/visual-editor/src/types/images.ts`:
- Around line 73-82: The type guard isLocalizedAssetImage currently rejects
locale-only objects because it requires "hasLocalizedValue" or "defaultValue";
update it to treat any non-null object without a top-level "url" as a
LocalizedAssetImage if it either has "hasLocalizedValue"/"defaultValue" OR
contains any key whose value is an object with a string "url" (i.e., detect
locale keys by scanning Object.keys(value) and checking for value[key]?.url as a
string). Apply the same change to the other guard referenced around lines 92-96
so locale-only shapes like { fr: { url: "..." } } are accepted and won't resolve
to undefined.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0647a2a1-a47b-4c0f-852f-3cbc1fe97cb7

📥 Commits

Reviewing files that changed from the base of the PR and between 8fd3018 and c06d303.

📒 Files selected for processing (5)
  • packages/visual-editor/src/components/footer/FooterExpandedLinksWrapper.tsx
  • packages/visual-editor/src/types/images.ts
  • packages/visual-editor/src/utils/plainText.test.ts
  • packages/visual-editor/src/utils/resolveYextEntityField.test.ts
  • packages/visual-editor/src/utils/resolveYextEntityField.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/visual-editor/src/utils/resolveYextEntityField.test.ts
  • packages/visual-editor/src/components/footer/FooterExpandedLinksWrapper.tsx
  • packages/visual-editor/src/utils/plainText.test.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
packages/visual-editor/src/types/images.ts (1)

74-82: ⚠️ Potential issue | 🟠 Major

Guard rejects valid locale-only localized images.

At Line 81, requiring hasLocalizedValue or defaultValue means { fr: { url: "..." } } is not recognized as localized. Downstream, resolveLocalizedAssetImage then returns undefined instead of the locale image.

💡 Proposed fix
 export const isLocalizedAssetImage = (
   value: unknown
 ): value is LocalizedAssetImage => {
-  return (
-    !!value &&
-    typeof value === "object" &&
-    !("url" in value) &&
-    ("hasLocalizedValue" in value || "defaultValue" in value)
-  );
+  if (!value || typeof value !== "object" || "url" in value) {
+    return false;
+  }
+
+  const record = value as Record<string, unknown>;
+  if ("hasLocalizedValue" in record || "defaultValue" in record) {
+    return true;
+  }
+
+  return Object.entries(record).some(
+    ([key, entry]) =>
+      key !== "hasLocalizedValue" &&
+      key !== "defaultValue" &&
+      !!entry &&
+      typeof entry === "object" &&
+      "url" in (entry as Record<string, unknown>)
+  );
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/src/types/images.ts` around lines 74 - 82, The type
guard is too strict: update isLocalizedAssetImage to recognize locale-keyed
objects like { fr: { url: "..." } } by removing the requirement for
"hasLocalizedValue" or "defaultValue"; instead, simply ensure value is a
non-null object (not an array) and does NOT have a top-level "url" property (so
functions like resolveLocalizedAssetImage will accept locale-only shapes).
Modify the isLocalizedAssetImage implementation accordingly (referencing the
isLocalizedAssetImage function and ensuring compatibility with
resolveLocalizedAssetImage).
packages/visual-editor/src/docs/ai/components.d.ts (1)

1124-1129: ⚠️ Potential issue | 🟠 Major

LocalizedAssetImage still rejects hasLocalizedValue: "true" under the index signature.

Line 1128’s index signature excludes "true", so Line 1125 becomes incompatible in TypeScript when that property is present.

Proposed fix
 type LocalizedAssetImage = {
   hasLocalizedValue?: "true";
   defaultValue?: AssetImageType;
 } & {
-  [locale: string]: AssetImageType | undefined;
+  [locale: string]: AssetImageType | "true" | undefined;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/src/docs/ai/components.d.ts` around lines 1124 - 1129,
The index signature in LocalizedAssetImage ([locale: string]: AssetImageType |
undefined) conflicts with the explicit property hasLocalizedValue?: "true"
because string keys include "hasLocalizedValue"; update the index signature to
allow the "true" literal as a possible value (e.g. [locale: string]:
AssetImageType | "true" | undefined) so hasLocalizedValue?: "true" is compatible
without changing property names or shapes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/visual-editor/src/docs/ai/components.d.ts`:
- Around line 1124-1129: The index signature in LocalizedAssetImage ([locale:
string]: AssetImageType | undefined) conflicts with the explicit property
hasLocalizedValue?: "true" because string keys include "hasLocalizedValue";
update the index signature to allow the "true" literal as a possible value (e.g.
[locale: string]: AssetImageType | "true" | undefined) so hasLocalizedValue?:
"true" is compatible without changing property names or shapes.

In `@packages/visual-editor/src/types/images.ts`:
- Around line 74-82: The type guard is too strict: update isLocalizedAssetImage
to recognize locale-keyed objects like { fr: { url: "..." } } by removing the
requirement for "hasLocalizedValue" or "defaultValue"; instead, simply ensure
value is a non-null object (not an array) and does NOT have a top-level "url"
property (so functions like resolveLocalizedAssetImage will accept locale-only
shapes). Modify the isLocalizedAssetImage implementation accordingly
(referencing the isLocalizedAssetImage function and ensuring compatibility with
resolveLocalizedAssetImage).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2b8fe3c0-13bf-4aa3-a75b-c05af6e02323

📥 Commits

Reviewing files that changed from the base of the PR and between c06d303 and e6257b5.

📒 Files selected for processing (2)
  • packages/visual-editor/src/docs/ai/components.d.ts
  • packages/visual-editor/src/types/images.ts

@asanehisa asanehisa requested a review from mkilpatrick March 5, 2026 18:51
@asanehisa asanehisa merged commit 3fa23f7 into main Mar 6, 2026
18 checks passed
@asanehisa asanehisa deleted the default-data branch March 6, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

create-dev-release Triggers dev release workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants