Skip to content

Content slots: editor-controlled pattern regions outside post_content #94

Description

@parisek

Feature request, filed to anchor the idea rather than to specify it — the design needs working through from several angles before anyone builds it.

The need

Sections that appear on every post of a type but are not part of any post's content: related articles, a CTA under an article, a newsletter box above the footer. Today these live in the Twig template, which means changing one is a code change and a deploy. Editors cannot touch them.

The concrete case that prompted this: three fixed patterns at the end of every article, editable in one place.

Why not just FSE

WordPress solves this natively with block templates — templates/single-post.html wrapping <!-- wp:post-content /--> with whatever surrounds it. That is the right idea and the wrong implementation for this stack.

  • A full block theme discards the Twig layer, the component system, and everything this package is.
  • A hybrid (add_theme_support( 'block-template-parts' ) + theme.json + parts/) is technically supported and is the dangerous option: it gives the site a template-part editor that competes with Twig for ownership of the layout. Two competing sources of truth about what a page looks like is worse than one imperfect source. It fails late — the editor UI works, someone edits a part, and the front end does not change because Twig owns that region.

What is worth taking from FSE is the principle, not the machinery: regions outside the post content are editable content, not code. That principle can be adopted with the tools this stack already has.

Sketch

A named slot registered alongside the other theme config, resolved to a pattern and rendered by the theme:

  • Slots declared by the consuming theme, in the manner of StarterBase::$options_pages — a name, the post types it applies to, and where it renders.
  • The value is an ACF post_object field restricted to post type wp_block, on an options page for the global default, optionally overridden per post.
  • Rendering goes through core rather than by hand:
    do_blocks( '<!-- wp:block {"ref":' . (int) $id . '} /-->' )
    core/block's own render callback handles a missing target, nested patterns, and recursion protection. Loading the wp_block post and calling do_blocks( $post->post_content ) directly bypasses all of that.
  • Exposed to Twig through the existing context so a template can place it: {{ slot('after_article') }} or similar.

Design questions to settle first

These are the reason this is a feature request and not a plan.

  1. The three-state cascade. An empty per-post field is ambiguous: "inherit the global" or "show nothing here"? Two states cannot express three intents, and without an explicit answer there is no way to give one article no slot at all. Probably an explicit override toggle beside the picker, but it is a UX decision as much as a data one.

  2. Cache invalidation. A pattern's content is not in any rendering post, so editing it changes no post's post_modified. Every page rendering that pattern must be purged when it is saved, and nothing does that by default — the symptom is an edit that appears not to have saved. portadesign-mcp exposes portadesign_mcp_cache_clearers for registering a clearer, so there is a place to hook; the harder part is knowing which pages render a given pattern, which argues for storing the reference somewhere queryable rather than only in an options row.

  3. Scope of a slot. Per post type is the obvious granularity. Per taxonomy term, per language, or per template are all plausible extensions and each one multiplies the resolution logic. Worth deciding how far the first version goes and saying so.

  4. Translation. The pattern is a post, so it translates as a post; a post_object reference remaps through wpml_object_id. Whether the slot assignment is per language or shared is a separate decision — an options field under ACFML can be either, and the two give quite different editorial workflows. This package already carries Acfml/ and Wpml/, so the machinery is at hand, but the intent needs choosing.

  5. How much can editors break? The point of moving a region out of code is that editors can change it. The point of it being a fixed layout is that they cannot change it wrongly. Where that line sits — which patterns are selectable, whether arbitrary ones are — decides whether this is safer or less safe than the status quo.

Why post_object specifically

Worth recording because it is the one decision that is cheap now and expensive later. If the reference is a post_object field rather than a text or number field holding a raw id, it is a first-class reference to every tool that already understands ACF references — the portadesign-mcp connector remaps it for translations, finds it when it dangles, and can read and write it as an ordinary field. A raw id in a text field is an opaque number that none of that machinery can see.

Related

  • portadesign/portadesign-mcp#182 — exposing synced patterns through the MCP connector (promote a block to a pattern, insert one). Complementary: that issue is about the agent noticing repetition and proposing a pattern; this one is about placing a chosen pattern in a fixed region.
  • portadesign/portadesign-mcp#183template_lock being unenforced on the write path. Same underlying theme: page regions governed by a rule stored outside the content they affect.
  • Deliberation: where the line between code and content should sit — Twig, FSE, and what standardising would actually cost #95 — the deliberation this proposal came out of: whether to move to native block templates at all, and why locking them would remove the reason to. This issue is the non-migratory alternative that discussion landed on.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions