Capability map

Gillish Cairnstone: product & technical brief.

The capability layer that sits between the lean product vision in PRODUCT.md and the phase-by-phase implementation roadmap in the Foundation plan. If a feature exists in the codebase, it’s in this document. If it’s not in this document, it doesn’t ship.

Identity#

NameGillish Cairnstone
VendorGillish
Entry filegillish-cairnstone.php
NamespaceGillish\Cairnstone\* (PSR-4, strict types)
Asset prefixgc-
ConstantsGC_*
Min PHP8.3 (floor walked 8.1 → 8.2 → 8.3 across 2026-05-11 and 2026-05-21; tested 8.3 → 8.5). The plugin header hard-requires it; older PHP gets an admin notice and the plugin does not load.
Min WordPress6.9 hard floor (walked 6.6 → 6.8 → 6.9 across 2026-05-21). The Block Bindings API plus getFieldsList that the Cairnstone data panel builds on are 6.9 additions, alongside the stable InspectorControls slot, the editor.BlockEdit HOC filter, and register_block_type apiVersion 3 with render_callback. Later WP 7.0 features (DataViews, native AI client) are used opportunistically with graceful degradation; tested through 7.0.
LicenseGPLv2-or-later
Distributionwp.org plugin directory
CompanionGillish Node, link strategy + content-graph SEO toolkit
Code quality0 errors / 0 warnings in Plugin Check. PHPStan level 9 clean.
Performance principleNo wrapper bloat. Conditional Content runs through the render_block filter, enabling it adds zero extra DOM nodes. Frontend emits only the winning HTML.

The pitch#

Cairnstone is the one block plugin a WordPress site owner needs. A curated library (fourteen blocks today, growing toward a 100+ roadmap target: Hero, Pricing Table, FAQ, Testimonial, Table of Contents, and more) covers the typical need out of the box. When the library doesn’t have it, the user opens Cairnstone → Blocks → New and composes a custom block in the standard Gutenberg post editor, using core blocks, Cairnstone primitives, third-party blocks, or library blocks. Saving registers the composed markup as a real block type that appears in the inserter on every other post and page.

Cairnstone’s distinctive trick is four universal capabilities injected into every block’s existing Gutenberg Block Inspector: a Look panel (per-state styling, colour sets, typography, border, spacing, shadow (with savable presets), and opacity, each able to differ on hover / focus / active / disabled), a Visibility panel (visitor-based show/hide rules), a Schema panel for blocks whose manifests opt into Schema.org emission, and a Cairnstone data panel that lets any block read its content from somewhere else: a field on the post, a field on the post’s category, content from another block on the page, or a link from Gillish Node. Panels live side-by-side with Gutenberg’s native Colour / Typography / Dimensions panels in the same right-sidebar region, no separate “Cairnstone” icon next to the gear, no second sidebar to switch to. Available on any block in any post or page, not just on the custom-blocks CPT, because the controls are injected via the editor.BlockEdit higher-order-component filter rather than registered as a separate sidebar surface. This is the architectural commitment that defines Cairnstone: personalisation, universal styling, and content-reuse treated as first-class capabilities, not buried add-ons.

The Cairnstone data panel began on WordPress 6.9’s Block Bindings API (Phase 11, v2.3.13 through v2.3.67) and then evolved its author model to inline data tokens (v2.3.74 through v2.3.79). For a non-technical author it now reads as plain text with a field tag dropped in: a heading of Welcome to {Title} shows the live page title, and several tags can mix in one field ({Title} by {Author}), with the author’s own words around them. An “Insert a field” dropdown lists named fields (Title, Excerpt, the category Name, and so on); a stored field is typed by hand as {price}. Image fields keep a whole-value pick (the featured image or a stored image field), and a button link accepts a Gillish Node [node id] short link. The panel itself moved out of the branded Cairnstone group to sit directly under the block’s own settings, so content sourcing reads as part of the block, not a separate feature. Under the hood Cairnstone owns the frontend resolver, so the same surface that makes a price reusable across twenty product pages also keeps a block’s content machine-readable, the foundation the later Abilities-API work builds on.

The third pillar, first-class import/export of blocks as .gcblock.json files via both UI and WP-CLI, is no longer aspirational: it shipped in Phase 12 (v2.3.34 through v2.3.52) as a canonical wire format, a REST surface, WP-CLI verbs (list-blocks, export-block, import-block, wipe-blocks), a single-block export button, bulk ZIP export from the block list, and a drag-and-drop import modal with a per-file preview. No other block plugin ships block portability as a peer feature to “create” and “save”.

Standalone, Cairnstone ships a built-in crawler classifier, a working CC engine, the Container and Columns primitives, the launch Conditional Content wrapper block, and a curated library of fourteen hand-built blocks (thirteen free, one Pro). The visitor-country signal is provided by Gillish Node: country rules need Node installed and active. Better-together with Gillish Node, a button link can carry a Node [node id] short link, which Cairnstone resolves to the managed link’s live address at render through Node’s gillish_node_resolve_link bridge filter (added in Gillish Node 2.91.13). Deeper link-aware features (managed-link health badges, anchor inheritance, automatic affiliate-disclosure injection) are planned, not yet built. The better-together composition is the default home for commercial and runtime-dynamic features: Cairnstone declares the form, Node owns the runtime decision.

Conditional Content as a capability (vs. a block)#

The architectural decision that defines Cairnstone:

  • Every block in the editor gets four conditional attributes (conditions, match, renderMode, stickyCookie) injected via a shared blocks.registerBlockType JS filter (mirrors the PHP register_block_type_args filter for server-side consistency). The injection touches every block, including core blocks and third-party blocks, not just Cairnstone-namespaced blocks.
  • The Cairnstone Visibility panel (injected into Gutenberg’s Block Inspector via the editor.BlockEdit HOC) exposes a plain-language rule builder for those attributes: Country, Language, Device, Browser, Login status, UTM tag, Visits, Local time as the signals, with operators like “is”, “is not”, “is one of”, “contains”, “is between”.
  • A render_block filter at priority 10 intercepts every block render. If the block has conditions set, the centralised Shared\RuleEvaluator runs against Shared\VisitorContext and the filter either returns the original HTML (rule passed) or returns '' (rule failed → block disappears entirely; no wrapper, no placeholder, no DOM cost).
  • The standalone gillish-cairnstone/conditional-content block stays in the library as the wrap-arbitrary-content escape hatch, for swapping multi-block sections, classic-editor flows, or content shapes that don’t fit a single block.

This is the architectural commitment that makes Cairnstone distinctive: no competitor block library treats personalisation as a first-class universal attribute. Most ship “an A/B testing block” or “a geo-content block” as one isolated block; Cairnstone makes every block conditionable from the same set of inspector panels.

Cairnstone controls in the Block Inspector#

The headline experience: Cairnstone injects three universal panels (Look, Visibility, Schema) directly into Gutenberg’s existing Block Inspector (the right sidebar that already holds Gutenberg’s Colour, Typography, and Layout panels). The mechanism is InspectorControls slots via the editor.BlockEdit higher-order-component filter, the same pattern Stackable, Kadence, and GenerateBlocks use. No separate Plugin Sidebar, no separate icon to discover, no manual switching between sidebars.

UX architecture: contextual, not complete#

The biggest UX risk of injecting three universal panels into every block’s inspector is overwhelming the user: padding, margin, gap, four colour pickers, four opacity sliders, eight condition signals, schema mapping fields … on every block. That defeats the point.

The sidebar is built as a layered decision tool, not a settings dump:

┌──────────────────────────────────────┐
│ Cairnstone                               │  ← Section header
├──────────────────────────────────────┤
│ 👁 Look · 2 changes             ↗   │  ← Summary row is a
│ 👁 Visibility · Only on mobile  ↗   │ control hub. Eye
│                                       │ toggles "Show /
├──────────────────────────────────────┤ Conditional".
│ ▼ Look (2)                           │    ↗ click-to-jump
│ What this block looks like         │ opens that section.
│ Spacing: padding 16px              │
│ Color: background #1d2733          │    (Schema row hidden
│   [Advanced ▾]                       │ here because the
├──────────────────────────────────────┤ block's manifest
│ ▶ Visibility (1)                     │ does not declare
│ Who sees this block, and when      │ schemaEmission.)
├──────────────────────────────────────┤
│ Reset Cairnstone for this block          │  ← Footer tools.
│ Copy Cairnstone settings                 │
└──────────────────────────────────────┘

Principles enforced by this architecture:

  • Summary first, and the summary is interactive. Each summary line is a control hub, not a label: an eye-icon toggles Visibility’s “Show everyone / Show conditionally” state directly from the summary, and a chevron jumps to the relevant section. Schema does not get a summary-row quick toggle, enabling schema is a multi-step decision, not a one-click yes/no.
  • Role-based defaults, same layout. The structure is identical everywhere; only the default-open section changes. CPT block-builder (designer-context) opens Look; regular post / page (author-context) opens Visibility.
  • Schema is read-only-by-default in author context. Schema fields the designer mapped in the CPT show as read-only with an “Override for this instance” disclosure. Authors don’t have to relearn Schema per instance.
  • Section visibility is manifest-driven and wholesale. A Spacer or Separator has no Schema → the Schema accordion and its summary row do not render at all. Both granularities, entire sections and individual controls, are manifest-driven. Avoids “Schema: None” / “Gap: N/A” noise.
  • Task-oriented subtitles, not headings. Each accordion header carries a short subtitle (“Look, How this block looks”, “Visibility, Who sees this block, and when”). Subtitles disappear once the user customises the section (replaced by the badge count).
  • One section open at a time by default. Badge counts per section (Look (3)). Manifest-driven control rendering, only controls meaningful for the selected block render.
  • No duplication of Gutenberg native controls. When Gutenberg already exposes a control for a block-type (e.g. Colour panel for a Heading), Cairnstone hides its own and shows a note: “For text colour, see the Colour panel above.” Cairnstone’s Look fills the gaps, never duplicates the overlap.
  • Advanced behind explicit disclosure. Render mode, sticky cookie, regex operators, state variants, schema mapping details, none visible until the user clicks “Advanced”.
  • Per-section reset. “Reset visibility rules” is mentally safer than “Reset everything”.

Four injected panels#

Look. Universal styling applied via a gcStyle attribute injected into every block, built around visual states. A state switcher (Default plus Hover / Focus / Active / Disabled) heads the panel, and every axis can carry a different value per state: a colour set (named brand roles), background / text / link colour, a two-colour gradient background, typography (font, weight, line height, letter spacing, decoration, case, italic), border (width / style / colour / per-corner radius), drop shadow (a saved preset by name, or your own values), spacing (padding + margin), opacity, and a transform (lift + scale). An Advanced panel groups the power controls, including Animation, one block-wide setting that eases every state change instead of snapping. The builder previews all of this live: hovering, focusing, or pressing a block in the editor canvas shows its real hover / focus / active look and eases it, so the designer sees the interaction without publishing the page. In the Cairnstone Block workspace these controls are the one place to style a block and stand in for Gutenberg’s native colour / typography / border panels; on a regular post or page the block keeps Gutenberg’s native styling. The default look renders as inline style and each extra state as a small scoped rule. (The first state-driven controls shipped in Phase 8; per-state colour for colour-owning blocks like Badge closed the model, and the v2.3.x line added gradient backgrounds, transform, Animation, savable admin-defined shadow presets (a named catalogue the per-block picker reads, mirroring the brand colour roles), and the live in-canvas preview.)

Visibility. Plain-language toggle at the top: “Show this block to everyone” / “Only show under certain conditions”. When the second option is selected, recipes appear first, not the rule builder. The recipe list is grouped by intent, five flat subsections inside the Visibility accordion (NOT nested accordions):

GroupRecipes
Device & layoutOnly on mobile · Only on tablet · Only on desktop
Audience & authOnly for logged-in users · Only for logged-out users · Hide from administrators · Show only to administrators
Location & timeOnly in [Country] · Not in [Country] · Only in [list of countries] · Only between [time] and [time] today
Traffic sourceOnly from [referrer host] · Show only to traffic from [UTM source] · Hide from search engines · Show only to search engines
BehaviourShow to first-time visitors · Show to returning visitors · Show to frequent visitors

Each recipe translates to the same conditions shape the rule builder produces; under the hood it’s one engine. Below the recipes, a [Build custom rule ▾] disclosure expands the full rule builder for power users. Render mode + sticky cookie live under a separate Advanced disclosure.

Schema. A guided assistant, not a settings panel, structured as a three-step task-mode flow (Overview → Details → Advanced) because Schema is the one Cairnstone section that is genuinely multi-step. Look and Visibility do not use task-mode (they’re a flat field-list and a recipe-picker respectively).

  • Overview, yes/no toggle (“Emit schema for this block”) + type picker if yes. Plain-language type list (FAQ, Product, Review, Recipe, Article, Organization, BreadcrumbList, etc.). For library blocks with a default mapping, this step shows “Using FAQ, confirm?” and the user is done in one click.
  • Details, revealed when the chosen type needs author input (e.g. Product needs price and availability). Only the fields that don’t auto-map from block attributes are surfaced for input here; auto-mapped fields are listed read-only at the bottom (“Name: inherited from the block’s Heading”).
  • Advanced, full property mapping override, inherited-field overrides, per-property mainEntityOfPage tweaks, manual JSON-LD pass-through.

Cairnstone data. Inline data tokens. An author types a field tag like {Title} straight into a text field and mixes it with their own words; the frontend resolver swaps each recognised tag for the live value at render. An “Insert a field” dropdown per text field lists the post and category standard fields and drops the tag at the caret; stored fields are typed by hand as {price}, and an unrecognised tag is left verbatim so stray braces are safe. Image fields use a slim image-only picker (featured image, a stored image field, or an image from another block) rather than the text dropdown; a button link resolves a Gillish Node [node id] short link to its live address. The model began on the WP 6.9 Block Bindings API (Phase 11) and moved to tokens in v2.3.74 through v2.3.79; Cairnstone owns the resolver, so resolution does not depend on WP-core binding-source quirks and the panel sits directly under the block’s own settings rather than inside the branded group.

Per-block manifests#

Every block in src/Modules/<BlockName>/ declares its attributes, default values, and metadata in a manifest.json validated against Shared\ManifestSchema. The same manifest drives the PHP-side register_block_type and (shipped in Phase 10) the JSON-LD schema emission. One source of truth for what a block can do.

Live integration with Gillish Node (when both plugins active)#

What ships today: a link field can carry a Gillish Node [node id] / [node slug] short link, which Cairnstone resolves to the managed link’s live address at render through Node’s gillish_node_resolve_link bridge filter (the link drops if Node is inactive or the id does not resolve). The cairnstone/list-node-links AI ability also registers only when Node is active, so an assistant can discover managed links. When Node is missing (!GC_NODE_ACTIVE) the bridge is inert and Cairnstone stays fully usable.

Planned, not yet built (these wait on Node publishing the JS picker / health APIs): an editor-load REST typeahead of managed links, a per-link health probe showing Healthy / Pending / Broken inline, and sponsored-flag introspection that warns when the Affiliate Disclosure block is missing.

Preview-as inside the editor#

The same Shared\VisitorContext that powers server-side rendering is exposed to a Preview-as toolbar in the post editor. Toggling between fixtures (iPhone US, Visitor from Norway, Logged-in subscriber, Search-engine crawler) re-evaluates every block’s conditional rules in place, the editor canvas updates without reloading, so the author sees instantly which blocks disappear and which stay.

Deferred UX explorations (parked, in-scope but post-launch)#

Two inspector-side UX ideas surfaced during 2026 review that are Cairnstone-scope but deliberately deferred, the launch experience does not need them, and shipping them now would expand surface area before the core inspector pattern has stabilised.

  • Cairnstone presets, saved Style + Visibility combinations, apply with one click. A “preset” is a named bundle of gcStyle + conditions values that an author can apply to any block instance (“Brand hero”, “Mobile-only CTA”, “Logged-in-only product callout”). Two halves: per-user presets (user-meta) and site presets (options). Deferred to post-Phase 11. Free / Pro split open; lean toward per-user free, site presets Pro.
  • Cairnstone lens, document-level Cairnstone view. A document-scope Plugin Sidebar (separate from the block-level Inspector) that lists every Cairnstone-configured block on the current post: which are hidden on mobile, which target a UTM source, which emit Schema. Audit / overview tool, not an editing tool. Deferred to the Phase 17–18 tier. Likely Pro; not committed.

Both ideas are concrete enough that the Inspector pattern (Phase 2) and the CPT save flow (Phase 5) must leave room for them, attributes.gcStyle + attributes.conditions need to remain serialisable in isolation, and the inspector summary row must reserve space for a future “Apply preset ▾” control.

Custom blocks via the gc_cairnstone_block CPT#

A backend-only custom post type for designing reusable blocks. CPT posts are workspaces, not content. They have no frontend URL, do not appear in search results, do not get canonical URLs, are not queryable on the front end, and never render as standalone pages.

CPT post (workspace) → save → registered block type →
appears in Gutenberg inserter → author inserts into a regular
post or page → frontend renders the instance

The CPT post itself never shows up on the frontend.

  • Composition. The designer opens a CPT post in the standard Gutenberg post editor and composes a block template with any registered block. Cairnstone’s three universal inspector panels appear next to Gutenberg’s native panels. The Look and Visibility settings the designer configures here become the block type’s defaults, instances inherit these unless the author overrides per instance.
  • Save. On save_post for gc_cairnstone_block (priority 10), the post_content is parsed into a block tree and validated against the manifest schema. The block tree + panelConfig + schemaVersion get stored in CPT meta.
  • Registration. On init priority 12, SavedBlockRegistry::register_all() iterates published CPT posts and calls register_block_type() for each, with a render_callback that returns the stored markup at frontend render time. Inner blocks carry as <InnerBlocks> with templateLock="contentOnly", the author edits content per instance; the designer owns the structure.
  • Inserter category. Custom CPT-built blocks appear under “Cairnstone, My blocks”. Library blocks appear under “Cairnstone, Layout / Content / Conversion / Interactive”.
  • Schema versioning. Each block-type instance persists its own schemaVersion attribute. A lower version than the current one runs through a migrate callback. Allows non-breaking evolution of a CPT block’s saved markup over time.
  • Live edit propagation. When the designer edits the CPT post and saves again, all instances of that block type on regular posts pick up the new layout / style / CC defaults automatically.

Live frontend preview from the CPT editor#

The CPT is backend-only, its posts never publish to the frontend. But designers still need to see what their block looks like when rendered with real theme styles, real visitor context, and real render_callback output. A “Preview live” button in the CPT editor opens a render of the block in a theme-styled frontend shell, without ever creating a public URL for the CPT post.

The mechanism, with five layers of defence so a single misconfiguration can’t leak the preview to the public:

  1. CPT registration. The five frontend-invisibility flags listed above. WordPress itself returns 404 for any non-admin request that targets the CPT post directly.
  2. Dedicated preview route, not the post’s permalink. The preview URL is https://site.tld/?gc_cairnstone_block_preview={ID}&_wpnonce={NONCE}, a registered query var, NOT the CPT’s would-be slug.
  3. Capability + nonce check, both required. The handler verifies wp_verify_nonce AND current_user_can( 'edit_post', $id ). Either failing → 404 (not 401 or 403; zero signal that a preview URL might exist).
  4. Hard noindex on the preview response. The response carries X-Robots-Tag: noindex, nofollow, noarchive, nosnippet, noimageindex HTTP header AND matching <meta> in <head>. The nonce is single-use and short-lived (24 hours).
  5. Explicit SEO-plugin exclusion hooks. Filter callbacks on wpseo_sitemap_exclude_post_type (Yoast), rank_math/sitemap/exclude_post_type (Rank Math), aioseo_sitemap_post_types (All in One SEO), wp_sitemaps_post_types (WP core), and a generic catch-all gillish_cairnstone_sitemap_exclude_post_types.

To leak a CPT post to the public, all five layers would have to break simultaneously, which requires deliberate code change to Cairnstone + a misconfigured WordPress installation + a misconfigured SEO plugin + a shared preview URL with a non-expired nonce + Google ignoring the robots header. The probability is meaningfully zero.

Lifecycle safeguards (block-type evolution)#

The biggest architectural risk in the CPT model is frozen content vs evolving structure: a designer edits a CPT-block in a way that’s incompatible with existing instances, and suddenly thousands of published posts show block-validation errors. Three guardrails, all shipped as part of Phase 5:

  1. Breaking-change detection on CPT save. Diff old template against new. Structural changes, removed blocks, renamed inner-block slots, removed attributes, changed block types, are flagged with an admin notice. Three paths: write a migrate callback; publish as a new slug (e.g. pricing-grid-v2); cancel.
  2. Snapshot rollback. Every CPT save also stores the previous template as a backup in CPT meta (_gc_template_snapshot). When render_callback for an instance throws, the fallback path renders against the snapshot and an admin-only notice surfaces. Frontend never crashes; only admin sees the warning.
  3. Dry-run validation pre-publish. When the designer hits “Update”, Cairnstone runs every existing instance through a dry render against the new template, server-side. If any instance fails, the designer sees a list and the save is blocked until they choose a path.

Two editor surfaces; one frontend render surface#

Cairnstone’s inspector panels appear automatically in the Block Inspector in two distinct editor contexts, doing different jobs:

  • Surface A, regular post / page editor. Author is writing real content destined for the frontend. Cairnstone’s panels apply universal Look + CC to instances. Settings here override the block type’s defaults for that one instance.
  • Surface B, gc_cairnstone_block CPT editor. Designer is building a block template. Cairnstone’s panels configure the block type’s defaults, what every instance starts with before the author touches it.

Both surfaces write to the same attribute schema; the difference is which level the writes affect. The frontend render surface (Surface C, no editor) reads the resolved combination at render time: instance attributes if set, falling back to block-type defaults.

The block library#

Tiering

Each block carries a 'tier' on its ModuleRegistry entry:

  • 'free', always available, drives adoption.
  • 'pro', gated by Shared\Licensing::has_tier( 'pro' ). Today the helper returns true unconditionally (dev mode); the real license-key implementation is Phase 18. ModuleRegistry::is_enabled() ANDs the per-module on/off toggle with the tier gate.

Roadmap target: 100+ blocks

The library is sliced into seven strategic categories. Every block in every category supports the Conditional Content attributes universally. The full 100-block list is in the appendix.

CategoryCoverage
Conversion & sales (15)Product comparison, pricing tables, Pros/Cons, TL;DR, Amazon card, animated buy buttons, discount banners, trust badges, scarcity, product grids, checklists, Affiliate Disclosure, sticky bars, exit-intent overlays, lead-magnets.
Content & SEO structure (15)FAQ schema, breadcrumbs, TOC, author bio, post meta, related posts, section dividers, anchor menu, glossary tooltips, footnotes, post navigation, search bar, sitemap visualiser, code snippet, blockquote variants.
Layout & design framework (15)Advanced columns, container with overlays, masonry, CSS-grid layout, parallax, Lottie, shape dividers, responsive spacer, z-index layer, icon box, feature list, image comparison slider, video popup, hero sections, team grid.
Social & interactive (15)Instagram / X / YouTube / Pinterest / Facebook / WhatsApp / Telegram embeds; RSS reader; social-proof popups; thumbs up/down; quiz; poll; contact form; Google Maps; donation button.
Dynamic & contextual (10)Device-specific box, geo-targeted text, countdown timer, dynamic data table, user-role-gated content, time-of-day greetings, referrer greeting, weather widget, stock ticker, currency converter.
Specialised & niche (10)Event timeline, recipe card, event listings, podcast player, PDF embedder, count-up counter, star-rating grid, step-by-step guide, comparison slider, logo carousel.
Tools & calculators (10)BMI, loan / mortgage, percentage, crypto price card, business hours, team org chart, interactive map, job board, real-estate card, restaurant menu.
Advanced visual effects (10)Glassmorphism, neumorphism, typing animation, hover flip card, scroll-reveal, floating images, background-video hero, gradient text, content reveal, custom-HTML wrapper.

The target is to be WordPress’ best block library, not just by count but by polish. Every block is treated as a product: settings defaults that work without configuration, a block-editor preview that matches the frontend render, dark-mode pairings, accessibility review, performance budget.

Currently shipped

Fourteen library blocks are live: thirteen free, one Pro. Each is hand-built with its own variations, controls, and frontend behaviour, and every one carries the universal Look / Visibility / Schema / Cairnstone data panels. Two layout primitives (Container, Columns) and the standalone Conditional Content wrapper sit alongside the library.

BlockTierOne-line
HeroFreeTop-of-page heading, subheading, and two calls to action. Six layout variations; focal-point and overlay controls on the image.
FAQFreeQuestion/answer accordions as native <details>. Zero JavaScript, keyboard-accessible, optional FAQPage schema.
Pricing TableFreeSide-by-side tiers with name, price, feature list, one CTA each. Five variations.
TestimonialFreeA single quoted endorsement with photo, name, role, and optional star rating.
Author Bio CardFreeShort author profile with photo, role, bio, and social links sourced from a site-wide setting.
BadgeFreeCompact trust/status mark. Fifteen presets, seven silhouettes, five colour roles.
Discount BannerFreeTime-windowed promo with optional coupon copy and countdown. Auto-hides after its end date.
Logo RowFreeA row of logos you feature as social proof: partners, press, or customers. Configurable height, alignment, monochrome treatment, and surface (band or cards); per-logo links.
Section DividerFreeDecorative break between sections. Five styles including a centered label.
Table of ContentsFreeAuto-generated nested list of the current post’s headings. Configurable depth, numbering, mobile collapse.
Affiliate DisclosureFreeBaseline disclosure copy for pages with affiliate links (advertising-transparency rules, not FTC-only). Four pre-written templates and a site-wide default override.
How-toFreeNumbered ordered steps, each with a title, instructions, and an optional image. Optional HowTo structured data so search engines and AI read the steps in order.
ImageFreeA single conditionally-visible image that loads the right size and, on click, opens full size, a gallery, a link, or a download. Optional caption.
Pricing ProProPricing Table plus annual/monthly toggle with auto-computed savings, per-tier dual prices, sticky comparison header, and a recommended-tier picker.
Conditional ContentProThe standalone wrap-anything-with-rules block. Same rule engine that powers the universal Visibility capability across the editor.
ContainerFreePrimitive. Wraps inner blocks in a stylable section with rich Gutenberg supports.
ColumnsFreePrimitive. Multi-column layout.

Saved blocks built in the Cairnstone Block workspace appear in a third inserter category, “Cairnstone, My blocks”, distinct from the shipped library bucket.

Conditional Content (the launch block)#

Two render modes

ModePicks variantSEOPage-cache compatible
Client (default)Server emits all variants in <template> + visible default; runtime picks in browser.Stable, search engines see the default variant.Yes, same HTML for all visitors, runtime swaps after cache hit.
Server (opt-in)PHP at the_content time, emits only the winner.Best, variant selected from actual visitor.No, cached HTML pins the first visitor’s variant for everyone.

Default flipped to client-mode 2026-05-11. Most modern WordPress hosts cache pages aggressively (managed WP, Cloudflare, LiteSpeed, WP Rocket, W3 Total Cache). Server-mode + page cache = silent cross-visitor pollution. Client-mode pays a ~100 ms flicker for cache safety, the correct trade-off for most sites. Server-mode is still available as an explicit opt-in for cache-free environments.

Cache-plugin detection. On init, Cairnstone checks for known caching layers (WP_Rocket, LiteSpeed_Cache, W3_Plugin_TotalCache, WP_Super_Cache, Hummingbird, HTTP_CF_RAY). When detected, an admin notice fires in the CC settings panel.

Two authoring surfaces

  • Gutenberg block, gillish-cairnstone/conditional-content (parent) + gillish-cairnstone/cc-variant (child). Inspector rule builder with human-grade pickers (250-country typeahead, OS / browser / role dropdowns, multi-token “is one of” fields). Preview-as toolbar with editable visitor-profile fixtures. Inline double-click rename. Copy variant.
  • Shortcode, [node_swap] / [node_when] / [node_else] for Classic Editor / arbitrary content. Compact attr syntax (country="US,CA", country_not="DE", match="any"). Same rule evaluator as the block.

Rule DSL

Signals: country · device.os · device.type · browser · language · referrer · logged_in · user_role · utm_source · utm_medium · utm_campaign · visit_count · hour_local (browser-side only).

Operators: eq · neq · in · not_in · contains · matches (regex) · gt · lt · between · not_between.

Unknown signal handling. Positive ops (eq/in/gt/between) return false; negative ops (neq/not_in/not_between) return true. The documented “fail closed” contract, typo’d rules surface as “rule never matches”, not “rule matches everyone.”

Editable Preview-as fixtures

The block-editor toolbar’s “Preview as: …” dropdown shows visitor profiles the author can simulate. Built-in defaults ship (iPhone US, Android phone US, iPad US, Mac desktop US, Windows desktop US, Visitor from Norway, Visitor from EU, Visitor from United States, Logged-in subscriber US). Authors edit the list under Settings → Conditional Content → Preview-as fixtures. Country dropdown sourced from the 250-country alpha-2 catalogue; language dropdown is country-aware. Label auto-syncs with the country via a hidden label_template. “Live (no preview)” and “Search-engine crawler” sentinel entries always sit at the top and bottom.

Sticky bucketing (opt-in per block)

gc_cc_<blockId> cookie pins the visitor to the variant they saw on first pageview for the configured lifetime (default 30 days, clamped to [1, 365], filterable via gillish_canvas_cc_sticky_cookie_lifetime_days). Cookie writes are client-side only (page-cache safety). Server reads $_COOKIE and short-circuits rule eval.

Crawler short-circuit

gillish_canvas_cc_is_crawler filter forces the Default fallback for bot user-agents, keeping server-rendered output stable for indexing. Default implementation: a curated 23-pattern list covering search-engine crawlers (Googlebot, Bingbot, DuckDuckBot, BaiduSpider, YandexBot), SEO tools (AhrefsBot, SemrushBot, MJ12Bot), link-preview fetchers (Facebook, Twitter, LinkedIn, Pinterest, Telegram, Discord, WhatsApp), and AI crawlers (GPTBot, ClaudeBot, PerplexityBot, CCBot).

DNT respect

When the visitor sends DNT: 1 AND Options::cc_respect_dnt() is on (default true), all advanced signals (UTM × 3, visit count, hour-local) resolve to null, no cookies are read or written, and the analytics action does not fire.

Analytics

Each server-mode render writes one row to gillish_canvas_events via the gillish_canvas_cc_variant_served action listener. The Cairnstone → Conditional Content admin page reports an overview (total renders + top blocks by volume in 7/30/90-day ranges), per-variant drill-down, and a daily trend SVG bar chart. Daily prune cron trims rows older than Options::cc_retention_days() (default 90 days, clamped to [7, 3650], 0 = forever).

Caveat: the action only fires for server-mode renders. Client-mode-rendered blocks don’t appear in the admin analytics page (the server doesn’t know which variant the browser picked). Surfaced in the editor as an info hint.

REST endpoint

GET /wp-json/gillish/cairnstone/v1/cc/country, per-visitor country lookup the client-mode runtime calls after the page-cache hit. Cache-Control: private, max-age=300.

GeoIP (delegated to Gillish Node)#

Cairnstone’s country signal is provided entirely by Gillish Node. Cairnstone owns no geolocation of its own: no bundled database, no MaxMind reader, no Cloudflare-header path, no external request. When Gillish Node is active it registers the gillish_node_resolve_country_code filter chain; Cairnstone reads the visitor country through that single server-side hook.

The gate is strict (decision 2026-05-19): country resolves only when the GeoIP setting is on (Settings → Cairnstone → Integrations) AND Gillish Node is active. Otherwise the result is empty and country rules never match, consistent with the rule evaluator’s unknown-signal contract. No site, visitor, or IP data ever leaves the server from Cairnstone; the work happens inside Node. Settings → Integrations is a single on / off toggle with a status line telling the user whether Node is active to provide the data.

Link-aware design (Better Together with Gillish Node)#

When Gillish Node is active, Cairnstone transforms from a design tool into a data-driven conversion tool. Three layers of integration, all opt-in per block.

Status (2026-06-15): this three-layer design is planned, not yet built. Today only layer 2’s destination-URL resolution ships, via the gillish_node_resolve_link filter on a [node id] link field. Metadata/anchor inheritance, automatic affiliate-disclosure injection, and the health-awareness layer are not in the codebase (the gillish_node_resolve_managed_link filter, the the_content disclosure injection, and the LinkHealthChecker editor query do not exist yet; they wait on Node publishing the picker / health APIs).

1. Metadata Inheritance

A button or product card in Cairnstone can be linked to a gillish_node managed-link ID. The block then inherits:

  • Preferred Anchor Text, Node’s per-link “anchor override” field. If the Cairnstone block doesn’t have a custom button label, it falls back to this.
  • Destination URL, always Node’s resolved redirect URL (after Node’s dynamic-rule evaluation, A/B routing, and shortener replacement). A visitor in NO can be sent to a different affiliate destination than a visitor in US, automatically, without Cairnstone needing to know about Node’s rule engine.
  • Link metadata, rel="sponsored" flag, category, etc.

Data direction is one-way (Node → Cairnstone). Cairnstone blocks read Node link IDs at render time via apply_filters( 'gillish_node_resolve_managed_link', null, $link_id ). Node never has to know Cairnstone exists.

2. Automated Affiliate Disclosure

Cairnstone ships a dedicated Affiliate Disclosure block (typography, spacing, dark-mode pair, a11y review, designed to look professional in any theme).

When Node is active, Node scans post content for any Cairnstone blocks referencing managed links flagged rel="sponsored". If at least one is present, Node automatically injects the Cairnstone Disclosure block at the top of the post via the the_content filter, UNLESS the post already contains a Disclosure block (idempotent).

The site author can override the auto-injection per post (a meta-box toggle on the post editor) or globally (Settings → Privacy → Disclosure). The default-on behaviour adds the disclosure note automatically on every affiliate post a user publishes.

3. Health Awareness

Node’s LinkHealthChecker already tracks per-managed-link HTTP status. Cairnstone blocks that reference a managed link query Node’s status at editor-load time:

  • Healthy, block renders normally, no warning.
  • Broken (404 / 410 / DNS / timeout), block sidebar shows a red warning: “Linked managed link is broken.” Deep-links to Node’s Manage Links page filtered to the affected link.
  • Pending, link not yet checked. Subtle hint, no action.

This catches a whole class of regressions: an affiliate program shutting down, the redirect dying, nobody noticing for weeks because the links were buried in old posts.

Extended integration opportunities (planning, not committed)

Beyond the three integrations above, ten further opportunities are tracked for future planning. Tagged by editor surface: A = regular post / page editor; B = gc_cairnstone_block CPT editor; C = frontend render-time.

  1. Universal link picker. (A + B) Every URL field in every Cairnstone block (button, image link, hero CTA, even inline links in paragraph via a Gutenberg RichText format) can pick a Node managed-link ID instead of typing a raw URL.
  2. Internal-linking suggestions from Node’s content graph. (A only) While the author writes a regular post, the Cairnstone inspector shows “this paragraph mentions [topic]; you have a post about [topic] → link it?”
  3. Per-block click analytics fed by Node. (A + B) Cairnstone blocks that reference Node links inherit per-link click stats.
  4. Geo-routed product cards. (A + B + C) A “Product Card” library block bound to a Node managed link with geo-rules renders the correct affiliate URL per visitor, no Cairnstone logic, Cairnstone just reads what Node resolved.
  5. Schema.org enrichment from Node-stored product metadata. (B configures, C resolves) When the designer maps a Schema type, fields like price, currency, availability can read from Node link metadata at frontend render time.
  6. Link-expiry-aware visibility. (B configures, C resolves) Node’s per-link metadata can carry an expiry date. The designer’s Cairnstone Visibility panel in the CPT offers “auto-add CC rule: hide after <link.expires_at>”.
  7. Per-link visibility rules pushed from Node. (B configures, C resolves) Node could expose “this link is geo-restricted to US only.” The Cairnstone block respects it automatically.
  8. Bulk-relink across CPT block instances. (admin, no editor) When a Node link target changes, every Cairnstone block instance referring to that link auto-updates at render time.
  9. Disclosure-block injection scope per surface. (A + B) Auto-disclosure for surface A is on by default; for surface B, the designer might want the disclosure baked into the CPT block-type template.
  10. Compliance reporting. (admin) A combined “sponsored links + disclosure coverage” report.

Import / export#

First-class import/export of CPT-built blocks via both UI and WP-CLI, a community multiplier no other block plugin ships as a peer feature to “create” and “save”.

  • Format. .gcblock.json with formatVersion, slug, title, composition (block tree), panelConfig, schemaVersion.
  • REST. GET /wp-json/gillish/cairnstone/v1/blocks/<slug>/export + POST /wp-json/gillish/cairnstone/v1/blocks/import. permission_callback: manage_options + wp_rest nonce.
  • WP-CLI. wp gillish-cairnstone list-blocks · export-block · import-block · wipe-blocks --yes.
  • UI. “Export” / “Import” as peer buttons in the CPT list view (surfaced as a row action and as a bulk action) and on individual CPT edit screens via a PluginPostStatusInfo slot in the document settings (NOT the Block Inspector, import/export is a document-level concern). An imported block lands as a new gc_cairnstone_block post in draft for review/edit.

AI integration via WordPress Abilities API + MCP#

Cairnstone is operable by AI assistants through the WordPress Abilities API (Core integration tracked for WP 7.0; usable today via the Abilities API canonical plugin). A user can connect Claude Code, Cursor, ChatGPT Plus, or any MCP-speaking AI assistant to their WordPress site and say “build me an FAQ block about pricing”, the assistant introspects Cairnstone’s capabilities and produces a working block via the same internal contracts the human UI uses. Phase 13 shipped this base surface on 2026-05-30 (v2.3.90 → v2.3.93): five read-only discovery abilities, four draft-only mutation abilities behind an “AI assistants” Settings opt-in, a Cairnstone → AI activity audit page over a 30-day log, and an “AI draft” marker on AI-created blocks in the list. Phase 14 then shipped richer introspection (v2.3.102 → v2.3.108): a semanticType tag per library block, the cairnstone/validate-composition dry-run validator (manifest-attribute + parent/child nesting + Conditional-Content checks, plus a content-stripped warning), and the cairnstone/list-style-controls universal style catalogue. A pre-save gate then runs the validator before every AI write, so a block that cannot work is refused before it is saved, with a human-readable reason that names the block the way a designer sees it. Phase 15 then closed the arc on 2026-05-31 (v2.3.110 → v2.3.111): cairnstone/preview-block renders a saved block as a chosen visitor profile would see it, with a per-block show/hide trace, so an assistant can confirm a Conditional Content rule before it commits; dry_run on create / update / import returns the resolved result without writing; and cairnstone/batch applies an ordered list of mixed create / update / delete / import in one call, partial-success by default with an atomic all-or-nothing mode. Every ability is runtime-verified over the wire against a live install. The AI-integration arc (Phases 13–15) is complete; the surface stays free of any accumulating quota or rate limit, with only a soft max_items runaway guard on batch.

Why this matters

The 100-block library is comprehensive, but no library covers every site-specific need. The CPT block-builder solves this for designers; the AI integration solves it for users who can describe what they want but don’t know how to build it. Plain-language “an accordion of my service hours by location” produces a draft block in admin without the user opening the block editor.

Tier model

The originally-planned Free monthly mutation quota (with a Pro unlock) plus per-hour rate limit was dropped before launch. A counter that disables included functionality reads as wp.org trialware, and on a second look the limiter had no real abuse vector to close: the calls run against the site owner’s own AI client, and the surface is already gated by authentication + manage_options + draft-only + the admin opt-in toggle. So there is no quota and no rate limit on the AI surface, and no 402 or 429.

  • Discovery abilities (read-only, capability-gated) and the four base mutation abilities (create / update / delete / import, draft-only) ship in the free wp.org plugin. Both tiers get full discovery and the base mutation set.
  • Pro AI differentiates on capability, not on a counter. Advanced abilities (bulk edit, multi-block refactor, premium-block-aware create) live in a separate Gillish Cairnstone Pro add-on hosted outside wp.org, which registers additional cairnstone/* abilities. A heavy free-tier day hits a capability gap, the converting moment, not a paywall.

Ability surface

The discovery + mutation abilities below shipped in Phase 13 (13a/13b), and validate-composition + list-style-controls in Phase 14. preview-block and create-blocks (batch authoring) shipped in Phase 15. The live surface is fourteen abilities, re-verified end-to-end against the floor install.

AbilityPurpose
cairnstone/list-primitivesCatalogue of Cairnstone primitives (Container, Columns, etc.) the agent can compose with.
cairnstone/list-library-blocksCatalogue of shipped library blocks, with semantic tags (e.g. semanticType: 'faq-accordion') so the agent can match user intent to the right pattern.
cairnstone/list-style-controlsThe universal gcStyle controls the agent can set on any block (opacity, colours, padding, margin, drop shadow + presets, …), with their ranges and the five states they apply to. Block-agnostic: the styles are universal. Phase 14.
cairnstone/list-blocksAll gc_cairnstone_block CPT posts on the site.
cairnstone/get-blockBlock tree + panelConfig + schemaVersion for a given slug.
cairnstone/get-schema-typesThe structured-data (schema.org) types Cairnstone can emit, for a block that wants rich-result markup.
cairnstone/list-node-links(When Node is active.) Managed-link directory.
cairnstone/create-blockCreate a new CPT block. The tree is checked by validate-composition before saving (a non-working block is refused) and sanitised. Lands as draft.
cairnstone/update-blockUpdate an existing CPT block. Same pre-save check; publish is refused (a human reviewer publishes).
cairnstone/delete-blockSoft-delete (move to trash), never hard-deletes.
cairnstone/import-blockImport a .gcblock.json payload.
cairnstone/validate-compositionDry-run a proposed tree through three layers (manifest-attribute, parent/child nesting, Conditional-Content rule validity) and return { valid, findings } with a per-finding path; a content-stripped warning flags HTML the save would clean. Phase 14.
cairnstone/preview-blockRender a block tree against a VisitorContext fixture and return HTML. Phase 15.
cairnstone/batchBatch multiple create / update / delete calls for bulk authoring. Phase 15.

Worked example: the accordion request

Implementation cost

Modest: a thin layer that translates each WordPress ability call to Cairnstone’s internal API. The dry-run validator and the import sanitiser already exist; cairnstone/create-block feeds the same engine the human UI saves through. The AI integration is not a separate code path, it’s an alternate input to the same engine.

Security and capability discipline

Five non-negotiable mitigations, all locked into the AI base phase (Phases 13–15, shipped):

  1. Admin-opt-in gate. Site-wide setting “Allow AI agents to create blocks” defaults OFF; mutation abilities return 403 until enabled. Discovery abilities stay on by default (read-only, capability-gated).
  2. Capability gates. Every mutation ability requires manage_options + valid wp_rest nonce.
  3. Draft-only enforcement. AI-created blocks land as draft CPT posts; trying to publish via ability returns 403. The “AI draft + human review + human publish” flow is enforced at the ability layer.
  4. Output sanitisation beyond schema. Reject <script> in attribute values, javascript: URLs, data: URLs, HTML event handlers, and any attribute not in the manifest schema.
  5. Soft-delete only. cairnstone/delete-block moves to trash; never hard-deletes.

Audit log. Every ability call (timestamp, user, ability name, payload hash, result, IP) logs to an event-type row in gillish_cairnstone_ai_events. A Cairnstone → AI activity admin page renders the last 30 days.

Performance philosophy: lean frontend output#

WordPress block plugins have earned a reputation for bloated frontend output, three nested <div>s per block, per-instance stylesheets, per-block React apps, font loads for every typeface offered in the editor. Cairnstone opts out of that pattern. The philosophy is: the frontend is the user’s site, not Cairnstone’s. Cairnstone’s job is to disappear at render time.

Targets (the bar every release must clear)

MetricTarget
Lighthouse Performance (mobile)≥ 95 on the standard 4-block test post (Hero + Container + Heading + Pricing + FAQ).
TTFB≤ 200 ms (PHP execution under typical hosting).
LCP≤ 2.5 s on 3G Fast throttling.
CLS0 (Cairnstone blocks never inject layout-shifting content after first paint).
HTML per block instance≤ 1 KB average across the library.
CSS per block file≤ 2 KB. Only enqueued when has_block() for that block is true.
Frontend JS0 KB by default. Interactive blocks share one @wordpress/interactivity runtime; never a per-block React app.
Font loads0. System fonts only on the frontend. No Google Fonts auto-embedded.

Architectural rules that enforce the targets

  • Server-side render by default. Every block ships a render_callback. JS only when a block is genuinely interactive (Accordion, Tabs, Modal, Carousel), and then via the WP Interactivity API, not React.
  • No wrapper divs unless structurally required. A block that renders a heading renders <h2>, not <div class="wp-block-cairnstone-heading"><h2>…</h2></div>.
  • gcStyle ships as inline style= on the rendered element, not as per-instance stylesheets. Inline style is one HTTP cost (the HTML itself) instead of a roundtrip for a tiny CSS file.
  • Per-block CSS only enqueued when the block is present on the page. has_block() gate on every wp_enqueue_style call.
  • One shared interactivity runtime. All interactive Cairnstone blocks lean on the same @wordpress/interactivity bundle that WP ships in core.
  • No editor-only JS on the frontend. The Block Inspector code (the Cairnstone Look / Visibility / Schema panels) is editor-only.
  • No layout-property animations. Only opacity and transform. Respects prefers-reduced-motion absolutely.

Performance budget (planned automated guard)

The targets above are the bar every release clears today. A microbenchmark against a synthetic 200-block document (195 with no conditions, 5 with typical CC rules) measuring total gate_block cost and the cached VisitorContext::get() path is planned as an automated guard but is not yet wired. The shipped gate runs locally (PHPUnit, composer audit, accessibility, Vitest); GitHub Actions was retired 2026-05-24.

Architecture overview#

Cairnstone mirrors Gillish Node’s 1:1 directory tree, naming conventions, and bootstrap patterns. A developer who has read Node’s CLAUDE.md can open Cairnstone’s source tree and immediately know where to look for any concept, settings, hooks, options, admin pages, modules, REST routes, cron events. Same map, different territory.

Centralised RuleEvaluator + VisitorContext

  • Shared/RuleEvaluator.php, pure rule → bool resolver. Ops, dot-path signal lookup, unknown-signal handling per documented contract. No I/O. Promoted from Modules/ConditionalContent/ in v0.3.0 so the whole block library leans on the same evaluator.
  • Shared/VisitorContext.php, cross-block visitor signal collector. Country comes from Modules\GeoIp\GeoIp::resolve_country(), which delegates to Gillish Node through the gillish_node_resolve_country_code bridge. Request-cached so multiple blocks per page share one resolution cost.
  • Shared/ConditionalRender.php, two-listener engine. inject_attributes() on FILTER_REGISTER_BLOCK_ARGS adds conditions/match/renderMode/stickyCookie to any block whose manifest opts in. gate_block() on render_block (priority 10) reads those attrs at render time, evaluates, and returns either the original HTML or ''.

Atomic block design: shared UI primitives

The library is built from a shared kit, not as 100 isolated mini-products. Per-block manifest.json declares attributes, defaults, preview fixtures, and which inspector primitives the block opts into. Same manifest drives Cairnstone’s inspector-panel rendering AND Gutenberg block registration, one source of truth.

Shared inspector primitives (delivered as part of Phase 3 of the post-pivot plan): BoxModelControl (padding + margin per side), ColorControl (with Gutenberg HEX picker + opacity slider), NumericControl (slider + numeric + reset), OpacityControl, CollapsibleSection (accordion section with persisted open state).

Free / paid model#

Free tier (wp.org, no payment, no key)

  • All Cairnstone primitives (Container, Columns, future layout primitives).
  • ~25–30 library blocks across all categories.
  • Universal Cairnstone Look panel on every block.
  • Basic Conditional Content: 3 signals (country, device.type, logged_in), the 18 recipes, plain operators.
  • Full CPT block-builder (no instance cap).
  • Import / export.
  • Basic AI integration via Abilities API (discovery unrestricted; mutation behind the AI-assistants opt-in).

Pro tier (direct sale, license key)

  • Full library (100+ blocks).
  • Full Conditional Content: all 13 signals, regex operators, state-driven controls (active / hover / focus / disabled), interaction system.
  • Schema.org emission per block.
  • Unlimited AI mutation calls (gated by the admin AI-assistants opt-in; no per-call quota or rate limit).
  • Node integrations beyond the universal link picker.
  • Future: site presets (Cairnstone presets, deferred to post-Phase 11) and Cairnstone lens (deferred to Phase 17–18 tier).

The mechanism (already built as stub)

Shared\Licensing::has_tier( string $tier ): bool returns true unconditionally today (dev mode). When the licensing layer ships (Phase 18), only this helper changes. Every Pro-gated feature already routes through this contract, no hardcoded checks, no “we’ll figure out monetisation later”.

Provider-agnostic Licensing contract

The contract surface stays fixed regardless of backend choice (Lemon Squeezy, Freemius, EDD Software Licensing, Paddle, custom):

interface LicensingContract {
 public function has_tier( string $tier ): bool;
 public function license_key(): string;
 public function license_status(): array;   // tier, expiry, sites used vs allowed, last validation
 public function refresh_now(): void;
}

Activation flow: on key entry, REST call to the chosen backend’s validation endpoint, payload signed (HMAC) so it can’t be forged; result cached in wp_option (gillish_cairnstone_license_status) with 14-day grace window. Weekly cron re-validation; failure within grace window keeps the cached status; failure beyond grace window downgrades to free.

EU Cyber Resilience Act (CRA) readiness#

The EU CRA (Regulation (EU) 2024/2847) applies to commercial software with digital elements sold in the EU. Pro-tier Cairnstone is in scope. The Act is in force; reporting obligations land 2026-09-11, full applicability 2027-12-11.

Cairnstone’s CRA-readiness checklist:

  • Published security contact. security@gillish.no with a documented response window: 24h acknowledgement, 7d for critical, 30d for high. Page lives at gillish.com/security.
  • Responsible disclosure policy. “Report privately first; we’ll publish after the patch ships.”
  • Declaration of Conformity per shipped plugin, linked from the security page. Updated at each major release.
  • Support-period commitments. 5-year minimum from the release date of each major version.
  • SBOM per plugin (CycloneDX format). Generated at build time, linked from the security page. Tracks any runtime dependencies; Cairnstone currently bundles none.
  • ENISA reporting commitments. Manufacturer-side filings tracked on the security page.

Competitive landscape#

Cairnstone overlaps three existing market segments. Each has a leader or two; Cairnstone’s wedge is the combination none of them ship.

Block libraries

GenerateBlocks (~$2M ARR), Stackable, Kadence, Spectra. Mature, polished, big libraries. Their tone is developer-friendly (GenerateBlocks especially) or WordPress-cheery (Stackable, Kadence). None ships universal Conditional Content as a first-class attribute on every block; none ships first-class block import/export.

Personalisation tools

If-So, Logic Hop, Convert Plug. Strong at the rule engine; weak at the design surface. Either ship a single block (“a personalisation wrapper”) or a separate admin page divorced from the block editor.

Page builders

Elementor, Bricks, Oxygen, Breakdance. Different category entirely, they replace Gutenberg, not extend it. Cairnstone is explicitly Gutenberg-native; the page-builder market is not the one Cairnstone competes in.

Cairnstone’s three-fold wedge

  1. Plain-language UX over every other block plugin. The brand voice is “built for humans, not developers”, no CSS literacy assumed, no spec numbers in labels.
  2. Universal Conditional Content as a first-class attribute. Personalisation isn’t a separate tool; it’s a property of every block.
  3. First-class block import/export. Community multiplier no competitor ships.

The fourth pillar, AI-native via Abilities API + MCP, is becoming a wedge as the WordPress ecosystem moves toward AI integration. Cairnstone’s schema-validated ability surface is meaningfully ahead of where competitors are.

What Cairnstone is NOT (yet)#

  • Not a page builder. Cairnstone builds blocks; the block editor builds pages. Bricks, Elementor, Oxygen are different products.
  • Not a theme. Cairnstone works with any block theme.
  • Not an A/B testing tool. The rule engine is for “show this to that audience”, not split-testing variants.
  • Not an analytics platform. CC analytics report served-variant volume; they don’t track clicks, conversions, or funnels.
  • Not multisite-aware. Network-activate is explicitly blocked at the activation hook with a clear error message.
  • Not a custom payment processor. Pro sales go through Lemon Squeezy / Freemius / etc.; we don’t build our own billing infrastructure.

Appendix: the 100-block catalogue#

The library’s target. Numbering reflects implementation order, roughly grouped by impact + difficulty. Every block in every category supports the Conditional Content attributes universally.

Shipped so far: fourteen library blocks (thirteen free, one Pro). This catalogue is the roadmap aspiration, not a snapshot of what exists today. Entries already live are tagged (shipped) below; everything untagged is still roadmap. Two shipped blocks, Testimonial and Badge, refined the plan and do not map to a single catalogue line, and the Container and Columns primitives plus the standalone Conditional Content wrapper ship alongside the library rather than as catalogue entries. The “Currently shipped” table earlier on this page lists the full live set.

Conversion & sales (1–15)

  1. Pricing table (toggle monthly / yearly) (shipped)
  2. Product comparison grid
  3. Affiliate Disclosure (auto-injected by Node) (shipped)
  4. Pros & cons box
  5. Summary box (TL;DR)
  6. Animated buy button
  7. Discount banner with copy-to-clipboard (shipped)
  8. Logo Row (shipped)
  9. Scarcity / stock notifier
  10. Product grid
  11. Amazon product card (via API)
  12. Checklist
  13. Sticky bar
  14. Exit-intent overlay
  15. Lead-magnet opt-in

Content & SEO structure (16–30)

  1. FAQ Schema Block (auto JSON-LD) (shipped)
  2. Breadcrumbs
  3. Table of contents (floating sidebar) (shipped)
  4. Author bio card (shipped)
  5. Post meta
  6. Related posts
  7. Section divider (SVG shapes / waves) (shipped)
  8. Anchor-link menu
  9. Glossary tooltips
  10. Footnotes (auto-numbered)
  11. Post navigation
  12. Search bar
  13. Sitemap visualiser
  14. Code snippet (syntax highlighting + copy)
  15. Modern blockquote / pull-quote

Layout & design framework (31–45)

  1. Advanced columns (mobile / tablet / desktop control)
  2. Container with overlays
  3. Masonry gallery
  4. CSS-grid layout
  5. Parallax background
  6. Lottie animation container
  7. Shape divider
  8. Responsive spacer
  9. Z-index layer
  10. Icon box
  11. Feature list
  12. Image comparison (before / after slider)
  13. Video popup / lightbox
  14. Hero section (shipped)
  15. Team grid

Social & interactive (46–60)

  1. Instagram embed
  2. X (Twitter) embed
  3. YouTube playlist
  4. Pinterest embed
  5. Facebook page embed
  6. WhatsApp share button
  7. Telegram embed
  8. RSS reader
  9. Social-proof popup
  10. Thumbs up/down feedback
  11. Interactive quiz
  12. Poll
  13. Lightweight contact form
  14. Google Maps with custom pins
  15. Donation button (PayPal / Stripe)

Dynamic & contextual (61–70): Cairnstone-defining

  1. Device-specific box
  2. Geo-targeted text (“Hi from [Country]”)
  3. Countdown timer (evergreen or fixed)
  4. Dynamic data table from custom fields
  5. User-role-gated content
  6. Time-of-day greetings
  7. Referrer-host greeting
  8. Weather widget
  9. Stock ticker
  10. Currency converter (uses GeoIP for default currency)

Specialised & niche (71–80)

  1. Event timeline
  2. Recipe card with Recipe schema
  3. Event listings
  4. Podcast audio player
  5. PDF embedder
  6. Count-up counter
  7. Star-rating grid (many-product comparisons)
  8. Step-by-step guide
  9. Comparison slider
  10. Logo carousel

Tools & calculators (81–90)

  1. BMI calculator
  2. Loan / mortgage calculator
  3. Percentage calculator
  4. Crypto price card
  5. Business hours
  6. Team org chart
  7. Interactive map with clickable regions
  8. Job board listing
  9. Real-estate property card
  10. Restaurant menu / price list

Advanced visual effects (91–100)

  1. Glassmorphism
  2. Neumorphism
  3. Typing animation
  4. Hover flip card
  5. Scroll-reveal
  6. Floating images
  7. Background-video hero
  8. Gradient text with animated colours
  9. Content reveal
  10. Custom-HTML wrapper (sandbox mode)