Skip to content

feat: use breadcrumbPrefix for breadcrumb urls#1006

Open
briantstephan wants to merge 23 commits intomainfrom
breadcrumbs-url-templates
Open

feat: use breadcrumbPrefix for breadcrumb urls#1006
briantstephan wants to merge 23 commits intomainfrom
breadcrumbs-url-templates

Conversation

@briantstephan
Copy link
Contributor

@briantstephan briantstephan commented Jan 28, 2026

This updates breadcrumbs to use the new pathInfo.breadcrumbPrefix field as the prefix before the directory parent slug.

Feel free to test it here: https://dev.yext.com/s/1000167375/yextsites/66601/pagesets

@briantstephan briantstephan self-assigned this Jan 28, 2026
@briantstephan briantstephan added the create-dev-release Triggers dev release workflow label Jan 28, 2026
@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.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 28, 2026

commit: 3ce2e6b

@briantstephan briantstephan changed the title feat: use breadcrumbTemplates for breadcrumb urls feat: use breadcrumbPrefix for breadcrumb urls Feb 13, 2026
@briantstephan briantstephan marked this pull request as ready for review February 13, 2026 17:28
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

Walkthrough

This PR refactors the breadcrumb resolution logic in the visual editor by introducing a new multi-strategy resolver pattern. The Breadcrumbs component is updated to use a new resolveBreadcrumbs function instead of directly calling getDirectoryParents. The StreamDocument type is modified to replace the breadcrumbTemplates field with breadcrumbPrefix (changing from an optional array to an optional string). New utility functions resolveBreadcrumbsFromPathInfo and buildBreadcrumbsFromDirectory implement resolution strategies that attempt pathInfo-based breadcrumbs first, then fall back to directory-parent-based construction. Comprehensive test coverage is added for both resolution approaches and the orchestrating resolver.

Sequence Diagram

sequenceDiagram
    participant BC as Breadcrumbs Component
    participant RB as resolveBreadcrumbs
    participant RPI as resolveBreadcrumbsFromPathInfo
    participant BD as buildBreadcrumbsFromDirectory
    participant DP as getDirectoryParents
    participant SD as StreamDocument

    BC->>RB: resolveBreadcrumbs(streamDocument)
    
    RB->>RPI: Strategy 1: resolveBreadcrumbsFromPathInfo
    activate RPI
    RPI->>SD: Read breadcrumbPrefix from pathInfo
    RPI->>SD: Read directoryParents array
    alt Valid breadcrumbPrefix and directoryParents
        RPI->>RPI: Build breadcrumbs with locale prefix
        RPI-->>RB: Return BreadcrumbLink[]
    else Missing data
        RPI-->>RB: Return undefined
    end
    deactivate RPI
    
    alt Non-empty breadcrumbs from pathInfo
        RB-->>BC: Return breadcrumbs
    else Empty or undefined
        RB->>BD: Strategy 2: buildBreadcrumbsFromDirectory
        activate BD
        BD->>DP: getDirectoryParents(streamDocument)
        DP-->>BD: Return parent array
        alt Valid parents found
            BD->>BD: Build breadcrumbs from parents + document name
            BD-->>RB: Return BreadcrumbLink[]
        else No parents
            BD-->>RB: Return undefined
        end
        deactivate BD
        RB-->>BC: Return breadcrumbs or empty array
    end
Loading

Possibly related PRs

Suggested reviewers

  • mkilpatrick
  • benlife5
  • asanehisa
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: transitioning breadcrumb generation to use the new pathInfo.breadcrumbPrefix field.
Description check ✅ Passed The description is directly related to the changeset, explaining that breadcrumbs now use pathInfo.breadcrumbPrefix as a prefix for directory parent slugs.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch breadcrumbs-url-templates

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

🤖 Fix all issues with AI agents
In `@packages/visual-editor/src/utils/urls/resolveBreadcrumbsFromPathInfo.ts`:
- Line 77: The final crumb push in resolveBreadcrumbsFromPathInfo.ts currently
always appends { name: streamDocument.name ?? "", slug: "" } which can produce
an empty breadcrumb label; change this to either skip pushing when
streamDocument.name is falsy (check streamDocument.name before crumbs.push) or
provide a fallback label (e.g., use a defaultTitle or derive from path) so the
breadcrumb list never contains an empty name—align behavior with the filtering
used in resolveBreadcrumbs.ts by applying the same truthy-name check or fallback
logic when adding the final crumb.
🧹 Nitpick comments (5)
packages/visual-editor/src/utils/resolveYextEntityField.ts (1)

1-1: Prefer import type since YextEntityField is only used as a type here.

YextEntityField appears solely in type positions (lines 7 and 57). Switching from import type to a value import means the module won't be elided by TypeScript, which can matter under verbatimModuleSyntax. Consider keeping the type-only import:

Suggested fix
-import { YextEntityField } from "../editor/YextEntityFieldSelector.tsx";
+import type { YextEntityField } from "../editor/YextEntityFieldSelector.tsx";
packages/visual-editor/src/utils/urls/resolveBreadcrumbsFromPathInfo.ts (2)

22-25: Locale requirement is undocumented and may surprise callers.

The function silently returns undefined when locale is missing. The JSDoc (lines 10-13) only mentions breadcrumbPrefix and dm_directoryParents_* as prerequisites but doesn't mention locale is also required. Consider adding locale to the doc, or logging a warning, so debugging is easier when breadcrumbs unexpectedly disappear.


35-42: Dynamic field lookup is pragmatic but fragile if multiple dm_directoryParents_* keys exist.

Object.entries(...).find(...) returns the first match. If a document ever contains more than one dm_directoryParents_* field (e.g., from multiple entity types), the selected one is nondeterministic due to object key ordering. This is likely fine for current usage, but worth a brief comment noting the assumption of a single match.

packages/visual-editor/src/utils/urls/resolveBreadcrumbs.ts (1)

17-31: Silent error swallowing in the catch block.

The bare catch discards all errors, including unexpected ones (e.g., TypeError from a bug). Consider logging at debug/warn level so issues don't silently vanish during development.

💡 Suggestion
     } catch {
-      // continue to next resolver
+      // continue to next resolver
+      // Consider: console.warn("Breadcrumb resolver failed", e);
     }
packages/visual-editor/src/utils/urls/resolveBreadcrumbsFromPathInfo.test.ts (1)

29-76: Good core coverage; consider adding edge-case tests.

The three scenarios (primary locale, non-primary locale, empty prefix) cover the main paths well. Missing coverage for:

  • breadcrumbPrefix being undefined (expected: returns undefined)
  • Missing locale (expected: returns undefined)
  • includeLocalePrefixForPrimaryLocale: true with the primary locale
  • Directory parents with missing/malformed entries

These are optional additions for robustness but not blocking.

});

describe("resolveBreadcrumbs", () => {
it("prefers pathInfo breadcrumbs over directory parents", () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

interesting wording, more like uses pathInfo breadcrumbsPrefix if exists?

.replace(/\/+/g, "/")
.replace(/^\/+|\/+$/g, "");

const directoryParentsEntry = Object.entries(streamDocument).find(
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this use the same getDirectoryParents as the other function?

@@ -1,4 +1,4 @@
import { type YextEntityField } from "../editor/YextEntityFieldSelector.tsx";
import { YextEntityField } from "../editor/YextEntityFieldSelector.tsx";
Copy link
Contributor

Choose a reason for hiding this comment

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

any reason for this?

return [];
};

const buildBreadcrumbsFromDirectory = (
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this one in this file but resolveBreadcrumbsFromPathInfo in its own file?

) => BreadcrumbLink[] | undefined;

const resolvers: BreadcrumbResolver[] = [
(streamDocument) => resolveBreadcrumbsFromPathInfo(streamDocument),
Copy link
Collaborator

Choose a reason for hiding this comment

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

One is called resolve and one is called build. Should we keep it consistent?

): BreadcrumbLink[] | undefined => {
const breadcrumbPrefix = streamDocument.__?.pathInfo?.breadcrumbPrefix;
if (typeof breadcrumbPrefix !== "string") {
return undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can just return (without undefined)

return undefined;
}

const includeLocalePrefix =
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we want to do the same locale prefix logic. It depends on if Spruce has changed the DM config to have the same urlTemplate across all slug values of the DM or if they have the normal DM config to handle locale.

.replace(/\/+/g, "/")
.replace(/^\/+|\/+$/g, "");

const directoryParentsEntry = Object.entries(streamDocument).find(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can't you just call getDirectoryParents, iterate through it, and apply the prefix?

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.

3 participants