Blocks Marketing Starter

Blocks & themesStable
@wabbit/tome-blocks-marketing-starterv0.35.0

Free-tier landing-page and marketing blocks — high-impact hero, feature hero, CTA, logo slider, pricing, banner, FAQ, testimonial, and exhibit.

Install
  1. Add our registry to the .npmrc at the root of your project. Free packages install without a token.

    @wabbit:registry=https://npm.wabbit.com/
  2. Then install:

    npm install @wabbit/tome-blocks-marketing-starter

Overview

@wabbit/tome-blocks-marketing-starter

Tier: Free — included in every Tome site by default.

Essential blocks for landing pages and marketing sites.

Block List

| Slug | Name | Variants | |---|---|---| | high-impact-hero | High Impact Hero | default, centered, dark, split, illustrationHero, quick-ask, venue | | feature-hero | Feature Hero | default, minimal, dark, playful | | cta | Call to Action | default, minimal, with-form, footer, door-strip, doors | | logo-slider | Logo Slider | default, dark, static-grid, marquee-editorial | | pricing-plans (Payload slug pricingPlans; config subpath ./blocks/pricing) | Pricing | default, compact, dark, editorial | | testimonial | Testimonial | default, editorial, dark, carousel | | faq | FAQ | default, minimal, two-column, dark | | banner | Banner | default, editorial, dark, sticky | | exhibit | Exhibit Wall | gallery-wall |

High Impact Hero `quick-ask`. Text left, media right. Three optional groups, shown only for this variant: quickAsk (a visible label, options[], a button label, an action URL or #anchor, and a paramName, default need) renders a plain GET form with a real <select>, so it works without JavaScript: an action of #contact submits to ?need=<value>#contact. callAction (label, phone, showOnDesktop) renders a tel: link that leads the actions on phones; from 768px it is hidden unless showOnDesktop is ticked (default off), in which case it follows the quick ask. proofLine (an optional bold proofLead such as "Next opening:", text, optional status dot) sits under the actions; the dot and its first phrase (the lead, or the first word of the text) never wrap apart. The job caption is supportingImages[].caption. Theme hooks: data-block-variant="quick-ask", data-hero-actions, data-hero-call (with data-hero-call-desktop="true|false"), data-hero-quick-ask, data-hero-proof (with data-hero-proof-start on the no-wrap dot group and data-hero-proof-lead on the lead), data-status-dot, data-hero-media, data-hero-caption.

High Impact Hero `venue`. The opener for a place people visit: a short meta line (eyebrow), the venue's name set very large (headline), one line (subheadline) and its action (links), with the photo from backgroundOptions.backgroundImage (Background Type: Image). One optional group, shown only for this variant and with no default value: venue.mediaPlacement, behind (empty reads as behind) or above. Behind, the photo fills the hero and the copy sits at its foot over a scrim layer, on a fixed dark plate with light ink; the scrim is the theme token --tome-hero-scrim (default a bottom-up dark gradient, at least 62% under the copy). Above, the photo runs full width on top and the copy is set beneath it on the page; from 1024px the name takes the wide left column and the line and action sit to its right. --tome-hero-scrim defaults to none there. The venue does not render description, reviews, supporting images, the marquee, orientation or the size selects. Theme hooks: data-block-variant="venue", data-hero-media-placement="behind|above", data-hero-media, data-hero-scrim, data-hero-meta, data-hero-name, data-hero-line, data-hero-actions.

High Impact Hero `split` call, image label and caption line. callAction (label, phone) is also shown for split, where it renders as an inline tel: link after the buttons, e.g. "or call (555) 010-2030", at every width (showOnDesktop applies to quick-ask only). An empty label reads "or call" after buttons and "Call" when there are none; a phone with fewer than three digits renders nothing. In every variant that shows supporting images, supportingImages[].label sets a short tag over the image (e.g. "Illustrative photo") and supportingImages[].captionMeta adds a smaller second caption line. Each renders only when set, so existing images keep their markup. Theme hooks: data-hero-call, data-compliance-label, data-caption-meta.

Testimonial `quotes-grid` layout and compliance labels. The layout select adds quotes-grid: every testimonial (the related list, else the single relation, else every entry, whatever the mode) becomes a short quote in a grid, one column on phones, two from 640px, three from 1024px. The optional stat group (value, text, label, position) adds one figure tile; position is its 1-based cell among the quotes, default 2, and past the end it goes last. The tile shows when a value or a text is set. Each testimonial's optional complianceLabel (the same field name a testimonials collection can use) renders with its quote in every layout. Theme hooks: data-testimonial-layout="quotes-grid" on the root, data-testimonial-cell on every cell (each quote's <figure> and the tile), data-testimonial-stat on the tile, data-compliance-label on each label. The grid's column count is --tome-testimonial-grid-cols (it sets every width when a theme sets it; scope it in a media query to vary it). An optional eyebrow (quotes-grid only) renders a small section label above the section title, <p data-block-eyebrow>. Star a run of the stat value to set it smaller: "9 in 10" renders "in" as <em data-testimonial-stat-unit> (upright, --tome-testimonial-stat-unit-size, default 0.5em); a value with no stars renders as before.

FAQ `openFirst` and part hooks. A checkbox (default off) that renders the first <details> open; the root then carries data-faq-open-first. Every FAQ carries part hooks: data-faq-item on each <details>, data-faq-question on its <summary>, and data-faq-answer on a plain, unstyled <div> around the answer. The answer's own element is rendered by your rich-text adapter, which accepts only a class name, so the hook sits on the wrapper; the wrapper renders only when there is an answer. The header group carries data-block-header and the badge data-block-eyebrow. The closing callout carries data-faq-callout and its link data-faq-callout-link; calloutStyle (box by default, or line) sets it either as the centred panel with a filled button or as a plain start-aligned line with a text link (data-faq-callout-style="line").

Note on "Footer CTA": The spec references "Footer CTA" as an owned block. This is the footer variant of the cta block (slug: 'cta', variant slug 'footer'), not a separate block slug. The cta block is included here with all six variants.

Install

Tome site (via meta-package — recommended)

If your site uses @wabbit/tome-blocks, you already have this bundle. No additional install needed. The meta-package's registerAll() call includes marketing-starter.

Stock Payload site — add to an existing blocks field

npm install @wabbit/tome-blocks-marketing-starter

@wabbit/tome-blocks-core and @wabbit/tome-blocks-extras are required peers and install automatically (npm 7+/pnpm). Add the blocks you want alongside your existing ones and render them — full canonical walkthrough (including the two colliding slugs below) lives in @wabbit/tome-blocks-core's "Add Tome blocks to an existing Payload project":

// payload.config.ts
import { ctaBlock, faqBlock } from '@wabbit/tome-blocks-marketing-starter'
// blocks: [...existingBlocks, ctaBlock.block(), faqBlock.block()]
// blockComponents.ts
import { renderers as marketingRenderers } from '@wabbit/tome-blocks-marketing-starter/render/register'
import { adaptRenderersForPayload } from '@wabbit/tome-blocks-core/render'
// blockComponents: { ...adaptRenderersForPayload(marketingRenderers) }
@import '@wabbit/tome-blocks-core/styles.css';

Render through this package's RenderBlocks with the grid class — skip the grid and blocks render flush to the viewport edge with no error:

// RenderBlocks.tsx
import { blockComponents } from './blockComponents'
import { RenderBlocks as TomeRenderBlocks } from '@wabbit/tome-blocks-core/render'
import gridStyles from '@wabbit/tome-ui/grid'

export function RenderBlocks({ blocks }: { blocks: LayoutBlock[] }) {
  return <TomeRenderBlocks blocks={blocks} components={blockComponents} gridClassName={gridStyles.grid} />
}

cta and banner collide with the Payload website/ecommerce templates' own block slugs of the same name — see @wabbit/tome-blocks-core's "Coming from the Payload website template" section for the slug-override fix.

Registering everything from scratch instead, wire the register() call into your Payload config:

// payload.config.ts
import { BlockRegistry, BundleRegistry } from '@wabbit/tome-blocks-core/registry'
import { register } from '@wabbit/tome-blocks-marketing-starter'

const blockRegistry = new BlockRegistry()
const bundleRegistry = new BundleRegistry()

register(blockRegistry, bundleRegistry)

export default buildConfig({
  collections: [
    {
      slug: 'pages',
      fields: [
        {
          name: 'layout',
          type: 'blocks',
          blocks: blockRegistry.resolveAll(),
        },
      ],
    },
  ],
})

Collections the blocks relate to. Upload fields point at media (pass mediaCollection to change it) and links at pages, the collections Payload's website template already has. Pricing cards and testimonials are entered on the block by default, so the pack needs no collection of its own; to keep them in a collection instead, pass its slug (productCardsCollection for pricing, testimonialsCollection for testimonial), e.g. blockRegistry.resolveAll({ testimonialsCollection: 'testimonials' }). Payload refuses to start if a relationship points at a collection your config doesn't have.

Using BlockPicker (optional)

If you want the visual block picker UI, also install @wabbit/tome-blocks-core and wire in the BlockPicker admin component. See @wabbit/tome-blocks-core README for full BlockPicker setup.

Peer dependencies

Generated from package.json#peerDependencies (the README gate fails if this table and the manifest disagree).

| Peer | Range | Required | |---|---|---| | @payloadcms/richtext-lexical | >=3.67.0 | yes | | payload | >=3.67.0 | yes | | react | >=19.0.0 | yes | | react-dom | >=19.0.0 | yes | | @wabbit/tome-blocks-core | >=0.35.0 <1.0.0 | yes | | @wabbit/tome-blocks-extras | >=0.28.0 <1.0.0 | yes |

Public API

| Export | Subpath | Description | |---|---|---| | Block descriptors (highImpactHeroBlock, featureHeroBlock, ctaBlock, logoSliderBlock, pricingBlock, testimonialBlock, faqBlock, bannerBlock, exhibitBlock) + register(blockRegistry, bundleRegistry) | . | Payload block config descriptors and bundle registration | | HighImpactHero, FeatureHero, Cta, LogoSlider, PricingPlans, Testimonial, Faq, Banner, Exhibit | ./render | Legacy self-registering render barrel | | renderers map + registerRenderers() | ./render/register | Explicit registration (server-safe adapter contract) — no side effects on import | | getDemoProps(blockSlug, variant, ctx?) (plus tree-shakeable per-block demo functions) | ./demo | Pack-level demo-props dispatcher feeding the auto-gallery route; returns null for unknown slugs | | Bundle/block metadata (slugs + variants) | ./meta | Payload-free surface for the gallery storefront | | bannerBlock, ctaBlock, exhibitBlock, faqBlock, featureHeroBlock, highImpactHeroBlock, logoSliderBlock, pricingBlock, testimonialBlock | ./blocks/* | Per-block config subpath (one per block slug) |

Server / client posture

4 of this pack's 9 renderers are plain static components — Banner, Cta, Faq, Exhibit. The other 5 (Testimonial, HighImpactHero, FeatureHero, PricingPlans, LogoSlider) carry a 'use client' directive (verified 2026-07-12; scripts/assert-rsc-boundaries.mjs MANIFEST: blocks-marketing-starter: 6 — the 6th file is PricingPlanCard, an internal sub-component already living inside the client PricingPlans boundary, not a separate top-level renderer) — each needs interactivity (carousel/slider motion, animated counters, plan-toggle state) that a Server Component can't provide. This is the highest client-ratio pack in the family; treat it as the reference example that "marketing-tier ≠ automatically static." Both render subpaths (./render/register and the legacy ./render barrel) load under plain Node — each renderer's per-block stylesheet ships precompiled as a JS module — so seed scripts and tests can import them. Keep payload.config.ts on the root entry point (block descriptors + register(), no React): that keeps React out of your config. Between the two render subpaths, prefer ./render/register: it has no import-time side effects.

Compatibility

Required peers:

| payload | react | react-dom | @payloadcms/richtext-lexical | |---|---|---|---| | >=3.67.0 | >=19.0.0 | >=19.0.0 | >=3.67.0 |

Blocks

high-impact-hero

The default marketing hero — headline, subheadline, description, CTAs, social proof (rating + avatars), supporting images, optional marquee, and full background control. Seven layout variants cover most landing-page openers, including `quick-ask` for a lead-capture opener (one-question form + tap-to-call) and `venue` for a place people visit (the name large over or under a photo of the room).

When to use
  • The opening section of a landing or marketing page
  • Any page that needs a strong above-the-fold statement with CTAs and/or social proof
Page types
  • landing
  • marketing
  • about
How to use

Set headline + subheadline + description and one or two CTA links. Pick a variant for the layout (orientation also shifts left/center/right). Add reviews (rating + avatars) for social proof, supporting images for a richer composition, or a marquee for movement. Use a color or image background (with gradient overlay for legibility). For a quieter look, leave media/reviews empty — this hero degrades gracefully (it supersedes the old low/medium-impact heroes).

Pairs with
  • feature
  • featureHero
  • logo-slider
  • testimonial
  • pricing-plans
  • cta
  • faq
Precedes
  • feature
  • featureHero
  • logo-slider
Avoid when
  • Article/post headers — use `article-hero`
  • Type-forward or image-dominant premium openers — use `hero-pro`
Register in

marketing-landing

feature-hero

The feature showcase — a heading + a list of features, rendered in one of six layouts (cards, three-steps, large-media, icon-grid, media-feature, masonry). One block covers every "here are our features/steps/benefits" section.

When to use
  • A "features", "how it works", or "benefits" section on a landing/marketing page
  • Any grid/list of titled items with optional icons, images, or descriptions
Page types
  • landing
  • marketing
  • about
How to use

Pick a layout, then fill the features array (title + optional description/icon/image). Icon-grid suits many short items; three-steps suits an ordered process (toggle the timeline connectors); large-media / media-feature lead with a hero image; cards/masonry suit richer feature blurbs. Set heading + eyebrow + subtitle for the section header, and an optional CTA. Follows a hero; precedes social proof / pricing / CTA.

Pairs with
  • hero
  • logo-slider
  • testimonial
  • pricing-plans
  • cta
  • faq
Follows
  • hero
Precedes
  • testimonial
  • pricing-plans
  • cta
Avoid when
  • A single image-led statement (use `hero` / `hero-pro`)
  • Long-form editorial prose (use the editorial blocks)
Register in

marketing-landing

cta

The conversion band — an eyebrow + headline + supporting copy, up to three action buttons, optional image, and a fine-print disclaimer. Six variants (default, minimal, with-form for inline name/email capture, footer for dark section ends, door-strip for a compact two-door router, doors for the full two-door close) cover most "ask for the click" moments.

When to use
  • A dedicated conversion prompt mid-page or closing a landing/marketing page
  • A standalone "sign up / book / get started" band between content sections
  • Inline lead capture without sending the visitor to a separate form (the with-form variant)
Page types
  • landing
  • marketing
  • pricing
  • about
How to use

Write a single clear ask in the rich text and one or two links — keep the primary action first and outline a secondary. Pick a variant: default for a standard band, minimal for a flat editorial treatment, with-form to append the inline name/email group (schema variant — save and reload to surface its fields), footer for a compact dark close. Set backgroundColor + appearance for emphasis; add an image only when it reinforces the ask. Use the disclaimer for fine print (guarantees, terms). Closes a section or a page — does not open one.

Pairs with
  • hero
  • featureHero
  • pricing-plans
  • testimonial
  • faq
  • logo-slider
Follows
  • featureHero
  • pricing-plans
  • testimonial
  • faq
Avoid when
  • The page opener — use `hero` / `high-impact-hero` (which already carry CTAs above the fold)
  • A full pricing decision — use `pricing-plans`; reserve the CTA for the single resulting action
  • Persistent site-wide announcements — use `banner`
Register in

marketing-landing

logo-slider

The trust strip — a row of client, partner, or press logos with an optional heading. Four variants run from a moving marquee (default, dark) through a slow, muted editorial drift (marquee-editorial) to a still grid (static-grid) so it reads as "trusted by" proof rather than decoration.

When to use
  • Establishing credibility right under the hero with recognizable client/partner logos
  • A "trusted by" or "as seen in" press strip on a landing or marketing page
  • A quiet logo wall on an about or portfolio page where motion would distract
Page types
  • landing
  • marketing
  • about
  • portfolio
How to use

Add 5+ logos (more reads better at marquee speeds) with accurate alt text per company and optional link-out URLs. Set a short heading like "Trusted by". Pick a variant: default/dark scroll automatically (tune speed, size, spacing); marquee-editorial drifts at half speed with wider spacing and muted opacity; static-grid sits still in a wrapping grid where logos with a URL link out. Use the dark/brand background to sit it against a colored band. Requires the motion capability for the scrolling variants. Typically follows the hero as the first proof point.

Pairs with
  • hero
  • featureHero
  • testimonial
  • cta
  • pricing-plans
Follows
  • hero
Precedes
  • featureHero
  • testimonial
Avoid when
  • You have only one or two logos — they read as thin; use a `testimonial` or inline mention instead
  • Quoted social proof with a name and face is the goal — use `testimonial`
  • A clickable partner directory or grid of cards is needed — this is a brand strip, not navigation
Register in

marketing-landing

pricing-plans

The plans-and-pricing section — a titled grid of subscription and/or project pricing cards (entered on the block, or drawn from a card collection the site names) with an optional toggle between the two. Four variants (default full-width, compact table-like, dark, editorial typographic) cover the spectrum from SaaS-grid to minimal price list.

When to use
  • A pricing page laying out subscription tiers and/or one-off project packages
  • A "plans" or "packages" section on a landing/marketing page where price is the decision
  • Offering both recurring and one-time options via the subscription/project toggle
Page types
  • pricing
  • landing
  • marketing
How to use

Add the subscription and/or project cards on the block (or, when the site configured a card collection, relate cards from it) and set a section title. Enable the toggle when you offer both recurring and one-time options, and choose which list shows first. Pick a variant: default for full-width cards, compact for a denser table-like list, dark to sit on a colored band, editorial for a hairline typographic price list with no card surfaces. Requires the motion capability. Usually the central decision block — follow it with a CTA and an FAQ that answers pricing objections.

Pairs with
  • hero
  • featureHero
  • faq
  • cta
  • testimonial
  • logo-slider
Follows
  • featureHero
  • testimonial
Precedes
  • faq
  • cta
Avoid when
  • Comparing features rather than prices — use `featureHero` (a comparison/grid layout)
  • A single offer with one action — a `cta` band is enough; reserve pricing for real choice
  • You have no product-card content authored yet — the cards drive this block
Register in

marketing-landing

testimonial

The social-proof block — pulls quoted testimonials from the testimonials collection, shown as a single featured quote or a paginated carousel. Four variants (default card, editorial pull-quote, dark with a star strip, carousel) place a customer voice wherever proof is needed.

When to use
  • Backing a claim with a named customer quote on a landing or marketing page
  • A rotating set of short quotes via the carousel mode/variant
  • An author sign-off or end-matter quote on an article (editorial-colophon layout)
  • Several short quotes side by side, optionally with one supporting figure (quotes-grid layout)
Page types
  • landing
  • marketing
  • about
  • editorial-article
How to use

Pick a mode: single relates one testimonial; carousel relates many (order sets the sequence, dots let viewers jump). Quote content lives in the testimonials collection — override only the section title here when needed. Choose a variant: default card, editorial large pull-quote (no card), dark with an accent star rating, or carousel. Set text color, padding, and an optional background band; show the publication date for recency. For article end-matter use the editorial-colophon layout. For several short quotes at once use the quotes-grid layout: every testimonial becomes a grid cell, and the optional stat tile (value, text, label) takes the cell its 1-based position names (default 2). Each testimonial can carry a complianceLabel (e.g. "Illustrative quote"), shown with it in every layout. Requires the motion capability. Strongest right after a feature or pricing block, before the closing CTA.

Pairs with
  • hero
  • featureHero
  • logo-slider
  • pricing-plans
  • cta
  • faq
Follows
  • featureHero
  • logo-slider
  • pricing-plans
Precedes
  • cta
Avoid when
  • You have only logos, not quotes — use `logo-slider` for a brand trust strip
  • No real attributable testimonial exists — fabricated proof undermines the page; omit the block
  • A primary headline statement is the goal — that belongs in the `hero`
Register in

marketing-landing

faq

The objection-handler — a headline, optional intro, and an accordion of question/answer pairs with a closing callout link. Four variants (default accordion, minimal hairline, two-column for dense sets, dark) answer the questions that block a decision.

When to use
  • Pre-empting buying objections near the bottom of a landing or pricing page
  • A dedicated FAQ / support page or a help section
  • Any "common questions" set where progressive disclosure keeps the page scannable
Page types
  • faq
  • landing
  • pricing
  • marketing
  • docs
How to use

Write a headline and a badge ("FAQ"), then add question/answer pairs (answers are rich text — keep them tight). Pick a variant: default accordion, minimal for hairline-separated editorial pages, two-column when the list is long, dark to match a colored band. Tick openFirst to show the first answer open on load. Set the layout width and add a callout link to route unanswered questions to contact or a CTA. Order questions by likely concern; place it after pricing, before the final CTA so it clears the last objection.

Pairs with
  • pricing-plans
  • featureHero
  • cta
  • hero
  • testimonial
Follows
  • pricing-plans
  • featureHero
Precedes
  • cta
Avoid when
  • Long-form documentation or guides — use the docs/editorial blocks, not a Q&A accordion
  • A single message or note — use `banner`
  • The questions are really a feature list — use `featureHero`
Register in

marketing-landing

banner

The announcement bar — a short title/description with optional links, styled as an info/warning/error/success alert or as a quiet editorial aside. Four variants (default alert, editorial hairline callout, dark announcement bar, sticky persistent bar) carry a one-line message without taking over the page.

When to use
  • A site-wide notice — promo, launch, sale, maintenance, or policy/cookie message
  • A persistent top/bottom bar that stays as the visitor scrolls (the sticky variant)
  • An inline editorial callout or marginalia aside within body content (editorial mode)
Page types
  • landing
  • marketing
  • editorial-article
  • blog
  • docs
How to use

Keep the message to a title plus one line of description and at most one or two links. Set the alert style (info/warning/error/success) or switch on editorial mode for hairline + muted treatment with a grid placement (inline / marginalia / wide). Choose a variant: default alert, editorial aside, dark bar, or sticky for persistent top placement. Use position (top/bottom) and defaultVisible for dismissible notices. It is a thin one-liner — not a conversion section.

Pairs with
  • hero
  • cta
  • pricing-plans
  • faq
Precedes
  • hero
Avoid when
  • The real goal is conversion with a headline and buttons — use `cta`
  • A multi-paragraph announcement or rich content — use a `hero` or content block
  • A pricing or feature decision — use `pricing-plans` / `featureHero`
Register in

marketing-landing

exhibit

The museum wall — an asymmetric 12-col grid of hairline-framed white canvases, each holding a REAL nested block render (not a screenshot) under a plate caption naming it and its source. One variant (gallery-wall) cycles four offset positions so the wall reads as curated, not tiled.

When to use
  • Showing off what a page can actually contain — real live block renders, wired to real data, framed as exhibits
  • A capability/gallery/showcase section that needs to prove range across several block families in one composition
  • Closing a platform or agency page with tangible proof instead of another round of prose
Page types
  • landing
  • marketing
  • portfolio
How to use

Add 1-6 exhibits. Each exhibit gets a bold plateTitle (required) and an optional plateNote for attribution (e.g. "Marketing pack · live testimonial"), then a blocks array holding the ONE (or few) real nested block(s) to frame — populate it from the site's curated nested-allowlist, not arbitrary blocks. The allowlist itself is a factory-config contract: pass `exhibitBlocks` (an array of Payload Block configs) when registering this block; omit it and the wall defaults to this pack's own `testimonial` block so the field is never empty. Position cycles exA/exB/exC/exD automatically by exhibit index — no manual placement control.

Pairs with
  • feature-hero
  • testimonial
  • cta
  • pricing-plans
Follows
  • feature-hero
  • pricing-plans
Precedes
  • cta
Avoid when
  • Only screenshots or static mockups are available — that is what this block explicitly replaces; use a plain image block instead
  • A single message needs full attention — a wall of several exhibits dilutes focus; use one block directly
  • The nested content is not real/live — placeholder-only exhibits undercut the "exhibits, not screenshots" promise
Register in

marketing-landing

Exports

  • @wabbit/tome-blocks-marketing-starter
  • @wabbit/tome-blocks-marketing-starter/render
  • @wabbit/tome-blocks-marketing-starter/render/register
  • @wabbit/tome-blocks-marketing-starter/demo
  • @wabbit/tome-blocks-marketing-starter/meta
  • @wabbit/tome-blocks-marketing-starter/blocks/banner
  • @wabbit/tome-blocks-marketing-starter/blocks/cta
  • @wabbit/tome-blocks-marketing-starter/blocks/exhibit
  • @wabbit/tome-blocks-marketing-starter/blocks/faq
  • @wabbit/tome-blocks-marketing-starter/blocks/feature-hero
  • @wabbit/tome-blocks-marketing-starter/blocks/high-impact-hero
  • @wabbit/tome-blocks-marketing-starter/blocks/logo-slider
  • @wabbit/tome-blocks-marketing-starter/blocks/pricing
  • @wabbit/tome-blocks-marketing-starter/blocks/testimonial

Changelog

v0.35.0minor

f4a08ca: **BREAKING:** these packs now need `@wabbit/tome-blocks-core` 0.35.0 or later, because they read the wrapper's new `data-block-placement` instead of its inline style text. **Migration:** upgrade `@wabbit/tome-blocks-core` to 0.35.0 or later alongside these packs. A site that renders its own block wrapper (with `data-tome-block-wrapper`) on a span other than `1 / -1` should add `data-block-placement={blockPlacementFor(span)}` to it; a wrapper without the attribute is read as full width. - `@wabbit/tome-blocks-local-pack`: in Safari, the price list's teaser list and the logistics block's content touched the viewport edges once the page had scrolled. Their side padding depended on matching the wrapper's `style` text, which WebKit rewrites as `grid-column-start`/`grid-column-end` longhands as soon as the scroll reveal writes to it. The padding rule now reads `data-block-placement`. Chromium and Firefox render as before. - `@wabbit/tome-blocks-house`: `LaneGrid` decides whether to subgrid its block wrapper (full row, content column, or its own tracks for any other span) from `data-block-placement`, so the decision no longer changes in Safari after the scroll reveal runs. - `@wabbit/tome-blocks-marketing-starter`: `exhibit`'s nested block wrappers carry `data-block-placement`, matching `RenderBlocks`.

  • f4a08ca: **BREAKING:** these packs now need `@wabbit/tome-blocks-core` 0.35.0 or later, because they read the wrapper's new `data-block-placement` instead of its inline style text. **Migration:** upgrade `@wabbit/tome-blocks-core` to 0.35.0 or later alongside these packs. A site that renders its own block wrapper (with `data-tome-block-wrapper`) on a span other than `1 / -1` should add `data-block-placement={blockPlacementFor(span)}` to it; a wrapper without the attribute is read as full width. - `@wabbit/tome-blocks-local-pack`: in Safari, the price list's teaser list and the logistics block's content touched the viewport edges once the page had scrolled. Their side padding depended on matching the wrapper's `style` text, which WebKit rewrites as `grid-column-start`/`grid-column-end` longhands as soon as the scroll reveal writes to it. The padding rule now reads `data-block-placement`. Chromium and Firefox render as before. - `@wabbit/tome-blocks-house`: `LaneGrid` decides whether to subgrid its block wrapper (full row, content column, or its own tracks for any other span) from `data-block-placement`, so the decision no longer changes in Safari after the scroll reveal runs. - `@wabbit/tome-blocks-marketing-starter`: `exhibit`'s nested block wrappers carry `data-block-placement`, matching `RenderBlocks`.
v0.34.1patch

e8213a7: Wide layouts start at 768px again, and the FAQ and the team-roster `people` variant keep their inner lines on the page grid. - **768px is the wide layout.** The narrow-screen viewport queries added in the previous release were inclusive (`max-width: 768px`), so at exactly 768px these blocks stacked although the platform's other queries and the designs start the wide layout there. They now end just below the breakpoint (`max-width: 767.98px`; 639.98px for `SectionHeader`): `high-impact-hero` (marketing-starter); `BlogHero`, `ChapterHero`, `CustomHero`, `ImageHero`, `LowImpactHero`, `MediumImpactHero`, `PageHero`, `StudyHero`, `TypographyHero` and `SectionHeader` (extras); `PostHero` (content-writer); `SplitView` (agency-essentials). Each pack's test fails a converted query that includes its breakpoint. - **Subgrid column gaps.** The `faq` root and the team-roster `people` section are subgrids of the page grid, but each also set a column gap (the FAQ through its `gap` shorthand, the people section through the shared roster `gap`). The page grid has no column gap, so every line inside them moved by half the gap: a theme's FAQ list on `reading-start` started 9px inside the reading column, and the people header and grid 9px inside the content lane. Both now set a row gap only (`column-gap: normal`, the parent's gap on a subgrid).

  • e8213a7: Wide layouts start at 768px again, and the FAQ and the team-roster `people` variant keep their inner lines on the page grid. - **768px is the wide layout.** The narrow-screen viewport queries added in the previous release were inclusive (`max-width: 768px`), so at exactly 768px these blocks stacked although the platform's other queries and the designs start the wide layout there. They now end just below the breakpoint (`max-width: 767.98px`; 639.98px for `SectionHeader`): `high-impact-hero` (marketing-starter); `BlogHero`, `ChapterHero`, `CustomHero`, `ImageHero`, `LowImpactHero`, `MediumImpactHero`, `PageHero`, `StudyHero`, `TypographyHero` and `SectionHeader` (extras); `PostHero` (content-writer); `SplitView` (agency-essentials). Each pack's test fails a converted query that includes its breakpoint. - **Subgrid column gaps.** The `faq` root and the team-roster `people` section are subgrids of the page grid, but each also set a column gap (the FAQ through its `gap` shorthand, the people section through the shared roster `gap`). The page grid has no column gap, so every line inside them moved by half the gap: a theme's FAQ list on `reading-start` started 9px inside the reading column, and the people header and grid 9px inside the content lane. Both now set a row gap only (`column-gap: normal`, the parent's gap on a subgrid).
v0.34.0minor

e8b4d32: testimonial `quotes-grid` and faq gain the options a theme needs. - **testimonial `quotes-grid`:** new optional `eyebrow`, a small section label above the section title (`<p data-block-eyebrow>`); the block had no label field, so a "Client voice" label had nowhere to go. The grid's column count is the token `--tome-testimonial-grid-cols` (defaults unchanged: 1, 2 from 640px, 3 from 1024px). The stat value supports the `*asterisk*` accent convention: "9 _in_ 10" renders "in" as `<em data-testimonial-stat-unit>`, upright and smaller (`--tome-testimonial-stat-unit-size`, default `0.5em`). A value with no stars renders as before. - **faq:** new `calloutStyle` (`box` by default, or `line`). `line` sets the closing "still have a question?" bridge as a plain start-aligned line with a text link and marks it `data-faq-callout-style="line"`. The bridge and its link carry `data-faq-callout` and `data-faq-callout-link`, and the header group and badge carry `data-block-header` and `data-block-eyebrow`, so a theme can restyle the badge chip.

  • e8b4d32: testimonial `quotes-grid` and faq gain the options a theme needs. - **testimonial `quotes-grid`:** new optional `eyebrow`, a small section label above the section title (`<p data-block-eyebrow>`); the block had no label field, so a "Client voice" label had nowhere to go. The grid's column count is the token `--tome-testimonial-grid-cols` (defaults unchanged: 1, 2 from 640px, 3 from 1024px). The stat value supports the `*asterisk*` accent convention: "9 _in_ 10" renders "in" as `<em data-testimonial-stat-unit>`, upright and smaller (`--tome-testimonial-stat-unit-size`, default `0.5em`). A value with no stars renders as before. - **faq:** new `calloutStyle` (`box` by default, or `line`). `line` sets the closing "still have a question?" bridge as a plain start-aligned line with a text link and marks it `data-faq-callout-style="line"`. The bridge and its link carry `data-faq-callout` and `data-faq-callout-link`, and the header group and badge carry `data-block-header` and `data-block-eyebrow`, so a theme can restyle the badge chip.
  • 3e4f0a7: Narrow-screen rules that never fired now fire. These blocks wrote their phone layout as an unnamed `@container` query, but nothing around them declares a size container (not tome-ui, the blocks core or the starter), so the rules never matched: the `high-impact-hero` `split` and default layouts never stacked on phones. Declaring the container on the block root would not work either, because size containment turns a `subgrid` root into a plain grid and drops the page-grid lines its children sit on. The rules are viewport queries now, at the same breakpoints; the blocks are full-bleed, so the viewport is their width. - `@wabbit/tome-blocks-marketing-starter`: `high-impact-hero` (two columns stack to one, large headline sizes step down, at 768px and below). - `@wabbit/tome-blocks-extras`: `BlogHero`, `ChapterHero`, `CustomHero`, `ImageHero`, `LowImpactHero`, `MediumImpactHero`, `PageHero`, `StudyHero`, `TypographyHero` (768px) and `SectionHeader` (640px). - `@wabbit/tome-blocks-content-writer`: `PostHero` (768px). - `@wabbit/tome-blocks-agency-essentials`: `SplitView` stacks at 768px. Above those widths nothing changes.
v0.33.0minor

a9424eb: New `venue` variant for `high-impact-hero` and new `reserve` variant for `contact`, for places people visit. - **`high-impact-hero` `venue`:** the venue name set very large, with a meta line, one line and one action. The background photo sits either behind the copy, over a theme-set scrim (`--tome-hero-scrim`), or full width above it. It is chosen by the new optional `venue.mediaPlacement` field (`behind`, the default when empty, or `above`). Hooks: `data-hero-media-placement`, `data-hero-scrim`, `data-hero-meta`, `data-hero-name` and `data-hero-line`, plus the existing `data-hero-media` and `data-hero-actions`. - **`contact` `reserve`:** a dark booking band that links out to the venue's booking provider. It has a GET form for party, date and time (labels `partyLabel`, `dateLabel` and `timeLabel`, default "Party", "Date" and "Time"), built from an editor-written `reserve.urlTemplate` with `{party}`, `{date}` and `{time}` tokens, and works without JavaScript when each token is a whole query value. Under the form come labelled sample-time chips (`sampleSlots[]` and `sampleSlotsLabel`, never live availability), then an "Or:" row (lead-in `altLabel`, default "Or:") with a `tel:` call action and `altActions[]` (`link`, `call`, or `order`, which opens the ordering drawer). Every label is optional and localized; empty, it renders the English default. With no provider set, the form hides and the band leads with its actions; contact rows render as a facts row. `eyebrow` and `callAction` are now also shown for `reserve`. - **New subpath `@wabbit/tome-blocks-agency-essentials/reserve`:** the band's pure URL helpers, plus `enhanceReserveBands()`, an optional, framework-free client enhancement. It fills today's date, keeps the chips in step with the form, and fills templates the GET form cannot carry whole. The renderer stays a server component. All new fields are optional, variant-gated and have no default value, so existing content renders byte-identical.

  • a9424eb: New `venue` variant for `high-impact-hero` and new `reserve` variant for `contact`, for places people visit. - **`high-impact-hero` `venue`:** the venue name set very large, with a meta line, one line and one action. The background photo sits either behind the copy, over a theme-set scrim (`--tome-hero-scrim`), or full width above it. It is chosen by the new optional `venue.mediaPlacement` field (`behind`, the default when empty, or `above`). Hooks: `data-hero-media-placement`, `data-hero-scrim`, `data-hero-meta`, `data-hero-name` and `data-hero-line`, plus the existing `data-hero-media` and `data-hero-actions`. - **`contact` `reserve`:** a dark booking band that links out to the venue's booking provider. It has a GET form for party, date and time (labels `partyLabel`, `dateLabel` and `timeLabel`, default "Party", "Date" and "Time"), built from an editor-written `reserve.urlTemplate` with `{party}`, `{date}` and `{time}` tokens, and works without JavaScript when each token is a whole query value. Under the form come labelled sample-time chips (`sampleSlots[]` and `sampleSlotsLabel`, never live availability), then an "Or:" row (lead-in `altLabel`, default "Or:") with a `tel:` call action and `altActions[]` (`link`, `call`, or `order`, which opens the ordering drawer). Every label is optional and localized; empty, it renders the English default. With no provider set, the form hides and the band leads with its actions; contact rows render as a facts row. `eyebrow` and `callAction` are now also shown for `reserve`. - **New subpath `@wabbit/tome-blocks-agency-essentials/reserve`:** the band's pure URL helpers, plus `enhanceReserveBands()`, an optional, framework-free client enhancement. It fills today's date, keeps the chips in step with the form, and fills templates the GET form cannot carry whole. The renderer stays a server component. All new fields are optional, variant-gated and have no default value, so existing content renders byte-identical.
v0.32.0minor

072f973: New optional fields and theme hooks: an inline call link on the split hero, image labels, a quotes-grid testimonial layout, an open-first FAQ, and a stat-strip note. **marketing-starter, `high-impact-hero`:** the `split` variant now shows `callAction` (`label`, `phone`) and renders it as an inline `tel:` link after the buttons, for example "or call (555) 010-2030" (`data-hero-call`). An empty label reads "or call" after buttons and "Call" without them. In every variant that shows supporting images, `supportingImages[]` gains `label`, a short tag set over the image such as "Illustrative photo" (`data-compliance-label`), and `captionMeta`, a smaller second caption line (`data-caption-meta`). Every variant that renders actions marks its action row `data-hero-actions` (quick-ask already did): the row holding the buttons and the inline call on `split`, otherwise the button row itself. The attribute is the only markup change. **marketing-starter, `testimonial`:** each entry gains an optional `complianceLabel`, shown with its quote in every layout (`data-compliance-label`). The `layout` select adds `quotes-grid`: every testimonial becomes a short quote in a responsive grid, with an optional `stat` tile (`value`, `text`, `label`, `position`) placed at its 1-based cell among the quotes, default 2. Hooks: `data-testimonial-layout="quotes-grid"`, `data-testimonial-cell` on every cell, `data-testimonial-stat` on the tile, `data-testimonial-stat-value` on its value. **marketing-starter, `faq`:** an `openFirst` checkbox (default off) renders the first question open and marks the root with `data-faq-open-first`. Every FAQ now carries part hooks: `data-faq-item` on each `<details>`, `data-faq-question` on its `<summary>`, and `data-faq-answer` on a new plain `<div>` around each answer (the answer's own element comes from your rich-text adapter, which accepts only a class name). **proposal-pack, `proposal-stat-strip`:** an optional `note` renders a source or compliance line under the figures (`data-stat-note`), with the same tokens as the stats. When the section header does not show, the root now carries `data-stat-strip-headless`; nothing renders in the header's place, so no gap is left above the figures. Every new field is optional, and the rendered markup is unchanged while they are empty; the additions to existing output are the FAQ part-hook attributes, the unstyled answer wrapper, the `data-hero-actions` attribute on hero action rows, and the `data-stat-strip-headless` attribute on stat strips that already had no header. The new fields add columns, so run your Payload migration and regenerate types. **extras:** the shared `HeroLinkList` accepts an optional `containerData` (extra `data-*` attributes for its row element). Omitted, the row renders exactly as before.

  • 072f973: New optional fields and theme hooks: an inline call link on the split hero, image labels, a quotes-grid testimonial layout, an open-first FAQ, and a stat-strip note. **marketing-starter, `high-impact-hero`:** the `split` variant now shows `callAction` (`label`, `phone`) and renders it as an inline `tel:` link after the buttons, for example "or call (555) 010-2030" (`data-hero-call`). An empty label reads "or call" after buttons and "Call" without them. In every variant that shows supporting images, `supportingImages[]` gains `label`, a short tag set over the image such as "Illustrative photo" (`data-compliance-label`), and `captionMeta`, a smaller second caption line (`data-caption-meta`). Every variant that renders actions marks its action row `data-hero-actions` (quick-ask already did): the row holding the buttons and the inline call on `split`, otherwise the button row itself. The attribute is the only markup change. **marketing-starter, `testimonial`:** each entry gains an optional `complianceLabel`, shown with its quote in every layout (`data-compliance-label`). The `layout` select adds `quotes-grid`: every testimonial becomes a short quote in a responsive grid, with an optional `stat` tile (`value`, `text`, `label`, `position`) placed at its 1-based cell among the quotes, default 2. Hooks: `data-testimonial-layout="quotes-grid"`, `data-testimonial-cell` on every cell, `data-testimonial-stat` on the tile, `data-testimonial-stat-value` on its value. **marketing-starter, `faq`:** an `openFirst` checkbox (default off) renders the first question open and marks the root with `data-faq-open-first`. Every FAQ now carries part hooks: `data-faq-item` on each `<details>`, `data-faq-question` on its `<summary>`, and `data-faq-answer` on a new plain `<div>` around each answer (the answer's own element comes from your rich-text adapter, which accepts only a class name). **proposal-pack, `proposal-stat-strip`:** an optional `note` renders a source or compliance line under the figures (`data-stat-note`), with the same tokens as the stats. When the section header does not show, the root now carries `data-stat-strip-headless`; nothing renders in the header's place, so no gap is left above the figures. Every new field is optional, and the rendered markup is unchanged while they are empty; the additions to existing output are the FAQ part-hook attributes, the unstyled answer wrapper, the `data-hero-actions` attribute on hero action rows, and the `data-stat-strip-headless` attribute on stat strips that already had no header. The new fields add columns, so run your Payload migration and regenerate types. **extras:** the shared `HeroLinkList` accepts an optional `containerData` (extra `data-*` attributes for its row element). Omitted, the row renders exactly as before.
v0.31.0minor

5f2662c: Groundwork v1.1 block fields for the quick-ask hero and the estimate contact band. **`high-impact-hero` `quick-ask`:** `proofLine` gains an optional `proofLead` text field, rendered as a `<strong data-hero-proof-lead>` before the proof text (for example "Next opening:"). Fixed: at phone widths the status dot could wrap onto its own line away from its text. The proof line is now inline flow, and the dot shares a no-wrap run (`data-hero-proof-start`) with its first phrase: the lead, or the first word of the text. The rest of the line wraps normally. With no lead and no dot, the markup is unchanged. **`contact` `estimate`:** two optional text fields. `eyebrow` is a small meta-voice kicker above the heading (`data-contact-eyebrow`). `formNote` is a short meta line beside the form card heading (`data-contact-form-note`). When it is set, the heading and note share a wrapping head row (`data-contact-card-head`). When both fields are empty, the output is unchanged.

  • 5f2662c: Groundwork v1.1 block fields for the quick-ask hero and the estimate contact band. **`high-impact-hero` `quick-ask`:** `proofLine` gains an optional `proofLead` text field, rendered as a `<strong data-hero-proof-lead>` before the proof text (for example "Next opening:"). Fixed: at phone widths the status dot could wrap onto its own line away from its text. The proof line is now inline flow, and the dot shares a no-wrap run (`data-hero-proof-start`) with its first phrase: the lead, or the first word of the text. The rest of the line wraps normally. With no lead and no dot, the markup is unchanged. **`contact` `estimate`:** two optional text fields. `eyebrow` is a small meta-voice kicker above the heading (`data-contact-eyebrow`). `formNote` is a short meta line beside the form card heading (`data-contact-form-note`). When it is set, the heading and note share a wrapping head row (`data-contact-card-head`). When both fields are empty, the output is unchanged.
  • b8ccf85: Payload-free `./meta` entries now match what each pack registers, and `@wabbit/tome-dispatch`, `@wabbit/tome-longform` and `@wabbit/tome-readout` add a `./demo` entry. `./demo` (dispatch, longform, readout): the root barrel's demo props (`getDemoProps`, the per-block getters, `DemoContext`) on their own subpath, with no Payload import, so a client bundle such as a block gallery can use them without pulling in the block configs. Additive; the root barrel still exports them. These packages ship raw Payload `Block` configs rather than `defineBlock` descriptors, so they have no `./meta` entry. `./meta` (extras, signal-theme): new `extrasDeprecatedBlockMeta` and `signalThemeDeprecatedBlockMeta` lists describe the 16 deprecated blocks each pack still registers for stored content but no longer offers. `extrasBlockMeta` and `signalThemeBlockMeta` are unchanged: they still list only the offered blocks. The deprecated signal-theme blocks' metadata moved from inline `defineBlock` arguments into payload-free `<block>.meta.ts` files; the registered descriptors are unchanged. `./meta` (extras, marketing-starter): six motion blocks (`image-marquee`, `marquee`, `showcase`, `logo-slider`, `pricing-plans`, `testimonial`) now declare `requiredCapabilities: ['motion']` in their metadata. `defineBlock` already added it at registration, so the registered descriptors are unchanged, but the `./meta` entries lacked it and a gallery reading them could not tell these blocks need a motion provider.
v0.30.0minor

fd848e0: **BREAKING:** the `high-impact-hero` `quick-ask` call action now shows on phones only, unless the new `callAction.showOnDesktop` checkbox is ticked. **Migration:** to keep the call action on screens 768px and wider, tick **Show on desktop** on the hero's Call Action (or set `callAction.showOnDesktop: true`). Phones are unchanged: the call still leads the actions there. The call link carries `data-hero-call-desktop="true|false"` so a theme can tell the two apart.

  • fd848e0: **BREAKING:** the `high-impact-hero` `quick-ask` call action now shows on phones only, unless the new `callAction.showOnDesktop` checkbox is ticked. **Migration:** to keep the call action on screens 768px and wider, tick **Show on desktop** on the hero's Call Action (or set `callAction.showOnDesktop: true`). Phones are unchanged: the call still leads the actions there. The call link carries `data-hero-call-desktop="true|false"` so a theme can tell the two apart.
v0.29.0minor

698c314: High Impact Hero gains a `quick-ask` variant: text left, media right, with a one-question form, a tap-to-call action and a proof line. Additive: the three new groups are optional, carry no defaults and appear in the admin only when `quick-ask` is selected, so existing heroes render as before. Adding the fields to a Payload config adds their columns, so run your usual migration. - **`quickAsk`** (`label`, `options[]` of `label` + `value`, `submitLabel`, `action`, `paramName`). Renders a plain GET form with a visible label and a real `<select>`, so it works without JavaScript. The selected value is sent as `?<paramName>=<value>` (default `need`) to the action, a URL or `#anchor` (default `#contact`); params already in the action are kept as hidden inputs. - **`callAction`** (`label`, `phone`). A `tel:` link. It is the first action on phones (below 768px) and follows the form on wider screens. - **`proofLine`** (`text`, `showDot`). One short line under the actions, with an optional status dot. - The job caption reuses `supportingImages[].caption`. - Theme hooks: `data-block-variant="quick-ask"`, `data-hero-actions`, `data-hero-call`, `data-hero-quick-ask`, `data-hero-proof`, `data-status-dot`, `data-hero-media`, `data-hero-caption`. Every hero variant root now also carries `data-block-variant`.

  • 698c314: High Impact Hero gains a `quick-ask` variant: text left, media right, with a one-question form, a tap-to-call action and a proof line. Additive: the three new groups are optional, carry no defaults and appear in the admin only when `quick-ask` is selected, so existing heroes render as before. Adding the fields to a Payload config adds their columns, so run your usual migration. - **`quickAsk`** (`label`, `options[]` of `label` + `value`, `submitLabel`, `action`, `paramName`). Renders a plain GET form with a visible label and a real `<select>`, so it works without JavaScript. The selected value is sent as `?<paramName>=<value>` (default `need`) to the action, a URL or `#anchor` (default `#contact`); params already in the action are kept as hidden inputs. - **`callAction`** (`label`, `phone`). A `tel:` link. It is the first action on phones (below 768px) and follows the form on wider screens. - **`proofLine`** (`text`, `showDot`). One short line under the actions, with an optional status dot. - The job caption reuses `supportingImages[].caption`. - Theme hooks: `data-block-variant="quick-ask"`, `data-hero-actions`, `data-hero-call`, `data-hero-quick-ask`, `data-hero-proof`, `data-status-dot`, `data-hero-media`, `data-hero-caption`. Every hero variant root now also carries `data-block-variant`.
v0.28.5patch

775f90a: Published packages now contain compiled JavaScript and type declarations under a one-line licence banner, and no longer include source maps. What you install: one compiled `.js` (ESM) and `.cjs` (CommonJS) file per source module, its `.d.ts` / `.d.cts` declarations, and the stylesheets, fonts and other assets a package already shipped. Every JavaScript module opens with a comment naming the package and its licence: `/*! @wabbit/<package> — © Wabbit, LLC. Wabbit Tome Commercial License (see LICENSE.md). Not for redistribution. */`. The `.map` files and the `sourceMappingURL` comments that pointed at them are gone, which roughly halves the size of each tarball. Debugging: the code is still unbundled and unminified, one readable file per module, so a stack trace points at real code with real names. Line numbers in a stack trace are one higher than before, because of the banner line. A `'use client'` directive stays the first statement of its module (the banner is a comment above it), so React Server Component boundaries are unchanged. No API change, no runtime behaviour change, and nothing to do on upgrade. In `@wabbit/tome-blocks-gallery`, the source snapshots `extractGallerySource` writes from an installed pack leave out the licence banner line, so a component or config snapshot starts at the code and a paid block's preview shows its first 15 lines of real code.

  • 775f90a: Published packages now contain compiled JavaScript and type declarations under a one-line licence banner, and no longer include source maps. What you install: one compiled `.js` (ESM) and `.cjs` (CommonJS) file per source module, its `.d.ts` / `.d.cts` declarations, and the stylesheets, fonts and other assets a package already shipped. Every JavaScript module opens with a comment naming the package and its licence: `/*! @wabbit/<package> — © Wabbit, LLC. Wabbit Tome Commercial License (see LICENSE.md). Not for redistribution. */`. The `.map` files and the `sourceMappingURL` comments that pointed at them are gone, which roughly halves the size of each tarball. Debugging: the code is still unbundled and unminified, one readable file per module, so a stack trace points at real code with real names. Line numbers in a stack trace are one higher than before, because of the banner line. A `'use client'` directive stays the first statement of its module (the banner is a comment above it), so React Server Component boundaries are unchanged. No API change, no runtime behaviour change, and nothing to do on upgrade. In `@wabbit/tome-blocks-gallery`, the source snapshots `extractGallerySource` writes from an installed pack leave out the licence banner line, so a component or config snapshot starts at the code and a paid block's preview shows its first 15 lines of real code.
v0.28.0patch

483e0a1: The marketing starter's preview data now uses invented brand and client names throughout. Hero, pricing, logo and testimonial previews swap two brand names for fictional ones. Block fields and variants are unchanged.

  • 483e0a1: The marketing starter's preview data now uses invented brand and client names throughout. Hero, pricing, logo and testimonial previews swap two brand names for fictional ones. Block fields and variants are unchanged.
  • 868c087: The logo slider and the high-impact hero's marquee strip can now be paused by hover, keyboard focus, touch tap and a pause button that appears on keyboard focus. On the logo slider, hovering or focusing a logo link or tapping the band pauses the scroll, and the pause button comes before the logo links in tab order. On the hero, the marquee strip gets the same pause button and pause behaviour. Under `prefers-reduced-motion: reduce` neither moves. Needs `@wabbit/tome-blocks-extras` with the `MotionPauseControl` export.
v0.27.0minor

c14a133: The pricing and testimonial blocks work without collections of their own: by default their cards and testimonials are entered on the block. **Pricing.** The block used to relate to a `product-cards` collection, which a site that only installed this pack doesn't have, so Payload refused to start once the block was registered. With no `productCardsCollection` option, the block now has two card lists (`subscriptionCards`, `projectCards`) with the fields the cards render: title, description, tier, badge, price, frequency, a custom-quote switch, features, and button text and link. Passing `productCardsCollection` keeps the relationship fields (`selectedSubscriptionCards`, `selectedProjectCards`) exactly as before. The renderer reads either. **Testimonial.** The block used to relate to a `testimonials` collection, with the same startup failure on a site without one. With no `testimonialsCollection` option it now has an `entries` list (quote, name, role, company, image, date): single mode shows the first, carousel shows them all. Passing `testimonialsCollection` keeps the `testimonial` / `testimonials` relationships exactly as before. **BREAKING:** a site that used either block with its old default must now pass the collection to keep its schema and stored data: `productCardsCollection: 'product-cards'` for pricing, `testimonialsCollection: 'testimonials'` for testimonial.

  • c14a133: The pricing and testimonial blocks work without collections of their own: by default their cards and testimonials are entered on the block. **Pricing.** The block used to relate to a `product-cards` collection, which a site that only installed this pack doesn't have, so Payload refused to start once the block was registered. With no `productCardsCollection` option, the block now has two card lists (`subscriptionCards`, `projectCards`) with the fields the cards render: title, description, tier, badge, price, frequency, a custom-quote switch, features, and button text and link. Passing `productCardsCollection` keeps the relationship fields (`selectedSubscriptionCards`, `selectedProjectCards`) exactly as before. The renderer reads either. **Testimonial.** The block used to relate to a `testimonials` collection, with the same startup failure on a site without one. With no `testimonialsCollection` option it now has an `entries` list (quote, name, role, company, image, date): single mode shows the first, carousel shows them all. Passing `testimonialsCollection` keeps the `testimonial` / `testimonials` relationships exactly as before. **BREAKING:** a site that used either block with its old default must now pass the collection to keep its schema and stored data: `productCardsCollection: 'product-cards'` for pricing, `testimonialsCollection: 'testimonials'` for testimonial.
  • c14a133: The pack works on a stock Next.js site: its per-block stylesheets now ship precompiled, so the site needs no next.config plugin. Each renderer's `.tome-css` stylesheet is compiled when the pack is built, into a JS module next to it (`<Name>.tome-css.js` / `.cjs`), and the renderers import that. A site no longer has to wrap next.config with `withTomeBlockStyles` to use the pack, and `./render` and `./render/register` now load under plain Node, so seed scripts, tests and the Payload CLI can import them. Each block's CSS is still inlined only on pages that render the block. A site that already uses `withTomeBlockStyles` needs no change. Scoped class names change once, because they are now keyed on the package rather than on where it is installed. The raw `.tome-css` files stay in the package as readable source.
v0.24.0minor

**Breaking: block stylesheets are now per-block (`.tome-css`).** Each block's CSS ships only on pages that render it, instead of in every page's CSS bundle. The 10 stylesheets moved from `X.module.css` to `X.tome-css`, and each renderer renders `<BlockStyles sheet={styles} />` from `@wabbit/tome-blocks-core/block-styles`. **Required in the consuming site:** wrap next.config with `withTomeBlockStyles` (`@wabbit/tome-blocks-core/next`, blocks-core 0.22.0 or later); without it the `.tome-css` imports fail to build. See the blocks-core README, "Per-block stylesheets". - The `@wabbit/tome-blocks-core` peer range is now `>=0.22.0 <1.0.0`. - FeatureHero, HighImpactHero, LogoSlider, PricingPlans and Testimonial (client components) are now server-safe wrappers (`X.tsx`) around `X.client.tsx`; PricingPlanCard imports PricingPlans' stylesheet for class names. Exported names and props are unchanged. - Block CSS now loads after all bundled CSS. A site-level rule that overrode one of this pack's classes at equal specificity, and won only by loading later, no longer wins.

  • **Breaking: block stylesheets are now per-block (`.tome-css`).** Each block's CSS ships only on pages that render it, instead of in every page's CSS bundle. The 10 stylesheets moved from `X.module.css` to `X.tome-css`, and each renderer renders `<BlockStyles sheet={styles} />` from `@wabbit/tome-blocks-core/block-styles`. **Required in the consuming site:** wrap next.config with `withTomeBlockStyles` (`@wabbit/tome-blocks-core/next`, blocks-core 0.22.0 or later); without it the `.tome-css` imports fail to build. See the blocks-core README, "Per-block stylesheets". - The `@wabbit/tome-blocks-core` peer range is now `>=0.22.0 <1.0.0`. - FeatureHero, HighImpactHero, LogoSlider, PricingPlans and Testimonial (client components) are now server-safe wrappers (`X.tsx`) around `X.client.tsx`; PricingPlanCard imports PricingPlans' stylesheet for class names. Exported names and props are unchanged. - Block CSS now loads after all bundled CSS. A site-level rule that overrode one of this pack's classes at equal specificity, and won only by loading later, no longer wins.
v0.20.1patch

cc6c65e: Fixes a badge/eyebrow overlap on narrow cards and two layout/contrast issues in the marketing-starter CTA and exhibit blocks. `@wabbit/tome-blocks-catalog-pack`: the `featured-product` block's badge no longer overlaps the eyebrow text on narrow cards — below roughly 30rem of available width it drops out of its absolute-positioned corner and flows in-line above the eyebrow as a chip instead. `@wabbit/tome-blocks-marketing-starter`: the `cta` block's `doors` variant fixes a low-contrast underline on its "toolkit"-voice link, which was drawing its border from a surface-tint accent color instead of the paired text-safe accent token. The `exhibit` block's `gallery-wall` variant increases the vertical gap between its staggered plates at desktop widths so they no longer visually touch, and now centers the third plate on the grid when a wall has exactly three exhibits instead of leaving it flush left.

  • cc6c65e: Fixes a badge/eyebrow overlap on narrow cards and two layout/contrast issues in the marketing-starter CTA and exhibit blocks. `@wabbit/tome-blocks-catalog-pack`: the `featured-product` block's badge no longer overlaps the eyebrow text on narrow cards — below roughly 30rem of available width it drops out of its absolute-positioned corner and flows in-line above the eyebrow as a chip instead. `@wabbit/tome-blocks-marketing-starter`: the `cta` block's `doors` variant fixes a low-contrast underline on its "toolkit"-voice link, which was drawing its border from a surface-tint accent color instead of the paired text-safe accent token. The `exhibit` block's `gallery-wall` variant increases the vertical gap between its staggered plates at desktop widths so they no longer visually touch, and now centers the third plate on the grid when a wall has exactly three exhibits instead of leaving it flush left.
v0.18.3patch

6530765: CSS files are now copied to `dist/` by a post-build script instead of a tsup `onSuccess` hook; no behaviour change, and the published `dist/` is identical.

  • 6530765: CSS files are now copied to `dist/` by a post-build script instead of a tsup `onSuccess` hook; no behaviour change, and the published `dist/` is identical.
  • 786853e: The pricing block's catalog slug is now `pricing-plans`, so gallery and inserter URLs keyed on `pricing` change to `pricing-plans`. The render registry turns kebab slugs into camelCase, so `pricing-plans` now resolves to the Payload block `pricingPlans` without an alias. Stored content is untouched: `blockType` stays `pricingPlans`, and the `./blocks/pricing` subpath export keeps its name. `getDemoProps` answers both `pricing-plans` and `pricing`. Update any hard-coded `/blocks/.../pricing` links, and every `pairsWith` / `sequence` reference inside the pack now names `pricing-plans`.
  • 786853e: `logo-slider`'s Static Grid variant now renders a still, wrapping grid of logos, and Marquee Editorial now scrolls slower, wider and muted. Before, both variants rendered the default marquee and only set `data-variant`. Static Grid mounts no marquee. Logo Size keeps its marquee heights, Logo Spacing becomes the gap, and a logo with a URL is a real link. Marquee Editorial runs at half the Scroll Speed with 1.5× the Logo Spacing, at 60% opacity. Documents already on these variants change appearance to match their labels. That is a fix, and no consumer action is needed, so this is a patch. The `cta`, `faq`, `high-impact-hero`, `exhibit` and `logo-slider` descriptions now say which variants are `data-variant` hooks for site or theme CSS, matching `banner` and `feature-hero`.
v0.18.1patch

c8827e7: Internal refactor: collection slugs are now typed through one package-internal helper instead of inline casts scattered across the source. No API or behaviour change.

  • c8827e7: Internal refactor: collection slugs are now typed through one package-internal helper instead of inline casts scattered across the source. No API or behaviour change.
  • c193d4a: `banner` and `feature-hero` descriptions now say what their variants actually do. The variants set `data-variant` for site or theme CSS; the inline `style` / `variant` selects still pick the alert colour and layout, and stay because stored documents carry them.
v0.18.0patch

c3468b0: `register()` is now built with blocks-core's `createPackRegistrar`. Behaviour and signature are unchanged. The `@wabbit/tome-blocks-core` peer floor goes up to `>=0.18.0` because that is the first version exporting the helper.

  • c3468b0: `register()` is now built with blocks-core's `createPackRegistrar`. Behaviour and signature are unchanged. The `@wabbit/tome-blocks-core` peer floor goes up to `>=0.18.0` because that is the first version exporting the helper.
v0.17.0minor

404d325: Tome block packs now install into an existing Payload project the way the README says: one `npm install`, one CSS import, no undocumented steps. Proven by the new fresh-install smoke test (`scripts/blocks-fresh-install-smoke.mjs`) against a brand-new `create-payload-app` website-template site. **Consumers: list `@wabbit/tome-blocks-core` and `@wabbit/tome-ui` in your own `package.json`** if you import from them (npm 7+ and pnpm install required peers automatically, so a fresh `npm install` of a pack already brings them in). - **One shared `blocks-core` per site.** Every pack, `blocks-house` and `blocks-extras` now declare `@wabbit/tome-blocks-core` (and, where used, `-house` / `-extras`) as a required peer with an explicit range instead of a regular dependency, so a site gets exactly one hoisted copy and one adapter registry. - **No more ERESOLVE in plain Payload sites.** `blocks-core` no longer declares `@wabbit/tome-core` or `@wabbit/tome-catalog` (their optional peer graph pulled `better-auth` → `@sveltejs/kit` → `vite@8` against a site's `vite@7`). The `block-bundle` product type still auto-registers when both are installed; new structural types `BlockBundleProductTypeDeps`, `BlockBundleProductTypeRegistryLike`, `RegisterProductTypeHooksLike`. - **Tokens in one line:** `@import '@wabbit/tome-blocks-core/styles.css';` (new export; imports `@wabbit/tome-ui/tokens`). `@wabbit/tome-ui` is now a required peer of `blocks-core`. - **Rich text and images render with no adapter setup.** Built-in defaults render Lexical through `@payloadcms/richtext-lexical/react` and resolve populated Payload uploads; an unpopulated upload id warns once in every environment (previously content vanished silently in production). Registered adapters still win. - **Payload's spread-props convention:** new `adaptRenderersForPayload(renderers)` / `adaptRendererForPayload(Component)` wrap any pack's `renderers` map for a site that renders `<Block {...block} />`. - **Slug collisions with Payload's templates** (`cta`, `banner`, `archive`, `content`, `code`): new `applyBlockSlugOverrides(blocks, overrides)` and `remapRendererSlugs(renderers, overrides)` (`@wabbit/tome-blocks-core/slugOverrides`). Defaults are unchanged; no stored data migrates. - **`blocks-house`** owns `gsap` and `hls.js` as dependencies (previously optional peers that still broke the build when missing), and registers GSAP's `ScrollTrigger` itself before first use. - **Full-bleed bands actually span the grid.** Eight `pinnedBand` blocks (cinema-pack AmbientBand, MediaPanel, PullInterlude, SceneCaption, ScenePlate, ScrubStory, StatementBand; blocks-house FullBleedInterstitial) now declare `grid-column: 1 / -1` at their root as the contract requires. **Visible change:** inside a tome-ui `.grid`, these render edge to edge where they were previously squeezed to content width. - **`@wabbit/tome-ui`:** `.grid` declares `reading-start` / `reading-end` below 768px (aliased to the content column), so blocks placed on the reading column no longer collapse to a sliver on phones. - Every pack README gains an "Install into an existing Payload project" section and a peer table that matches `package.json`; `blocks-core`'s README carries the full walkthrough.

  • 404d325: Tome block packs now install into an existing Payload project the way the README says: one `npm install`, one CSS import, no undocumented steps. Proven by the new fresh-install smoke test (`scripts/blocks-fresh-install-smoke.mjs`) against a brand-new `create-payload-app` website-template site. **Consumers: list `@wabbit/tome-blocks-core` and `@wabbit/tome-ui` in your own `package.json`** if you import from them (npm 7+ and pnpm install required peers automatically, so a fresh `npm install` of a pack already brings them in). - **One shared `blocks-core` per site.** Every pack, `blocks-house` and `blocks-extras` now declare `@wabbit/tome-blocks-core` (and, where used, `-house` / `-extras`) as a required peer with an explicit range instead of a regular dependency, so a site gets exactly one hoisted copy and one adapter registry. - **No more ERESOLVE in plain Payload sites.** `blocks-core` no longer declares `@wabbit/tome-core` or `@wabbit/tome-catalog` (their optional peer graph pulled `better-auth` → `@sveltejs/kit` → `vite@8` against a site's `vite@7`). The `block-bundle` product type still auto-registers when both are installed; new structural types `BlockBundleProductTypeDeps`, `BlockBundleProductTypeRegistryLike`, `RegisterProductTypeHooksLike`. - **Tokens in one line:** `@import '@wabbit/tome-blocks-core/styles.css';` (new export; imports `@wabbit/tome-ui/tokens`). `@wabbit/tome-ui` is now a required peer of `blocks-core`. - **Rich text and images render with no adapter setup.** Built-in defaults render Lexical through `@payloadcms/richtext-lexical/react` and resolve populated Payload uploads; an unpopulated upload id warns once in every environment (previously content vanished silently in production). Registered adapters still win. - **Payload's spread-props convention:** new `adaptRenderersForPayload(renderers)` / `adaptRendererForPayload(Component)` wrap any pack's `renderers` map for a site that renders `<Block {...block} />`. - **Slug collisions with Payload's templates** (`cta`, `banner`, `archive`, `content`, `code`): new `applyBlockSlugOverrides(blocks, overrides)` and `remapRendererSlugs(renderers, overrides)` (`@wabbit/tome-blocks-core/slugOverrides`). Defaults are unchanged; no stored data migrates. - **`blocks-house`** owns `gsap` and `hls.js` as dependencies (previously optional peers that still broke the build when missing), and registers GSAP's `ScrollTrigger` itself before first use. - **Full-bleed bands actually span the grid.** Eight `pinnedBand` blocks (cinema-pack AmbientBand, MediaPanel, PullInterlude, SceneCaption, ScenePlate, ScrubStory, StatementBand; blocks-house FullBleedInterstitial) now declare `grid-column: 1 / -1` at their root as the contract requires. **Visible change:** inside a tome-ui `.grid`, these render edge to edge where they were previously squeezed to content width. - **`@wabbit/tome-ui`:** `.grid` declares `reading-start` / `reading-end` below 768px (aliased to the content column), so blocks placed on the reading column no longer collapse to a sliver on phones. - Every pack README gains an "Install into an existing Payload project" section and a peer table that matches `package.json`; `blocks-core`'s README carries the full walkthrough.
v0.16.0minor

b01ca1f: Raise the `react` / `react-dom` peer floor to `>=19.0.0` across the linked blocks family. Every pack advertised `react: >=18.0.0` while `@wabbit/tome-core`, `chrome`, `forms`, `dispatch`, `longform` and `readout` all require `>=19` — so the React 18 support the packs claimed was **unreachable in any real Tome stack**: no consumer could satisfy both halves of the graph. The advertised range was not a supported configuration, it was a range nobody could install into. Ruled 2026-09-01: the floor becomes the truth. Their `devDependencies` said the same thing from the other direction: `react` and `@types/react` pinned to `^18.0.0` while the root `pnpm.overrides` has pinned `@types/react` to `19.2.14` for months, so every pack has in fact been developed and tested against React 19 types the whole time. Those pins move to `^19.0.0` — a manifest correction, not a version change; the resolved tree is byte-identical. One coordinated bump for the family (these eleven are `linked` in `.changeset/config.json`, so they version together by design). Consumer impact: a consumer genuinely on React 18 can no longer install these packs. That consumer could not have had a working Tome install anyway — the kernel would have refused the same graph. Anyone on React 19 sees no change.

  • b01ca1f: Raise the `react` / `react-dom` peer floor to `>=19.0.0` across the linked blocks family. Every pack advertised `react: >=18.0.0` while `@wabbit/tome-core`, `chrome`, `forms`, `dispatch`, `longform` and `readout` all require `>=19` — so the React 18 support the packs claimed was **unreachable in any real Tome stack**: no consumer could satisfy both halves of the graph. The advertised range was not a supported configuration, it was a range nobody could install into. Ruled 2026-09-01: the floor becomes the truth. Their `devDependencies` said the same thing from the other direction: `react` and `@types/react` pinned to `^18.0.0` while the root `pnpm.overrides` has pinned `@types/react` to `19.2.14` for months, so every pack has in fact been developed and tested against React 19 types the whole time. Those pins move to `^19.0.0` — a manifest correction, not a version change; the resolved tree is byte-identical. One coordinated bump for the family (these eleven are `linked` in `.changeset/config.json`, so they version together by design). Consumer impact: a consumer genuinely on React 18 can no longer install these packs. That consumer could not have had a working Tome install anyway — the kernel would have refused the same graph. Anyone on React 19 sees no change.
  • 0836ef5: dist now raw-Node loadable: relative specifiers get explicit extensions post-build. `build` gains `&& node ../../scripts/fix-dist-extensions.mjs --strict` as its last step, joining the 13 packages that already ran it. tsup builds `bundle: false` and emits relative specifiers exactly as the TypeScript source wrote them — extensionless — which bundlers resolve and raw Node does not (ESM `ERR_MODULE_NOT_FOUND`; CJS worse, `require('./x')` finds the ESM `.js` twin and Node 22+ `require(esm)` then dies on that file's own extensionless import). Every consumer outside a bundler hit this: the payload CLI under plain node, `generate:types`, `generate:importmap`, ops scripts, codegen tools. No source changes, no API changes, and bundler consumers are unaffected — extensioned relative specifiers are universally resolvable. Two supporting changes made the wiring possible, both in repo scripts rather than package source. `fix-dist-extensions.mjs` now skips bundler-asset specifiers (`.css`, `.module.css`, `.scss`, fonts, images, shaders) by explicit extension allowlist instead of reporting them as unresolvable — that single gap is why the 13 prior adopters were exactly the 13 packages that ship no CSS, since `--strict` exited 1 on any package with a relative stylesheet import. Dotted MODULE names (`./config.meta`, `./x.variants`, `./y.demo`) are deliberately NOT treated as assets and still get `.js`/`.cjs` appended. `assert-node-loadable.mjs` gained the matching carve-outs so the new repo-wide CI gate reports real defects only: a resolution failure whose path lands under `node_modules` is a peer SKIP (next@15 has no exports map, so `next/image` fails as an absolute path), and a bundler-asset load failure is an environmental SKIP (CJS surfaces it as `SyntaxError: Unexpected token '.'` raised from inside the stylesheet). Verified before/after on four packages built one at a time: print 8 FAIL → 0, readout 22 FAIL → 0, ai 3 FAIL → 0, gamification 2 FAIL → 0 (its failure was the other signature — a `directory import` missing `/index`). cop was already clean on a fresh build, so the audit's "27 of 46 fail" figure includes at least one package whose local dist was merely stale.
  • 73081e6: Manifest metadata: `homepage`, `bugs`, `engines`. All 46 publishable manifests were missing the three fields a consumer sees before any code (2026-09-01 sale-readiness audit §6). Metadata only — no source, no build, no runtime change. - `homepage` deep-links to that package README on GitHub (`.../tree/main/packages/<dir>#readme`). Without it a registry page links to the monorepo root and the reader has to guess which of 46 folders they want. - `bugs.url` points at the repo issue tracker, so a paying customer has a place to report a defect that is not email. - `engines.node` is `>=22`, matching the root `engines` and `.nvmrc` set the same day. This is a real floor, not decoration: CI on Node 20 could not expand the glob the block packs use for `node --test`, and a package installed on Node 20 fails at a runtime the installer cannot connect back to the version. The forcing function ships with the change: `scripts/assert-manifest-metadata.mjs` (root `pnpm assert:manifest-metadata`, wired into `platform-discipline.yml` beside `assert:license-metadata`) fails when any publishable manifest lacks `description`, `repository.directory` matching its own folder, `homepage`, `bugs`, `engines.node` equal to the repo floor, `license`, `files` or `sideEffects`. It reported 138 violations before this change and 0 after.
  • 04309f5: Fix a hydration mismatch in `Testimonial` and adopt the shared UTC-pinned date formatter across five renderers. `@wabbit/tome-blocks-marketing-starter`'s `Testimonial` is a `'use client'` component and rendered its publication date with a bare `new Date(iso).toLocaleDateString()`. That resolves against the RUNTIME locale and the RUNTIME time zone, so the server produced one string and the browser produced another and React reported a hydration mismatch on the block. The 2026-07-11 audit flagged it; it was still there on 2026-09-01. The same bare call sat in four server renderers — `blocks-editorial-pack`'s `MetricStrip` and `StatusBoard`, `blocks-agency-essentials`' `TeamRoster` (twice) and `Timeline`. No hydration mismatch there, but the rendered output changed with the deploy host's locale and offset, which is its own kind of wrong. All six call sites now use `formatDisplayDate` from `@wabbit/tome-blocks-core/utilities/formatDisplayDate` with an explicit `locale: 'en-US'`, a numeric `M/D/YYYY` `dateStyle` and `timeZone: 'UTC'`. The numeric shape is deliberate: it is byte-identical to what a US-locale runtime already produced, so this fixes the determinism without silently restyling anyone's dates. Every pack already declared `@wabbit/tome-blocks-core`, so no dependency changes. Shipped with its forcing function: a `no-restricted-syntax` rule in `eslint.config.mjs` warns on bare `toLocaleDateString`/`toLocaleString`/`toLocaleTimeString` in every `packages/*/src/**/*.tsx` and every block pack's `render/` directory, and points at `formatDisplayDate` and at `blocks-gallery`'s `ADDED_AT_FORMATTER` as the two accepted shapes.
  • 73081e6: README peer tables, and the gate that now requires them. Sixteen packages declared `peerDependencies` and documented them nowhere a reader could scan — in prose inside an install paragraph, in a transposed "compatibility matrix" with the peers as columns, or not at all. Docs only; no source, no manifest, no runtime change (the one manifest change in this PR, admin's `sonner` peer, has its own changeset). Each of the sixteen gains a `## Peer dependencies` section generated from its own `package.json` — `| Peer | Range | Required |`, one row per peer, the range verbatim, `no (optional)` read from `peerDependenciesMeta`, plus one sentence on what is a real `dependency` rather than a peer and why the optional ones are optional. The worst omissions this surfaced: `@wabbit/tome-core` documented 2 of its 13 peers and left out both `next` and `@payloadcms/richtext-lexical`, which are required; `@wabbit/tome-admin` listed 5 of 20; `@wabbit/tome-readout` and `@wabbit/tome-sc` listed none. Eight block packs carried a hand-typed compatibility table that had drifted a full React major — still `>=18` after the peer floor moved to `>=19.0.0` — and none of the eight listed `react-dom` at all. Those tables are retired in favour of the generated one, with a line saying what they used to claim so the next reader does not reinstate them. The forcing function ships with the fix: `scripts/assert-readme-contract.mjs` now FAILS a package that declares peers without a peer table (a markdown table whose header row names a Peer and a Range column — the existing `Optional?` and `Notes` third columns still pass, so the thirty already-conforming READMEs were not touched). It is deliberately shape-only, not row-level: asserting that each row agrees with the manifest is the Tier 2 generation work. Verified non-vacuous by breaking one table's header and watching the gate fail, then restoring it. `CONTRIBUTING.md`'s assert-script list — which said "five" while sixteen existed — and the three guides that describe this gate were corrected in the same pass.
  • Updated dependencies [57875ba]
  • Updated dependencies [b01ca1f]
  • Updated dependencies [0836ef5]
  • Updated dependencies [73081e6]
  • Updated dependencies [090e984]
  • Updated dependencies [73081e6] - @wabbit/tome-blocks-core@0.16.0 - @wabbit/tome-blocks-extras@0.16.0
v0.15.12patch

54ff357: Republish with correctly-built artifacts, and close the hole that made it necessary. The 0.15.11 line shipped `defaultBreakout` in its version notes but **not in its tarballs**: source on main carried all 151 descriptors, the published `dist/` carried zero. `pnpm publish` does not build — it packs whatever is already in `dist/` — so a release ships whatever a previous, possibly unrelated, build left behind. Here the dist had been built from a branch that predated the metadata, and nothing in the pipeline compares artifact to source. Caught by grepping the _installed_ package in a consumer rather than trusting the version number. **Systemic fix:** `prepublishOnly: pnpm run build` added to all 41 publishable packages that lacked it (only `blocks-org-pack` had one — which is why it was the single package whose build ran during the previous publish). Every publish now rebuilds from source first, so a stale-dist release becomes impossible rather than merely unlikely. Same family as the two publish hazards already documented in this repo (`workspace:*` literals reaching the registry, and exact-pin dependencies forcing nested duplicate copies): the publish path had no guard that what ships matches what is committed.

  • 54ff357: Republish with correctly-built artifacts, and close the hole that made it necessary. The 0.15.11 line shipped `defaultBreakout` in its version notes but **not in its tarballs**: source on main carried all 151 descriptors, the published `dist/` carried zero. `pnpm publish` does not build — it packs whatever is already in `dist/` — so a release ships whatever a previous, possibly unrelated, build left behind. Here the dist had been built from a branch that predated the metadata, and nothing in the pipeline compares artifact to source. Caught by grepping the _installed_ package in a consumer rather than trusting the version number. **Systemic fix:** `prepublishOnly: pnpm run build` added to all 41 publishable packages that lacked it (only `blocks-org-pack` had one — which is why it was the single package whose build ran during the previous publish). Every publish now rebuilds from source first, so a stale-dist release becomes impossible rather than merely unlikely. Same family as the two publish hazards already documented in this repo (`workspace:*` literals reaching the registry, and exact-pin dependencies forcing nested duplicate copies): the publish path had no guard that what ships matches what is committed.
  • Updated dependencies [54ff357] - @wabbit/tome-blocks-extras@0.15.12
v0.15.11patch

1bebcdc: Populate `BlockMeta.defaultBreakout` across all nine block packs — 151 descriptors now declare their natural width on the page grid. The 2026-06-28 layout-grid + nesting contract (Decision 3 / Amendment A1) defined `defaultBreakout` as the per-block breakout POLICY co-located with the block, replacing a central hand-maintained table. No pack had ever filled it in, so every consumer fell through to `'article'` (the reading column) and a full-bleed hero previewed at prose width. Values are drawn from the canonical `@wabbit/tome-ui` `BreakoutWidthValue` vocabulary and assigned from each block's render CSS, not its name: - Root at a named grid line (`content-start / content-end`, `full-start / full-end`, `marginalia-right-*`, …) — `defaultBreakout` mirrors that exact line. - Root `1 / -1` + subgrid with an inner wrapper at `content-start / content-end` — a self-banding block: `'full-bleed'`. - Root and inner both `1 / -1` (width-agnostic) — assigned editorially: bands/heroes `'full-bleed'`, page sections `'content'`, cards and single-column components `'breakout-md'`, prose/inline components `'article'`. - Where a block already ships its own `breakoutWidthField({ defaultValue })`, `defaultBreakout` matches that value exactly rather than contradicting it. Distribution: `content` 52, `full-bleed` 41, `article` 33, `breakout-md` 23, `breakout-lg` 1, `marginalia-right` 1. Also declares `pinnedBand: true` on the three blocks that are unambiguously full-bleed bands whose own `breakoutWidth` field drives INNER content (`compareColumns`, `editorialSection`, `editorialSpread`), and `nestable: false` on 22 full-bleed heroes / band primitives / containers that carried no `nestable` declaration. Purely additive optional metadata: no descriptor field, block structure, or CSS changed, and no existing `nestable: true` was flipped, so the derived `layoutGrid` child allowlist is byte-identical (98 nestable blocks before and after).

  • 1bebcdc: Populate `BlockMeta.defaultBreakout` across all nine block packs — 151 descriptors now declare their natural width on the page grid. The 2026-06-28 layout-grid + nesting contract (Decision 3 / Amendment A1) defined `defaultBreakout` as the per-block breakout POLICY co-located with the block, replacing a central hand-maintained table. No pack had ever filled it in, so every consumer fell through to `'article'` (the reading column) and a full-bleed hero previewed at prose width. Values are drawn from the canonical `@wabbit/tome-ui` `BreakoutWidthValue` vocabulary and assigned from each block's render CSS, not its name: - Root at a named grid line (`content-start / content-end`, `full-start / full-end`, `marginalia-right-*`, …) — `defaultBreakout` mirrors that exact line. - Root `1 / -1` + subgrid with an inner wrapper at `content-start / content-end` — a self-banding block: `'full-bleed'`. - Root and inner both `1 / -1` (width-agnostic) — assigned editorially: bands/heroes `'full-bleed'`, page sections `'content'`, cards and single-column components `'breakout-md'`, prose/inline components `'article'`. - Where a block already ships its own `breakoutWidthField({ defaultValue })`, `defaultBreakout` matches that value exactly rather than contradicting it. Distribution: `content` 52, `full-bleed` 41, `article` 33, `breakout-md` 23, `breakout-lg` 1, `marginalia-right` 1. Also declares `pinnedBand: true` on the three blocks that are unambiguously full-bleed bands whose own `breakoutWidth` field drives INNER content (`compareColumns`, `editorialSection`, `editorialSpread`), and `nestable: false` on 22 full-bleed heroes / band primitives / containers that carried no `nestable` declaration. Purely additive optional metadata: no descriptor field, block structure, or CSS changed, and no existing `nestable: true` was flipped, so the derived `layoutGrid` child allowlist is byte-identical (98 nestable blocks before and after).
  • 48773ac: Fix a systemic, invisible-text readability defect across the block packs: a text colour and the surface under it were coming from sources a consumer can set independently, so the pair could split. Measured live on starter.wabbit.com with a canvas-based contrast parser walking every rendered text node: the worst pairs sat at **1.00–1.03:1** — near-black text on a near-black surface, copy that renders but cannot be read. Nothing in CI could see it, because every unit test and every route smoke passes with perfectly invisible text. This is the second time this bug family has shipped. The first sweep added `--tome-color-on-solid-dark` (blocks-lms-pack 0.12.1) and fixed chrome, lms-pack and catalog-pack; the themed packs were missed. This closes the rest and adds the tokens whose absence is why the misuse kept spreading. ## Three mechanisms, one root cause **1. Split pairs.** Blocks paired `--tome-color-card` — not a house token at all; the house name is `--tome-color-surface` — carrying a DARK literal fallback, against `--tome-color-foreground`, which every themed consumer does define, carrying a LIGHT one. A fallback pair is only safe when both sides fall back together. The same shape appeared as cross-family pairing (`surface`, the CARD family, paired with `foreground`, the PAGE family) and as clobbering: a band setting `color: background` on itself while its children hardcoded their own `color: foreground`, which wins. longform had a third variant — it read `--tome-color-muted-foreground` 32 times and `--tome-color-muted` 3 times, and **neither has ever been a house token**, so the entire muted tier silently fell through to `currentColor` and inherited whatever ink an ancestor happened to have. **2. Alpha-dimmed text.** `opacity: 0.4–0.9` on a label, and `color-mix(<colour> 30–70%, transparent)` as a `color:`, composite against whatever happens to be behind them, so the ratio is unknowable at author time. Worst measured: 1.46:1. Several were an ancestor `opacity` aimed at a rule that dimmed the real text in the same container along with it. **3. Status and brand colours used as text.** `warning`/`success`/`error`/ `destructive` are FILL colours, tuned to be painted as a badge with an ink on top. Used as `color:` the default amber measures 2.13:1 and the green 2.82:1. Brand hues have the mirror problem: `--tome-color-primary` as text is fine on the page (near-black by default, 5.5:1 even under the starter's oxide theme) but becomes 2.22:1 inside a band whose fill the consumer chooses. ## New in `@wabbit/tome-ui` - **`--tome-color-{success,warning,error,destructive,info}-text`** — the missing text-weight companions. Literals with inverted `[data-theme="dark"]` values, each pinned to clear 4.5:1 against both `--tome-color-background` and `--tome-color-surface`. `info` never had a fill token either, which is why packs reached for `primary`. Rule of thumb: `warning` paints a box, `warning-text` writes a word. - **`--muted-foreground` retuned** (`hsl(215 16% 47%)` → `hsl(215 20% 38%)` light, `65%` → `72%` dark). That token is `--tome-color-on-surface-muted`, the tier every pack uses for captions, labels, metadata and table headers, and at the old value it reached only 4.27:1 on `--card`. The entire secondary text tier platform-wide sat just under AA — which is also why packs kept reaching past it for something with more presence. Standalone default only. Because these are literals rather than Layer 1 aliases, `@wabbit/tome-cop` restates them: a pack that is dark without being `[data-theme="dark"]` would otherwise inherit the light values. ## Fix shape, per pack **dispatch, readout, blocks-signal-theme** are permanently dark by product identity. Each owns a pack-scoped surface/ink SET (`--dispatch-*`, `--readout-*`, `--signal-*`) with internally consistent dark defaults compiled into every block module as inline fallbacks. No rule in these packs reads a house surface or text token for a panel, so no consumer theming can split the pair. The house `surface-solid-dark`/`on-solid-dark` pair was rejected here for a stated reason: it is one flat pure-black surface with a single ink, and these packs need a layered palette. tome-cop drives all three sets so its theming still applies. signal-theme's accents split into three roles — identity fill, lightened on-panel text, and per-hue ink for accent fills — because one value cannot serve both a dark panel and a light article. **longform, content-writer, editorial-pack, marketing-starter, agency-essentials, extras** follow the ambient theme and are fixed with the house vocabulary: correct pairs (`surface`/`on-surface`, `background`/ `foreground`, `primary`/`on-primary`), the new `-text` weights for status copy, and solid ink steps in place of alpha. Painted bands publish their own ink as a local `--_on-band`, and brand/status text reads `var(--_on-band, <its normal one declaration per band with no combinatorial selectors. longform additionally derives `--_accent-ink` by mixing the injected tome-cop accent half-and-half with `--tome-color-foreground`, which keeps the hue while binding legibility to a pair the house guarantees, and inverts by itself in dark mode. Blocks that deliberately paint NOTHING and sit in the prose flow keep the house PAGE pair. Migrating those to pack ink would be the same bug pointing the other way — a near-white ink on a light article. ## A fourth mechanism, found on the second pass: cross-namespace `:root` emission `@wabbit/tome-cop` drives the three packs' surface/ink sets, and it declared those aliases inside its `:root, [data-tome-pack="cop"]` rule. `:root` there is load-bearing for the `--cop-*` namespace and justified in that file on collision-safety grounds — no other package can declare a `--cop-*` property. `--dispatch-*`, `--readout-*` and `--signal-*` are other packages' namespaces, so the argument does not carry, and the consequence was that **importing** tome-cop's stylesheet — without ever setting `[data-tome-pack="cop"]`, which is the documented opt-in — re-themed three packs the site never opted into. Both declarations sit at `:root`, cop loads last, cop wins. Measured on the starter block gallery, a light bone/ink theme: `--dispatch-surface`, `--readout-surface` and `--signal-panel` all computed to `hsl(0 0% 100%)`, identical to the consumer's `--card`, while the packs' on-dark accents kept painting on top — 1.5–1.9:1 across dispatch, readout and signal-theme. The accents were correct as authored; the panel beneath them had been replaced. Every cross-namespace alias in that file — §5.2.3–§5.2.6's `--readout-*` / `--dispatch-*` status aliases, all of §5.2.8, and §5.2.8b's surface/ink sets — now lives in a `[data-tome-pack="cop"]`-only rule. Outside a cop-themed subtree each pack falls back to its own literals, which are contrast-checked against its own surface. This also removes a second failure the first one was masking: cop's zinc `oklch(45% 0.01 0)` for `--readout-objective-pending` and `--readout-personnel-inactive` reads 2.6:1 against readout's own dark panel, where the pack's own `hsl(0 0% 54%)` reads 5.4:1. Scoping rule going forward: a theme pack may emit its OWN namespace at `:root`; anything that re-themes a namespace it does not own goes behind the pack attribute. ## A fifth mechanism, found on the third pass: ink flipped, surface never painted Four hero-shaped blocks flip to light ink the moment a background image is declared — the copy is meant to sit on a photo under a dark scrim — but none of them painted a surface an ancestor of that copy could pair against. extras' **StudyHero** and **CustomHero** (its `cop`/`sitrep` families) painted no surface at all; marketing-starter's **HighImpactHero** painted its plate on the absolutely positioned background LAYER, a sibling of the content rather than an ancestor of it. So the real backdrop under the glyph was the page: measured 1.00–1.06:1, and the same failure reaches any consumer whose asset is absent, transparent, letterboxed, or simply slow to load. A hero added without an image rendered invisible copy. Each now paints the plate on the section itself, defaulting to the theme-relative partner of the ink it already chose — the shape BlogHero, ChapterHero and TypographyHero were already using. It is painted unconditionally rather than behind a `has-image` flag (org-pack's CampaignBanner `data-has-banner` shape) because both states want the same colour: with a photo it is the plate underneath; without one it is the dark band the ink was designed for, so the degraded state is a legible dark hero instead of a blank one. Each band publishes its ink as `--_on-band`, which matters most in HighImpactHero, where the muted tier is a DARK ink chosen for the page and would otherwise be dark-on-dark inside the new plate. marketing-starter's **Faq** had the mirror of this: `.bg-dark` set `color` on the section, but `.headline` / `.intro` / `.question` / `.answer` and the `+`/`−` marker each re-declared their own, and a child declaration beats an inherited one. `.question` was an exact foreground-on-foreground render at 1.00:1. signal-theme's **SignalDataTable** caption is the one piece of text in that block that is NOT inside the painted panel, and it kept `--signal-ink-muted`, a light grey tuned for `--signal-panel` — 2.17:1 on a light article. It now uses the house muted tier, the same rule SignalImageGrid's captions and SignalFootnotes already follow: panel-painted text uses `--signal-*`, prose-flow text uses the house vocabulary that tracks the ambient theme. ## Also fixed: a third icon-name-as-text renderer agency-essentials' **Timeline** rendered `section.icon` as children, painting the authored names (`rocket`, `briefcase`, `globe`, `zap`) as literal text — bone on bone, 1.00:1, on its dark variant — even though the block's own authoring guidance says "use icon names your renderer maps to an icon component". Same house pattern as catalog-pack's CategoryStrip and extras' own icon-bearing blocks: mapped names render an icon at `size="1em"` so the slot's font-size owns sizing, unmapped name-shaped strings render nothing, and an authored emoji still renders as text. Rather than add a third copy of the name→component map, `resolveLucideIcon` is now exported from `@wabbit/tome-blocks-extras/render/shared` — the barrel that already exists for helpers a consuming pack needs, and the package that already owns the `lucide-react` peer. Timeline's marker chip also hardcoded the page background as its fill while its glyph inherits the band ink, so on the dark variant it was a light chip carrying light ink. ## Also fixed, and not a contrast issue dispatch's CommsTranscript rendered redacted lines as the real message text with `color: transparent` under a painted bar. Invisible to sighted readers, still announced by screen readers and still present in the copied DOM — the redacted content leaked to exactly the readers a redaction exists for. The renderers now emit no message text at all for a redacted line. Every reference to a newly added token carries a literal fallback. An undefined custom property makes the declaration invalid and the element inherits its ancestor's colour, which is the 1.0:1 failure mode itself.
  • Updated dependencies [1bebcdc]
  • Updated dependencies [48773ac] - @wabbit/tome-blocks-extras@0.15.11
v0.15.6patch

ddb50a0: Design-wave fixes (2026-08-09 mockup, section 2): exhibit gallery-wall plates now size to their nested content — `align-items: start` on the wall grid and no `min-height`/flex-fill on the canvas, with generous space-3xl/space-2xl padding — instead of stretching to the row's tallest neighbor and centering a small render in ~50% empty frame. The high-impact-hero `dark` variant demo now supplies `backgroundColorBehindImage: var(--tome-color-surface-solid-dark)` so the gallery preview shows its light text on a dark plate rather than near-white-on-white when the placeholder media is light or unresolved.

  • ddb50a0: Design-wave fixes (2026-08-09 mockup, section 2): exhibit gallery-wall plates now size to their nested content — `align-items: start` on the wall grid and no `min-height`/flex-fill on the canvas, with generous space-3xl/space-2xl padding — instead of stretching to the row's tallest neighbor and centering a small render in ~50% empty frame. The high-impact-hero `dark` variant demo now supplies `backgroundColorBehindImage: var(--tome-color-surface-solid-dark)` so the gallery preview shows its light text on a dark plate rather than near-white-on-white when the placeholder media is light or unresolved.
v0.15.0minor

510036f: Route pack block links through the `LinkAdapter` instead of raw `<a href>`. `LinkAdapter` (0.14.0) shipped the seam; this connects it. **67 anchors across 55 renderer files in 10 packs** now render through `<TomeLink>`, so a consuming site that registers a link adapter gets its route transition on pack blocks — previously impossible by construction, since a pack cannot import the consumer's transition component and the consumer cannot reach into a pack's render tree. **New in `@wabbit/tome-blocks-core`: `<TomeLink>`**, a component form of `resolveLink()`. `resolveRichText()` / `resolveMedia()` are functions because they turn a data value into content; a link _wraps children_, and the function form forces multi-line JSX through a `children:` prop. `<TomeLink href={…}>…</TomeLink>` is a drop-in for the `<a>` it replaces. It delegates to `resolveLink()`, so there is exactly one resolution path, and it stays directive-free so RSC pack renderers can use it without becoming client components. **`LinkProps.href` is now `string | null | undefined`.** Block data routinely carries an optional URL, and the raw `<a href={undefined}>` these calls replaced was legal markup. Narrowing it to `string` would have forced ~10 non-null assertions across the packs and changed behavior at each. `NOOP_LINK_ADAPTER` normalises null to `undefined` so React omits the attribute — the unregistered path stays byte-identical to the pre-adapter markup. **Behaviour is unchanged for every consumer that has not registered a link adapter**, which is currently all of them: `resolveLink` falls back to a plain `<a>`. Deliberately left as raw `<a>`: - `EditorialFootnotes` — its `#fnref-*` anchors are in-page backlinks. Client-routing them would play a page transition for a jump within the same document. - `PricingPlans` / `PricingPlanCard` — these already accept an injectable anchor component, a more expressive consumer mechanism that predates the adapter. - `LogoSlider` — a self-closing, childless `target="_blank"` overlay anchor. Always external, so the adapter would hand it straight back to the browser. - `@wabbit/tome-longform` — it has zero runtime dependencies and does not peer on `blocks-core`. Adding that edge to the layer graph is its own decision, not a sweep side effect.

  • 510036f: Route pack block links through the `LinkAdapter` instead of raw `<a href>`. `LinkAdapter` (0.14.0) shipped the seam; this connects it. **67 anchors across 55 renderer files in 10 packs** now render through `<TomeLink>`, so a consuming site that registers a link adapter gets its route transition on pack blocks — previously impossible by construction, since a pack cannot import the consumer's transition component and the consumer cannot reach into a pack's render tree. **New in `@wabbit/tome-blocks-core`: `<TomeLink>`**, a component form of `resolveLink()`. `resolveRichText()` / `resolveMedia()` are functions because they turn a data value into content; a link _wraps children_, and the function form forces multi-line JSX through a `children:` prop. `<TomeLink href={…}>…</TomeLink>` is a drop-in for the `<a>` it replaces. It delegates to `resolveLink()`, so there is exactly one resolution path, and it stays directive-free so RSC pack renderers can use it without becoming client components. **`LinkProps.href` is now `string | null | undefined`.** Block data routinely carries an optional URL, and the raw `<a href={undefined}>` these calls replaced was legal markup. Narrowing it to `string` would have forced ~10 non-null assertions across the packs and changed behavior at each. `NOOP_LINK_ADAPTER` normalises null to `undefined` so React omits the attribute — the unregistered path stays byte-identical to the pre-adapter markup. **Behaviour is unchanged for every consumer that has not registered a link adapter**, which is currently all of them: `resolveLink` falls back to a plain `<a>`. Deliberately left as raw `<a>`: - `EditorialFootnotes` — its `#fnref-*` anchors are in-page backlinks. Client-routing them would play a page transition for a jump within the same document. - `PricingPlans` / `PricingPlanCard` — these already accept an injectable anchor component, a more expressive consumer mechanism that predates the adapter. - `LogoSlider` — a self-closing, childless `target="_blank"` overlay anchor. Always external, so the adapter would hand it straight back to the browser. - `@wabbit/tome-longform` — it has zero runtime dependencies and does not peer on `blocks-core`. Adding that edge to the layer graph is its own decision, not a sweep side effect.
  • Updated dependencies [510036f] - @wabbit/tome-blocks-core@0.15.0 - @wabbit/tome-blocks-extras@0.15.0
v0.14.0patch

Updated dependencies - @wabbit/tome-blocks-core@0.14.0 - @wabbit/tome-blocks-extras@0.14.0

  • Updated dependencies - @wabbit/tome-blocks-core@0.14.0 - @wabbit/tome-blocks-extras@0.14.0
v0.13.0minor

f4d55c9: cta: `door-strip` + `doors` variants graduated from tome-starter (showcase program Phase 3.7). Adds a `doors` array field (min/max 2 rows: voice, kicker, heading, body, link) and a `columnSpan` field to the base cta schema — both additive, backward-compatible. `door-strip` is a compact two-door router (whole-surface links, mono kicker, serif line, arrow affordance); `doors` is a full two-door close (kicker, serif heading, body, underlined go-link per door, scroll-reveal via the new `@wabbit/tome-blocks-core` `Reveal` helper — seed with `columnSpan: '1 / -1'` for full bleed). Both are style-only variants: the `doors` field lives on the base schema behind an `admin.condition`, not a `fieldOverrides` schema variant, because per-document schema divergence can't be expressed at config-build time (see comment in `cta/variants.ts`).

  • f4d55c9: cta: `door-strip` + `doors` variants graduated from tome-starter (showcase program Phase 3.7). Adds a `doors` array field (min/max 2 rows: voice, kicker, heading, body, link) and a `columnSpan` field to the base cta schema — both additive, backward-compatible. `door-strip` is a compact two-door router (whole-surface links, mono kicker, serif line, arrow affordance); `doors` is a full two-door close (kicker, serif heading, body, underlined go-link per door, scroll-reveal via the new `@wabbit/tome-blocks-core` `Reveal` helper — seed with `columnSpan: '1 / -1'` for full bleed). Both are style-only variants: the `doors` field lives on the base schema behind an `admin.condition`, not a `fieldOverrides` schema variant, because per-document schema divergence can't be expressed at config-build time (see comment in `cta/variants.ts`).
  • 9116174: exhibit: new gallery-wall block graduated from tome-starter (showcase Phase 3.7) — museum plates framing real nested block renders; nested allowlist via factory config (exhibitBlocks). Ports the starter's `src/blocks/Exhibit/` (config.ts, Component.tsx, Component.module.css) into a new `exhibit` block: an asymmetric 12-col wall of hairline-framed white canvases, each holding a REAL nested block render (not a screenshot) under a plate caption (title + pack attribution). One style-only variant (`gallery-wall`), registered via plain `defineBlock(meta, factory, variants)` matching `testimonialBlock`'s idiom. The nested `blocks` allowlist is a factory-config contract — pass `config.exhibitBlocks` (an array of Payload `Block` configs) when invoking `exhibitBlock.block(config)` to curate what's exhibitable; omitted, it defaults to this pack's own `testimonial` block so the field is never empty and the pack stays self-contained (no import of `lms`/`catalog`/`org` packs). The `Exhibit` render component takes an optional `nestedComponents` prop (registry-free resolution map for the nested per-exhibit blocks, checked before the shared `@wabbit/tome-blocks-core` render registry) and replicates `RenderBlocks`' per-block subgrid wrapper (`data-tome-block-wrapper`, `data-block-background`, `gridColumn`/`gridTemplateColumns: subgrid`) by hand around each `RenderBlock` call for DOM/CSS parity with the rest of the pack. Registered in both render paths: the legacy `'use client'` side-effect barrel (`./render`) and the server-safe explicit registry (`./render/register`).
  • Updated dependencies [f4d55c9]
  • Updated dependencies [eb403d4] - @wabbit/tome-blocks-core@0.13.0 - @wabbit/tome-blocks-extras@0.13.0
v0.11.2patch

Updated dependencies [e11d5a2] - @wabbit/tome-blocks-core@0.11.2 - @wabbit/tome-blocks-extras@0.11.2

  • Updated dependencies [e11d5a2] - @wabbit/tome-blocks-core@0.11.2 - @wabbit/tome-blocks-extras@0.11.2
v0.11.0patch

26dfa07: Pre-existing test-suite fixes (unrelated to recent feature work): - `high-impact-hero`'s `illustrationHero` variant carried two tags (`brand`, `illustration`) outside the canonical taxonomy declared in `v2-coverage.test.ts`. Fixed to `['editorial', 'playful']`. The `illustrationHero` slug itself is kept camelCase (not renamed to kebab-case) because it shipped in the published `0.5.0` release (2026-05-21) and is stored verbatim in consumer content as a `_variant` field value — renaming would silently break every stored document that already selected it. The kebab-case rule now carries a documented, slug-scoped exception with a deprecation trigger (next content-breaking major version for this package, when a stored-content migration pass is already budgeted). - `blocks-core`'s `test/top20-variants.test.ts` imported `@wabbit/tome-blocks-marketing-starter` and the other bundle packs directly, which a later refactor made unresolvable when it removed blocks-core's devDeps on those packs to break the blocks-core ↔ marketing-starter dependency cycle. Relocated the test to `@wabbit/tome-blocks` (the meta-package that already depends on every pack for exactly this purpose) rather than re-adding the removed devDeps and recreating the cycle. Test-only change; no runtime behavior changed in either package.

  • 26dfa07: Pre-existing test-suite fixes (unrelated to recent feature work): - `high-impact-hero`'s `illustrationHero` variant carried two tags (`brand`, `illustration`) outside the canonical taxonomy declared in `v2-coverage.test.ts`. Fixed to `['editorial', 'playful']`. The `illustrationHero` slug itself is kept camelCase (not renamed to kebab-case) because it shipped in the published `0.5.0` release (2026-05-21) and is stored verbatim in consumer content as a `_variant` field value — renaming would silently break every stored document that already selected it. The kebab-case rule now carries a documented, slug-scoped exception with a deprecation trigger (next content-breaking major version for this package, when a stored-content migration pass is already budgeted). - `blocks-core`'s `test/top20-variants.test.ts` imported `@wabbit/tome-blocks-marketing-starter` and the other bundle packs directly, which a later refactor made unresolvable when it removed blocks-core's devDeps on those packs to break the blocks-core ↔ marketing-starter dependency cycle. Relocated the test to `@wabbit/tome-blocks` (the meta-package that already depends on every pack for exactly this purpose) rather than re-adding the removed devDeps and recreating the cycle. Test-only change; no runtime behavior changed in either package.
  • 36e537a: Every package now declares an explicit `sideEffects` field (38 added; motion/engine/forms already correct). Registration-bearing modules (render files' `registerRenderer`, `blocks/*/index.ts` `defineBlock` self-registration, widget `register.ts` files, productHooks, permission self-registrations, print templates, chrome built-in variants) are listed so bundlers can tree-shake everything else WITHOUT dropping import-time registrations — previously the field was unset, which blocked cross-module tree-shaking through the barrels entirely. Never blanket `false` on a package with registration or CSS.
  • a93f478: Re-render and cleanup fixes: chrome's HeaderClient dead theme state + unreachable effect deleted; Navbar6/7 body-scroll-lock now saves and restores the pre-existing overflow value (LearnerSidebar pattern) instead of clobbering to ''; Navbar7's scroll listener is rAF-throttled. marketing-starter's Testimonial derives the clamped slide index during render instead of an effect. forms' `FieldRenderer` is wrapped in `React.memo` (call-site props verified stable), cutting whole-step re-render work per keystroke in multi-field forms. lms-ui's `useLearnerPrefs` gains optional `initialPrefs` server-seeding (non-breaking) + in-flight dedup with TTL for the unseeded path.
  • 5f78397: The clientization migration: 127 render components across seven packs dropped `'use client'` — every file individually re-verified hook/handler/context-free before stripping; adapter-consuming static blocks converted to `resolveRichText`/`resolveMedia`. Exactly 20 of 155 renderers remain client, each for a verified reason (state/effects/motion, or a documented client-shell composition contract), enforced by the new `assert:rsc-boundaries` CI script (per-pack manifest; fails loudly if a directive creeps back or a count drifts). Every renderer-bearing pack now exports `./render/register` (`renderers` map + explicit `registerRenderers()`), aggregated by `@wabbit/tome-blocks`'s new `registerAllRenderers()` — the format-safe registration path for server component graphs, where the legacy import-time barrel registration never executes (that legacy path is unchanged and remains supported until the spec's deprecation trigger). `RenderBlock` is rewritten server-safe: directive-free, optional `components` prop (RenderBlocks parity) → registry fallback, dev warn-once naming both fixes on a miss; its docs state the explicit-registration prerequisite. Rendered output is byte-identical everywhere; behavior change only for consumers rendering migrated blocks in RSC WITHOUT a provider or registration — they get the documented warn + graceful degradation instead of silent client bundling.
  • aef2725: Monolith decompositions (behavior- and markup-preserving; public APIs unchanged; markup identity mechanically verified per file): forms' FieldRenderer 633→84 via a field-control registry + shared FieldChrome (consent/checkbox byte-identical branches merged) and TomeForm 656→451 via four extracted hooks (the ordering-critical resolver sync deliberately stays inline, documented); rpg's CharacterSheet 841→130 across panels + three editing hooks + persistence hook (the StrictMode XP-ledger charRef guard preserved verbatim); gallery's GalleryIndex 1032→431 (BlockThumb/BlockCard/Toolbar/useFilteredCatalog siblings, T2's debounce+memo preserved); webgl's WebglCanvasProvider 938→546 (useTransitionOrchestrator + useCanvasRenderer extracted; settle thresholds hoisted to named consts); admin's mergeAdminComponents 828→404 orchestrator + four helpers (all docblocks relocated, 717 tests unmodified) and Nav's config-reading now typed (6 of 8 `as any` casts eliminated); marketing-starter's PricingPlans extracts its GSAP toggle timeline hook + a memoized card. rpg additionally trusts the denormalized `xpTotal` on sheet load/save hot paths (full recompute stays at the XP-recording reconciliation point).
  • Updated dependencies [26dfa07]
  • Updated dependencies [36e537a]
  • Updated dependencies [36e537a]
  • Updated dependencies [36e537a]
  • Updated dependencies [a93f478]
  • Updated dependencies [5f78397]
  • Updated dependencies [5f78397]
  • Updated dependencies [aef2725]
  • Updated dependencies [aef2725] - @wabbit/tome-blocks-core@0.11.0 - @wabbit/tome-blocks-extras@0.11.0
v0.10.0patch

Updated dependencies - @wabbit/tome-blocks-core@0.10.0 - @wabbit/tome-blocks-extras@0.10.0

  • Updated dependencies - @wabbit/tome-blocks-core@0.10.0 - @wabbit/tome-blocks-extras@0.10.0
v0.9.5patch

Demo-kit diversification (10 fictional brands across all demo props) and block fixes: pricing/testimonial demos supply real card objects instead of placeholder-ID strings; PostHero/EditorialOpener/BlogHero/ChapterHero format display dates with a fixed locale (ISO preserved in the time dateTime attribute); PostHero background layer no longer collapses to the content row (abs-pos grid-item containing-block fix) and fills via the Media adapter; Testimonial renders plain-string quotes.

  • Demo-kit diversification (10 fictional brands across all demo props) and block fixes: pricing/testimonial demos supply real card objects instead of placeholder-ID strings; PostHero/EditorialOpener/BlogHero/ChapterHero format display dates with a fixed locale (ISO preserved in the time dateTime attribute); PostHero background layer no longer collapses to the content row (abs-pos grid-item containing-block fix) and fills via the Media adapter; Testimonial renders plain-string quotes.
  • Updated dependencies - @wabbit/tome-blocks-extras@0.9.5
v0.9.4patch

bed3f90: Docs-manifest emitter pipeline (W3 ship-readiness). `@wabbit/tome-blocks-core` now ships a standalone Node ESM CLI at `scripts/emit-docs-manifests.mjs` that emits per-package documentation manifests (index.json, packages/<slug>.json, changelog.json) by reading what packages already carry — READMEs, the payload-free `<pkg>/meta` block-usage barrels, package.json exports maps, and CHANGELOG.md. It is the docs-pipeline sibling of the gallery source extractor and is consumed by host sites at prebuild: `node node_modules/@wabbit/tome-blocks-core/scripts/emit-docs-manifests.mjs --output-dir <dir> --scope <scope.json>`. To let the emitter import block metadata uniformly without dragging Payload config into a build script, the `./meta` payload-free subpath (BlockMetaEntry[]) is extended to the remaining offered blocks packs — agency-essentials, catalog-pack, lms-pack, org-pack, and signal-theme — mirroring the existing editorial-pack / marketing-starter / content-writer / extras barrels. Each block's `BlockMeta` was relocated verbatim into a payload-free sibling meta module and re-imported by its block config; no meta values changed. Every supported-core package additionally adds `CHANGELOG.md` to its published `files` array so the next publish cascade ships changelogs the emitter can read from installed tarballs at prebuild.

  • bed3f90: Docs-manifest emitter pipeline (W3 ship-readiness). `@wabbit/tome-blocks-core` now ships a standalone Node ESM CLI at `scripts/emit-docs-manifests.mjs` that emits per-package documentation manifests (index.json, packages/<slug>.json, changelog.json) by reading what packages already carry — READMEs, the payload-free `<pkg>/meta` block-usage barrels, package.json exports maps, and CHANGELOG.md. It is the docs-pipeline sibling of the gallery source extractor and is consumed by host sites at prebuild: `node node_modules/@wabbit/tome-blocks-core/scripts/emit-docs-manifests.mjs --output-dir <dir> --scope <scope.json>`. To let the emitter import block metadata uniformly without dragging Payload config into a build script, the `./meta` payload-free subpath (BlockMetaEntry[]) is extended to the remaining offered blocks packs — agency-essentials, catalog-pack, lms-pack, org-pack, and signal-theme — mirroring the existing editorial-pack / marketing-starter / content-writer / extras barrels. Each block's `BlockMeta` was relocated verbatim into a payload-free sibling meta module and re-imported by its block config; no meta values changed. Every supported-core package additionally adds `CHANGELOG.md` to its published `files` array so the next publish cascade ships changelogs the emitter can read from installed tarballs at prebuild.
  • Updated dependencies [bed3f90] - @wabbit/tome-blocks-core@0.9.4 - @wabbit/tome-blocks-extras@0.9.4
v0.9.2patch

Updated dependencies - @wabbit/tome-blocks-core@0.9.2 - @wabbit/tome-blocks-extras@0.9.2

  • Updated dependencies - @wabbit/tome-blocks-core@0.9.2 - @wabbit/tome-blocks-extras@0.9.2
v0.9.1patch

Updated dependencies [c07f3c8] - @wabbit/tome-blocks-extras@0.9.1

  • Updated dependencies [c07f3c8] - @wabbit/tome-blocks-extras@0.9.1
v0.9.0minor

c7d0afc: layoutGrid nesting capability audit + render hardening (Wave 2). Tags the `nestable` capability flag (and a `minColSpan` floor for internally-wide blocks) across the offered blocks in the core packs (extras, marketing, content, agency, editorial, signal), so the `layoutGrid` container's derived child allowlist — `blockRegistry.getNestableBlocks()` — is populated rather than empty. Excluded by design (left non-nestable): containers (`editorialSpread`, `editorialSection`, `split-view`, `stacking-wrapper`, `grid`, `layoutGrid`) to enforce the one-level depth cap; full-bleed heroes/banners (band-owners); and inline Lexical blocks (not block-level grid children). `minColSpan: 2` is set on the internally multi-column blocks (`card-grid`, `bento-section`, `content-two-column`, `signal-stats`, `signal-image-grid`, `signal-data-table`) so they cannot be crammed into a single-column cell. Also hardens `LayoutGrid`'s child↔span pairing: children are now flattened with null slots preserved (instead of `React.Children.toArray`, which drops nulls), so an unrenderable child can no longer shift every later child's span metadata onto the wrong block. Adds a dev-mode warning when the consumer's child count doesn't match the item count. `@wabbit/tome-blocks-core` is bumped to release the layoutGrid platform primitives merged earlier but never published (the registry's `0.8.0` predates that merge): `BlockMeta.nestable`/`minColSpan`, `withChildPlacement`, the reserved `_colSpan`/`_rowSpan`/`_order` field constants, and `blockRegistry.getNestableBlocks()`. Without this, a consumer wiring the layoutGrid allowlist would call a `getNestableBlocks` that its installed `blocks-core@0.8.0` does not have. Domain packs (lms, catalog, sc, org) are intentionally deferred — they compose into their own domain layouts and can opt in when a consumer needs them.

  • c7d0afc: layoutGrid nesting capability audit + render hardening (Wave 2). Tags the `nestable` capability flag (and a `minColSpan` floor for internally-wide blocks) across the offered blocks in the core packs (extras, marketing, content, agency, editorial, signal), so the `layoutGrid` container's derived child allowlist — `blockRegistry.getNestableBlocks()` — is populated rather than empty. Excluded by design (left non-nestable): containers (`editorialSpread`, `editorialSection`, `split-view`, `stacking-wrapper`, `grid`, `layoutGrid`) to enforce the one-level depth cap; full-bleed heroes/banners (band-owners); and inline Lexical blocks (not block-level grid children). `minColSpan: 2` is set on the internally multi-column blocks (`card-grid`, `bento-section`, `content-two-column`, `signal-stats`, `signal-image-grid`, `signal-data-table`) so they cannot be crammed into a single-column cell. Also hardens `LayoutGrid`'s child↔span pairing: children are now flattened with null slots preserved (instead of `React.Children.toArray`, which drops nulls), so an unrenderable child can no longer shift every later child's span metadata onto the wrong block. Adds a dev-mode warning when the consumer's child count doesn't match the item count. `@wabbit/tome-blocks-core` is bumped to release the layoutGrid platform primitives merged earlier but never published (the registry's `0.8.0` predates that merge): `BlockMeta.nestable`/`minColSpan`, `withChildPlacement`, the reserved `_colSpan`/`_rowSpan`/`_order` field constants, and `blockRegistry.getNestableBlocks()`. Without this, a consumer wiring the layoutGrid allowlist would call a `getNestableBlocks` that its installed `blocks-core@0.8.0` does not have. Domain packs (lms, catalog, sc, org) are intentionally deferred — they compose into their own domain layouts and can opt in when a consumer needs them.
  • Updated dependencies [c7d0afc] - @wabbit/tome-blocks-core@0.9.0 - @wabbit/tome-blocks-extras@0.9.0
v0.8.0minor

249b670: Batch 1 (2026-06-27 inserter/variant architecture): author the usage/intent layer on the free `high-impact-hero` — it is the canonical free workhorse hero and the migration target for the deprecated low/medium-impact heroes. Additive metadata only. Deferred to the live-run pass (coupled with the instance data-migration + a visual A/B): the slug rename `high-impact-hero` → `hero` (with a back-compat alias) and the `heroTextTheme` parity field.

  • 249b670: Batch 1 (2026-06-27 inserter/variant architecture): author the usage/intent layer on the free `high-impact-hero` — it is the canonical free workhorse hero and the migration target for the deprecated low/medium-impact heroes. Additive metadata only. Deferred to the live-run pass (coupled with the instance data-migration + a visual A/B): the slug rename `high-impact-hero` → `hero` (with a back-compat alias) and the `heroTextTheme` parity field.
  • 249b670: Batch 2 feature consolidation (2026-06-27 inserter/variant architecture) — collapse the scattered feature blocks into the single `featureHero`. - **Deprecated** (still registered + rendered for back-compat, removed from the offered `extras` bundle + client-safe gallery meta): `feature-masonry`, `feature-with-large-media`, `feature-with-three-steps`, `media-feature` → the marketing-starter `featureHero`. Together with Batch 1's `feature-hero-with-cards` + `feature-with-icon-grid`, the whole feature family now consolidates to `featureHero`, whose 6-layout `variant` select already covers them all. - **Authored usage/intent metadata** on `featureHero` (now the sole offered feature block). - **Deferred to the live-run reconciliation pass** (brand-preserving but a field-semantics swap + data migration): `featureHero`'s dual variant mechanism — make the 6 LAYOUTS the `_variant` (so the VariantPicker drives layout, not the unrelated 4-value style axis), move the style axis to a secondary field, and drop the inline `variant`. Instance migration (standalone feature blocks → `featureHero` + the right layout) is deferred to a later release. Tier note: the feature layouts consolidate onto the FREE `featureHero` (it already carried all 6 free); unlike heroes there is no distinct premium feature layout to gate, so no paid `feature-pro` — flag if a paid feature tier is wanted. Ships in the linked family's 0.8.0 minor.
  • 249b670: Batch 4 — Marketing & Data (2026-06-27 inserter/variant architecture). These six blocks already ship as one-block-plus-4-variants (clean `_variant`, no scattered duplicates), so this batch is audit + usage-authoring, not consolidation. - **Authored usage/intent metadata** (Decision 4) on `cta`, `logo-slider`, `pricing`, `testimonial`, `faq`, `banner` — so assembling agents + the gallery/inserter can select, order, and configure them. Additive metadata only. - **Audit finding flagged for the live-run reconciliation pass:** `pricing` carries a slug-split (Payload block `slug: 'pricingPlans'` camel vs meta `slug: 'pricing'` kebab) AND a redundant inline `displayOptions.variant` ('full'|'compact') alongside the `_variant` axis. Documented in pricing/meta.ts; deferred (couples with the slug-split decision + a data migration). The other five are clean. Ships in the linked family's 0.8.0 minor.
  • Updated dependencies [249b670]
  • Updated dependencies [249b670]
  • Updated dependencies [249b670]
  • Updated dependencies [249b670] - @wabbit/tome-blocks-extras@0.8.0 - @wabbit/tome-blocks-core@0.8.0
v0.7.0minor

28802fa: Expose a client-safe `./demo` export (the already-built, payload-free `getDemoProps` module) on each block pack, separate from the payload-importing root barrel. The root barrel (`.`) eagerly pulls each block's config, which imports `payload` (→ `richtext-lexical` → `pino` → `worker_threads`). A consumer that registers packs **client-side** (the wabbit `/blocks` gallery storefront, B6) can't import `getDemoProps` from the root barrel without dragging `payload`/`worker_threads` into the browser bundle (build failure). The `dist/demo.*` module is already built and contains only demo-data + type imports — this change just makes it importable as `@wabbit/tome-blocks-<pack>/demo`. Additive; no code or runtime change to the packs. NOTE: this is the `getDemoProps` half of the client-safe gallery-registration fix. The companion piece — a client-safe **block-meta/descriptor** export (slug/label/variants/tier, separate from the payload-importing config the gallery bridges currently read `.meta` off) — is still needed before B6 can register packs entirely off the root barrel. Tracked separately.

  • 28802fa: Expose a client-safe `./demo` export (the already-built, payload-free `getDemoProps` module) on each block pack, separate from the payload-importing root barrel. The root barrel (`.`) eagerly pulls each block's config, which imports `payload` (→ `richtext-lexical` → `pino` → `worker_threads`). A consumer that registers packs **client-side** (the wabbit `/blocks` gallery storefront, B6) can't import `getDemoProps` from the root barrel without dragging `payload`/`worker_threads` into the browser bundle (build failure). The `dist/demo.*` module is already built and contains only demo-data + type imports — this change just makes it importable as `@wabbit/tome-blocks-<pack>/demo`. Additive; no code or runtime change to the packs. NOTE: this is the `getDemoProps` half of the client-safe gallery-registration fix. The companion piece — a client-safe **block-meta/descriptor** export (slug/label/variants/tier, separate from the payload-importing config the gallery bridges currently read `.meta` off) — is still needed before B6 can register packs entirely off the root barrel. Tracked separately.
  • 8958d41: Expose a client-safe `./meta` export on each block pack: payload-free block descriptor metadata (slug/name/description/category/tags/source + variants), separate from the payload-importing root barrel. This is the companion to the `./demo` export. Each block's `meta` literal is now extracted into a co-located payload-free `meta` module that the block config imports, and a pack-level `./meta` entry exposes the full descriptor list as `<pack>BlockMeta`. A consumer registering packs client-side (the wabbit `/blocks` gallery storefront, B6) can now read block metadata for gallery entries without importing the root barrel, which eagerly pulls each block's config (`payload` -> `richtext-lexical` -> `pino` -> `worker_threads`) into the browser bundle. Additive and behavior-preserving: `defineBlock` receives the same meta object (now imported rather than inline); the block registry, configs, demos, and existing exports are unchanged. The pack `BlockMeta` array is also re-exported from the root barrel for path-alias consumers.
  • Updated dependencies [28802fa]
  • Updated dependencies [66c611c]
  • Updated dependencies [8958d41] - @wabbit/tome-blocks-extras@0.7.0 - @wabbit/tome-blocks-core@0.7.0
v0.6.2patch

Updated dependencies [4b2f368] - @wabbit/tome-blocks-core@0.6.2 - @wabbit/tome-blocks-extras@0.6.2

  • Updated dependencies [4b2f368] - @wabbit/tome-blocks-core@0.6.2 - @wabbit/tome-blocks-extras@0.6.2
v0.6.1patch

f37fa00: feat(demo): add getDemoProps dispatchers to agency-essentials, marketing-starter, and signal-theme Each pack now ships a `demo.ts` barrel with a `getDemoProps(blockSlug, variant, ctx?)` dispatcher and per-block demo functions. The auto-gallery route in tome-starter can replace the `noopDemoProps` stub for all three bundles, eliminating the warning cards that appeared for every block × variant. - agency-essentials: 10 blocks × 4 variants each (about, contact, team-roster, gallery, timeline, stat, stat-bar, split-view, media, form) - marketing-starter: 8 blocks × 4-5 variants each (high-impact-hero, feature-hero, cta, logo-slider, pricing, testimonial, faq, banner) - signal-theme: 33 blocks (all signal-\* slugs); blocks with multi-variant configs covered (accordion: stacked/single, callout: tactical/lore) - DemoContext interface, individual block-level functions, and getDemoProps all re-exported from each pack barrel - richText fields intentionally omitted — GalleryRichTextProvider supplies the Lexical state at gallery render time - Relationship fields (pricing, testimonial) emit sentinel strings; gallery degrades gracefully Fixes risk R1 from the gallery gap audit. - @wabbit/tome-blocks-core@0.5.9

  • f37fa00: feat(demo): add getDemoProps dispatchers to agency-essentials, marketing-starter, and signal-theme Each pack now ships a `demo.ts` barrel with a `getDemoProps(blockSlug, variant, ctx?)` dispatcher and per-block demo functions. The auto-gallery route in tome-starter can replace the `noopDemoProps` stub for all three bundles, eliminating the warning cards that appeared for every block × variant. - agency-essentials: 10 blocks × 4 variants each (about, contact, team-roster, gallery, timeline, stat, stat-bar, split-view, media, form) - marketing-starter: 8 blocks × 4-5 variants each (high-impact-hero, feature-hero, cta, logo-slider, pricing, testimonial, faq, banner) - signal-theme: 33 blocks (all signal-\* slugs); blocks with multi-variant configs covered (accordion: stacked/single, callout: tactical/lore) - DemoContext interface, individual block-level functions, and getDemoProps all re-exported from each pack barrel - richText fields intentionally omitted — GalleryRichTextProvider supplies the Lexical state at gallery render time - Relationship fields (pricing, testimonial) emit sentinel strings; gallery degrades gracefully Fixes risk R1 from the gallery gap audit. - @wabbit/tome-blocks-core@0.5.9
v0.5.9patch

8947ff1: Three additive packaging fixes surfaced by a consumer's registry-consumption migration (path-aliasing was masking these — the actual package contracts didn't cover them): - `@wabbit/tome-blocks-marketing-starter`: add `./blocks/*` subpath exports for the 8 block directories (`banner`, `cta`, `faq`, `feature-hero`, `high-impact-hero`, `logo-slider`, `pricing`, `testimonial`). Source already shipped these as directories with `index.ts`; the `exports` map only declared `.` and `./render`, so any consumer of a specific block from the registry got a module-not-found error. Path-aliasing bypassed the exports map, hiding the gap. - `@wabbit/tome-core`: add `./auth/collections/Roles` (capital R) alongside the existing lowercase `./auth/collections/roles`. Both resolve to the same file (`./dist/auth/collections/Roles.{js,cjs,d.ts}`). The source file is `Roles.ts`; the exports map declared only lowercase, so consumers using the file's actual case (which is what TS path-aliasing produced when reading the source directly) couldn't import via the package's public API. - `@wabbit/tome-ui`: add `./tokens.css` alongside the existing `./tokens` (both point at `./dist/tokens.css`). Lets consumers write `import '@wabbit/tome-ui/tokens.css'` to match the CSS-file naming convention as well as the existing `import '@wabbit/tome-ui/tokens'`. All three additions are purely additive — no existing exports removed or changed, so existing consumers stay compatible. - @wabbit/tome-blocks-core@0.5.9 - @wabbit/tome-blocks-extras@0.5.9

  • 8947ff1: Three additive packaging fixes surfaced by a consumer's registry-consumption migration (path-aliasing was masking these — the actual package contracts didn't cover them): - `@wabbit/tome-blocks-marketing-starter`: add `./blocks/*` subpath exports for the 8 block directories (`banner`, `cta`, `faq`, `feature-hero`, `high-impact-hero`, `logo-slider`, `pricing`, `testimonial`). Source already shipped these as directories with `index.ts`; the `exports` map only declared `.` and `./render`, so any consumer of a specific block from the registry got a module-not-found error. Path-aliasing bypassed the exports map, hiding the gap. - `@wabbit/tome-core`: add `./auth/collections/Roles` (capital R) alongside the existing lowercase `./auth/collections/roles`. Both resolve to the same file (`./dist/auth/collections/Roles.{js,cjs,d.ts}`). The source file is `Roles.ts`; the exports map declared only lowercase, so consumers using the file's actual case (which is what TS path-aliasing produced when reading the source directly) couldn't import via the package's public API. - `@wabbit/tome-ui`: add `./tokens.css` alongside the existing `./tokens` (both point at `./dist/tokens.css`). Lets consumers write `import '@wabbit/tome-ui/tokens.css'` to match the CSS-file naming convention as well as the existing `import '@wabbit/tome-ui/tokens'`. All three additions are purely additive — no existing exports removed or changed, so existing consumers stay compatible. - @wabbit/tome-blocks-core@0.5.9 - @wabbit/tome-blocks-extras@0.5.9
v0.5.7patch

@wabbit/tome-blocks-core@0.5.7

  • @wabbit/tome-blocks-core@0.5.7
  • @wabbit/tome-blocks-extras@0.5.7
v0.5.0minor

`high-impact-hero`: add an `illustrationHero` variant (`characterImage` upload + `characterPosition`) for character-led heroes.

  • `high-impact-hero`: add an `illustrationHero` variant (`characterImage` upload + `characterPosition`) for character-led heroes.
v0.4.2patch

Updated dependencies - @wabbit/tome-blocks-extras@0.4.2 - @wabbit/tome-blocks-core@0.4.2

  • Updated dependencies - @wabbit/tome-blocks-extras@0.4.2 - @wabbit/tome-blocks-core@0.4.2
v0.4.1patch

Updated dependencies - @wabbit/tome-blocks-extras@0.4.1 - @wabbit/tome-blocks-core@0.4.0

  • Updated dependencies - @wabbit/tome-blocks-extras@0.4.1 - @wabbit/tome-blocks-core@0.4.0
v0.4.0patch

Updated dependencies [b76f684]

  • Updated dependencies [b76f684]
  • Updated dependencies [90a694d] - @wabbit/tome-blocks-core@0.4.0 - @wabbit/tome-blocks-extras@0.4.0
v0.3.0minor

f2202cd: Sprint 3 blocks split — v0.2.0 Extracted the Tome blocks monolith (@wabbit/tome-blocks) into independently publishable bundle packages. Each bundle is independently installable, tree-shakeable, and testable in isolation. **New packages (all v0.2.0):** - `@wabbit/tome-blocks-core` — registries, defineBlock/defineBundle, variants, thumbnails, admin components (BlockPicker, VariantPicker) - `@wabbit/tome-blocks-marketing-starter` (free) — 8 marketing blocks: Hero, FeatureHero, CTA, LogoSlider, Pricing, Testimonial, FAQ, Banner - `@wabbit/tome-blocks-content-writer` (free) — 10 editorial blocks: Blog, Archive, PostHero, RelatedPosts, EditorialOpener, EditorialBridge, EditorialSidenote, EditorialFigure, EditorialColophon, EditorialFootnotes - `@wabbit/tome-blocks-agency-essentials` (starter) — 10 agency blocks: About, Contact/Form, Gallery, Media, SplitView, Stat, StatBar, TeamRoster, Timeline - `@wabbit/tome-blocks-editorial-pack` (pro) — 8 editorial blocks: DataHero, Feature, InfoPanel, MessagePanel, MetricStrip, SplitPanel, StatusBoard, TextReveal - `@wabbit/tome-blocks-signal-theme` (pro) — 33 Signal narrative blocks - `@wabbit/tome-blocks-lms-pack` (pro) — scaffold for LMS blocks (Wave 3) - `@wabbit/tome-blocks-catalog-pack` (pro) — scaffold for catalog/ecommerce blocks (Wave 3) - `@wabbit/tome-blocks-sc-pack` (niche) — scaffold for Star Citizen blocks (Wave 3) - `@wabbit/tome-blocks-extras` (pro) — 47 residual blocks (heroes, layout, content, marketing) **@wabbit/tome-blocks is now a meta-package** that re-exports all bundle packages and provides `registerAll(blockRegistry, bundleRegistry)` as a convenience function. **Render colocation:** All 113 render `.tsx` components migrated from the monolith into their owning bundle packages (`./render` subpath on each bundle). **Breaking changes (internal):** - `@wabbit/tome-blocks/blocks` and `@wabbit/tome-blocks/bundles` subpaths removed (were Sprint 2 shims) - `packages/blocks/src/render/` category index files removed (replaced by per-bundle `./render` subpaths) **Migration:** ```ts // Before (monolith singleton, all blocks loaded) import "@wabbit/tome-blocks"; // After (explicit registration, tree-shakeable) import { blockRegistry, bundleRegistry, } from "@wabbit/tome-blocks-core/registry"; import { register } from "@wabbit/tome-blocks-marketing-starter"; register(blockRegistry, bundleRegistry); // Or use the meta-package convenience function import { blockRegistry, bundleRegistry, registerAll, } from "@wabbit/tome-blocks"; registerAll(blockRegistry, bundleRegistry); ```

  • f2202cd: Sprint 3 blocks split — v0.2.0 Extracted the Tome blocks monolith (@wabbit/tome-blocks) into independently publishable bundle packages. Each bundle is independently installable, tree-shakeable, and testable in isolation. **New packages (all v0.2.0):** - `@wabbit/tome-blocks-core` — registries, defineBlock/defineBundle, variants, thumbnails, admin components (BlockPicker, VariantPicker) - `@wabbit/tome-blocks-marketing-starter` (free) — 8 marketing blocks: Hero, FeatureHero, CTA, LogoSlider, Pricing, Testimonial, FAQ, Banner - `@wabbit/tome-blocks-content-writer` (free) — 10 editorial blocks: Blog, Archive, PostHero, RelatedPosts, EditorialOpener, EditorialBridge, EditorialSidenote, EditorialFigure, EditorialColophon, EditorialFootnotes - `@wabbit/tome-blocks-agency-essentials` (starter) — 10 agency blocks: About, Contact/Form, Gallery, Media, SplitView, Stat, StatBar, TeamRoster, Timeline - `@wabbit/tome-blocks-editorial-pack` (pro) — 8 editorial blocks: DataHero, Feature, InfoPanel, MessagePanel, MetricStrip, SplitPanel, StatusBoard, TextReveal - `@wabbit/tome-blocks-signal-theme` (pro) — 33 Signal narrative blocks - `@wabbit/tome-blocks-lms-pack` (pro) — scaffold for LMS blocks (Wave 3) - `@wabbit/tome-blocks-catalog-pack` (pro) — scaffold for catalog/ecommerce blocks (Wave 3) - `@wabbit/tome-blocks-sc-pack` (niche) — scaffold for Star Citizen blocks (Wave 3) - `@wabbit/tome-blocks-extras` (pro) — 47 residual blocks (heroes, layout, content, marketing) **@wabbit/tome-blocks is now a meta-package** that re-exports all bundle packages and provides `registerAll(blockRegistry, bundleRegistry)` as a convenience function. **Render colocation:** All 113 render `.tsx` components migrated from the monolith into their owning bundle packages (`./render` subpath on each bundle). **Breaking changes (internal):** - `@wabbit/tome-blocks/blocks` and `@wabbit/tome-blocks/bundles` subpaths removed (were Sprint 2 shims) - `packages/blocks/src/render/` category index files removed (replaced by per-bundle `./render` subpaths) **Migration:** ```ts // Before (monolith singleton, all blocks loaded) import "@wabbit/tome-blocks"; // After (explicit registration, tree-shakeable) import { blockRegistry, bundleRegistry, } from "@wabbit/tome-blocks-core/registry"; import { register } from "@wabbit/tome-blocks-marketing-starter"; register(blockRegistry, bundleRegistry); // Or use the meta-package convenience function import { blockRegistry, bundleRegistry, registerAll, } from "@wabbit/tome-blocks"; registerAll(blockRegistry, bundleRegistry); ```
  • Updated dependencies [f2202cd] - @wabbit/tome-blocks-core@0.3.0 - @wabbit/tome-blocks-extras@0.3.0