Skip to content

feat: customized result styles per entity type#1058

Open
anguyen-yext2 wants to merge 8 commits intomainfrom
result-styles-per-entity-type
Open

feat: customized result styles per entity type#1058
anguyen-yext2 wants to merge 8 commits intomainfrom
result-styles-per-entity-type

Conversation

@anguyen-yext2
Copy link
Contributor

@anguyen-yext2 anguyen-yext2 commented Feb 24, 2026

For each entity type, user can customize pin icon, pin color, and result card.
When a pin is selected, apply gray100 (#F9F9F9) to the corresponding result card's background color.
Hide primary CTA in result card if entity scope is not attached to an entity pageset

J=WAT-5362
TEST=manual

tested in dev mode

For each entity type, user can customize: pin icon, pin color, and result card.
When a pin is selected, apply gray100 (#F9F9F9) color to the corresponding result card's background.

TODOs:
- use actual entity types from the document once we support multiple entity types per page set
- hide primary CTA in result card for standalone locator

J=WAT-5362
TEST=manual

tested in dev mode
@anguyen-yext2 anguyen-yext2 requested a review from a team February 24, 2026 16:34
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 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

Adds per-entity-type styling and per-entity result-card configuration to the visual-editor Locator. LocatorProps gain locationStyles and resultCard is now an array of per-entity entries; LocatorComponent adds resolveFields and resolveData. Map and LocatorMapPin accept locationStyleConfig to render pins with entity-specific color/icon. MapPinIcon, LocatorResultCard, Combobox, locator utils, StreamDocument type, many locale JSONs, and THIRD-PARTY-NOTICES/package.json were updated.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Locator as LocatorComponent
    participant Doc as StreamDocumentStore
    participant Map as Map
    participant Pin as LocatorMapPin

    User->>Locator: open locator / apply filters
    Locator->>Doc: request documents (resolveFields/resolveData)
    Doc-->>Locator: return documents (with entityType, location, metadata)
    Locator->>Locator: infer entityTypes & build locationStyleConfig
    Locator->>Map: send markers + locationStyleConfig + resultCard props
    Map->>Pin: render each marker with color/icon based on entityType
    Pin-->>Map: rendered pin (SVG/image)
    Map-->>User: display map with per-entity pins and result cards
Loading

Possibly related PRs

Suggested labels

create-dev-release

Suggested reviewers

  • benlife5
  • mkilpatrick
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: customized result styles per entity type' accurately summarizes the main feature being added: per-entity-type customization of result styles (pin icons, colors, and result cards).
Description check ✅ Passed The description is directly related to the changeset, detailing specific features added: per-entity-type customization of pin icons, colors, result cards, gray background styling on pin selection, and CTA hiding logic.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch result-styles-per-entity-type

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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/visual-editor/src/internal/puck/ui/Combobox.tsx (1)

92-124: ⚠️ Potential issue | 🟠 Major

Move hook usage out of the map to satisfy Rules of Hooks.

useState inside group.options.map(...) violates React's hook rules and can throw when the options list changes between renders. Please refactor the per-option state into a child component.

♻️ Suggested refactor (extract option row component)
-                  {group.options.map((option) => {
-                    const [colorHexValue, setColorHexValue] = React.useState<
-                      string | undefined
-                    >(undefined);
-
-                    return (
-                      <CommandItem
-                        className="ve-cursor-pointer"
-                        key={option.label}
-                        value={option.value}
-                        onSelect={() => {
-                          onChange(option.value);
-                          setOpen(false);
-                        }}
-                      >
-                        <Check
-                          className={cn(
-                            "ve-mr-2 ve-h-4 ve-w-4",
-                            selectedOption.value === option.value
-                              ? "ve-opacity-100"
-                              : "ve-opacity-0"
-                          )}
-                        />
-                        <ColorIndicator
-                          color={option.color}
-                          setHexValue={setColorHexValue}
-                        />
-                        {option.icon && (
-                          <img
-                            src={option.icon}
-                            alt={option.label}
-                            className="ve-w-4 ve-h-4 ve-mr-2"
-                          />
-                        )}
-                        <div className="ve-flex ve-flex-col ve-gap-0.5">
-                          {option.label}
-                          {colorHexValue && (
-                            <span className="ve-text-sm ve-text-[`#5B5D60`]">
-                              {colorHexValue}
-                            </span>
-                          )}
-                        </div>
-                      </CommandItem>
-                    );
-                  })}
+                  {group.options.map((option) => (
+                    <ComboboxOptionItem
+                      key={option.label}
+                      option={option}
+                      selected={selectedOption.value === option.value}
+                      onSelect={() => {
+                        onChange(option.value);
+                        setOpen(false);
+                      }}
+                    />
+                  ))}
+const ComboboxOptionItem = ({
+  option,
+  selected,
+  onSelect,
+}: {
+  option: ComboboxProps["selectedOption"];
+  selected: boolean;
+  onSelect: () => void;
+}) => {
+  const [colorHexValue, setColorHexValue] = React.useState<string | undefined>(
+    undefined
+  );
+
+  return (
+    <CommandItem className="ve-cursor-pointer" value={option.value} onSelect={onSelect}>
+      <Check
+        className={cn(
+          "ve-mr-2 ve-h-4 ve-w-4",
+          selected ? "ve-opacity-100" : "ve-opacity-0"
+        )}
+      />
+      <ColorIndicator color={option.color} setHexValue={setColorHexValue} />
+      {option.icon && (
+        <img
+          src={option.icon}
+          alt={option.label}
+          className="ve-w-4 ve-h-4 ve-mr-2"
+        />
+      )}
+      <div className="ve-flex ve-flex-col ve-gap-0.5">
+        {option.label}
+        {colorHexValue && (
+          <span className="ve-text-sm ve-text-[`#5B5D60`]">{colorHexValue}</span>
+        )}
+      </div>
+    </CommandItem>
+  );
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/src/internal/puck/ui/Combobox.tsx` around lines 92 -
124, You have a useState (colorHexValue/setColorHexValue) being called inside
group.options.map which violates the Rules of Hooks; extract the per-option UI
into a new child component (e.g., OptionRow) that receives props: option,
selectedOption, onChange, setOpen (and any needed handlers), move the useState
hook (colorHexValue/setColorHexValue) and the ColorIndicator usage into that
child, and replace the inline JSX inside group.options.map with <OptionRow
key={option.label} ... /> so the hook is declared consistently at the top level
of the child component rather than inside the map.
🧹 Nitpick comments (1)
packages/visual-editor/src/components/LocatorResultCard.tsx (1)

784-786: TODO: finalize primary CTA behavior for standalone locator.
Consider tracking this as an issue (or implementing before merge) so it doesn’t ship as a TODO.

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

In `@packages/visual-editor/src/components/LocatorResultCard.tsx` around lines 784
- 786, The TODO comment regarding hiding the primary CTA for standalone locator
in the LocatorResultCard component needs to be resolved before merging. Either
implement the described functionality to conditionally hide the primary CTA for
standalone locator scenarios (by adding the necessary logic around the
props.primaryCTA.liveVisibility check), or create a tracking issue for this work
and remove the inline TODO comment to prevent shipping incomplete work.
🤖 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/components/Locator.tsx`:
- Around line 1794-1806: The memo in LocatorMapPin is missing
locationStyleConfig from its dependency list so changes to locationStyleConfig
won’t update color/icon; update the React.useMemo dependencies to include
locationStyleConfig (or remove the memo entirely) and ensure you reference
entityLocationStyle inside the memo callback so color and icon are recomputed
when locationStyleConfig changes; target the LocatorMapPin component and the
useMemo that returns { color, icon } to make this change.
- Around line 1214-1238: mapProps is only initialized once so updates to
locationStylesConfig (from locationStyles) don’t propagate; add a
React.useEffect that watches locationStylesConfig and calls setMapProps(prev =>
({ ...prev, locationStyleConfig })) to merge the new config into the existing
MapProps without overwriting other fields like center/zoom. Reference the
existing symbols locationStylesConfig, mapProps, setMapProps and the MapProps
shape to ensure only the locationStyleConfig key is replaced.

In `@packages/visual-editor/src/components/MapPinIcon.tsx`:
- Around line 123-143: The JSX branch in MapPinIcon that renders the numeric pin
uses a truthy check "resultIndex &&" which prevents rendering when resultIndex
is 0; update the conditional to explicitly check for non-null/undefined (e.g.,
"resultIndex != null &&") so zero indexes render correctly. Locate the
conditional in the MapPinIcon component (the block that renders <image ... />
else the <text ...>{resultIndex}</text>) and replace the truthy guard with an
explicit null/undefined check while preserving the existing rendering logic and
styles.

In `@packages/visual-editor/src/internal/puck/ui/Combobox.tsx`:
- Around line 42-47: The icon image tags (e.g., the <img> rendering for
selectedOption.icon in Combobox.tsx and the similar image used for list options)
are currently using the visible label as alt text which causes duplicate
screen-reader announcements; make decorative icons non‑semantic by setting
alt="" and adding aria-hidden="true" (and keep the existing className), so
update the <img> for selectedOption.icon and the corresponding option icon
render (lines around the option list) to use alt="" and aria-hidden="true"
instead of the label.

---

Outside diff comments:
In `@packages/visual-editor/src/internal/puck/ui/Combobox.tsx`:
- Around line 92-124: You have a useState (colorHexValue/setColorHexValue) being
called inside group.options.map which violates the Rules of Hooks; extract the
per-option UI into a new child component (e.g., OptionRow) that receives props:
option, selectedOption, onChange, setOpen (and any needed handlers), move the
useState hook (colorHexValue/setColorHexValue) and the ColorIndicator usage into
that child, and replace the inline JSX inside group.options.map with <OptionRow
key={option.label} ... /> so the hook is declared consistently at the top level
of the child component rather than inside the map.

---

Nitpick comments:
In `@packages/visual-editor/src/components/LocatorResultCard.tsx`:
- Around line 784-786: The TODO comment regarding hiding the primary CTA for
standalone locator in the LocatorResultCard component needs to be resolved
before merging. Either implement the described functionality to conditionally
hide the primary CTA for standalone locator scenarios (by adding the necessary
logic around the props.primaryCTA.liveVisibility check), or create a tracking
issue for this work and remove the inline TODO comment to prevent shipping
incomplete work.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 441f37a and 50dc780.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (31)
  • packages/visual-editor/locales/platform/cs/visual-editor.json
  • packages/visual-editor/locales/platform/da/visual-editor.json
  • packages/visual-editor/locales/platform/de/visual-editor.json
  • packages/visual-editor/locales/platform/en-GB/visual-editor.json
  • packages/visual-editor/locales/platform/en/visual-editor.json
  • packages/visual-editor/locales/platform/es/visual-editor.json
  • packages/visual-editor/locales/platform/et/visual-editor.json
  • packages/visual-editor/locales/platform/fi/visual-editor.json
  • packages/visual-editor/locales/platform/fr/visual-editor.json
  • packages/visual-editor/locales/platform/hr/visual-editor.json
  • packages/visual-editor/locales/platform/hu/visual-editor.json
  • packages/visual-editor/locales/platform/it/visual-editor.json
  • packages/visual-editor/locales/platform/ja/visual-editor.json
  • packages/visual-editor/locales/platform/lt/visual-editor.json
  • packages/visual-editor/locales/platform/lv/visual-editor.json
  • packages/visual-editor/locales/platform/nb/visual-editor.json
  • packages/visual-editor/locales/platform/nl/visual-editor.json
  • packages/visual-editor/locales/platform/pl/visual-editor.json
  • packages/visual-editor/locales/platform/pt/visual-editor.json
  • packages/visual-editor/locales/platform/ro/visual-editor.json
  • packages/visual-editor/locales/platform/sk/visual-editor.json
  • packages/visual-editor/locales/platform/sv/visual-editor.json
  • packages/visual-editor/locales/platform/tr/visual-editor.json
  • packages/visual-editor/locales/platform/zh-TW/visual-editor.json
  • packages/visual-editor/locales/platform/zh/visual-editor.json
  • packages/visual-editor/package.json
  • packages/visual-editor/src/components/Locator.tsx
  • packages/visual-editor/src/components/LocatorResultCard.tsx
  • packages/visual-editor/src/components/MapPinIcon.tsx
  • packages/visual-editor/src/internal/puck/ui/Combobox.tsx
  • packages/visual-editor/src/internal/types/combobox.ts

@github-actions
Copy link
Contributor

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.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/visual-editor/src/components/MapPinIcon.tsx (1)

52-145: ⚠️ Potential issue | 🟠 Major

Enforce resultIndex ≥ 1 before rendering pin labels.
Right now any non-positive value silently suppresses the label. Please add a guard/default here (and a small regression test) so invalid indices can’t slip through.

🔧 Suggested guard
 export const MapPinIcon = ({
   color,
   resultIndex,
   icon,
   selected,
 }: {
   color?: BackgroundStyle;
   resultIndex?: number;
   icon?: string;
   selected?: boolean;
 }) => {
+  const safeResultIndex =
+    typeof resultIndex === "number" && resultIndex >= 1
+      ? resultIndex
+      : undefined;
+
   const pinHeight = 41;
   const pinWidth = 27;
   const pinScale = 1.5;
@@
-      ) : (
-        !!resultIndex && (
+      ) : (
+        safeResultIndex != null && (
           <text
             textAnchor="middle"
             fontWeight="bold"
             fontSize={iconSize}
             x="50%"
             y="50%"
             fill={contrastColor ?? "white"}
           >
-            {resultIndex}
+            {safeResultIndex}
           </text>
         )
       )}

Based on learnings: In the visual-editor codebase, ensure that the resultIndex prop used by MapPinIcon (and related components) is always >= 1. Do not allow 0 or negative values. enforce via type definitions, default values, and runtime checks, and add tests to guard against regression. This guideline applies to MapPinIcon.tsx and any closely related components in packages/visual-editor/src/components.

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

In `@packages/visual-editor/src/components/MapPinIcon.tsx` around lines 52 - 145,
MapPinIcon currently lets zero/negative resultIndex silently suppress labels;
update the component to enforce resultIndex >= 1 by (1) changing the prop
handling in MapPinIcon to compute a validatedIndex (e.g., const validatedIndex =
typeof resultIndex === "number" && resultIndex >= 1 ? resultIndex : undefined)
and use validatedIndex for the label rendering branch instead of resultIndex,
(2) add a default/prop-type note so callers know resultIndex must be >=1, and
(3) add a small regression test (e.g., MapPinIcon.test.tsx) that verifies
positive indices render the <text> label and that 0 or negative values do not
render a label (and that a missing prop behaves the same as invalid values).
🧹 Nitpick comments (1)
packages/visual-editor/src/components/LocatorResultCard.tsx (1)

795-809: Track the standalone-locator CTA TODO.
If this is still on the roadmap, consider converting the TODO into a tracked ticket/flag so it doesn’t get lost.

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

In `@packages/visual-editor/src/components/LocatorResultCard.tsx` around lines 795
- 809, The comment flags a TODO to hide the primary CTA for standalone locators;
create a tracked work item and reference it in-code: open a ticket/flag in your
issue tracker and add the ticket ID next to the existing TODO in
LocatorResultCard (the block conditional on props.primaryCTA.liveVisibility
rendering the CTA component), e.g., replace the plain TODO with "TODO: hide
primary CTA for standalone locator — tracking: <ISSUE_ID>", and if desired wire
a feature flag or prop (e.g., add a prop/flag like hideStandaloneLocatorCTA or
use an existing feature flag) to the CTA render logic (CTA with link
resolvedUrl, label resolution via resolveComponentData, variant from
props.primaryCTA, and onClick handleVisitPageClick) so the behavior can be
toggled from the tracked ticket rather than remaining an untracked comment.
🤖 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/components/Locator.tsx`:
- Around line 181-184: getEntityType currently calls useDocument (via const
entityDocument: StreamDocument = useDocument()) but getEntityType is invoked
inside the Puck callback getOptions which violates Hooks rules; refactor by
removing useDocument from getEntityType: obtain the entity type earlier from
resolveFields metadata (or call useDocument in the surrounding React
component/custom hook) and pass that value into getEntityType via a closure or
update the field configuration dynamically so getOptions receives the
pre-derived entityType; update references to getEntityType, getOptions and
resolveFields accordingly to use the passed-in entityType rather than calling
useDocument inside getEntityType.

---

Outside diff comments:
In `@packages/visual-editor/src/components/MapPinIcon.tsx`:
- Around line 52-145: MapPinIcon currently lets zero/negative resultIndex
silently suppress labels; update the component to enforce resultIndex >= 1 by
(1) changing the prop handling in MapPinIcon to compute a validatedIndex (e.g.,
const validatedIndex = typeof resultIndex === "number" && resultIndex >= 1 ?
resultIndex : undefined) and use validatedIndex for the label rendering branch
instead of resultIndex, (2) add a default/prop-type note so callers know
resultIndex must be >=1, and (3) add a small regression test (e.g.,
MapPinIcon.test.tsx) that verifies positive indices render the <text> label and
that 0 or negative values do not render a label (and that a missing prop behaves
the same as invalid values).

---

Nitpick comments:
In `@packages/visual-editor/src/components/LocatorResultCard.tsx`:
- Around line 795-809: The comment flags a TODO to hide the primary CTA for
standalone locators; create a tracked work item and reference it in-code: open a
ticket/flag in your issue tracker and add the ticket ID next to the existing
TODO in LocatorResultCard (the block conditional on
props.primaryCTA.liveVisibility rendering the CTA component), e.g., replace the
plain TODO with "TODO: hide primary CTA for standalone locator — tracking:
<ISSUE_ID>", and if desired wire a feature flag or prop (e.g., add a prop/flag
like hideStandaloneLocatorCTA or use an existing feature flag) to the CTA render
logic (CTA with link resolvedUrl, label resolution via resolveComponentData,
variant from props.primaryCTA, and onClick handleVisitPageClick) so the behavior
can be toggled from the tracked ticket rather than remaining an untracked
comment.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50dc780 and 4321f6e.

📒 Files selected for processing (32)
  • packages/visual-editor/THIRD-PARTY-NOTICES
  • packages/visual-editor/locales/platform/cs/visual-editor.json
  • packages/visual-editor/locales/platform/da/visual-editor.json
  • packages/visual-editor/locales/platform/de/visual-editor.json
  • packages/visual-editor/locales/platform/en-GB/visual-editor.json
  • packages/visual-editor/locales/platform/en/visual-editor.json
  • packages/visual-editor/locales/platform/es/visual-editor.json
  • packages/visual-editor/locales/platform/et/visual-editor.json
  • packages/visual-editor/locales/platform/fi/visual-editor.json
  • packages/visual-editor/locales/platform/fr/visual-editor.json
  • packages/visual-editor/locales/platform/hr/visual-editor.json
  • packages/visual-editor/locales/platform/hu/visual-editor.json
  • packages/visual-editor/locales/platform/it/visual-editor.json
  • packages/visual-editor/locales/platform/ja/visual-editor.json
  • packages/visual-editor/locales/platform/lt/visual-editor.json
  • packages/visual-editor/locales/platform/lv/visual-editor.json
  • packages/visual-editor/locales/platform/nb/visual-editor.json
  • packages/visual-editor/locales/platform/nl/visual-editor.json
  • packages/visual-editor/locales/platform/pl/visual-editor.json
  • packages/visual-editor/locales/platform/pt/visual-editor.json
  • packages/visual-editor/locales/platform/ro/visual-editor.json
  • packages/visual-editor/locales/platform/sk/visual-editor.json
  • packages/visual-editor/locales/platform/sv/visual-editor.json
  • packages/visual-editor/locales/platform/tr/visual-editor.json
  • packages/visual-editor/locales/platform/zh-TW/visual-editor.json
  • packages/visual-editor/locales/platform/zh/visual-editor.json
  • packages/visual-editor/package.json
  • packages/visual-editor/src/components/Locator.tsx
  • packages/visual-editor/src/components/LocatorResultCard.tsx
  • packages/visual-editor/src/components/MapPinIcon.tsx
  • packages/visual-editor/src/docs/components.md
  • packages/visual-editor/src/internal/puck/ui/Combobox.tsx
✅ Files skipped from review due to trivial changes (2)
  • packages/visual-editor/locales/platform/pl/visual-editor.json
  • packages/visual-editor/locales/platform/hr/visual-editor.json
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/visual-editor/package.json
  • packages/visual-editor/locales/platform/cs/visual-editor.json
  • packages/visual-editor/src/internal/puck/ui/Combobox.tsx
  • packages/visual-editor/locales/platform/en/visual-editor.json
  • packages/visual-editor/locales/platform/ja/visual-editor.json
  • packages/visual-editor/locales/platform/fr/visual-editor.json
  • packages/visual-editor/locales/platform/zh/visual-editor.json
  • packages/visual-editor/locales/platform/lt/visual-editor.json
  • packages/visual-editor/locales/platform/nb/visual-editor.json

…ta when there is no entity scope is not attached to a pageset
@anguyen-yext2 anguyen-yext2 changed the title WIP: feat: customized result styles per entity type feat: customized result styles per entity type Feb 25, 2026
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

♻️ Duplicate comments (1)
packages/visual-editor/src/components/Locator.tsx (1)

182-185: ⚠️ Potential issue | 🔴 Critical

Do not call useDocument() from getOptions execution flow.

getOptions is a framework callback, not a React component/custom hook. Calling getEntityType() there causes a hook call outside valid hook context.

🛠️ Refactor direction
-const getEntityType = (entityTypeEnvVar?: string) => {
-  const entityDocument: StreamDocument = useDocument();
-  return getEntityTypeFromDocument(entityDocument, entityTypeEnvVar);
-};
+const getEntityType = (
+  entityDocument: StreamDocument,
+  entityTypeEnvVar?: string
+) => getEntityTypeFromDocument(entityDocument, entityTypeEnvVar);

Then pass a precomputed entity type into getOptions (e.g., via resolveFields + setDeep) instead of invoking a hook during callback execution.

Based on learnings: Do not use React hooks inside arbitrary callback functions; hooks must only run at the top level of React components or custom hooks.

Also applies to: 760-763

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

In `@packages/visual-editor/src/components/Locator.tsx` around lines 182 - 185,
getOptions is calling getEntityType which invokes the React hook useDocument
(via getEntityTypeFromDocument) outside a component/hook context; remove hook
usage from getOptions by computing the entity type earlier in component/render
flow (using useDocument inside the component or a custom hook) and pass that
precomputed value into getOptions. Specifically, stop calling
getEntityType/useDocument from within getOptions and instead derive the entity
type in the component (or a resolveFields callback) and supply it to getOptions
(e.g., pass the entityType into getOptions or use resolveFields + setDeep to
inject it) so getEntityTypeFromDocument is only used where hooks are allowed.
🧹 Nitpick comments (1)
packages/visual-editor/src/components/Locator.tsx (1)

1918-1922: Normalize result.index before passing it to MapPinIcon.

This callsite forwards result.index directly without enforcing >= 1. Add a local clamp/fallback to preserve the pin-index contract.

🛠️ Proposed safeguard
 const LocatorMapPin = <T,>(props: LocatorMapPinProps<T>) => {
   const { result, selected, locationStyleConfig } = props;
+  const safeResultIndex =
+    typeof result.index === "number" && result.index >= 1 ? result.index : 1;
   const entityType = result.entityType;
   const entityLocationStyle = entityType
     ? locationStyleConfig?.[entityType]
     : undefined;

   return (
     <MapPinIcon
       color={entityLocationStyle?.color}
-      resultIndex={result.index}
+      resultIndex={safeResultIndex}
       icon={entityLocationStyle?.icon}
       selected={selected}
     />
   );
 };

Based on learnings: In closely related map-pin components, resultIndex should always be enforced as >= 1.

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

In `@packages/visual-editor/src/components/Locator.tsx` around lines 1918 - 1922,
Clamp/fallback result.index before passing to the MapPinIcon resultIndex prop:
ensure the value is normalized to be >= 1 (e.g. compute a local normalizedIndex
= max(1, result.index || 1)) and pass normalizedIndex to MapPinIcon instead of
result.index; update the callsite in the Locator.tsx component where MapPinIcon
is rendered so the pin-index contract is preserved.
🤖 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/utils/locator.ts`:
- Around line 13-23: The function that reads locatorSourcePageSets currently
returns undefined when parsing succeeds but yields no valid entityType values;
update the logic in the block that computes pageSetMap/entityTypes so that on
successful parse you return an array (Array.from(new Set(entityTypes))) even if
it's empty (i.e., return [] when entityTypes.length === 0) instead of falling
through to undefined, while keeping the existing catch branch to log and return
undefined on parse errors; reference the variables locatorSourcePageSets,
pageSetMap, and entityTypes (and downstream ResultCardPropsField) to locate
where to change the return.

---

Duplicate comments:
In `@packages/visual-editor/src/components/Locator.tsx`:
- Around line 182-185: getOptions is calling getEntityType which invokes the
React hook useDocument (via getEntityTypeFromDocument) outside a component/hook
context; remove hook usage from getOptions by computing the entity type earlier
in component/render flow (using useDocument inside the component or a custom
hook) and pass that precomputed value into getOptions. Specifically, stop
calling getEntityType/useDocument from within getOptions and instead derive the
entity type in the component (or a resolveFields callback) and supply it to
getOptions (e.g., pass the entityType into getOptions or use resolveFields +
setDeep to inject it) so getEntityTypeFromDocument is only used where hooks are
allowed.

---

Nitpick comments:
In `@packages/visual-editor/src/components/Locator.tsx`:
- Around line 1918-1922: Clamp/fallback result.index before passing to the
MapPinIcon resultIndex prop: ensure the value is normalized to be >= 1 (e.g.
compute a local normalizedIndex = max(1, result.index || 1)) and pass
normalizedIndex to MapPinIcon instead of result.index; update the callsite in
the Locator.tsx component where MapPinIcon is rendered so the pin-index contract
is preserved.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4321f6e and 7076087.

📒 Files selected for processing (4)
  • packages/visual-editor/src/components/Locator.tsx
  • packages/visual-editor/src/components/LocatorResultCard.tsx
  • packages/visual-editor/src/utils/locator.ts
  • packages/visual-editor/src/utils/types/StreamDocument.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/visual-editor/src/components/LocatorResultCard.tsx

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: 10

♻️ Duplicate comments (1)
packages/visual-editor/src/components/Locator.tsx (1)

182-185: ⚠️ Potential issue | 🔴 Critical

useDocument() is still being called through a non-hook helper inside getOptions.

getEntityType() (Line 182) calls useDocument() (Line 183), and that helper is invoked from facetFields.getOptions (Line 803), which is an arbitrary callback path. This can break Rules of Hooks.

Based on learnings: Do not use React hooks inside arbitrary callback functions; hooks must be called at the top level of React components or custom hooks.

Also applies to: 803-806

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

In `@packages/visual-editor/src/components/Locator.tsx` around lines 182 - 185,
getEntityType currently calls the React hook useDocument inside a helper that’s
invoked from an arbitrary callback (facetFields.getOptions), which violates the
Rules of Hooks; fix by removing the hook call from getEntityType and instead
call useDocument at the top level of the component or inside a custom hook, then
pass the resulting document into getEntityType (or rename to
getEntityTypeFromDoc) from that top-level location; specifically, update
getEntityType to accept a StreamDocument parameter (or create a pure
getEntityTypeFromDocument(document, entityTypeEnvVar)) and change the code path
that defines facetFields.getOptions to capture the document from useDocument()
at the component/custom-hook top level and supply it to getEntityType when
invoking the options callback.
🧹 Nitpick comments (10)
packages/visual-editor/locales/platform/es/visual-editor.json (1)

405-407: Consider using map-specific terminology for pin labels in Spanish.

For UI clarity, pinColor/pinIcon may read more naturally as “marcador” (map marker) instead of “alfiler”.

✍️ Suggested wording tweak
-    "pinColor": "Color del alfiler",
-    "pinIcon": "Icono de alfiler",
+    "pinColor": "Color del marcador",
+    "pinIcon": "Ícono del marcador",
     "pinStyles": "Estilos de ubicación",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/locales/platform/es/visual-editor.json` around lines
405 - 407, Replace the literal translations for the map pin labels to use
map-specific terminology: update the values for "pinColor" and "pinIcon" (and
optionally "pinStyles") in the visual-editor.json locale to use "marcador"
instead of "alfiler" (e.g., "Color del marcador", "Icono de marcador", "Estilos
de marcador") to improve UI clarity while keeping the same keys "pinColor",
"pinIcon", and "pinStyles".
packages/visual-editor/locales/platform/sk/visual-editor.json (1)

331-371: Normalize option label capitalization for UI consistency.

Line 340 ("hotely") and Line 366 ("žiadne") are lowercase while neighboring dropdown labels are title-cased, so these two entries will render inconsistently.

💡 Suggested localization tweak
-      "hotels": "hotely",
+      "hotels": "Hotely",
...
-      "none": "žiadne",
+      "none": "Žiadne",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/locales/platform/sk/visual-editor.json` around lines
331 - 371, The entries for the "hotels" and "none" localization keys are
lowercased and should be title-cased to match neighboring dropdown labels;
update the value for key "hotels" from "hotely" to "Hotely" and the value for
key "none" from "žiadne" to "Žiadne" in the visual-editor.json so UI labels are
consistent.
packages/visual-editor/locales/platform/zh/visual-editor.json (1)

357-357: Prefer consistent wording for “locations” in this locale

Line 357 uses 地点, while many existing strings in this same file use 位置 for location context. Consider standardizing to one term for UI consistency.

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

In `@packages/visual-editor/locales/platform/zh/visual-editor.json` at line 357,
The locale key "locations" in
packages/visual-editor/locales/platform/zh/visual-editor.json currently maps to
"地点" but other entries in this file use "位置"; update the value for the
"locations" key to "位置" to keep UI wording consistent (search for other
location-related keys like "location", "locations", or similar to ensure uniform
terminology across the file).
packages/visual-editor/locales/platform/ja/visual-editor.json (1)

357-357: Consider term consistency for locations in JA.

fields.options.locations is 所在地, while many nearby user-facing strings use 場所. Aligning terminology would keep the UI vocabulary more consistent.

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

In `@packages/visual-editor/locales/platform/ja/visual-editor.json` at line 357,
Update the JSON value for the key "locations" in visual-editor.json to use the
term "場所" (replacing "所在地") so it matches nearby user-facing strings and the
existing vocabulary; locate the "locations" key in the locales JSON and change
its value to "場所" to ensure term consistency across fields.options.locations and
related UI labels.
packages/visual-editor/locales/platform/sv/visual-editor.json (1)

331-331: "Finansiella proffs" is colloquial – consider a more formal register.

"Proffs" is a casual borrowing from English "pros". For a professional-facing CMS editor, "Finansiella yrkesverksamma" or "Finansproffs" (the latter is at least a set compound) would read more naturally in a formal Swedish UI context. Low-priority nit, defer if consistent with other locales in the PR.

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

In `@packages/visual-editor/locales/platform/sv/visual-editor.json` at line 331,
Replace the colloquial Swedish value for the "financialProfessionals" key with a
more formal wording; locate the "financialProfessionals" entry and change its
value from "Finansiella proffs" to a formal alternative such as "Finansiella
yrkesverksamma" (or "Finansproffs" if you prefer a compound), ensuring the JSON
string is updated consistently with other locale choices.
packages/visual-editor/locales/platform/de/visual-editor.json (1)

366-366: Consider unifying the “none” wording within the same locale file.

Line 366 uses "Keiner" while another none option in this file (Line 682) is "Keine". If both represent the same “no selection” concept, standardizing avoids inconsistent UI copy.

💡 Suggested tweak
-      "none": "Keiner",
+      "none": "Keine",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/locales/platform/de/visual-editor.json` at line 366,
Two entries use different German translations for the same JSON key "none"
("Keiner" vs "Keine"); pick the correct gender/grammar for the intended UI
context and make both "none" entries identical. Search for the JSON key "none"
in the visual-editor locale (the two occurrences shown) and replace the
inconsistent string so both entries use the chosen translation (e.g., change
"Keiner" to "Keine" or vice versa) to ensure consistent UI copy.
packages/visual-editor/locales/platform/cs/visual-editor.json (1)

340-340: Option label capitalization is inconsistent with adjacent entries.

Line 340 uses "hotely" while neighboring newly added category labels are capitalized. Consider capitalizing for consistent dropdown presentation.

💡 Suggested tweak
-      "hotels": "hotely",
+      "hotels": "Hotely",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/locales/platform/cs/visual-editor.json` at line 340,
The "hotels" option label in the locales JSON is inconsistent with adjacent
capitalized category labels; update the value for the "hotels" key in
packages/visual-editor/locales/platform/cs/visual-editor.json from "hotely" to a
capitalized form (e.g., "Hotely") so dropdown labels match the capitalization
style used by neighboring entries.
packages/visual-editor/locales/platform/en-GB/visual-editor.json (1)

407-407: Consider consistent title casing for this field label.

Line 407 uses "Location styles" while neighboring labels are title-cased ("Pin Color", "Pin Icon"). Using "Location Styles" would keep UI label style consistent.

💡 Suggested tweak
-    "pinStyles": "Location styles",
+    "pinStyles": "Location Styles",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/locales/platform/en-GB/visual-editor.json` at line
407, Update the "pinStyles" label value to use title case for consistency with
neighboring labels: change the value for the key "pinStyles" from "Location
styles" to "Location Styles" (adjust the string literal in the
visual-editor.json entry for pinStyles).
packages/visual-editor/locales/platform/et/visual-editor.json (1)

405-405: Pin label terminology/casing looks inconsistent with adjacent new labels.

Line 405 ("Pin Värv") differs in style/term from Line 406 ("Kinnitusikoon") and Line 407 ("Asukoha stiilid"). Consider aligning terminology within this pin-settings group for a more coherent UI.

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

In `@packages/visual-editor/locales/platform/et/visual-editor.json` at line 405,
The "pinColor" label value "Pin Värv" is inconsistent with adjacent labels;
update the JSON value for the "pinColor" key to use the same Estonian
terminology/casing as the neighboring entries (e.g., change "Pin Värv" to
"Kinnitus värv" to match the "Kinnitusikoon" / "Asukoha stiilid" style).
packages/visual-editor/src/components/Locator.tsx (1)

1979-1981: Enforce resultIndex >= 1 before passing to MapPinIcon.

Line 1980 forwards result.index directly. Please clamp/validate at this boundary to guarantee the MapPinIcon contract.

🛠️ Proposed guard
+  const safeResultIndex =
+    typeof result.index === "number" && result.index > 0 ? result.index : 1;
...
-      resultIndex={result.index}
+      resultIndex={safeResultIndex}

Based on learnings: ensure the resultIndex prop used by MapPinIcon (and related components) is always >= 1 with type/default/runtime enforcement and regression tests.

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

In `@packages/visual-editor/src/components/Locator.tsx` around lines 1979 - 1981,
The code currently forwards result.index directly to MapPinIcon; enforce
resultIndex >= 1 by clamping/validating before passing it. In Locator.tsx,
replace direct use of result.index with a sanitized value like Math.max(1,
Number(result.index) || 1) (or equivalent) and pass that sanitized variable to
MapPinIcon; also ensure MapPinIcon's prop type (resultIndex) accepts a number
and has a default/validation so runtime callers cannot pass <1, and add a small
regression test that renders Locator/MapPinIcon with index 0/undefined to
confirm it receives >=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/locales/platform/en/visual-editor.json`:
- Around line 405-407: The value for the "pinStyles" localization key is
inconsistent with the title case used by other multi-word labels; update the
"pinStyles" string from "Location styles" to "Location Styles" to match the
casing used by "pinColor" ("Pin Color") and "pinIcon" ("Pin Icon") so all
multi-word field labels use Title Case.

In `@packages/visual-editor/locales/platform/fr/visual-editor.json`:
- Around line 405-407: Replace the inconsistent French term for "pin" in the
localization keys so both use "épingle": update the value of "pinColor" from
"Couleur de la broche" to "Couleur de l'épingle" to match "pinIcon" ("Icône
d'épingle"); leave "pinStyles" ("Styles d'emplacement") unchanged. Ensure you
edit the entries for pinColor and confirm pinIcon remains as-is.

In `@packages/visual-editor/locales/platform/hr/visual-editor.json`:
- Around line 405-407: The translation for the key "pinStyles" is inconsistent
with its siblings "pinColor" and "pinIcon"; change the value of pinStyles from
"Stilovi lokacije" to "Stilovi pribadače" so all pin-related keys use the same
term ("pribadače") for consistency across the locale file.

In `@packages/visual-editor/locales/platform/hu/visual-editor.json`:
- Line 406: The translation for the key pinIcon uses title case ("Pin Ikon") but
surrounding Hungarian UI strings use sentence case; update the value for
"pinIcon" to sentence case (e.g., "Pin ikon") in the visual-editor.json so
capitalization matches other labels and the UI appears consistent.

In `@packages/visual-editor/locales/platform/it/visual-editor.json`:
- Line 407: The translation for the JSON key "pinStyles" is incorrect: replace
the value "Stili di localizzazione" with a context-appropriate Italian phrase
such as "Stili pin" or "Stili del pin" to reflect map-pin styling rather than
software localization; locate the "pinStyles" entry in the visual-editor locale
file and update its string, keeping the key name unchanged and aligning style
with the existing "locator" label usage.

In `@packages/visual-editor/locales/platform/sk/visual-editor.json`:
- Around line 405-407: The three locale keys use inconsistent Slovak terms for
"pin": update the value of "pinStyles" to use the same "špendlík" term family as
"pinColor" and "pinIcon" so labels are consistent; locate the keys pinColor,
pinIcon, and pinStyles in visual-editor.json and change the "pinStyles"
translation from "Štýly umiestnenia" to a matching form such as "Štýly
špendlíka" (or another grammatically correct variant using "špendlík") to align
terminology across the UI.

In `@packages/visual-editor/locales/platform/sv/visual-editor.json`:
- Around line 405-407: The three translation keys pinColor, pinIcon, and
pinStyles are inconsistent: pinColor and pinIcon use the English loanword "Pin"
while pinStyles uses "Plats"; update these keys so they use the same Swedish
term across all entries (choose one approach and apply it consistently) — e.g.,
change "Pin färg" and "Pin ikon" to "Platsfärg" and "Platsikon" if you prefer
translation, or change "Platsstilar" to "Pin stilar" if you prefer the loanword;
after updating in this file, replicate the same consistent choice across the
other locale files added in the PR to ensure uniformity.

In `@packages/visual-editor/locales/platform/zh/visual-editor.json`:
- Around line 405-407: The current Chinese translations for the map pin keys
("pinColor", "pinIcon", "pinStyles") use 引脚/针 which implies a hardware pin;
update their values to use map-marker terminology such as 图钉 or 标记 (e.g., change
"引脚颜色" → "图钉颜色" or "标记颜色", "针图标" → "图钉图标"/"标记图标", and "地点风格" → "图钉样式"/"标记样式") so
the UI clearly refers to map markers rather than physical pins.

In `@packages/visual-editor/src/components/Locator.tsx`:
- Around line 739-740: The code accesses makiIconOptions[0].value without
checking for an empty array; update the logic in Locator.tsx where
makiIconOptions is used (the ternary that selects makiIconOptions[0].value as
default) to guard against an empty list by using optional chaining or by
computing a safe default when makiIconOptions is initialized (e.g., derive
defaultIcon = makiIconOptions?.[0]?.value ?? undefined and use defaultIcon),
ensuring you reference makiIconOptions and the default selection expression so
no direct index access occurs on an empty array.

In `@packages/visual-editor/src/components/LocatorResultCard.tsx`:
- Around line 792-795: The hide guard for the primary CTA incorrectly treats
undefined as truthy; update the logic around
getLocatorSourcePageSetsEntityTypes(streamDocument) by checking for an array and
emptiness explicitly (e.g., const hidePrimaryCta =
!Array.isArray(locatorSourcePageSetsEntityTypes) ||
locatorSourcePageSetsEntityTypes.length === 0) so that undefined or empty
results both hide the primary CTA; adjust any usages of hidePrimaryCta (the
primary CTA render conditional) accordingly in LocatorResultCard and related
render logic.

---

Duplicate comments:
In `@packages/visual-editor/src/components/Locator.tsx`:
- Around line 182-185: getEntityType currently calls the React hook useDocument
inside a helper that’s invoked from an arbitrary callback
(facetFields.getOptions), which violates the Rules of Hooks; fix by removing the
hook call from getEntityType and instead call useDocument at the top level of
the component or inside a custom hook, then pass the resulting document into
getEntityType (or rename to getEntityTypeFromDoc) from that top-level location;
specifically, update getEntityType to accept a StreamDocument parameter (or
create a pure getEntityTypeFromDocument(document, entityTypeEnvVar)) and change
the code path that defines facetFields.getOptions to capture the document from
useDocument() at the component/custom-hook top level and supply it to
getEntityType when invoking the options callback.

---

Nitpick comments:
In `@packages/visual-editor/locales/platform/cs/visual-editor.json`:
- Line 340: The "hotels" option label in the locales JSON is inconsistent with
adjacent capitalized category labels; update the value for the "hotels" key in
packages/visual-editor/locales/platform/cs/visual-editor.json from "hotely" to a
capitalized form (e.g., "Hotely") so dropdown labels match the capitalization
style used by neighboring entries.

In `@packages/visual-editor/locales/platform/de/visual-editor.json`:
- Line 366: Two entries use different German translations for the same JSON key
"none" ("Keiner" vs "Keine"); pick the correct gender/grammar for the intended
UI context and make both "none" entries identical. Search for the JSON key
"none" in the visual-editor locale (the two occurrences shown) and replace the
inconsistent string so both entries use the chosen translation (e.g., change
"Keiner" to "Keine" or vice versa) to ensure consistent UI copy.

In `@packages/visual-editor/locales/platform/en-GB/visual-editor.json`:
- Line 407: Update the "pinStyles" label value to use title case for consistency
with neighboring labels: change the value for the key "pinStyles" from "Location
styles" to "Location Styles" (adjust the string literal in the
visual-editor.json entry for pinStyles).

In `@packages/visual-editor/locales/platform/es/visual-editor.json`:
- Around line 405-407: Replace the literal translations for the map pin labels
to use map-specific terminology: update the values for "pinColor" and "pinIcon"
(and optionally "pinStyles") in the visual-editor.json locale to use "marcador"
instead of "alfiler" (e.g., "Color del marcador", "Icono de marcador", "Estilos
de marcador") to improve UI clarity while keeping the same keys "pinColor",
"pinIcon", and "pinStyles".

In `@packages/visual-editor/locales/platform/et/visual-editor.json`:
- Line 405: The "pinColor" label value "Pin Värv" is inconsistent with adjacent
labels; update the JSON value for the "pinColor" key to use the same Estonian
terminology/casing as the neighboring entries (e.g., change "Pin Värv" to
"Kinnitus värv" to match the "Kinnitusikoon" / "Asukoha stiilid" style).

In `@packages/visual-editor/locales/platform/ja/visual-editor.json`:
- Line 357: Update the JSON value for the key "locations" in visual-editor.json
to use the term "場所" (replacing "所在地") so it matches nearby user-facing strings
and the existing vocabulary; locate the "locations" key in the locales JSON and
change its value to "場所" to ensure term consistency across
fields.options.locations and related UI labels.

In `@packages/visual-editor/locales/platform/sk/visual-editor.json`:
- Around line 331-371: The entries for the "hotels" and "none" localization keys
are lowercased and should be title-cased to match neighboring dropdown labels;
update the value for key "hotels" from "hotely" to "Hotely" and the value for
key "none" from "žiadne" to "Žiadne" in the visual-editor.json so UI labels are
consistent.

In `@packages/visual-editor/locales/platform/sv/visual-editor.json`:
- Line 331: Replace the colloquial Swedish value for the
"financialProfessionals" key with a more formal wording; locate the
"financialProfessionals" entry and change its value from "Finansiella proffs" to
a formal alternative such as "Finansiella yrkesverksamma" (or "Finansproffs" if
you prefer a compound), ensuring the JSON string is updated consistently with
other locale choices.

In `@packages/visual-editor/locales/platform/zh/visual-editor.json`:
- Line 357: The locale key "locations" in
packages/visual-editor/locales/platform/zh/visual-editor.json currently maps to
"地点" but other entries in this file use "位置"; update the value for the
"locations" key to "位置" to keep UI wording consistent (search for other
location-related keys like "location", "locations", or similar to ensure uniform
terminology across the file).

In `@packages/visual-editor/src/components/Locator.tsx`:
- Around line 1979-1981: The code currently forwards result.index directly to
MapPinIcon; enforce resultIndex >= 1 by clamping/validating before passing it.
In Locator.tsx, replace direct use of result.index with a sanitized value like
Math.max(1, Number(result.index) || 1) (or equivalent) and pass that sanitized
variable to MapPinIcon; also ensure MapPinIcon's prop type (resultIndex) accepts
a number and has a default/validation so runtime callers cannot pass <1, and add
a small regression test that renders Locator/MapPinIcon with index 0/undefined
to confirm it receives >=1.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7076087 and 2371be1.

📒 Files selected for processing (28)
  • packages/visual-editor/locales/platform/cs/visual-editor.json
  • packages/visual-editor/locales/platform/da/visual-editor.json
  • packages/visual-editor/locales/platform/de/visual-editor.json
  • packages/visual-editor/locales/platform/en-GB/visual-editor.json
  • packages/visual-editor/locales/platform/en/visual-editor.json
  • packages/visual-editor/locales/platform/es/visual-editor.json
  • packages/visual-editor/locales/platform/et/visual-editor.json
  • packages/visual-editor/locales/platform/fi/visual-editor.json
  • packages/visual-editor/locales/platform/fr/visual-editor.json
  • packages/visual-editor/locales/platform/hr/visual-editor.json
  • packages/visual-editor/locales/platform/hu/visual-editor.json
  • packages/visual-editor/locales/platform/it/visual-editor.json
  • packages/visual-editor/locales/platform/ja/visual-editor.json
  • packages/visual-editor/locales/platform/lt/visual-editor.json
  • packages/visual-editor/locales/platform/lv/visual-editor.json
  • packages/visual-editor/locales/platform/nb/visual-editor.json
  • packages/visual-editor/locales/platform/nl/visual-editor.json
  • packages/visual-editor/locales/platform/pl/visual-editor.json
  • packages/visual-editor/locales/platform/pt/visual-editor.json
  • packages/visual-editor/locales/platform/ro/visual-editor.json
  • packages/visual-editor/locales/platform/sk/visual-editor.json
  • packages/visual-editor/locales/platform/sv/visual-editor.json
  • packages/visual-editor/locales/platform/tr/visual-editor.json
  • packages/visual-editor/locales/platform/zh-TW/visual-editor.json
  • packages/visual-editor/locales/platform/zh/visual-editor.json
  • packages/visual-editor/package.json
  • packages/visual-editor/src/components/Locator.tsx
  • packages/visual-editor/src/components/LocatorResultCard.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/visual-editor/package.json
  • packages/visual-editor/locales/platform/da/visual-editor.json
  • packages/visual-editor/locales/platform/ro/visual-editor.json
  • packages/visual-editor/locales/platform/lv/visual-editor.json
  • packages/visual-editor/locales/platform/tr/visual-editor.json
  • packages/visual-editor/locales/platform/nl/visual-editor.json
  • packages/visual-editor/locales/platform/zh-TW/visual-editor.json

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

♻️ Duplicate comments (2)
packages/visual-editor/src/components/Locator.tsx (1)

184-187: ⚠️ Potential issue | 🔴 Critical

useDocument() is called from a non-hook utility that is used in a field callback.

Line 185 calls a React hook inside getEntityType, and Line 813 invokes that function inside getOptions (a framework callback, not a component/custom hook). This breaks Hooks rules and can trigger lint or runtime invalid-hook-call issues.

🛠️ Suggested direction
-const getEntityType = (entityTypeEnvVar?: string) => {
-  const entityDocument: StreamDocument = useDocument();
-  return getEntityTypeFromDocument(entityDocument, entityTypeEnvVar);
-};
+const getEntityType = (
+  entityDocument: StreamDocument,
+  entityTypeEnvVar?: string
+) => getEntityTypeFromDocument(entityDocument, entityTypeEnvVar);

Then pass a pre-derived entityType into facetFields.getOptions via resolveFields metadata wiring (instead of calling useDocument() from getOptions).

#!/bin/bash
set -euo pipefail

FILE="packages/visual-editor/src/components/Locator.tsx"

echo "Check non-hook helper calling useDocument:"
rg -n -C2 'const getEntityType =|useDocument\(' "$FILE"

echo
echo "Check facet field callback path calling getEntityType():"
rg -n -C3 'facetFields:|getOptions:\s*\(\)\s*=>|getEntityType\(' "$FILE"

Based on learnings: React hooks must only be called at the top level of React function components or custom hooks, not arbitrary callbacks.

Also applies to: 812-815

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

In `@packages/visual-editor/src/components/Locator.tsx` around lines 184 - 187,
getEntityType currently calls the React hook useDocument inside a non-hook
helper used by facetFields.getOptions, violating Hooks rules; instead, remove
useDocument from getEntityType and change the flow so a pre-derived entityType
(from useDocument called in a component or a custom hook) is passed into the
options callback via resolveFields metadata wiring. Concretely: stop calling
useDocument inside getEntityType/getEntityTypeFromDocument, change
facetFields.getOptions to accept an entityType parameter, and update the caller
at resolveFields (where the field callbacks are wired) to call useDocument once
in a component/custom hook and pass that entityType into facetFields.getOptions.
packages/visual-editor/src/components/LocatorResultCard.tsx (1)

792-795: ⚠️ Potential issue | 🟠 Major

Primary CTA hide guard still treats undefined as visible.

At Line 795, locatorSourcePageSetsEntityTypes?.length === 0 is false when the helper returns undefined, so Line 911 can still render the primary CTA in the no-page-set case.

🛠️ Suggested fix
-    const hidePrimaryCta = locatorSourcePageSetsEntityTypes?.length === 0;
+    const hidePrimaryCta = !locatorSourcePageSetsEntityTypes?.length;

Also applies to: 911-911

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

In `@packages/visual-editor/src/components/LocatorResultCard.tsx` around lines 792
- 795, The hide guard treats undefined as visible because hidePrimaryCta is set
to locatorSourcePageSetsEntityTypes?.length === 0; change the check to treat
undefined/null as empty — e.g. set hidePrimaryCta =
!locatorSourcePageSetsEntityTypes || locatorSourcePageSetsEntityTypes.length ===
0 (or use (locatorSourcePageSetsEntityTypes ?? []).length === 0) so that the
primary CTA render code (the Primary CTA section that reads hidePrimaryCta to
decide visibility) will be hidden when
getLocatorSourcePageSetsEntityTypes(streamDocument) returns undefined.
🧹 Nitpick comments (1)
packages/visual-editor/src/components/Locator.tsx (1)

1979-1991: Guard result.index before forwarding to MapPinIcon.

Line 1989 passes result.index directly. Add a local safety guard so this boundary always enforces resultIndex >= 1.

🛠️ Suggested hardening
 const LocatorMapPin = <T,>(props: LocatorMapPinProps<T>) => {
   const { result, selected, locationStyleConfig } = props;
+  const safeResultIndex =
+    typeof result.index === "number" && result.index > 0 ? result.index : 1;
   const entityType = result.entityType;
   const entityLocationStyle = entityType
     ? locationStyleConfig?.[entityType]
     : undefined;

   return (
     <MapPinIcon
       color={entityLocationStyle?.color}
-      resultIndex={result.index}
+      resultIndex={safeResultIndex}
       icon={entityLocationStyle?.icon}
       selected={selected}
     />
   );
 };

Based on learnings: for MapPinIcon and related components, enforce resultIndex >= 1 via runtime checks/types to prevent regressions.

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

In `@packages/visual-editor/src/components/Locator.tsx` around lines 1979 - 1991,
The LocatorMapPin component forwards result.index to MapPinIcon without
validation; ensure you compute a guarded value (e.g., const safeIndex =
Math.max(1, (result.index ?? 1))) and pass safeIndex as the resultIndex prop to
MapPinIcon so resultIndex is always >= 1; update the LocatorMapPin
implementation (referencing LocatorMapPin, result.index, and MapPinIcon) to use
this runtime check before rendering.
🤖 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/locales/platform/sk/visual-editor.json`:
- Line 366: The "none" localization key value is inconsistent with its
siblings—update the value for the "none" key in visual-editor.json from "žiadne"
to a capitalized form "Žiadne" so it matches the capitalization style of the
other option labels; locate the "none" entry and replace its string accordingly.

---

Duplicate comments:
In `@packages/visual-editor/src/components/Locator.tsx`:
- Around line 184-187: getEntityType currently calls the React hook useDocument
inside a non-hook helper used by facetFields.getOptions, violating Hooks rules;
instead, remove useDocument from getEntityType and change the flow so a
pre-derived entityType (from useDocument called in a component or a custom hook)
is passed into the options callback via resolveFields metadata wiring.
Concretely: stop calling useDocument inside
getEntityType/getEntityTypeFromDocument, change facetFields.getOptions to accept
an entityType parameter, and update the caller at resolveFields (where the field
callbacks are wired) to call useDocument once in a component/custom hook and
pass that entityType into facetFields.getOptions.

In `@packages/visual-editor/src/components/LocatorResultCard.tsx`:
- Around line 792-795: The hide guard treats undefined as visible because
hidePrimaryCta is set to locatorSourcePageSetsEntityTypes?.length === 0; change
the check to treat undefined/null as empty — e.g. set hidePrimaryCta =
!locatorSourcePageSetsEntityTypes || locatorSourcePageSetsEntityTypes.length ===
0 (or use (locatorSourcePageSetsEntityTypes ?? []).length === 0) so that the
primary CTA render code (the Primary CTA section that reads hidePrimaryCta to
decide visibility) will be hidden when
getLocatorSourcePageSetsEntityTypes(streamDocument) returns undefined.

---

Nitpick comments:
In `@packages/visual-editor/src/components/Locator.tsx`:
- Around line 1979-1991: The LocatorMapPin component forwards result.index to
MapPinIcon without validation; ensure you compute a guarded value (e.g., const
safeIndex = Math.max(1, (result.index ?? 1))) and pass safeIndex as the
resultIndex prop to MapPinIcon so resultIndex is always >= 1; update the
LocatorMapPin implementation (referencing LocatorMapPin, result.index, and
MapPinIcon) to use this runtime check before rendering.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2371be1 and 2d6560c.

📒 Files selected for processing (10)
  • packages/visual-editor/locales/platform/fr/visual-editor.json
  • packages/visual-editor/locales/platform/hr/visual-editor.json
  • packages/visual-editor/locales/platform/hu/visual-editor.json
  • packages/visual-editor/locales/platform/it/visual-editor.json
  • packages/visual-editor/locales/platform/sk/visual-editor.json
  • packages/visual-editor/locales/platform/sv/visual-editor.json
  • packages/visual-editor/locales/platform/zh/visual-editor.json
  • packages/visual-editor/src/components/Locator.tsx
  • packages/visual-editor/src/components/LocatorResultCard.tsx
  • packages/visual-editor/src/utils/locator.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/visual-editor/locales/platform/hu/visual-editor.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/visual-editor/src/utils/locator.ts
  • packages/visual-editor/locales/platform/it/visual-editor.json
  • packages/visual-editor/locales/platform/fr/visual-editor.json
  • packages/visual-editor/locales/platform/zh/visual-editor.json

"navigationDay": "Navigácia (deň)",
"navigationNight": "Navigácia (noc)",
"no": "Nie",
"none": "žiadne",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

"none" value uses lowercase while all sibling options are capitalized.

Every other option value in this block begins with an uppercase letter. "žiadne" is grammatically valid in Slovak but visually inconsistent as a UI label alongside capitalized siblings.

✏️ Proposed fix
-      "none": "žiadne",
+      "none": "Žiadne",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"none": "žiadne",
"none": "Žiadne",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor/locales/platform/sk/visual-editor.json` at line 366,
The "none" localization key value is inconsistent with its siblings—update the
value for the "none" key in visual-editor.json from "žiadne" to a capitalized
form "Žiadne" so it matches the capitalization style of the other option labels;
locate the "none" entry and replace its string accordingly.

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.

1 participant