Skip to content

feat(core): add breadcrumbs field to PublicPageContext#414

Open
jdevalk wants to merge 1 commit intoemdash-cms:mainfrom
jdevalk:feat/public-page-context-breadcrumbs
Open

feat(core): add breadcrumbs field to PublicPageContext#414
jdevalk wants to merge 1 commit intoemdash-cms:mainfrom
jdevalk:feat/public-page-context-breadcrumbs

Conversation

@jdevalk
Copy link
Copy Markdown
Contributor

@jdevalk jdevalk commented Apr 9, 2026

Summary

Adds an optional breadcrumbs?: BreadcrumbItem[] field to PublicPageContext so themes can publish a breadcrumb trail as part of the page context, and SEO plugins (or any other page:metadata consumer) can read it verbatim instead of inventing their own per-theme override mechanism.

Closes #413.

What changes

  • packages/core/src/plugins/types.ts — new BreadcrumbItem interface and breadcrumbs? field on PublicPageContext, with doc comment describing the undefined / [] / non-empty semantics for consumers.
  • packages/core/src/page/context.tscreatePublicPageContext now accepts and passes through breadcrumbs from template input.
  • packages/core/src/index.tsBreadcrumbItem exported from the emdash package root alongside PublicPageContext.
  • packages/core/tests/unit/plugins/page-context.test.ts — three new tests covering verbatim pass-through, default-undefined, and explicit empty-array preservation.
  • .changeset/public-page-context-breadcrumbs.mdminor bump for emdash.

Consumer contract

When breadcrumbs is present on the context, SEO plugins should:

  1. Use it verbatim for BreadcrumbList schema emission.
  2. Skip their own derivation/rule layers entirely for that page.
  3. Treat breadcrumbs: [] as "this page has no breadcrumbs" (e.g. homepage, error pages) and skip emission.

When breadcrumbs is undefined, plugins fall back to whatever they do today (path derivation, rule maps, etc).

Why the context and not a plugin config callback?

I considered that first. It doesn't work through the current plugin loader: generatePluginsModule at packages/core/src/astro/integration/virtual-modules.ts:200 serializes descriptor.options with JSON.stringify, so functions are stripped before they reach the runtime plugin. Any theme-supplied callback would be silently dropped.

Pushing the data onto the page context is cleaner anyway — it's observable, debuggable, and usable by any page:metadata consumer rather than coupling to one plugin's config shape.

Migration

Non-breaking:

  • Optional field, default undefined.
  • Existing themes work unchanged.
  • Existing SEO plugins continue to work; they can adopt the new field incrementally.

Downstream consumer

@jdevalk/emdash-plugin-seo v0.3.0 ships with path derivation, segment-label overrides, and per-pageType rule maps as its existing fallback chain. Once this lands, it will consume page.breadcrumbs as Layer 0 (highest priority) above all of those.

Test plan

  • pnpm --filter emdash typecheck passes
  • npx vitest run tests/unit/plugins/page-context.test.ts — 11 passed (3 new)
  • npx vitest run tests/unit/plugins/ — 530 passed across 23 files
  • CI green on PR

🤖 Generated with Claude Code

Themes can now publish a breadcrumb trail alongside the rest of the
page context, and SEO plugins (or any other page:metadata consumer)
can read it verbatim instead of inventing their own per-theme
override mechanism.

The field is optional and non-breaking:

- undefined — theme has no opinion; consumer falls back to its own
  derivation (path walking, rule maps, etc).
- [] — explicit opt-out; consumer should skip BreadcrumbList emission
  entirely (e.g. homepages, error pages).
- Non-empty array — used verbatim by consumers for BreadcrumbList
  schema output.

BreadcrumbItem is also exported from the emdash package root so
plugins can import the type directly.

Why the context and not a plugin config callback? The descriptor
options round-trip through JSON.stringify in generatePluginsModule
(packages/core/src/astro/integration/virtual-modules.ts:200), so
functions are stripped before they reach the runtime plugin. Pushing
the data onto the page context is both technically necessary and
semantically cleaner — it's observable, debuggable, and usable by
any hook consumer rather than coupling to one plugin's config shape.

Refs emdash-cms#413

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 9, 2026 18:28
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 9, 2026

🦋 Changeset detected

Latest commit: 924c614

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
emdash Minor
@emdash-cms/cloudflare Patch
@emdash-cms/plugin-ai-moderation Major
@emdash-cms/plugin-atproto Patch
@emdash-cms/plugin-audit-log Patch
@emdash-cms/plugin-color Major
@emdash-cms/plugin-embeds Major
@emdash-cms/plugin-forms Major
@emdash-cms/plugin-webhook-notifier Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 9, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@414

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@414

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@414

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@414

emdash

npm i https://pkg.pr.new/emdash@414

create-emdash

npm i https://pkg.pr.new/create-emdash@414

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@414

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@414

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@414

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@414

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@414

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@414

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@414

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@414

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@414

commit: 924c614

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class breadcrumb support to the core public page context so themes can provide an explicit breadcrumb trail and page:metadata consumers (e.g. SEO plugins) can use it verbatim.

Changes:

  • Introduces BreadcrumbItem and adds optional breadcrumbs?: BreadcrumbItem[] to PublicPageContext, including documented consumer semantics (undefined vs [] vs non-empty).
  • Extends createPublicPageContext to accept/pass through breadcrumbs from template input.
  • Exports BreadcrumbItem from the package root and adds unit tests + a changeset for the minor bump.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/core/src/plugins/types.ts Defines BreadcrumbItem and adds breadcrumbs? to PublicPageContext with a clear consumer contract.
packages/core/src/page/context.ts Accepts breadcrumbs on input and passes it through to the returned PublicPageContext.
packages/core/src/index.ts Re-exports BreadcrumbItem from the package root for downstream consumers.
packages/core/tests/unit/plugins/page-context.test.ts Adds coverage for pass-through, default undefined, and explicit empty-array preservation.
.changeset/public-page-context-breadcrumbs.md Declares a minor version bump and describes the new API surface/semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@nickgraynews
Copy link
Copy Markdown

Yes please!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add breadcrumbs?: BreadcrumbItem[] to PublicPageContext for theme-driven breadcrumb trails

3 participants