Skip to content

feat(breadcrumbs): add native breadcrumbs dynamic block#16

Closed
brandonmarshal wants to merge 3 commits into
developfrom
feature-breadcrumbs-dynamic-block
Closed

feat(breadcrumbs): add native breadcrumbs dynamic block#16
brandonmarshal wants to merge 3 commits into
developfrom
feature-breadcrumbs-dynamic-block

Conversation

@brandonmarshal

@brandonmarshal brandonmarshal commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Description

Adds a new ls-plugin/breadcrumbs dynamic block that renders a breadcrumb navigation trail (nav.ls-crumbs) built entirely from core WordPress data — no third-party plugin dependency. This is Part 1 of LS-1228; Part 2 (wiring the block into ls-theme's patterns/breadcrumbs.php and templates) is tracked separately in that repo.

Changes

  • New block: ls-plugin/breadcrumbs — dynamic block following this plugin's existing block conventions (search-filter/carousel/style-switcher)
  • New registration class: inc/class-breadcrumbs.php — registers the block from build/blocks/breadcrumbs
  • Plugin bootstrap: wired LS_Plugin_Breadcrumbs into ls_plugin_init() in ls-plugin.php
  • Changelog: added entry under [Unreleased]

Files Modified

Source Files:

  • src/blocks/breadcrumbs/block.json — block metadata (dynamic, render.php, category theme)
  • src/blocks/breadcrumbs/edit.js — static "Breadcrumbs (dynamic)" editor placeholder
  • src/blocks/breadcrumbs/save.js — returns null (dynamic block)
  • src/blocks/breadcrumbs/index.js — registers the block in JS
  • src/blocks/breadcrumbs/render.php — PHP render callback, the actual trail-building logic
  • src/blocks/breadcrumbs/style.scss — front-end nav.ls-crumbs styling + editor placeholder styling
  • build/blocks/breadcrumbs/* — compiled output of the above (6 files)
  • inc/class-breadcrumbs.php — new file, block registration
  • ls-plugin.php — +2 lines (require + instantiate)
  • CHANGELOG.md — +1 line (Unreleased entry)

Key Improvements

  • ✅ Trail built entirely from core WordPress conditional tags and functions — zero dependency on Yoast SEO or any other plugin (see "Decisions" below)
  • ✅ Walks nested ancestors correctly: pages via get_post_ancestors(), hierarchical CPTs the same way, and categories/tags/taxonomies via get_ancestors() — so a post in a child category renders Home > Parent Category > Child Category > Post, not just the immediate term
  • ✅ Handles front page, blog home, single posts/pages, search, and 404 distinctly, always ending in a non-linked <span aria-current="page">
  • ✅ All dynamic output escaped via esc_url() / esc_html()
  • ✅ Editor-side static placeholder so the block never appears blank/erroring in the inserter
  • ✅ Front-end styling matches the plugin's existing design-token conventions (var(--wp--custom--...) with fallbacks)

Decisions

  • Dropped Yoast SEO integration entirely. The ticket originally specified calling yoast_breadcrumb() when active, with a native fallback otherwise. During testing this surfaced a real blocker: Yoast SEO caches breadcrumb ancestor chains in its own Indexables tables, built on a normal editor save_post cycle — pages created/updated via non-editor paths didn't reliably trigger Yoast's hierarchy rebuild, so Yoast's own output silently dropped ancestor crumbs while everything else was correct (a Yoast caching quirk, not a bug in this code). Combined with a preference for zero third-party coupling, the yoast_breadcrumb() branch was removed — the block now always uses the native trail-building logic (originally written as the fallback). Full context in the LS-1228 comment thread.

Related Issues

Closes Part 1 of LS-1228 — build breadcrumb dynamic block (ls-plugin) + wire into breadcrumbs pattern (ls-theme).

Part 2 (wiring the block into patterns/breadcrumbs.php / theme templates) is tracked in the same issue and does not touch this repo.

Testing

  • Verified on local WP install via the localhost MCP connector + manual browser checks
  • Top-level page: Home > Parent Page
  • Nested/child page: Home > Parent Page > Child Page, incl. crumb links navigating correctly
  • Single post in a nested category: Home > Parent Category > Child Category > Post
  • Portfolio single item: Home > Portfolio Item
  • Editor placeholder renders correctly, no blank/error state
  • php -l clean on all changed files; phpcs shows only pre-existing tabs-vs-spaces findings identical to already-merged files, not introduced by this change
  • Category/Portfolio archive pages — not testable from this repo alone; requires Part 2 template wiring in ls-theme

Deployment Notes

  • No breaking changes — purely additive, new block only
  • No dependency on Yoast SEO or any other plugin being active
  • Build step required: npm run build (block auto-discovered by existing wp-scripts config, no webpack changes needed)

Checklist

  • No conflicts with develop branch
  • PHP lint passed (php -l)
  • Build verified (npm run build)
  • Manual QA on local WP install
  • Code review

Summary by CodeRabbit

  • New Features
    • Added a Breadcrumbs block for WordPress.
    • Automatically displays navigation trails for pages, posts, archives, searches, 404 pages, and nested content.
    • Includes an editor placeholder preview and responsive front-end styling.
    • Supports alignment, spacing controls, accessibility labels, and right-to-left layouts.

- Register ls-plugin/breadcrumbs, a dynamic block rendering nav.ls-crumbs
- Build the trail entirely from core WordPress data, no plugin dependency
- Walk nested ancestors for pages, posts, and categories/tags/taxonomies
- Handle front page, blog home, search, and 404 cases
- Add editor placeholder preview and front-end styling
@brandonmarshal brandonmarshal added area:navigation Menus & nav UX area:plugins Plugin configuration/internals lang:js JavaScript/TypeScript lang:php PHP code release:minor Backwards‑compatible enhancements requiring a MINOR version bump (e.g., new features, performance im status:needs-review Awaiting code review labels Jul 14, 2026
@brandonmarshal brandonmarshal self-assigned this Jul 14, 2026
@mergify

mergify Bot commented Jul 14, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new dynamic Breadcrumbs block (ls-plugin/breadcrumbs) that builds its navigation trail entirely from core WordPress data without any third-party dependencies. It includes block registration, editor components, styling, and a PHP rendering template. The review feedback highlights a critical issue in render.php where get_term_link() is called without checking for a WP_Error return value. If a WP_Error is returned, passing it directly to esc_url() will trigger a fatal TypeError in PHP 8.0+. The reviewer recommends using is_wp_error() checks to safely default the URL to null in these instances.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/blocks/breadcrumbs/render.php Outdated
Comment thread src/blocks/breadcrumbs/render.php Outdated
Comment thread src/blocks/breadcrumbs/render.php
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@brandonmarshal, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: d278e114-522f-4a71-8fa4-fcbfbc4bd97c

📥 Commits

Reviewing files that changed from the base of the PR and between e20f032 and eae90ca.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • build/blocks/breadcrumbs/render.php
  • inc/class-breadcrumbs.php
  • ls-plugin.php
  • src/blocks/breadcrumbs/render.php

Note

.coderabbit.yml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized keys: "version", "path_instructions"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Adds a dynamic ls-plugin/breadcrumbs WordPress block. It registers compiled assets, displays an editor placeholder, builds trails from core WordPress query contexts, renders escaped navigation markup, and includes LTR/RTL styling and changelog documentation.

Changes

Breadcrumbs dynamic block

Layer / File(s) Summary
Block registration and editor wiring
src/blocks/breadcrumbs/block.json, build/blocks/breadcrumbs/*, inc/class-breadcrumbs.php, ls-plugin.php, src/blocks/breadcrumbs/index.js, src/blocks/breadcrumbs/edit.js, src/blocks/breadcrumbs/save.js
Defines the block, loads and registers it during plugin initialization, and provides a dynamic editor placeholder with no saved markup.
Contextual breadcrumb rendering
src/blocks/breadcrumbs/render.php, build/blocks/breadcrumbs/render.php
Builds breadcrumb trails for core WordPress contexts and renders escaped links, separators, and the current-page item.
Breadcrumb presentation and release metadata
src/blocks/breadcrumbs/style.scss, build/blocks/breadcrumbs/style-index.css, build/blocks/breadcrumbs/style-index-rtl.css, CHANGELOG.md
Adds breadcrumb and placeholder styling for LTR and RTL output and documents the new block under Unreleased additions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Editor
  participant WordPress
  participant render_php
  participant QueryContext
  participant BreadcrumbNav
  Editor->>WordPress: insert ls-plugin/breadcrumbs block
  WordPress->>render_php: render dynamic block
  render_php->>QueryContext: inspect current request context
  QueryContext-->>render_php: provide breadcrumb trail data
  render_php->>BreadcrumbNav: output navigation markup
  BreadcrumbNav-->>Editor: display breadcrumb trail
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a native dynamic breadcrumbs block.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature-breadcrumbs-dynamic-block

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/blocks/breadcrumbs/render.php (1)

51-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated ancestor-walking logic across two branch pairs.

The taxonomy-ancestor loop (lines 51-66) is re-implemented for categories inside is_single() (lines 94-109) with 'category' hardcoded instead of the term's taxonomy, and the post-ancestor loop (lines 79-87) is duplicated verbatim in is_page() (lines 127-134). Any future change to how ancestor crumbs are built (e.g. adding a rel attribute or filter hook) needs to be applied in two places each.

Extract two small static helpers on LS_Plugin_Breadcrumbs (already loaded once via require_once) rather than declaring bare functions inside render.phprender.php backing a block.json render field can be executed more than once per page load if the block appears twice, and top-level function declarations there risk a "Cannot redeclare function" fatal.

♻️ Proposed refactor (helper methods in inc/class-breadcrumbs.php, called from render.php)
// inc/class-breadcrumbs.php (inside LS_Plugin_Breadcrumbs)
public static function add_taxonomy_ancestors( array &$trail, WP_Term $term ) {
	if ( ! is_taxonomy_hierarchical( $term->taxonomy ) ) {
		return;
	}

	foreach ( array_reverse( get_ancestors( $term->term_id, $term->taxonomy, 'taxonomy' ) ) as $ancestor_id ) {
		$ancestor_term = get_term( $ancestor_id, $term->taxonomy );

		if ( $ancestor_term instanceof WP_Term ) {
			$trail[] = array(
				'label' => $ancestor_term->name,
				'url'   => get_term_link( $ancestor_term ),
			);
		}
	}
}

public static function add_post_ancestors( array &$trail, WP_Post $post ) {
	foreach ( array_reverse( get_post_ancestors( $post ) ) as $ancestor_id ) {
		$trail[] = array(
			'label' => get_the_title( $ancestor_id ),
			'url'   => get_permalink( $ancestor_id ),
		);
	}
}
-			if ( is_taxonomy_hierarchical( $ls_breadcrumbs_term->taxonomy ) ) {
-				$ls_breadcrumbs_ancestor_ids = array_reverse(
-					get_ancestors( $ls_breadcrumbs_term->term_id, $ls_breadcrumbs_term->taxonomy, 'taxonomy' )
-				);
-
-				foreach ( $ls_breadcrumbs_ancestor_ids as $ls_breadcrumbs_ancestor_id ) {
-					$ls_breadcrumbs_ancestor_term = get_term( $ls_breadcrumbs_ancestor_id, $ls_breadcrumbs_term->taxonomy );
-
-					if ( $ls_breadcrumbs_ancestor_term instanceof WP_Term ) {
-						$ls_breadcrumbs_trail[] = array(
-							'label' => $ls_breadcrumbs_ancestor_term->name,
-							'url'   => get_term_link( $ls_breadcrumbs_ancestor_term ),
-						);
-					}
-				}
-			}
+			LS_Plugin_Breadcrumbs::add_taxonomy_ancestors( $ls_breadcrumbs_trail, $ls_breadcrumbs_term );

Apply the same replacement at lines 79-87 and 127-134 with add_post_ancestors(), and at lines 94-109 with add_taxonomy_ancestors().

Also applies to: 79-87, 94-109, 127-134

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/blocks/breadcrumbs/render.php` around lines 51 - 66, Extract the
duplicated ancestor-building logic into public static LS_Plugin_Breadcrumbs
helpers: add_taxonomy_ancestors(array &$trail, WP_Term $term) and
add_post_ancestors(array &$trail, WP_Post $post). Move the existing taxonomy and
post ancestor behavior into these helpers, using each term’s taxonomy, then
replace the taxonomy blocks at the current-term and single-post branches and
both post ancestor loops with helper calls; do not add bare functions to
render.php.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/blocks/breadcrumbs/render.php`:
- Around line 51-66: Extract the duplicated ancestor-building logic into public
static LS_Plugin_Breadcrumbs helpers: add_taxonomy_ancestors(array &$trail,
WP_Term $term) and add_post_ancestors(array &$trail, WP_Post $post). Move the
existing taxonomy and post ancestor behavior into these helpers, using each
term’s taxonomy, then replace the taxonomy blocks at the current-term and
single-post branches and both post ancestor loops with helper calls; do not add
bare functions to render.php.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 3980f656-7a7b-43a9-aa37-dfc97e6789b2

📥 Commits

Reviewing files that changed from the base of the PR and between dbde3b4 and e20f032.

📒 Files selected for processing (15)
  • CHANGELOG.md
  • build/blocks/breadcrumbs/block.json
  • build/blocks/breadcrumbs/index.asset.php
  • build/blocks/breadcrumbs/index.js
  • build/blocks/breadcrumbs/render.php
  • build/blocks/breadcrumbs/style-index-rtl.css
  • build/blocks/breadcrumbs/style-index.css
  • inc/class-breadcrumbs.php
  • ls-plugin.php
  • src/blocks/breadcrumbs/block.json
  • src/blocks/breadcrumbs/edit.js
  • src/blocks/breadcrumbs/index.js
  • src/blocks/breadcrumbs/render.php
  • src/blocks/breadcrumbs/save.js
  • src/blocks/breadcrumbs/style.scss

…stor logic

- Extract taxonomy/post ancestor-walking into static helpers on
  LS_Plugin_Breadcrumbs (add_taxonomy_ancestors, add_post_ancestors,
  get_term_url), removing 3x duplicated logic across render.php branches
- Fix a real PHP 8 TypeError risk: get_term_link() can return WP_Error,
  which was being passed straight into esc_url() unguarded
- Addresses Gemini Code Assist and CodeRabbit review feedback on PR #16
@brandonmarshal

Copy link
Copy Markdown
Collaborator Author

Closing Without Merging — Superseded by Yoast's Native Breadcrumbs Block

This PR built a fully custom ls-plugin/breadcrumbs dynamic block, but that approach is being dropped.

Context in LS-1228

The original plan was to call yoast_breadcrumb() when Yoast SEO is active, with a native fallback otherwise.

Testing surfaced a real Yoast caching quirk: Indexables ancestor data was not refreshing for programmatically updated page parents. Instead of investigating that further, this PR removed the Yoast integration entirely and rebuilt the breadcrumbs functionality from scratch.

However, Yoast SEO already provides its own dedicated yoast-seo/breadcrumbs block. There is therefore no need to build or maintain a custom equivalent in ls-plugin.

Corrected Approach

The corrected approach, documented in the course-correction and Part 2 re-approach plans, wires Yoast's native block directly into:

ls-theme/patterns/breadcrumbs.php

That work is currently in progress on:

feature/LS-1228-breadcrumbs-part-2

in the ls-theme repository.

Net Result

ls-plugin requires no breadcrumb-related code changes.

This PR and its branch, feature-breadcrumbs-dynamic-block, will therefore be closed and abandoned rather than merged.

@brandonmarshal
brandonmarshal deleted the feature-breadcrumbs-dynamic-block branch July 15, 2026 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:navigation Menus & nav UX area:plugins Plugin configuration/internals lang:js JavaScript/TypeScript lang:php PHP code release:minor Backwards‑compatible enhancements requiring a MINOR version bump (e.g., new features, performance im status:needs-review Awaiting code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant