Disposable local WordPress site with minn-admin active and five suites installed:
Kadence Blocks 3.7.8, Spectra 2.19.29, GenerateBlocks 2.3.0, Otter 3.2.0,
Essential Blocks 6.3.0. Stackable findings (the reference integration) live in
block-inspector.md and adapters/stackable.php.
Verified against a real Otter pattern page: islands preserve atomic-wind/suite markup
byte-identically through double saves, previews server-render, text runs are editable,
and the core editor suites pass untouched on the lab site (markdown 20/20 with all five
suites active). The generic machinery — islands, render probe, text runs, image swaps,
lazy-CSS pickup — needed nothing suite-specific.
| Suite | Blocks | is_dynamic |
Real server attrs | Render-probe pass |
|---|---|---|---|---|
| Essential Blocks | 75 | 75 | 75 | 6 |
| Kadence | 59 | 59 | 59 | 0 |
| GenerateBlocks | 16 | 16 | 16 | 0 |
| Spectra (uagb) | 13 | 12 | 13 | 9 |
| Otter (themeisle-blocks) | 40 | 11 | 40 | 6 |
The headline: is_dynamic is nearly meaningless as a "server-rendered" signal in this
ecosystem. Kadence and GenerateBlocks register every block with a render_callback that
exists to generate CSS or decorate saved content, not to produce markup — a bare
comment renders empty. Essential Blocks' ~40 "static" blocks have callbacks that just
return $content. The render probe is the only honest gate, and it holds: everything it
passes (post grids, FAQs, maps, feeds, breadcrumbs) genuinely works from a bare comment.
Unlike Stackable, all five register real attribute schemas server-side, so the generic inspector has material to work with. But Spectra's schemas are enormous (uagb/post-grid: 315 attributes, 313 form-renderable) — the generic form needs a scaling strategy before that's usable (see roadmap).
| Suite | Source | Markup as data? | Account? |
|---|---|---|---|
| Otter | 61 local PHP pattern files, registered in WP_Block_Patterns_Registry |
Yes, on disk | No |
| Essential Blocks | 12 local JSON pattern files (patterns/*.json), registered as patterns |
Yes, on disk | No (Templately packs are gated + not wired) |
| Kadence | Cloud (patterns.startertemplatecloud.com) via its own kb-design-library/v1 REST proxy; file-cached in uploads |
Yes — get_pattern_content returns serialized markup; free sections served with an empty api_key |
No for free tier |
| Spectra | Cloud (websitedemos.net REST) via its ajax proxy; catalog synced to JSON files in uploads |
Yes — template content endpoint returns markup | No for blocks/pages |
| GenerateBlocks | Cloud (patterns.generatepress.com) with a public key hardcoded in the plugin; own generateblocks/v1 REST proxy, transient-cached |
Yes | No |
Every one of the five publishes its design markup as fetchable data. The Stackable adapter shape (slim list endpoint + insert-ready template endpoint with image sideload) transfers directly; and for Otter + Essential Blocks not even that is needed — their patterns are already in the core pattern registry.
Confirmed empirically: uagb/faq and essential-blocks/post-grid enqueue nothing
during a bare do_blocks() render. Each suite generates instance CSS from attributes
and emits it through front-end-only machinery:
-
GenerateBlocks: per-block
cssattribute; render_block path prepends inline<style>only whendid_action('wp_head')or thegenerateblocks_do_inline_stylesfilter is true. One-line shim available. -
Kadence: per-block base stylesheets registered lazily at render (the queue-diff in render-blocks catches those), plus instance CSS prepended inline by
render_css()— but suppressed on block themes, where it goes through awp_enqueue_scriptshead pass that needs a real$post. -
Otter: CSS generated on save into postmeta
_themeisle_gutenberg_block_styles+ an uploads file; emitted only onwp/wp_head/wp_footerwithis_singular(). -
Spectra: per-post CSS built by
UAGB_Post_Assetsonwp_enqueue_scripts(inline by default; optional file mode inuploads/uag-plugin/). -
Essential Blocks: CSS baked into each block's
blockMetaattribute in the saved markup; materialized touploads/eb-style/eb-style-{post}.min.csson save and enqueued with a real$post. -
Gutenslider (added 2026-08-10): the sixth model, and the one no server-side collector can reach. Its base layout sheet (
build/vendor/gs-base.css, the grid the slide is built on) is fetched by the front-end script as a webpack async chunk, so it never passes throughwp_styles()at all. The plugin's ownwp_enqueue_style('eedee-gutenslider-block-editor')names a handle registered only as a script, so even that is a no-op. Without the sheet the background div and the slide content stack in normal flow and the content is clipped out of a fixed-height frame.
The render-blocks style-queue diff covers the lazy-register class (Stackable, Kadence
base styles). The per-post-generated class needs per-suite shims — candidates ranked in
the roadmap. The runtime-loaded class (Gutenslider) is covered by the runtime-CSS
harvest: harvestRuntimeStyles() loads the post's front end in the hidden same-origin
iframe the Otter warm-up already uses, reads the <link> sheets the page's own scripts
pulled in, and sends the new ones through the same scoper. Trigger is geometric, not
library-specific: a preview holding text at or past its own bottom edge. Runtime <style>
elements are deliberately skipped (a page's inline CSS is mostly what the server already
reported). Suite: tests/runtime-css.test.js, fixture minn-test/runtime-css.
- Flat
XxxUrl/XxxIdpairs (already handled): Essential Blocks (imageUrl/imageId, sometimesimageURL). bgImg↔bgImgID(Kadence rows/sections; alsooverlayBgImg, responsive variants intabletBackground/mobileBackgroundarrays): key +IDpairing not yet handled.- Media objects
{ url, id, alt }in one attribute (Spectraimage,backgroundImage,mediaGallery[]; Otterimage,backgroundImage; EBimage,sources[]): URL swap works (string replace), id retarget needs a same-object heuristic. - GenerateBlocks: no URL attribute at all —
mediaId+htmlAttributes.src+data-media-idon theimg. URL swap works; id lives inmediaId/data-media-id. - Otter slider:
img[data-id]carries the attachment id. - JSON-escaped
https:\/\/URL forms occur in server-authored markup — handled as of this sweep (Kadence's own importer does the same normalization, which validates the string-surgery approach wholesale).
Server-registered patterns in the slash menu— SHIPPED (same day):minn-admin/v1/patterns(slim list; blockTypes/templateTypes-contextual patterns excluded, postTypes restrictions client-filtered) +minn-admin/v1/pattern?name=(content; query arg because names contain slashes). Multi-root patterns insert as one island per top-level block viainsertPatternIslands(); on reload the load pipeline upgrades simple blocks to editable prose. Lab site surfaces 101 patterns (Otter 61 + theme 40); suite: tests/patterns.test.js.Preview CSS shims— SHIPPED (same day) for three of four: render-blocks flipsgenerateblocks_do_inline_styles(GB blocks inline their own<style>),adapters/essential-blocks.phpextractsblockMetadesktop CSS from the submitted markup, andadapters/otter.phprecovers the per-post CSS caches (_themeisle_gutenberg_block_styles+_atomic_wind_css— atomic-wind is Tailwind compiled in the browser on first front-end view, so a never-viewed section has no cache until someone views the page once). Plumbing: render-blocks accepts apostparam and applies theminn_admin_render_stylesfilter; editor-styles also carries handles the fired hooks enqueued directly (atomic-wind's base CSS). The client scoper now unwraps@layer(compiled Tailwind) and preserves CSS nesting. REMAINING: Spectra generates per-post CSS at request time with no persistent store — needs itsUAGB_Post_Assetsgenerator run over the edited post; investigate before committing.- Library adapters on the Stackable shape — Kadence SHIPPED
(
adapters/kadence.php: drives the plugin's ownkb-design-library/v1proxy viarest_do_requestso its file cache applies; 352 free sections, pro/locked filtered; images localized via the sharedadapters/media-localize.phphelper; client generalizes to aDESIGN_SOURCEStable — adding a source is one PHP adapter + one table row). GenerateBlocks SHIPPED (adapters/generateblocks.php: drives itsgenerateblocks/v1proxy via rest_do_request — NOTE its responses carry objects with protected props that only flatten via JsonSerializable, so normalize through a wp_json_encode/json_decode round-trip; the listing carries full markup inline, 37 free patterns, ids are{libraryId}--{patternId}across all enabled libraries). Spectra is a documented DEAD END: the websitedemos cloud has fully migrated to aspectra/*block system (v2/v3 entries, markup in the item response'soriginal_contentfield) that the shipping wp.org plugin (2.19.x,uagb/*) does NOT register — inserting it would create Gutenberg-unavailable blocks, the exact broken-insert class the render probe exists to prevent. Revisit when the plugin ships the new block system. Inspector form scaling— SHIPPED: fields explicitly set on the block (or adapter-ordered) stay in view; the rest collapse behind "More settings (N)" with a filter box (threshold 16). Setting a value promotes the field. Suite: tests/inspector-scaling.test.js against the minn-test/big-schema fixture block.Image-swap heuristic widening— SHIPPED: Img/Image keys pair with Id/ID suffixes (Kadence bgImg→bgImgID), media objects retargetidonly inside the object carrying the swapped URL, GBmediaIdcomment-scoped, and img-tag markers data-media-id / data-id / wp-image-N in either attribute order. Suite: tests/image-swap.test.js (synthetic block carrying every convention).
A multi-root Otter pattern page is a useful standing fixture for pattern-insert and preview-CSS checks. Tear down the disposable lab when suite work is done.