Skip to content

feat: add BreadcrumbsResolver for artisanpack/breadcrumbs (#565) - #575

Merged
ViewFromTheBox merged 2 commits into
feature/495-port-crosswinds-blocksfrom
feature/565-breadcrumbs-resolver
Jun 13, 2026
Merged

ViewFromTheBox merged 2 commits into
feature/495-port-crosswinds-blocksfrom
feature/565-breadcrumbs-resolver

Conversation

@ViewFromTheBox

@ViewFromTheBox ViewFromTheBox commented Jun 12, 2026 •

Copy link
Copy Markdown
Member

Description

Adds a BreadcrumbsResolver to the Blade renderer that stamps _resolvedTrail on every artisanpack/breadcrumbs block in the saved tree, so the existing Blade / React / Vue renderers (which already consume _resolvedTrail) emit a populated <ol> on the public frontend instead of an empty shell.

The default trail is Home → …ancestors → current. The Home hop reads from config('artisanpack.visual-editor.breadcrumbs.home_url') (falls back to url('/')); the last entry is auto-marked current: true so the renderer drops the link and emits aria-current="page". Hierarchical posts (pages with parent/parent_id) walk the ancestor chain bounded to 32 levels with cycle detection.

Hosts customize the trail through the ap.visual-editor.breadcrumbs.trail filter (e.g. to insert a 'Category' hop between Home and a blog post) without having to subclass the resolver. A pre-stamped _resolvedTrail on the saved tree wins over the resolver fallback, matching PostResolver's contract.

Also ships breadcrumbs.css to the frontend asset bundle and wires it into <x-ve-blocks-styles /> so the inline-flex layout + chevron separator applies on the public site without each host re-implementing the rules.

Closes: #565

Type of Change

  • New feature (adds new functionality)

Related Issue

Issue: #565

Motivation and Context

CW0 (#496) ported crosswinds-blocks/breadcrumbs → artisanpack/breadcrumbs with full Blade + React + Vue renderers that consume a _resolvedTrail attribute, but nothing stamped that attribute server-side — so the block rendered an empty <ol> on the public frontend. This PR is the resolver layer (analogous to PostResolver for the post-* family).

Changes Made

  • New BreadcrumbsResolver in packages/visual-editor-renderer-blade/src/Resolvers/ walking the block tree and stamping _resolvedTrail on every artisanpack/breadcrumbs block.
  • Wired the resolver into BlocksComponent (runs after PostResolver, gated behind :resolve-breadcrumbs="false" opt-out) and registered it as scoped in the service provider (matches LoginoutResolver's lifetime story for Octane / queue workers).
  • Added breadcrumbs section to the package config with home_url / home_label overrides.
  • Shipped breadcrumbs.css to the frontend asset bundle; wired into <x-ve-blocks-styles /> (independent of \$emitInteractive, same as grid / marquee / social-icons).
  • Tightened the inline gap from 0.5rem to 0.25rem on the breadcrumbs stylesheet (editor + frontend stay in sync).

How Has This Been Tested?

Testing Environment:

  • Operating System: macOS (Darwin 25.5.0)
  • Browser: Chrome
  • PHP Version: 8.4.3
  • Project Version: feature/495-port-crosswinds-blocks (1.1)

Tests Performed:

  1. Automated: 14 new feature tests in `BreadcrumbsResolverTest` (homepage shell, single post, page hierarchy, parent-chain cycle, filter hook, pre-stamped trail, custom home URL/label, blank-label drop, etc.); full `RendererBlade` suite still green (290 passing, 789 assertions).
  2. Manual: rendered a single-post page in the dev app with a top-level breadcrumbs block — confirmed the populated trail (Home → post title) with inline-flex layout + chevron separator + Schema.org annotations.
  3. Manual: confirmed homepage shell renders a single Home entry marked `aria-current="page"`.

Accessibility Tests Run

  • Keyboard navigation tested
  • Screen reader tested
  • Color contrast verified
  • ARIA labels checked

Details:

The renderer emits `aria-label="Breadcrumb"` (configurable via the block's `ariaLabel` attribute) on the wrapping `

` and `aria-current="page"` on the current entry. Schema.org `BreadcrumbList` itemtype annotations are gated on the `breadcrumbsSchema` attribute (default on) — verified both states render correctly.

Tests Added

  • Unit tests added/updated
  • Integration tests added/updated
  • All tests passing

Test details:

  • 14 new feature tests in `packages/visual-editor-renderer-blade/tests/Feature/BreadcrumbsResolverTest.php`:
    • Home-only trail when no post supplied
    • Homepage shell renders single Home entry as current
    • Home + post title trail for single-post context
    • End-to-end rendered markup via `x-ve-blocks` for a single post
    • Parent-chain walk for nested page hierarchy (top-down order)
    • Cycle guard against `A.parent = B; B.parent = A`
    • `breadcrumbsSchema` attribute toggles itemtype annotations
    • Host filter `ap.visual-editor.breadcrumbs.trail` overrides the trail
    • Filter receives block attributes for branching
    • Pre-stamped `_resolvedTrail` wins over the resolver fallback
    • Custom `home_url` / `home_label` config respected
    • Nested breadcrumbs inside container blocks walked recursively
    • Tolerates posts with `parent_id` but no `parent` accessor / no `newQuery()`
    • Filter-returned blank-label entries dropped
  • 1 new assertion in `BlocksStylesComponentTest` for the frontend stylesheet link.

Documentation

  • Inline code documentation added

Documentation details:

`BreadcrumbsResolver` ships PHPDoc on the class, every public method, and the filter hook semantics. The package `config/visual-editor.php` has a documented `breadcrumbs` section describing the override knobs and the filter hook.

Pre-Submission Checklist

  • Code passes all tests
  • Self-review completed
  • Comments added for complex code

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Breadcrumbs block added with configurable Home URL and label.
    • Automatic breadcrumb trail resolution from page hierarchy, with Schema.org markup support.
    • Breadcrumbs stylesheet now included in rendered pages.
  • Style

    • Reduced spacing between breadcrumb items for tighter layout.

Stamps `_resolvedTrail` on every `artisanpack/breadcrumbs` block in the
saved tree so the Blade / React / Vue renderers (which already consume
the attribute) emit a populated `<ol>` on the public frontend.

The default trail is `Home → …ancestors → current`. The Home hop reads
from `config('artisanpack.visual-editor.breadcrumbs.home_url')` (falls
back to `url('/')`); the last entry is auto-marked `current: true` so
the renderer drops the link and emits `aria-current="page"`. Hierarchical
posts (pages with `parent`/`parent_id`) walk the ancestor chain bounded
to 32 levels with cycle detection.

Hosts customize the trail through the `ap.visual-editor.breadcrumbs.trail`
filter (e.g. to insert a "Category" hop between Home and a blog post)
without subclassing. A pre-stamped `_resolvedTrail` on the saved tree
wins over the resolver fallback, matching PostResolver's contract.

Also ships `breadcrumbs.css` to the frontend asset bundle and wires it
into `<x-ve-blocks-styles />` so the inline-flex layout + chevron
separator applies on the public site without each host re-implementing
the rules. Tightens the inter-item gap from 0.5rem to 0.25rem.

Closes #565

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 12, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 972789ad-4c35-4d37-87c1-515a87c06b6c

📥 Commits

Reviewing files that changed from the base of the PR and between 0ad41d8 and 432aa7f.

📒 Files selected for processing (1)
  • packages/visual-editor-renderer-blade/src/Resolvers/BreadcrumbsResolver.php

📝 Walkthrough

Walkthrough

Server-side BreadcrumbsResolver stamps a normalized _resolvedTrail onto artisanpack/breadcrumbs blocks using post context (Home + ancestors + current), tied into the BlocksComponent pipeline, adds styling and config for home URL/label, and includes comprehensive tests.

Changes

Breadcrumbs Resolver and Component Integration

Layer / File(s) Summary
Configuration and Core Styling
config/visual-editor.php, packages/visual-editor-renderer-blade/resources/assets/frontend/breadcrumbs.css, resources/js/visual-editor/blocks/breadcrumbs/breadcrumbs.css, packages/visual-editor-renderer-blade/resources/views/components/blocks-styles.blade.php, packages/visual-editor-renderer-blade/src/View/Components/BlocksStylesComponent.php
Breadcrumbs config adds home_url and home_label; new frontend breadcrumbs stylesheet defines flex layout, separators, and current-item styling; editor spacing reduced to 0.25rem; template and component emit the stylesheet link and expose breadcrumbsStyleHref.
BreadcrumbsResolver Core Logic
packages/visual-editor-renderer-blade/src/Resolvers/BreadcrumbsResolver.php
Resolver walks block trees stamping _resolvedTrail, builds default Home+ancestors+current trails, applies ap.visual-editor.breadcrumbs.trail filter when available, normalizes entries (label/url/current), detects cycles and enforces max parent depth, and resolves labels/urls with safe fallbacks.
Component Pipeline and Service Container Integration
packages/visual-editor-renderer-blade/src/View/Components/BlocksComponent.php, packages/visual-editor-renderer-blade/src/VisualEditorRendererBladeServiceProvider.php
BlocksComponent injects BreadcrumbsResolver and conditionally calls stampTree during render preparation; ServiceProvider registers the resolver as scoped per request.
Template Stylesheet Link and Component Test
packages/visual-editor-renderer-blade/resources/views/components/blocks-styles.blade.php, packages/visual-editor-renderer-blade/tests/Feature/BlocksStylesComponentTest.php
blocks-styles.blade.php emits the breadcrumbs stylesheet link with data-ve-breadcrumbs; test verifies inclusion even when :interactive="false".
Comprehensive Feature Test Suite
packages/visual-editor-renderer-blade/tests/Feature/BreadcrumbsResolverTest.php
Extensive tests and helpers cover no-post/home, single post, nested ancestors, cycle handling, Schema.org toggling, host filter overrides and attribute passing, preserved pre-stamped trails, custom home config, nested blocks, parent_id-only posts, and blank-label filtering.

Sequence Diagram

sequenceDiagram
  participant BlocksComponent
  participant BreadcrumbsResolver
  participant PostModel
  participant TrailFilter
  participant BladeRenderer
  BlocksComponent->>BreadcrumbsResolver: stampTree(blockTree, post)
  BreadcrumbsResolver->>PostModel: collectAncestors(post) / parentPost lookup
  BreadcrumbsResolver->>TrailFilter: apply ap.visual-editor.breadcrumbs.trail (if available)
  BreadcrumbsResolver->>BreadcrumbsResolver: finalizeTrail(entries)
  BreadcrumbsResolver->>BlocksComponent: return stamped tree (_resolvedTrail)
  BlocksComponent->>BladeRenderer: render blocks (breadcrumb block reads _resolvedTrail)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ArtisanPack-UI/visual-editor#567: Adds React and Vue breadcrumb renderer implementations that consume the _resolvedTrail stamped by this PR's BreadcrumbsResolver.
  • ArtisanPack-UI/visual-editor#453: Also modifies BlocksStylesComponent and blocks-styles.blade.php to emit additional stylesheet links alongside the breadcrumbs stylesheet link.

Poem

🐰 I hop the trail from Home to now,
Ancestors counted, cycles I vow,
A stamped _resolvedTrail to show the way,
Filters may tweak crumbs as hosts may say,
Breadcrumbs neatly rendered — hop hooray! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: adding BreadcrumbsResolver for the artisanpack/breadcrumbs block, which is the primary objective of this changeset.
Linked Issues check ✅ Passed All core objectives from issue #565 are met: BreadcrumbsResolver implemented and wired into pipeline [#565], resolves trail with Home entry and current marking [#565], configurable home URL/label in config [#565], filter hook ap.visual-editor.breadcrumbs.trail for customization [#565], comprehensive feature tests covering homepage, hierarchy, cycles, and edge cases [#565], and pre-stamped trails preserved [#565].
Out of Scope Changes check ✅ Passed All changes directly support breadcrumbs resolver implementation: configuration additions, resolver class, component integration, styling, and comprehensive tests. No unrelated or extraneous modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/565-breadcrumbs-resolver

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

@ViewFromTheBox ViewFromTheBox moved this from Backlog to In Review in ArtisanPack UI Overview Jun 12, 2026
@ViewFromTheBox ViewFromTheBox linked an issue Jun 12, 2026 that may be closed by this pull request
4 tasks
@ViewFromTheBox
ViewFromTheBox marked this pull request as ready for review June 12, 2026 22:47
@ViewFromTheBox

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 12, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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)
packages/visual-editor-renderer-blade/src/Resolvers/BreadcrumbsResolver.php (1)

175-181: 📐 Maintainability & Code Quality | 💤 Low value

Optional: Simplify redundant conditional.

The onHomepage check at line 175 and the subsequent null === $post check at line 179 both return the same $trail value. Since any null post results in a Home-only trail (which the finalizer marks as current), the onHomepage guard doesn't gate different behavior.

♻️ Suggested simplification
-		// Homepage — when no post is in scope and the request URL matches
-		// the configured home URL, the trail collapses to a single Home
-		// entry. The finalizer marks it `current: true` so the renderer
-		// drops the link.
-		if ( null === $post && $this->onHomepage( $homeUrl ) ) {
-			return $trail;
-		}
-
 		if ( null === $post ) {
 			return $trail;
 		}
🤖 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 `@packages/visual-editor-renderer-blade/src/Resolvers/BreadcrumbsResolver.php`
around lines 175 - 181, The early-return block contains a redundant branch:
remove the specific onHomepage($homeUrl) check and consolidate to a single null
=== $post guard that returns $trail; in the BreadcrumbsResolver (method handling
$post/$trail) keep the null check (if ( null === $post ) return $trail;) and
delete the preceding if ( null === $post && $this->onHomepage( $homeUrl ) )
branch so behavior is unchanged but the conditional is simplified.
🤖 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 `@packages/visual-editor-renderer-blade/src/Resolvers/BreadcrumbsResolver.php`:
- Around line 175-181: The early-return block contains a redundant branch:
remove the specific onHomepage($homeUrl) check and consolidate to a single null
=== $post guard that returns $trail; in the BreadcrumbsResolver (method handling
$post/$trail) keep the null check (if ( null === $post ) return $trail;) and
delete the preceding if ( null === $post && $this->onHomepage( $homeUrl ) )
branch so behavior is unchanged but the conditional is simplified.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1624e946-2d8f-46ca-913b-add58534d57e

📥 Commits

Reviewing files that changed from the base of the PR and between 9d8827a and 0ad41d8.

📒 Files selected for processing (10)
  • config/visual-editor.php
  • packages/visual-editor-renderer-blade/resources/assets/frontend/breadcrumbs.css
  • packages/visual-editor-renderer-blade/resources/views/components/blocks-styles.blade.php
  • packages/visual-editor-renderer-blade/src/Resolvers/BreadcrumbsResolver.php
  • packages/visual-editor-renderer-blade/src/View/Components/BlocksComponent.php
  • packages/visual-editor-renderer-blade/src/View/Components/BlocksStylesComponent.php
  • packages/visual-editor-renderer-blade/src/VisualEditorRendererBladeServiceProvider.php
  • packages/visual-editor-renderer-blade/tests/Feature/BlocksStylesComponentTest.php
  • packages/visual-editor-renderer-blade/tests/Feature/BreadcrumbsResolverTest.php
  • resources/js/visual-editor/blocks/breadcrumbs/breadcrumbs.css

Remove the redundant `onHomepage()` short-circuit in
`BreadcrumbsResolver::defaultTrail()` — both the homepage-specific and
the generic `null === $post` branches returned the same trail, so the
extra check (and its `onHomepage()` helper + `Illuminate\Http\Request`
import) were dead surface. Behavior unchanged: a null post still
collapses to a single Home entry that the finalizer marks
`current: true`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ViewFromTheBox
ViewFromTheBox merged commit 63bff5a into feature/495-port-crosswinds-blocks Jun 13, 2026
1 check passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in ArtisanPack UI Overview Jun 13, 2026
@ViewFromTheBox
ViewFromTheBox deleted the feature/565-breadcrumbs-resolver branch June 13, 2026 00:02
@coderabbitai coderabbitai Bot mentioned this pull request Jun 14, 2026
9 of 22 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

BreadcrumbsResolver: stamp _resolvedTrail for artisanpack/breadcrumbs

1 participant