All notable changes to this project are documented in this file. The format is based on Keep a Changelog; this project adheres to Semantic Versioning.
[Unreleased]
[1.5.1] - 2026-07-26
Fixed
<!-- wp:template-part /-->references inside theme templates now render inline in the site-editor canvas (#674). Three-layered failure:TemplatePartController::index()ignored the?theme=&slug=filter the core-data shim sends for composite-id fallback, so the shim silently picked an arbitrary sibling (or the missing-record placeholder).TemplateAdaptershippedcontent.blocks: []for theme-file sources because cms-framework's filter contributor only populatesraw_content, and the shim's raw-fallback parser is scoped to nav-link / nav-submenu blocks. Since the I7 cutover (#415) removedregisterCoreBlocks(),core/template-partblocks land unregistered and get dropped; theartisanpack/template-partfork's edit delegated tocore/template-partviacreateForkedEntityEdit, which fell back to an empty<div>because the core lookup returned undefined. Fixed at all three layers — controller now filters bytheme+slug; adapter parsesrawserver-side via cms-framework 2.5+'sBlockMarkupParser(guarded withclass_existsso older versions degrade gracefully) and rewritescore/template-partto the fork in bothrawandblocks; the fork ships a real edit that composes the composite id fromslug+theme, hands off to the shim'suseEntityBlockEditor, and mounts the resolved tree throughuseInnerBlocksPropswithtemplateLock: 'all'. The composite call is gated behind a subcomponent so a legacy reference missingthemecan't leak through the shim's ambient-id fallback and mount the parent template recursively.- Site-editor canvas no longer paints a
padding: 24pxinset + a hardcodedbackground: #fffover the theme's own background (#674). Templates and template-parts span the full viewport on the front-end, so the inset made full-bleed sections cap short of the shell edges and dark themes read as a pale-rimmed rectangle. Scoped to.ap-site-editor__entity-canvas-surface; the pattern editor uses.ap-pattern-canvas__surfaceand is unaffected.
[1.5.0] - 2026-07-21
Added
-
Six new lifecycle hooks for editor and block integrations (#665). Fills in high-value extension points across block registration, rendering, post persistence, editor config assembly, and pattern rendering. All hooks live under the canonical
ap.visualEditor.*namespace introduced in #664:ap.visualEditor.blockRegistered— action, fires at the end of block registration with(string $name, array $config). Fires throughBlockTypeRegistry::register()so bothblock.jsonregistration and programmaticregisterBlockType()calls emit.ap.visualEditor.beforeRender— filter on(array $attributes, string $name)that runs insideBlockRenderer::renderBlock()after site-meta / loginout stamping. Non-array returns are discarded so a misbehaving callback can't blank the block.ap.visualEditor.postSaved— action fired fromWpEntityControllerafter every POST/PUT persistence with(int|string $postId, array $blocks).ap.visualEditor.postPublished— action fired from the same site whenever the current save transitions status into the WP-canonicalpublishvalue (create-with-publish or non-publish → publish on update). Same payload aspostSaved.ap.visualEditor.editorConfig— filter applied byVisualEditorComponenton the assembled config array with(array $config, string $screen);$screenis'post'for the current component. Filtered values are re-hydrated onto the component's typed props, so misbehaving returns can't poison a?stringprop.ap.visualEditor.patternRender— filter applied byPatternAdapter::toArray()on the rendered raw content of a pattern with(string $html, string $slug, array $context). Context carries source, synced flag, categories, block_types, and post_types so callbacks can gate per pattern shape.
Changed
- Bumped
artisanpack-ui/hooksrequirement to^1.3(#665). The hook fire sites added in #665 assume the helper globals are always available, so the previous^1.2constraint'sfunction_exists()guards have been dropped in favour of the newer floor.
Changed (BREAKING)
-
Renamed every visual-editor hook to camelCase (#664). The 13 PHP hooks the package fires or subscribes to, plus the
visual_editor.pre_publish_checkscross-package hook, theap.icons.register-icon-setsbridge hook, and three JS-only hooks (background-controls,canvas-styles,document-panels), now use camelCase — matching the ArtisanPack UI ecosystem's naming convention. Old names remain functional viadeprecateHook()aliases registered byArtisanPackUI\VisualEditor\Support\HookAliaseson the PHP side and a bidirectional@wordpress/hooksshim inresources/js/visual-editor/support/hook-aliases.tson the JS side; the JS shim runs atNumber.MIN_SAFE_INTEGERpriority so real subscribers on the paired name are surfaced before priority-sorted dispatch of the applied name's callbacks. A deprecation notice is logged the first time an alias resolves per process. Rename table:ap.visual-editor.resources→ap.visualEditor.resourcesap.visual-editor.templates→ap.visualEditor.templatesap.visual-editor.template-parts→ap.visualEditor.templatePartsap.visual-editor.patterns→ap.visualEditor.patternsap.visual-editor.global-styles→ap.visualEditor.globalStylesap.visual-editor.navigation→ap.visualEditor.navigationap.visual-editor.visibility.register-rules→ap.visualEditor.visibility.registerRulesap.visual-editor.visibility.evaluated→ap.visualEditor.visibility.evaluatedap.visual-editor.visibility.user-search-results→ap.visualEditor.visibility.userSearchResultsap.visual-editor.rendered-block→ap.visualEditor.renderedBlockap.visual-editor.breadcrumbs.trail→ap.visualEditor.breadcrumbs.trailap.visual-editor.loginout.envelope→ap.visualEditor.loginout.envelopeap.visual-editor.loginout.login-form→ap.visualEditor.loginout.loginFormvisual_editor.pre_publish_checks→ap.visualEditor.prePublishChecksap.icons.register-icon-sets→ap.icons.registerIconSetsap.visual-editor.background-controls→ap.visualEditor.backgroundControlsap.visual-editor.canvas-styles→ap.visualEditor.canvasStylesap.visual-editor.document-panels→ap.visualEditor.documentPanels
See
src/Support/HookAliases.phpfor the full canonical list. Bumps theartisanpack-ui/hooksComposer requirement to^1.2(v1.3.x in practice) to pick up thedeprecateHook()helper (v1.3.0). -
Site-editor map resolvers key by the entry's own identifier, not the raw filter key. Previously,
find( $rawKey )succeeded when a contributor supplied a filter map whose keys diverged from the entries' ownslug/location. That path is gone:find()now only resolves by the value object's identifier. Contributors that already stamp the matchingslug/locationon their entries (all first-party contributors, including cms-framework) are unaffected. -
Canvas CSS endpoint delegates to cms-framework's
ThemeStylesheetReader.GET /visual-editor/api/global-styles/cssnow delegates the theme-file read toArtisanPackUI\CMSFramework\Modules\SiteEditor\Support\ThemeStylesheetReaderwhen it's available (cms-framework ≥ 2.5). The endpoint now concatenates emitter output +themes/{slug}/style.css+themes/{slug}/editor.css(the last is new — closes the canvas half of cms-framework #199, giving the site editor a WordPressadd_editor_style()analog). Section banners switch from/* === theme stylesheet === */to per-file banners (/* === style.css === */,/* === editor.css === */) so devtools inspection matches cms-framework's endpoint. Falls back to the inlinereadThemeStylesheet()helper for cms-framework < 2.5 — the fallback preserves the pre-change behavior (style.css only, historical banner text) so themes on older cms-framework releases keep the canvas parity the endpoint already delivered.
Fixed
- Site-editor resolvers accept numeric-string map keys (cms-framework #203).
WordPress template hierarchy filenames like
404.html/500.htmldouble as slugs. PHP auto-coerces numeric-string array keys tointat insertion time andarray_mergerenumbers int-keyed entries sequentially, so contributors ofap.visualEditor.{templates,templateParts,patterns,navigation}cannot preserve the intended string key from upstream.AbstractMapResolvernow acceptsintkeys and re-keys the normalized output map by each value object's own identifier via a newidentifierOf()hook (slugfor templates/parts/patterns,locationfor menus). Empty identifiers and identifier collisions across entries now throwSiteEditorRegistrationExceptionat first read instead of silently producing an unaddressable / clobbered entry.
[1.4.0] - 2026-07-16
Added
- Dynamic content editor UX (#650). cms-framework Dynamic Content
integration inside the visual editor: new
dynamic_contentblock-binding source, batchedPOST /dynamic-content/resolve+GET /dynamic-content/sourcesREST endpoints (gated by theSiteEditorAccessGateand throttled at60,1),{{autocomplete + token chip decoration + a Token Inserter modal, a Dynamic Content tab on the link picker, and an Image block DC binding UI. Ships two new blocks:artisanpack/snippet(reusable block with cycle-guarded CRUD + admin surface at/visual-editor/snippets) andartisanpack/dynamic-loop(iterates a collection source and re-renders inner blocks per record). SSR wires binding resolution into the Blade renderer via an optionalBindingResolverdependency, and a newWantsInnerBlocksmarker lets dynamic blocks that need the inner tree at render time (loop) receive it. cms-framework is a soft dependency — everything degrades cleanly when it's not installed. Newdocs/dynamic-content.mdcovers tokens, block bindings, snippets, the loop block, and the render pipeline;docs/block-bindings.mdreconciled to cover the bindings sidecar,fallbackpolicy, and corrected endpoint paths. - Block Visibility (#491, #492, #493). Per-block runtime
visibility rules that evaluate server-side so hidden blocks
never emit markup. A single Visibility inspector panel exposes
three rule families: contextual (master Hide, screen size,
query string, referrer, browser/OS/device), user & auth
(login state, user role, specific user via
/visual-editor/users/searchwithSiteEditorAccessGateauthorization + LIKE escaping), and scheduling (single date/time window, recurring weekly schedule, per-rule timezone override, overnight window support). The editor canvas dims hidden blocks so authors can still see and select them while they're toggled off; the Blade, React, and Vue renderers each filter the tree throughfilterVisibleBlocks()+stampVisibilityScopes()and emit<style data-ve-visibility>media queries per hidden breakpoint. Configurable viaconfig('artisanpack.visual-editor.visibility')— setenabled => falseto bypass every rule site-wide during incident response. Newdocs/visibility.md. - Responsive preview device sizes (#617). Extended the
ViewportSwitcher into a unified device-preview + edit-scope
control. Selecting a preset atomically resizes the canvas iframe
to the breakpoint's
previewWidthPxand scopes subsequent style edits to that key.Breakpointgains optionallabelandpreviewWidthPxfields (both TS + PHP registries); PHP config accepts either the legacy scalar'sm' => '640px'shape or the new object shape'sm' => ['minWidthPx' => 640, 'previewWidthPx' => 375, 'label' => 'iPhone']. Ship defaults: Mobile (sm/375px), Tablet (md/768px), Desktop (lg/1440px), xl+ (1280px), 2xl+ (1536px). Both editor shells now stampdata-breakpointswith the merged registry snapshot so the React shell can hydrate labels and preview widths without a round-trip.docs/blocks/Responsive-Design-Tools.mdupdated with the object-form config and shipped defaults table. - Page pattern inserter modal (#639). WordPress-style "Choose a
pattern" modal that auto-opens when the editor loads a
never-saved record with no content, and can be re-opened at any
time from a new top-bar button next to the
+inserter. Patterns are grouped by category in a responsive grid, and — for thepagesresource — an optional template selector renders site-editor templates (/visual-editor/api/templates) alongside the pattern grid; picking one writes through the existingtemplatefield on the page. Auto-open is gated by a self-contained "never saved" heuristic (empty content pluscreated_at === updated_at), so any save — even a blank canvas — permanently suppresses the auto-open. Zero patterns for the current post-type context suppresses both auto-open and the toolbar button; a Blank starter (page/blank) ships as the seed so the modal always has at least one entry. The modal fetches with?source=themeso user-created snippet patterns (saved via "Convert to pattern" in the sidebar inserter) don't leak into the whole-page picker — those still surface in the block inserter panel where they belong. Client-side the modal further tightens the fetch to patterns whosepost_typesarray explicitly includes the current context (with a carve-out that always keeps the built-inpage/blankseed) — the whole-page picker is meant for starter layouts a developer or theme deliberately declared, not the general pattern library. - Pattern registration
post_typesscope (#639). Every contributor entry to theap.visual-editor.patternsfilter may now carry an optionalpost_types: string[]array (Gutenberg convention). Patterns without a scope stay available in every post-type context; patterns with a scope surface only when the requested slug matches. The scope reaches the client via the newpost_typesfield onPatternAdapter's WP-shape output, and thePatternControllerindex accepts a new?post_type={slug}query param that applies the scope filter server-side. - CSS positioning support for blocks (#640). Per-block Position
panel in the inspector with a
static / relative / absolute / fixed / stickydropdown, per-side offset inputs (px / % / rem / em / vh / vw / auto), z-index, and per-breakpoint overrides via the responsive tab pattern from #487. Opt-in per block viasupports.position: trueinblock.json(Gutenberg's native{ sticky: true }object shape also counts). Emits scoped CSS in the editor canvas and inline styles + a<style data-ve-position>block on the frontend via the Blade renderer. Inspector shows a warning notice whenposition: absoluteis applied but no ancestor is positioned. Per-breakpoint authoring follows the top-bar viewport switcher (via the shared active-breakpoint store) — no separate control lives in the panel, matching every sibling responsive-aware inspector panel. - Enabled
supports.position: trueon 82 top-level artisanpack blocks (#645). Everyresources/js/visual-editor/blocks/*/block.jsonthat doesn't declare aparentorancestorrestriction now opts in. Child-only blocks (accordion internals, list items, columns, buttons children, query pagination, etc.) are deliberately skipped — positioning a child that a container relies on for layout breaks the container's contract.artisanpack/groupalready carried Gutenberg'ssupports.position: { sticky: true }object shape, whichpositionEnabled()also recognizes; no change needed. - Position support on core containers (#646). Functionally
subsumed by #645 given the I7 (#415) cutover — this repo no
longer calls
registerCoreBlocks(), so nocore/*block enters the editor. Every container listed in the parent issue (core/group,core/columns,core/column,core/cover,core/row,core/stack,core/grid,core/buttons,core/image) has anartisanpack/*fork covered by #645. - Integration + e2e test coverage (#647). Round-trip
integration tests exercising resolver + emitter for a full
base + per-breakpoint payload, legacy sticky no-churn, and the
static-toggle-preserves-orphan-fields flow. Playwright e2e
spec (
tests/E2E/positioning.spec.ts) documents the runtime contract for sticky groups, absolute covers, ancestor warnings, per-breakpoint viewport switching, and legacy sticky — commented pending the Playwright runner (matches theanimations.spec.tsprecedent). - Docs (#648). New
docs/position.mdcovering opt-in, attribute shape, position values, offset units, per-breakpoint inheritance, the ancestor warning, the two frontend emission channels (inline +<style data-ve-position>), and a troubleshooting section. Linked fromdocs/home.md. ap.visual-editor.background-controlsfilter (#649). New JS filter that lets third-party packages contribute panels to the background/appearance area of any block that opts into a background support — no per-packageeditor.BlockEditHOC required. The visual editor now owns the target-block decision via a single HOC that gates onsupports.backgroundandsupports.color.background, then renders filter-registered controls sorted by priority (default 10, lower first) and deduped by id (last-wins, mirroring@wordpress/hooks). The HOC runs innermost socontext.attributesis the breakpoint-merged / state-resolved view andcontext.setAttributesroutes through the responsive/state wrappers. Wired into both the post-editor and site-editor bootstraps.ap.visual-editor.rendered-blockfilter (PHP). Fires inBlockRenderer::renderBlockafter each block (static or dynamic) finishes rendering. Callbacks receive the HTML, the block name, and the normalized attributes, and must return an HTML string. Lets cross-cutting effects (frosted glass, contrast overlays, motion wrappers) post-process a block's markup without every host having to modify the renderer.ap.visual-editor.canvas-stylesfilter (JS). Filters the orderedCanvasStyle[]handed toBlockCanvas's__unstableEditorStylesprop. Callbacks return aCanvasStyle[]; non-array returns fall through to the base list and non-object entries are dropped. Lets packages push their stylesheets into the Gutenberg iframe (which is sandboxed from the parent document's Vite-injected styles) without touchingcanvas-styles.tsfor every new integration.
Upgrade notes
- Hosts that ran
php artisan vendor:publish --tag=visual-editor-blade-viewson a prior version must re-publish with--forcewhen upgrading. The publishedblocks.blade.phpandtemplate.blade.phpfiles shadow the package source; the pre-1.4 published copies don't include the new<style data-ve-position>output block, so the position CSS accumulator will flush its rules but they'll never land in the response body — sticky/absolute blocks render unpositioned on the frontend. Command:php artisan vendor:publish --tag=visual-editor-blade-views --force.
[1.3.0] - 2026-07-07
Added
-
AI-powered authoring affordances (#610–#614). Five optional AI-assisted authoring features built on top of
artisanpack-ui/ai. All default to off and honor per-feature toggles from the AI package'sFeatureRegistry; each surfaces suggestions the author must explicitly accept — no automatic mutations. Auto-registered viaVisualEditorServiceProvider::aiFeatures(), so hosts with the AI package installed pick up every affordance with zero manual wiring.Three agents live in this package under
src/Ai/Agents/:ContentBlockSuggestionAgent(#610) —visual_editor.suggest_next_block. Inline "+ suggest next block" affordance that ranks likely next blocks given the document so far. Ships as<SuggestNextBlockButton />.LayoutSuggestionAgent(#611) —visual_editor.suggest_layout. Given a section's content and your available pattern library, ranks matching section patterns from a whitelist. Ships as<SuggestLayoutPanel />.HeadingHierarchyAgent(#614) —visual_editor.heading_hierarchy. Audits the document for skipped heading levels, duplicate h1s, and ambiguous headings; returns suggested fixes with nestedinnerBlockstraversal. Ships as<HeadingHierarchyPanel />.
Two agents are consumed directly from
artisanpack-ui/aiso the same prompt + feature toggle powers them across every package that opts in:AltTextGenerationAgent(#612) —ai.alt_text. Suggests accessibility-friendly alt text when an image block is added or itssrcchanges andaltis empty. Ships as<AltTextSuggestionCard />.ContentRewriteAgent(#613) —ai.content_rewrite. Selection-toolbar / slash-command surface for "make shorter", "more formal", "reading level 6", and similar rewrites. Ships as<RewriteToolbar />.
Transports. Every affordance is reachable from any host stack:
- HTTP — six JSON endpoints under
/visual-editor/api/ai/*:GET /features,POST /suggest-next-block,POST /suggest-layout,POST /alt-text,POST /rewrite,POST /heading-hierarchy. Each endpoint has a dedicated Form Request class insrc/Http/Requests/Ai/and a consistent error envelope. All routes are guarded onclass_exists(FeatureRegistry)so hosts without the AI package installed don't 500. - Livewire —
artisanpack-visual-editor.ai.toolscomponent listens forap-ve-ai:*browser events and dispatches shapedsuccess/invalid-input/disabled/missing-credentials/errorevents back. Blade/Livewire hosts get the same behavior as the React surface without pulling in a client bundle. - React —
resources/js/visual-editor/ai/shipscreateAiApiClient,useAiFeaturesgate, per-feature hooks, and 5 UI components. Seedocs/ai-features.mdfor the full authoring guide.
Requirements:
artisanpack-ui/ai^1.0installed and configured, per-feature toggle enabled in the AI settings surface, and CSRF middleware active on/visual-editor/api/*so the shipped JS client'sX-CSRF-TOKENheader is honored.
Changed
composer.jsonnow requires PHP 8.2+ andartisanpack-ui/ai^1.0as an optional-but-recommended companion dependency. Hosts without the AI package installed continue to work — the AI surface simply stays hidden and the routes short-circuit before dispatching an agent.
CI
- Test suite runs on PHP 8.2 with AI dev deps excluded so hosts still targeting 8.2 have a green matrix even before adopting the AI package.
[1.2.0] - 2026-06-18
Added
-
Box / drop shadow control with solid + gradient color (#607). New Shadow tools panel in the inspector's Styles group, auto-enabled on every block with
__experimentalBordersupport (~94 blocks, no block.json changes required). Exposes X/Y offset, blur, spread, solid color, gradient color (with theme palette), inset toggle, and a preset chip row backed by the newsettings.shadow.presetsslot intheme.json. Writes route through the standardartisanpackStates/artisanpackResponsiveHOCs so per-state and per-breakpoint shadow overrides land in the right cascade bag automatically. Three emission modes (preset / solid / gradient) share one scoped<style>code path; gradient and inset-gradient shadows render through a::before/::afterpseudo-element withfilter: blur()and amask-composite: excludering mask for the inset variant. PHPBoxShadowResolver+BoxShadowEmittermirror the TS pair byte-for-byte so editor canvas, saved markup, and Blade-rendered output stay in lockstep. New scope classve-bs-<id>persisted onattributes.style.shadow._shadowScopeId. Front-end Blade rendering goes through a newBoxShadowCssAccumulator+BlockSupports::pushBoxShadow()+ auto-stamping inBlockSupports::compile(), so every block already routed through the supports compiler picks up shadow rendering with zero per-template changes. The supports-extension filter also strips the native WordPresssupports.shadowon opted-in blocks to keep the two systems from fighting over thestyle.shadowattribute slot. Mirrors the architecture established by gradient borders (#490). Known limitation: outer gradient shadows on blocks withoverflow: hidden(e.g. Cover) are visually clipped at the wrapper edge — gradient shadows need a::beforepseudo-element because the nativebox-shadowproperty doesn't accept gradient fills, and pseudo-elements (unlike box-shadow) are clipped by their host's overflow. Solid shadows and preset shadows are unaffected. Seedocs/box-shadows.mdfor the full authoring guide and workarounds. -
Per-post layout overrides on the Query Loop via post-variants (#591). New
artisanpack/post-variantblock, child ofartisanpack/post-template, declares an override template that swaps in for posts matching itsmatcherattribute. Four matcher kinds ship in V1:position(first/last/nth:<n>/range:<from>-<to>),pattern(odd/even/every-nth:<step>[:start:<offset>]),meta(sticky,featured,has-featured-image,author:<id>,taxonomy:<tax>:<slug>), andcustom(callback:<name>→apve_query_variant_match_<name>filter hook). A new "Post Variants" panel in the query inspector lists, adds, reorders, and deletes variants. Static rules (position / pattern) precompile to a 0-basedposition → variantOrdermap stored on the parent post-template as_compiledVariantMapfor O(1) lookup; dynamic rules (meta,custom) resolve at render time via the newArtisanPackUI\VisualEditor\Resources\VariantResolver. Precedence is fixed: instance > position > pattern > meta > custom > base, withpriorityascending as the tie-breaker. All three renderers (Blade, React, Vue) consume the same inlined tree — variants are stripped server-side byQueryInliner, so existing query loops with no variants render identically to before. Items rendered via a variant carry an extrais-variantclass on theircore/post-template-itemwrapper for downstream styling. -
Native flex layout panel on group / column / columns / grid-item (#595). New
Flex Layout+Flex Iteminspector panels expose every CSS flexbox property — direction, wrap, justify, align-items, align-content, place-content, row/column gap, plus per-item align-self, grow, shrink, basis, order — each per-breakpoint via the existing<ViewportSwitcher />. Replaces WordPress core's narrow Flex layout variation onartisanpack/group(suppressed via filter) and layers on top of the defaultartisanpack/columnsdistribution. Class output mirrors Tailwind's utility convention (ap-flex,md:ap-justify-between,ap-gap-x-[16px], …) and is asserted byte-identical across the Blade, React, and Vue renderers via a shared fixture set. Legacylayout.type === 'flex'content onartisanpack/groupmigrates automatically on first edit. See [[blocks/Flex Layout]] for the full surface.
[1.1.1] — 2026-06-15
Fixed
- Icon registration no longer collides with
owenvoke/blade-fontawesome(#587). In consumer apps that pull bothartisanpack-ui/visual-editorandowenvoke/blade-fontawesome(the default path vialivewire-ui-components), the visual-editor'sfas/far/fabicon sets collided with the prefixes blade-fontawesome registers, causingBladeUI\Icons\Exceptions\CannotRegisterIconSeton every<x-...>render and breaking Blade-rendered routes and Livewire tests.FontAwesomeFreeIconSets::register()now detects the blade-fontawesome service provider and stops publishing the FA Free sets throughap.icons.register-icon-sets, so the icons-package no longer forwards the conflicting prefixes toBladeUI\Icons\Factory::add(). The visual-editor's ownIconSvgResolverseeds its FA Free path map directly fromFontAwesomeFreeIconSets::discover(), so the icon picker preview and the rendered Icon Block still resolve the bundled SVGs.
[1.1.0] — 2026-06-14
The 1.1.0 release ships the full artisanpack/icon block (Phases 1–7),
a wave of new first-party artisanpack/* blocks, block bindings for
parent post/page/CPT data, block animations, border-gradient borders,
an auto-injected custom-block CSS pipeline for the editor canvas
iframe, a BreadcrumbsResolver, and a set of Cover block fixes. See
the new [[blocks/Icon Block]] page for the icon-block surface and the
per-block docs under docs/blocks/ for the new block families.
Added
- Block bindings — connect block attrs to parent post/page/CPT
data (#504). Block attributes can now bind to fields on the
surrounding post/page/CPT record, so editor placeholders render the
live value and front-end output stays in sync without hand-rolled
render_callbackwiring. - Block animations — entrance / hover / continuous + custom keyframes (#489). New animation panel on every supported block with entrance, hover, and continuous animation types plus a custom keyframe escape hatch. Animations are emitted as standard CSS on the wrapper so they survive both the editor canvas and the rendered front end.
- Border gradients — linear / radial / conic borders + tabbed color/gradient picker (#490). Border controls now accept a gradient as well as a solid color. Linear, radial, and conic gradient types are supported and exposed through a tabbed picker that shares its color/gradient surface with the existing palette controls.
- Auto-inject custom block CSS into the editor canvas iframe (#566). Custom CSS registered against a block via the block API is now mirrored into the editor canvas iframe so the canvas matches the rendered front end without the host app having to enqueue editor styles by hand.
BreadcrumbsResolverforartisanpack/breadcrumbs(#565). The breadcrumbs block now resolves its trail through a dedicated resolver, decoupling the trail computation from the block's server renderer so host apps can override how a trail is built for custom post types and routes.- New first-party
artisanpack/*block families (#495). The block library grows with:artisanpack/breadcrumbs(#496).artisanpack/accordion+artisanpack/tabsfamilies (#497).artisanpack/grid+artisanpack/grid-itemfamilies (#498).artisanpack/next-post+artisanpack/previous-postcontainer blocks (#499).- Site-chrome blocks —
artisanpack/copyright,artisanpack/marquee,artisanpack/comments-number(#500). - Single-post content cluster —
artisanpack/single-content,artisanpack/related-posts,artisanpack/author-social-icons,artisanpack/social-share-content(#501). - Search cluster (#502).
artisanpack/skills-slider(#503). All new blocks ship under theartisanpack/*namespace as first-party blocks; the inserter icons and categories were restyled and recategorised in the same wave (#495).
- Icon block — full Phase 1–7 surface (#552, #554, #555, #556,
#557, #558). The
artisanpack/iconblock lands across seven phases:- Phase 1 — block scaffold with server render + SVG sanitizer (#552).
- Phase 2 — bundled FA Free SVGs (Solid, Regular, Brands)
auto-registered against the icons package via the
ap.icons.register-icon-setsfilter, with inline-rendered SVGs on the front end (#554). - Phase 4 — picker UI with search, set chips, a recent tray, and a paginated grid (#555).
- Phase 5 — custom SVG paste / upload for one-off icons sanitized through the same SVG sanitizer (#556).
- Phase 6 — admin icon-sets settings + zip-upload pipeline for registering whole icon families through the admin UI (#557).
- Phase 7 — end-to-end coverage and docs (#558). Cross-
phase Pest tests now stitch the registration filter, catalog, picker
endpoints, admin uploader, sanitizer, and block renderer together so
regressions that only surface end-to-end are caught. New Vitest
coverage exercises the WP-style envelope plumbing and the
width/height override path. Docs add a dedicated [[blocks/Icon Block]]
page covering the block usage walkthrough, the developer recipe for
ap.icons.register-icon-sets, the admin upload walkthrough, and FA Pro guidance (BYO SVGs, no token storage).
- Icon block — independent width/height overrides. The
artisanpack/iconblock now supports per-axiswidthandheightattributes that override the uniformsize. The inspector ships aDimensionspanel with aNumberControl-backed size and width/ heightUnitControls (px/em/rem/%/vw/vh). All three controls emit changes on every keystroke so the canvas updates live. - Icon block — dedicated Icon color field. The standard WordPress
text-color control is replaced by a
Sidebar → Color → Iconpicker that writes to a newiconColorattribute and is applied directly to the body span ascolor. The bundled SVGs ship withfill: currentcolor, so the picked color flows through to the icon's fill. Mirrors the ndiego reference Icon Block split.
Fixed
- Icon block — WP style controls now apply on the canvas and front
end. Background, border, padding, and margin set via the
inspector's standard controls now reach the rendered block. The
block previously declared
__experimentalSkipSerialization: truefor every support and then never read the styles back, so author selections silently no-op'd. The block now lets WordPress serialize background/border/spacing onto the wrapper viauseBlockProps(), and the server renderer applies the same envelope to the wrapper div (with the legacy top-levelbackgroundColorattribute kept as a fallback for posts saved before the fix). Palette-color slugs resolve through the standardhas-{slug}-background-color/has-{slug}-border-colorclasses. - Icon block — decorative + linked icons now produce labeled
anchors. When
isDecorative,link, andariaLabelare all set, the suppliedariaLabelis now promoted onto the<a>itself rather than dropped. The body span remainsaria-hidden="true"(the SVG is the decorative element), but the anchor finally has an accessible name. The editor-sidehasDecorativeLinkConflict()warning still fires when noariaLabelis supplied, which is the scenario the warning was always meant to flag. - Cover block — background classes now route to the overlay span (#583). Palette-color background classes on the Cover block now reach the overlay span where the legacy markup expects them, instead of being applied to the wrapper and producing a flat, unblended fill.
- Cover block — unfreeze editor on overlay color pick + media select (#578). Picking an overlay color or a media item in the Cover block no longer hangs the editor. Stale refinement results are guarded against and the block's effect chain no longer reschedules itself in a tight loop.
[1.0.0] — 2026-06-08
First stable release of the V1 surface. Promotes 1.0.0-beta1 to GA
with the additions and fixes listed below. The post editor, site
editor, artisanpack/* block fork, and first-class
artisanpack-ui/cms-framework pairing — all introduced across
1.0.0-alpha.1 and 1.0.0-beta1 — are now considered stable. See the
README and the docs/ directory for the full V1
surface.
Added
- Laravel 13 support.
illuminate/supportconstraint updated to^11.0|^12.0|^13.0(Laravel 5.3–10 are no longer supported — the previous>=5.3floor was effectively dead code, sinceorchestra/ testbenchalready pinned us to Laravel 11+). Laravel 13 requires PHP 8.3+, which is enforced transitively through L13's ownphpconstraint; the package PHP floor (^8.2) is unchanged for users staying on Laravel 11/12.
Fixed
- Paragraph block-gap spacing. Paragraph blocks now correctly
inherit
is-layout-flowblock-gap spacing in the rendered output (#540). artisanpack/post-titleeditable inline. The post-title block now edits the live entity directly instead of getting stuck on its initial value (#546).- FontSizePicker duplicate-key warnings. Font-size presets are
now deduplicated before being handed to
FontSizePicker, silencing the React duplicate-key warning that surfaced under certain theme.json configurations (#547). tsc --noEmiterrors in the core-data shim. Resolved the TypeScript errors surfaced bytsc --noEmitin the core-data shim and its tests (#542).
[1.0.0-beta1] — V1 beta release
First public beta of the V1 surface. Ships the post editor, the site
editor, the block fork to the artisanpack/* namespace, and first-class
pairing with artisanpack-ui/cms-framework. See the README
and the docs/ directory for the full V1 surface.
Added
- Site editor (Phase H). Mounted at
/visual-editor/site. Templates, template parts, theme.json-backed global styles, navigation menus, and patterns — all editable through a custom shell built on@wordpress/block-editor. Backed by cms-framework'sTemplate/TemplatePart/GlobalStyles/Menu/Patternmodels when cms-framework is installed; fail-closedSiteEditorAccessGatedefaults to deny until the host binds a permissive gate (or installs cms-framework, which auto-bindsCmsFrameworkInstallGate). - Documentation set. Fifteen new / refreshed docs covering install,
content model, Blade component reference, post-editor surface, custom
blocks, renderers, site-editor surface, templates, global styles,
navigation, patterns, Livewire and Inertia embedding recipes,
theming, troubleshooting, and migration. Entry point:
docs/getting-started.md. - V1 expansion plan retained as historical record:
docs/plans/11-v1-expansion.md.
Changed
- README rewritten to reflect final V1 scope: post editor + site editor
- patterns + Livewire/Inertia embedding.
[1.0.0-alpha.1] — Gutenberg adoption marker
Added
- Block fork (Phase I) —
core/*→artisanpack/*. All 42 forked blocks plus the pre-existingartisanpack/calloutandartisanpack/formnow register under theartisanpack/*namespace. Clusters landed in order: I0 paragraph pilot (#408), I1 content (#409), I2 media (#410), I3 layout incl. grid/grid-item split (#411), I4 widgets (#412), I5 entity (#413), I6 loop/feed (#414), I7 cutover (#415). The editor bootstraps ineditor-app.tsxandsite-editor-app.tsxcallregisterArtisanPackBlocks()instead ofregisterCoreBlocks();@wordpress/block-libraryis demoted todevDependenciesand consumed only byscripts/upstream-diff.mjs. Per-blockupstream-state.jsonfiles keep the drift trail.from:core/*transforms ship on every fork so pasted upstream markup still converts. Full plan:docs/plans/13-block-fork.md. - Block fork completion gate (Phase I8, #416). Confirms the cutover
is complete and hands release-notes inputs to #325. Adds
docs/release-notes-inputs-1.0.0.mdwith the pinned@wordpress/*table and the visual-editor ↔ cms-framework version pair (v1.0.x↔^1.1). Wires the per-block upstream-diff CLI into CI so the first post-fork Renovate cycle triages drift before merging. - cms-framework integration (Phase G). First-class pairing with
artisanpack-ui/cms-framework. When both packages are installed, the visual editor can edit cms-framework'sPostandPagecontent end-to-end;core/site-*blocks read from cms-framework's settings store viaapGetSetting('site.*');core/post-*,core/archives,core/categories,core/tag-cloud,core/query, andcore/query-loopcome off the V1 deny-list and resolve against cms-framework's models and term endpoints. Loose coupling preserved — both packages remain usable standalone; cms-framework's editor wiring is guarded byclass_exists(\ArtisanPackUI\VisualEditor\VisualEditor::class). Pair-versioning matrix lives in the README; thedocs/g6-smoke-flow.mdflow runs against the version pair before every release tag. Full integration contract:docs/plans/12-cms-framework-integration.md. - "Using with cms-framework" README section. Covers install, migrations, the merged resource map under
ap.visual-editor.resources, and the version-pair contract. visual_editor.*permissions schema seeded into cms-framework's RBAC when both packages are installed (G5). Policies still use the "any authenticated user" baseline in V1.0; delegation lands in V1.1 behindartisanpack.visual-editor.authorization.delegate_to_cms_framework.artisanpack/formblock. Dynamic block that lets authors pick a form from the artisanpack-ui/forms package via the InspectorControls sidebar, and renders a<div data-keystone-form="…" data-form-id="…">mount-point on the public site. The host application supplies a JS island that hydrates the mount-point with the forms package's ReactFormRenderer. Registration is gated onclass_exists(ArtisanPackUI\Forms\Models\Form::class)so visual-editor still boots when forms is absent.- Stale-selection guard in the form block's editor preview. When the persisted
formIdno longer matches any form returned by/api/v1/forms, the canvas renders a distinct<Placeholder>with a "Reset selection" button that clears the attribute back to0. Replaces the previous silent fall-through to the 404 "inactive" message, which mis-coded deleted forms as merely deactivated.
Changed
FormBlock::validateAttrs()now parsesformIdwithFILTER_VALIDATE_INTvia anormalizeFormId()helper. Rejects float strings ("12.9") and scientific notation ("1e2") thatis_numeric+(int)previously truncated into unrelated form ids; non-positive values fall through to the "select a form" placeholder.- Form block editor preview's 404 error message updated from "This form is inactive…" to "This form is unavailable — it may have been deleted or deactivated." — neutral wording that fits both branches of
FormBlock::render's server-side check. - All
block.jsontextdomainvalues aligned with the editor runtime'sTEXT_DOMAINconstant (artisanpack-visual-editor). Translations for block titles/descriptions/keywords now resolve under the same domain as the rest of the editor strings.

