Commerce Blocks

Blocks & Themes
@wabbit/tome-blocks-catalog-packv0.18.0

Pro-tier catalog/e-commerce blocks — product card, product grid, category strip, price table, inventory badge, and featured product, with optional live-data hydration from @wabbit/tome-catalog.

Installnpm install @wabbit/tome-blocks-catalog-pack

Overview

@wabbit/tome-blocks-catalog-pack

Pro-tier Payload block bundle for catalog (e-commerce) use cases. Designed to work with @wabbit/tome-catalog for rich integrations, with graceful fallback to static props when @wabbit/tome-catalog is absent.

Status: 6 blocks shipped (Product Card, Product Grid, Category Strip, Price Table, Inventory Badge, Featured Product), each with 2–3 variants. Product Card, Featured Product, and Category Strip additionally support v1 live-data hydration via the separate /server subpath — see "Hydration (v1)" below.

Install

On a Tome site (meta-package path)

The @wabbit/tome-blocks meta-package re-exports all first-party bundles. No extra install needed if you are already on @wabbit/tome-blocks.

On a stock Payload site — add to an existing blocks field

npm install @wabbit/tome-blocks-catalog-pack

@wabbit/tome-blocks-core is a required peer and installs automatically (npm 7+/pnpm). Add the blocks you want alongside your existing ones and render them — see @wabbit/tome-blocks-core's "Add Tome blocks to an existing Payload project" for the full walkthrough:

// payload.config.ts
import { productCardBlock, productGridBlock } from '@wabbit/tome-blocks-catalog-pack'
// blocks: [...existingBlocks, productCardBlock.block(), productGridBlock.block()]
// blockComponents.ts
import { renderers as catalogRenderers } from '@wabbit/tome-blocks-catalog-pack/render/register'
import { adaptRenderersForPayload } from '@wabbit/tome-blocks-core/render'
// blockComponents: { ...adaptRenderersForPayload(catalogRenderers) }
@import '@wabbit/tome-blocks-core/styles.css';

Registering everything from scratch instead:

import { BlockRegistry, BundleRegistry } from '@wabbit/tome-blocks-core/registry'
import { register } from '@wabbit/tome-blocks-catalog-pack'

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

register(blockRegistry, bundleRegistry)

// Use blockRegistry.resolveAll() to get Payload Block configs

Peer dependencies

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

| Peer | Range | Required | |---|---|---| | lucide-react | >=0.460.0 | yes | | payload | >=3.67.0 | yes | | react | >=19.0.0 | yes | | react-dom | >=19.0.0 | yes | | @wabbit/tome-blocks-core | >=0.18.0 <1.0.0 | yes |

Hydration (v1) — @wabbit/tome-blocks-catalog-pack/server

Every block renders exactly the authored props by default — that never changes, with no peer required. A consumer who additionally wires the separate /server subpath gets live @wabbit/tome-catalog data overlaid on top of the authored fallback for three blocks. This is a server concern only: the static renderers above are unchanged and unaware of it.

import { registerRenderers, renderers as staticComponents } from '@wabbit/tome-blocks-catalog-pack/render/register'
import { createHydratedRenderers } from '@wabbit/tome-blocks-catalog-pack/server'
import { getPayload } from 'payload'
import config from '@payload-config'

const components = {
  ...staticComponents,
  ...createHydratedRenderers({ getPayload: () => getPayload({ config }) }),
}
// pass `components` into RenderBlock/RenderBlocks

v1 hydration matrix (verified against the real @wabbit/tome-catalog schema)

| Block | Lookup key | Live fields overlaid | Fallback | Notes | |---|---|---|---|---| | product-card | sku → catalog-products | title, description (from Product.excerpt) | all authored fields | price/comparePrice/badge/CTA stay authored — @wabbit/tome-catalog v1 has no pricing field at all, and this block has no image prop for featuredImage to fill | | featured-product | sku → catalog-products | title, description (from Product.excerpt) | all authored fields | same reasoning as product-card; highlights/price/CTA stay authored | | category-strip | each item's slug → catalog-categories (one batched query) | label (from Category.name) | authored label per item | url always stays authored (no live "route" concept); no live count — this block's render component has no count slot to receive one into even though payload.count() could compute it | | inventory-badge | — | none | fully static, deliberately | @wabbit/tome-catalog v1 has no stock/inventory field anywhere, and its status (draft/active/archived) is a publish flag, not availability — anonymous reads only ever see status: active regardless, so it carries no usable signal. Trigger to revisit: a future stock/quantity field or inventory collection in @wabbit/tome-catalog | | price-table | — | none | fully static, by design | authored pricing artifact — see its own meta | | product-grid | — | — | out of scope for v1 | this pack's one 'use client' renderer; a future wave could hydrate its products array from getProductsByCategory/getProductsByVendor |

Why the overlay matrix is narrow: an earlier design guessed price/image/availability as overlay targets for product-card/featured-product and "stock state" for inventory-badge — none of those have a live source (@wabbit/tome-catalog's Product type carries no pricing or stock field at all — "catalog spec excludes pricing" per packages/catalog/src/registry/defaultProductHooks.ts — and neither block has an image render prop). description (overlaid from Product.excerpt) was added because the field exists, type-matches, and is literally documented as "Short summary for cards/listings" in the catalog schema. Category-strip's "counts" were dropped for the same no-render-slot reason as product-card's image.

Access boundary

Every hydration query runs overrideAccess: false with no user in the request context — Payload evaluates access as an anonymous visitor. @wabbit/tome-catalog's publicReadActiveOnly restricts that to status: 'active' products; Categories are read: () => true (always public). Personalized/authenticated hydration is explicitly out of scope for v1.

Fallback contract

Layer absent (site doesn't have @wabbit/tome-catalog installed), entity missing (no doc matches the lookup key), or the query throws — all three collapse to the same outcome: the authored props pass through untouched. Hydration never removes or blanks an authored field; it only overlays a field once a live value was actually found. There is no isCatalogLayerPresent() gate in the resolvers — that flag is a fire-and-forget async check (see src/index.ts) that can still read false after the layer registers; attempting the query directly and catching failure handles every case correctly, including that race.

Per-request dedup

Resolvers use React.cache() where the host runtime provides it (Next.js App Router / React 19) so two blocks referencing the same SKU on one page cost one query, not two. This pack's own peer range ("react": ">=18.0.0") allows plain React 18, where cache doesn't exist at runtime — verified against this workspace's own installed react@18.3.1. Where it's unavailable, the pack falls back to calling straight through (no dedup, never a crash). There is no cache/tag machinery beyond this — cross-request freshness (ISR, ISR revalidation, ...) stays the consumer's concern.

Known v1 limitation

Collection slugs (catalog-products / catalog-categories) are hardcoded to @wabbit/tome-catalog's defaults rather than configurable through createHydratedRenderers's fixed { getPayload } contract. A site that renamed its catalog collections away from the defaults gets no hydration (safe fallback to authored props), not a crash — a slug-override option is the natural v2 extension if that's ever needed.

Detection mechanism (static blocks only)

This package uses hasLayer('@wabbit/tome-catalog') from @wabbit/tome-core/utilities/layerRegistry via a dynamic import() to expose isCatalogLayerPresent(). The import is wrapped in a try/catch so the package boots cleanly even when @wabbit/tome-core itself is not installed. The /server subpath does not use this detection mechanism — see "Fallback contract" above.

Compatibility matrix

Required peers:

| Payload | React | React DOM | lucide-react | |---------|-------|-----------|--------------| | >=3.67.0 | >=18.0.0 | >=18.0.0 | >=0.460.0 |

Optional integrations (not required peers, and not the same mechanism as each other):

  • @wabbit/tome-core — optionalDependencies (a real, auto-attempted npm install, not a peer). Used only for hasLayer('@wabbit/tome-catalog') detection via a try/catch dynamic import.
  • @wabbit/tome-catalog — optional peer (peerDependenciesMeta only; no enforced version range). This pack never imports it directly — hydration queries the Payload collections it registers (catalog-products, catalog-categories) by slug and falls back to authored props if absent.

Block list

  • product-card — Product title, description, price, CTA (v1 hydration: title/description)
  • product-grid — Grid of product cards with filtering controls (client component; not part of v1 hydration)
  • category-strip — Horizontal/grid category navigation strip (v1 hydration: label)
  • price-table — Tiered pricing comparison table (fully static, by design)
  • inventory-badge — In-stock / low-stock / out-of-stock indicator (fully static, deliberately — see its meta)
  • featured-product — Hero-style featured product showcase (v1 hydration: title/description)

Each block carries 2–3 variants with SVG thumbnails per the thumbnail-authoring guide.

Public API

| Export | Subpath | Description | |---|---|---| | Block descriptors (CategoryStrip, FeaturedProduct, InventoryBadge, PriceTable, ProductCard, ProductGrid configs) + register(blockRegistry, bundleRegistry) + isCatalogLayerPresent() | . | Payload block config descriptors, bundle registration, and the @wabbit/tome-catalog layer-detection flag | | CategoryStrip, FeaturedProduct, InventoryBadge, PriceTable, ProductCard, ProductGrid | ./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 | | createHydratedRenderers(), resolveProductCardData(), resolveFeaturedProductData(), resolveCategoryStripData() | ./server | v1 hydration contract — server-only (import 'server-only'); see "Hydration (v1)" above |

Server / client posture

5 of the pack's 6 renderers under src/render/ (CategoryStrip, FeaturedProduct, InventoryBadge, PriceTable, ProductCard) are plain static components with no 'use client' directive. ProductGrid alone is a client component (verified 2026-07-12; scripts/assert-rsc-boundaries.mjs MANIFEST: blocks-catalog-pack: 1) — its filtering UI needs a client boundary the other five don't. Prefer the ./render/register subpath (registerRenderers()) when registering from payload.config.ts or any Node/server context — the legacy ./render barrel re-exports CSS Modules at its root, which crashes Node's ESM loader outside a bundler (see @wabbit/tome-blocks's README, "Two rules for consumers"); ./render/register imports each component directly and sidesteps that barrel-level hazard.

Blocks

product-card

A single product tile — title, description, price (with optional compare-at strike), a label badge, and an add-to-cart CTA. Renders the authored props by default; no @wabbit/tome-catalog peer required. When a consumer wires `createHydratedRenderers` from `@wabbit/tome-blocks-catalog-pack/server` AND the SKU matches an active `catalog-products` doc, title and description hydrate live over the authored fallback — anonymous-visible data only (see the /server README). Three variants: default card, minimal (title + price only), and horizontal (image left, details right).

When to use
  • Highlighting one product on a landing, marketing, or about page
  • A "featured item" or "shop this" callout inside editorial or campaign content
  • A single hand-picked product where a full grid would be overkill
Page types
  • marketing
  • landing
  • about
How to use

Author title (required), description, price and compare-at price, SKU, an optional badge, and the CTA label/URL. The SKU is the v1 hydration lookup key: with @wabbit/tome-catalog present and `createHydratedRenderers` wired in, a matching `catalog-products` doc overlays its `title` and `excerpt` (into description) over these authored fields — nothing else. Price, compare-at price, badge, and the CTA are ALWAYS the authored values: @wabbit/tome-catalog carries no pricing or stock field at all (v1, by design), and this block has no `image` prop for a live featuredImage to fill. Choose the variant: default for a full card, minimal for a sparse price-led row, horizontal for a compact list-style item. For a set of products, use `product-grid` instead of stacking many cards — same product shape, but the grid takes the array (product-grid is not part of the v1 hydration wave).

Pairs with
  • product-grid
  • inventory-badge
  • price-table
  • featured-product
Precedes
  • inventory-badge
Avoid when
  • Showing several products at once — that is `product-grid` (it takes the array)
  • Spotlighting one product as a full-width hero — use `featured-product`
  • Comparing pricing tiers of a plan — use `price-table`
Register in

marketing-landing

product-grid

A responsive grid of products — a heading, a column count (2/3/4), an array of product items, optional filter controls, and a max-items cap. Fully static — always renders the authored list; no @wabbit/tome-catalog peer required. Three variants: default 3-column, compact dense 4-column, and editorial masonry.

When to use
  • A catalog, shop, or collection page listing many products at once
  • A "shop the collection" or "new arrivals" section on a marketing/landing page
  • Any place you would otherwise stack several product cards
Page types
  • marketing
  • landing
  • about
How to use

Set a heading and column count, then author the `products` array (title, price, badge, image, CTA per item) — this array is the only data source today; the filter controls have no effect on it yet. @wabbit/tome-catalog is not wired in yet; the isCatalogLayerPresent/resolveCatalogLayer seam in src/index.ts is scaffolding for a future hydration wave, to be wired when a consumer needs a live query and working filters to supply products instead. Choose the variant by density: default for standard cards, compact for a dense catalog wall, editorial for a varied masonry feel. This is the multi-product block — for a single product use `product-card`, and for a hero spotlight use `featured-product`.

Pairs with
  • product-card
  • category-strip
  • featured-product
  • inventory-badge
Follows
  • category-strip
  • featured-product
Avoid when
  • You only have one product to show — use `product-card`
  • The goal is to spotlight one hero product — use `featured-product`
  • Navigating between categories rather than listing products — use `category-strip`
Register in

marketing-landing

category-strip

A wayfinding row of product categories — a heading and an array of category tiles (label, slug, URL, icon), scrollable or wrapped. Renders the authored labels/URLs by default; no @wabbit/tome-catalog peer required. When a consumer wires `createHydratedRenderers` from `@wabbit/tome-blocks-catalog-pack/server`, each tile's `slug` is looked up (one batched query) against `catalog-categories` and a match overlays the live category name onto the label — the authored URL always stays the route (there is no live "route" field), and there is no live product count (this block has no count display slot). Three variants: scrollable default tiles, minimal text links, and a fixed-column grid with thumbnails.

When to use
  • A shop or catalog landing page that needs top-level category navigation
  • A "browse by category" row above a product grid
  • A homepage entry point that routes visitors into product collections
Page types
  • marketing
  • landing
  • about
How to use

Author a `categories` array (label required; optional slug, URL, and icon/emoji); toggle horizontal scrolling. The slug is the v1 hydration lookup key: with @wabbit/tome-catalog present and hydration wired in, a matching `catalog-categories` doc overlays its live `name` onto the tile's label — nothing else. The authored URL is always the route used (there is no live route to overlay it with), and an unmatched slug simply keeps its authored label. Choose the variant: default for icon tiles in a scroll strip, minimal for a clean text-link row, grid for a thumbnailed category board. This is navigation — it routes to product listings rather than displaying products, so it typically sits above a `product-grid`.

Pairs with
  • product-grid
  • product-card
  • featured-product
Precedes
  • product-grid
  • featured-product
Avoid when
  • You want to display products, not links to category pages — use `product-grid`
  • There is only one category — a strip of one is pointless
  • The page needs a product spotlight, not navigation — use `featured-product`
Register in

structural

price-table

A side-by-side pricing comparison — up to five tiers, each with a name, price, billing note, description, a per-tier feature checklist (included/excluded), a recommended-highlight flag, and its own CTA. Fully authored content with no catalog peer required. Three variants: default multi-tier table, minimal single highlighted plan, and dark with an accent on the recommended tier.

When to use
  • A pricing page comparing plan or package tiers where features differ by tier
  • A "choose your plan" section where the visitor picks among recurring options
  • A product page offering good/better/best variants of the same offering
Page types
  • pricing
  • landing
  • marketing
How to use

Author a `tiers` array (1-5): each tier needs a name, price, and CTA label/URL, plus optional price-note, description, a features array (each with an included checkbox), and a highlight flag for the recommended plan. Choose the variant: default for two-to-four comparable tiers, minimal when there is effectively one plan to present, dark to make the table the page's focal band. Use this when features-per-tier is the decision; for a single product's buy action use `product-card` or `featured-product`.

Pairs with
  • featured-product
  • product-card
  • product-grid
  • category-strip
Follows
  • featured-product
Avoid when
  • There is a single offer with one price — a `product-card` or `featured-product` CTA is enough
  • You are listing distinct products rather than tiers of one offering — use `product-grid`
  • Tiers have identical features and differ only in price — a simple list beats a comparison table
Register in

marketing-landing

inventory-badge

A small inline stock-status indicator — In Stock / Low Stock / Out of Stock, with a low-stock threshold and an optional exact count. Fully static — always shows the authored status; no @wabbit/tome-catalog peer required. Three variants: colored pill default, text-only minimal, and a with-count form ("Only 3 left").

When to use
  • Inline beside a product title or price to signal availability
  • A scarcity cue ("Low Stock") on a product or featured-product block
  • A catalog row where stock state affects whether the visitor can buy
Page types
  • marketing
  • landing
  • about
How to use

Provide a SKU, the status to show, a low-stock threshold, and an optional exact count for the with-count variant. Unlike product-card/featured-product/category-strip, this block is NOT part of the v1 hydration wave (`@wabbit/tome-blocks-catalog-pack/server`) — a deliberate decision, not an oversight: @wabbit/tome-catalog's Product schema (packages/catalog/src/collections/Products.ts) has no stock/quantity/inventory field at all, and its `status` (draft/active/archived) is a content-publish flag, not availability — anonymous reads only ever see `status: active` anyway (publicReadActiveOnly), so it carries zero real signal for this badge even if repurposed. The SKU field is kept (matching product-card/featured-product's shape for pairing) but is not looked up against anything. Trigger to revisit: if @wabbit/tome-catalog ever ships a stock/quantity field or a dedicated inventory collection, wire a resolveInventoryBadgeData resolver into @wabbit/tome-blocks-catalog-pack/server the same way the other three blocks are wired. Choose the variant: default for a colored pill, minimal for an unobtrusive text status, with-count when exact remaining quantity adds urgency. This is a status accent, not a standalone section — pair it with a product block rather than placing it alone.

Pairs with
  • product-card
  • featured-product
  • product-grid
Follows
  • product-card
  • featured-product
Avoid when
  • As a standalone block with no product context — it is an accent on a product
  • Stock state is irrelevant to the page (e.g. digital/always-available goods)
  • You need full pricing or purchase UI — use `product-card` or `price-table`
Register in

application

featured-product

A full-width hero spotlight for one product — eyebrow, title, description, price with compare-at strike, a highlights list, a badge, and a primary CTA. Renders the authored props by default; no @wabbit/tome-catalog peer required. When a consumer wires `createHydratedRenderers` from `@wabbit/tome-blocks-catalog-pack/server` AND the SKU matches an active `catalog-products` doc, title and description hydrate live over the authored fallback — anonymous-visible data only. Three variants: split (image left / details right), centered (image above, details below), and dark for a premium treatment.

When to use
  • The hero of a product or campaign landing page built around a single item
  • A "product of the month" or new-release spotlight above the catalog grid
  • Any place one product deserves full-width emphasis rather than a card
Page types
  • marketing
  • landing
  • about
How to use

Author title (required) and CTA label (required), plus eyebrow, description, price/compare-at, SKU, a highlights array (up to 8), an optional badge, and the CTA URL. The SKU is the v1 hydration lookup key: with @wabbit/tome-catalog present and `createHydratedRenderers` wired in, a matching `catalog-products` doc overlays its `title` and `excerpt` (into description) over these authored fields — nothing else. Price, compare-at price, highlights, badge, and the CTA all ALWAYS stay authored: @wabbit/tome-catalog carries no pricing/stock/highlights field in v1. Choose the variant: split for a classic image-and-details hero, centered for a symmetrical full-width feel, dark for premium contrast. This is the single-product hero — for several products use `product-grid`, and for a compact single item use `product-card`. Typically the lead block, followed by a grid of related products.

Pairs with
  • product-grid
  • product-card
  • inventory-badge
  • price-table
  • category-strip
Precedes
  • product-grid
  • price-table
  • inventory-badge
Avoid when
  • Showing many products — use `product-grid`; this block is for one hero item
  • A compact list-style product entry — use `product-card`
  • Comparing plan tiers rather than spotlighting a product — use `price-table`
Register in

marketing-landing

Exports

  • @wabbit/tome-blocks-catalog-pack
  • @wabbit/tome-blocks-catalog-pack/render
  • @wabbit/tome-blocks-catalog-pack/render/register
  • @wabbit/tome-blocks-catalog-pack/demo
  • @wabbit/tome-blocks-catalog-pack/meta
  • @wabbit/tome-blocks-catalog-pack/server

Changelog

v0.18.0patch

c3468b0: Layer detection now runs through blocks-core's `createLayerProbe` instead of a local `tryGetLayerRegistry` copy. The dynamic core import stays in this pack, and memo semantics 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: Layer detection now runs through blocks-core's `createLayerProbe` instead of a local `tryGetLayerRegistry` copy. The dynamic core import stays in this pack, and memo semantics 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.5patch

@wabbit/tome-blocks-core@0.16.0

  • @wabbit/tome-blocks-core@0.16.0
v0.16.4patch

@wabbit/tome-blocks-core@0.16.0

  • @wabbit/tome-blocks-core@0.16.0
v0.16.3patch

@wabbit/tome-blocks-core@0.16.0

  • @wabbit/tome-blocks-core@0.16.0
v0.16.2patch

@wabbit/tome-blocks-core@0.16.0

  • @wabbit/tome-blocks-core@0.16.0
v0.16.1patch

@wabbit/tome-blocks-core@0.16.0

  • @wabbit/tome-blocks-core@0.16.0
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.
  • 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
v0.15.23patch

@wabbit/tome-blocks-core@0.15.9

  • @wabbit/tome-blocks-core@0.15.9
v0.15.22patch

@wabbit/tome-blocks-core@0.15.9

  • @wabbit/tome-blocks-core@0.15.9
v0.15.21patch

@wabbit/tome-blocks-core@0.15.9

  • @wabbit/tome-blocks-core@0.15.9
v0.15.20patch

@wabbit/tome-blocks-core@0.15.9

  • @wabbit/tome-blocks-core@0.15.9
v0.15.19patch

@wabbit/tome-blocks-core@0.15.9

  • @wabbit/tome-blocks-core@0.15.9
v0.15.18patch

@wabbit/tome-blocks-core@0.15.9

  • @wabbit/tome-blocks-core@0.15.9
v0.15.17patch

@wabbit/tome-blocks-core@0.15.9

  • @wabbit/tome-blocks-core@0.15.9
v0.15.16patch

@wabbit/tome-blocks-core@0.15.9

  • @wabbit/tome-blocks-core@0.15.9
v0.15.15patch

@wabbit/tome-blocks-core@0.15.9

  • @wabbit/tome-blocks-core@0.15.9
v0.15.14patch

@wabbit/tome-blocks-core@0.15.9

  • @wabbit/tome-blocks-core@0.15.9
v0.15.13patch

@wabbit/tome-blocks-core@0.15.9

  • @wabbit/tome-blocks-core@0.15.9
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.
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).
v0.15.9patch

Updated dependencies [71d3b09] - @wabbit/tome-blocks-core@0.15.9

  • Updated dependencies [71d3b09] - @wabbit/tome-blocks-core@0.15.9
v0.15.8patch

Updated dependencies [6779aa1] - @wabbit/tome-blocks-core@0.15.8

  • Updated dependencies [6779aa1] - @wabbit/tome-blocks-core@0.15.8
v0.15.7patch

@wabbit/tome-blocks-core@0.15.0

  • @wabbit/tome-blocks-core@0.15.0
v0.15.5patch

8fbbaf5: Starter-launch fixes across four packages: - **tome-chrome:** Navbar5's desktop menu now hides on mobile — the responsive `.desktopMenu` class moved to a wrapper `<div>` so tome-ui's `navigation-menu` root rule (`display: flex`) no longer clobbers the `display: none` toggle below 64em (the bar was blowing out to ~500px on phones, pushing the hamburger off-canvas). - **tome-blocks-lms-pack:** CourseCard no longer renders the rating star twice — the JSX `★` is removed; the styleable `.tome-course-card__rating::before` star in styles.css is the single source. - **tome-blocks-catalog-pack:** CategoryStrip renders real lucide icons for kebab-case icon names (target, joystick, book-open, settings, package) instead of painting the raw name as text; unmapped names render nothing, authored emoji still render. Adds `lucide-react` as a peer dependency (`>=0.460.0`, matching tome-chrome). - **tome-blocks-content-writer:** archive, related-posts, and blog catalog copy (meta `description` / `usage.summary`) now leads with the supported mode and frames unimplemented query-driven modes as roadmap scope instead of "renders nothing". No behavior change. - **tome-blocks-org-pack:** CampaignBanner drops its 20rem min-height when no `bannerUrl` is set — the floor exists to give the banner image room; without one it rendered a tall empty box above the bottom-anchored content.

  • 8fbbaf5: Starter-launch fixes across four packages: - **tome-chrome:** Navbar5's desktop menu now hides on mobile — the responsive `.desktopMenu` class moved to a wrapper `<div>` so tome-ui's `navigation-menu` root rule (`display: flex`) no longer clobbers the `display: none` toggle below 64em (the bar was blowing out to ~500px on phones, pushing the hamburger off-canvas). - **tome-blocks-lms-pack:** CourseCard no longer renders the rating star twice — the JSX `★` is removed; the styleable `.tome-course-card__rating::before` star in styles.css is the single source. - **tome-blocks-catalog-pack:** CategoryStrip renders real lucide icons for kebab-case icon names (target, joystick, book-open, settings, package) instead of painting the raw name as text; unmapped names render nothing, authored emoji still render. Adds `lucide-react` as a peer dependency (`>=0.460.0`, matching tome-chrome). - **tome-blocks-content-writer:** archive, related-posts, and blog catalog copy (meta `description` / `usage.summary`) now leads with the supported mode and frames unimplemented query-driven modes as roadmap scope instead of "renders nothing". No behavior change. - **tome-blocks-org-pack:** CampaignBanner drops its 20rem min-height when no `bannerUrl` is set — the floor exists to give the banner image room; without one it rendered a tall empty box above the bottom-anchored content.
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
v0.14.0patch

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

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

Updated dependencies [f4d55c9] - @wabbit/tome-blocks-core@0.13.0

  • Updated dependencies [f4d55c9] - @wabbit/tome-blocks-core@0.13.0
v0.12.1patch

New token `--tome-color-on-solid-dark` (light text paired with `--tome-color-surface-solid-dark`). The inverse family's pairing contract is now documented: `on-inverse` is dark text FOR `surface-inverse` (white) — pairing it with the black solid-dark surface renders black-on-black. Fixed the consumers that made that pairing: chrome Footer 11 (Ledger), lms-pack's enrollment-cta dark variant, catalog-pack's FeaturedProduct/PriceTable dark variants — all now use `on-solid-dark` with a `surface-inverse` fallback for older tome-ui.

  • New token `--tome-color-on-solid-dark` (light text paired with `--tome-color-surface-solid-dark`). The inverse family's pairing contract is now documented: `on-inverse` is dark text FOR `surface-inverse` (white) — pairing it with the black solid-dark surface renders black-on-black. Fixed the consumers that made that pairing: chrome Footer 11 (Ledger), lms-pack's enrollment-cta dark variant, catalog-pack's FeaturedProduct/PriceTable dark variants — all now use `on-solid-dark` with a `surface-inverse` fallback for older tome-ui.
v0.12.0minor

Neutralize pack-block base styling to the --tome-\* token system (extract-don't-delete; the brand treatment moved to @wabbit/tome-blocks-industrial-theme). - lms-pack: CourseCard.module.css rewritten tokens-only — the legacy safety-yellow CTA/featured strip, charcoal italic type, and hard-coded gray palette are gone from the base; the card now inherits the consuming site's theme. - catalog-pack: ProductGrid.module.css and CategoryStrip.module.css rewritten tokens-only (same extraction). - catalog-pack: FeaturedProduct, ProductCard, PriceTable, and InventoryBadge previously shipped NO styles and rendered as bare text stacks; each now has a neutral token-driven CSS module baseline (org-pack pattern), so they render designed-neutral on any consumer out of the box. Visual-breaking for consumers that relied on the baked-in industrial look: opt back in with @wabbit/tome-blocks-industrial-theme (one stylesheet import + data-tome-theme="industrial").

  • Neutralize pack-block base styling to the --tome-\* token system (extract-don't-delete; the brand treatment moved to @wabbit/tome-blocks-industrial-theme). - lms-pack: CourseCard.module.css rewritten tokens-only — the legacy safety-yellow CTA/featured strip, charcoal italic type, and hard-coded gray palette are gone from the base; the card now inherits the consuming site's theme. - catalog-pack: ProductGrid.module.css and CategoryStrip.module.css rewritten tokens-only (same extraction). - catalog-pack: FeaturedProduct, ProductCard, PriceTable, and InventoryBadge previously shipped NO styles and rendered as bare text stacks; each now has a neutral token-driven CSS module baseline (org-pack pattern), so they render designed-neutral on any consumer out of the box. Visual-breaking for consumers that relied on the baked-in industrial look: opt back in with @wabbit/tome-blocks-industrial-theme (one stylesheet import + data-tome-theme="industrial").
v0.11.2patch

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

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

a5db69f: v1 hydration contract: new `@wabbit/tome-blocks-catalog-pack/server` subpath (`import 'server-only'`). `createHydratedRenderers({ getPayload })` returns server-component wrappers for `product-card`, `featured-product`, and `category-strip` — spread over the pack's static `components` map to overlay verified live `@wabbit/tome-catalog` fields on top of authored props, with zero client fetching and zero loading state. Per-block resolvers (`resolveProductCardData`, `resolveFeaturedProductData`, `resolveCategoryStripData`) are exported individually for use outside the block pipeline. The v1 matrix (verified against the real `@wabbit/tome-catalog` schema, not the original design guess): `product-card`/`featured-product` overlay `title` and `description` (from `Product.excerpt`) keyed by `sku` — `price`/`comparePrice`/`image`/CTA stay authored, since `@wabbit/tome-catalog` carries no pricing or stock field at all and neither block has an `image` render prop. `category-strip` overlays each item's `label` (from `Category.name`) keyed by `slug`, in one batched query — `url` stays authored and there is no live count (no render slot for one). `inventory-badge` is deliberately NOT hydrated in v1: `@wabbit/tome-catalog` models no stock/inventory field anywhere. `price-table` stays fully static by design (unchanged). `product-grid` is out of scope for this wave (its own `'use client'` boundary). Access boundary: every hydration query runs `overrideAccess: false` with no `user` — anonymous-visible data only (`publicReadActiveOnly` restricts Products to `status: active`; Categories are always public). Layer absent, entity missing, or a thrown query all fall back to the untouched authored props — never a crash, never an empty hole. Static usage is completely untouched: every block still renders exactly the authored props by default, with zero peer dependency and zero behavior change, whether or not a consumer ever imports `/server`.

  • a5db69f: v1 hydration contract: new `@wabbit/tome-blocks-catalog-pack/server` subpath (`import 'server-only'`). `createHydratedRenderers({ getPayload })` returns server-component wrappers for `product-card`, `featured-product`, and `category-strip` — spread over the pack's static `components` map to overlay verified live `@wabbit/tome-catalog` fields on top of authored props, with zero client fetching and zero loading state. Per-block resolvers (`resolveProductCardData`, `resolveFeaturedProductData`, `resolveCategoryStripData`) are exported individually for use outside the block pipeline. The v1 matrix (verified against the real `@wabbit/tome-catalog` schema, not the original design guess): `product-card`/`featured-product` overlay `title` and `description` (from `Product.excerpt`) keyed by `sku` — `price`/`comparePrice`/`image`/CTA stay authored, since `@wabbit/tome-catalog` carries no pricing or stock field at all and neither block has an `image` render prop. `category-strip` overlays each item's `label` (from `Category.name`) keyed by `slug`, in one batched query — `url` stays authored and there is no live count (no render slot for one). `inventory-badge` is deliberately NOT hydrated in v1: `@wabbit/tome-catalog` models no stock/inventory field anywhere. `price-table` stays fully static by design (unchanged). `product-grid` is out of scope for this wave (its own `'use client'` boundary). Access boundary: every hydration query runs `overrideAccess: false` with no `user` — anonymous-visible data only (`publicReadActiveOnly` restricts Products to `status: active`; Categories are always public). Layer absent, entity missing, or a thrown query all fall back to the untouched authored props — never a crash, never an empty hole. Static usage is completely untouched: every block still renders exactly the authored props by default, with zero peer dependency and zero behavior change, whether or not a consumer ever imports `/server`.
  • 6bc419c: sc-pack: dead CSS-copy tsup hook deleted (the pack ships zero CSS); its deliberately-lightweight profile (no meta.ts, rides tome-sc's token theme) is now documented in the source header with the convergence trigger (gallery browse surface needs meta). `./demo` subpath rule: all 10 renderer packs now expose it — added to org/lms/catalog/sc packs plus agency-essentials (found missing in the consistency sweep); verified the demo import graph never reaches registering code.
  • 36e537a: Documentation truth pass: all "hydrates from @wabbit/tome-X when present" claims across READMEs, block meta, bundle descriptions, render headers, and admin field descriptions are rewritten to the honest contract — these blocks are fully static today; the layer-presence flags are the seam for a future hydration wave (trigger documented in place). content-writer's `RelatedPosts` (auto mode) and `Archive` (collection mode) no longer render fake placeholder UI — the unimplemented modes render nothing and say so in the admin field description.
  • 36e537a: Peer/dependency contracts now tell the truth. blocks-core: importing the root barrel no longer hard-crashes when the optional peers (`@wabbit/tome-core`, `@wabbit/tome-catalog`) are absent — `productHooks` registration is lazily guarded; NEW explicit `registerBlockBundleProductType()` export (root barrel + `./registry/productHooks` subpath) for deterministic, format-safe registration from `payload.config.ts` (the import-time auto path no-ops under native ESM, which affects `generate:types`-visible product-type options — call the explicit API when composing catalog). chrome: `next` is now a required peer (`>=14`) — it was declared optional while `next/navigation`/`next/link` were hard-imported. readout: declares its real `next` peer; `createReadoutBlocks({ accentPalette })` is now implemented (field-tree narrowing, dispatch's mechanism) instead of a documented no-op. blocks-lms-pack / blocks-catalog-pack: `@wabbit/tome-core` moves from hard `dependencies` to `optionalDependencies`, matching org-pack and the packs' own documented degrade-gracefully design.
  • 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.
  • 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.
  • Updated dependencies [26dfa07]
  • Updated dependencies [36e537a]
  • Updated dependencies [36e537a]
  • Updated dependencies [36e537a]
  • Updated dependencies [a93f478]
  • Updated dependencies [5f78397]
  • Updated dependencies [5f78397]
  • Updated dependencies [aef2725] - @wabbit/tome-blocks-core@0.11.0
v0.10.3patch

Updated dependencies [66f394b] - @wabbit/tome-core@1.3.4 - @wabbit/tome-blocks-core@0.10.0

  • Updated dependencies [66f394b] - @wabbit/tome-core@1.3.4 - @wabbit/tome-blocks-core@0.10.0
v0.10.1patch

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

  • Updated dependencies - @wabbit/tome-core@1.3.3 - @wabbit/tome-blocks-core@0.10.0
v0.10.0patch

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

  • Updated dependencies - @wabbit/tome-blocks-core@0.10.0
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]
  • Updated dependencies [850d51c] - @wabbit/tome-blocks-core@0.9.4 - @wabbit/tome-core@1.2.1
v0.9.2patch

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

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

Updated dependencies [c7d0afc] - @wabbit/tome-blocks-core@0.9.0

  • Updated dependencies [c7d0afc] - @wabbit/tome-blocks-core@0.9.0
v0.8.0minor

249b670: Batch 6 (domain packs) + Batch 7 (primitives + usage manifest) of the 2026-06-27 inserter/variant architecture — audit + usage/intent authoring (Decision 4). The domain packs pull from distinct collections (courses / products / members / fleet), so per the spec they stay schema-distinct — this is metadata, not consolidation. - **Authored usage/intent metadata** on all 25 domain blocks: LMS (course-card, lesson-list, progress-bar, quiz-summary, instructor-card, enrollment-cta), Catalog (product-card, product-grid, category-strip, price-table, inventory-badge, featured-product), Org (division-card, member-card, member-grid, event-calendar, event-list-item, org-chart, document-link, campaign-banner), SC (fleet-summary, signal-hero-sc, task-force-roster, op-briefing-panel, rsi-handle-card). Also authored usage on the 9 free `extras-primitives` (section, spacer, grid, stacking-wrapper, content, text-block, code, section-header, content-two-column — in @wabbit/tome-blocks-extras, already bumping). - **Audit (clean):** no dual-mechanism drift, no slug-splits, and the card-vs-grid / item-vs-calendar pairs are genuine single-object-vs-array shape differences (NOT layout variants) — correctly kept as separate blocks. The relationship is encoded in each block's `usage.pairsWith`/`avoidWhen` so an assembling agent picks the right one. - **`buildUsageManifest` verified end-to-end** (@wabbit/tome-blocks-core, Batch 0): builds a sane manifest from the now-authored descriptors — `byRegister` (application / editorial / marketing-landing / structural / dossier), `byPageType`, variant flow-through, and `unauthored` tracking. The consumer-side manifest generation + exposure to assembling agents is a live-run wiring step. Note: the domain packs use the inline-meta pattern (BlockMeta passed to `defineBlock` in each block's index.ts), so `usage` was added there. Ships in the linked family's 0.8.0 minor.

  • 249b670: Batch 6 (domain packs) + Batch 7 (primitives + usage manifest) of the 2026-06-27 inserter/variant architecture — audit + usage/intent authoring (Decision 4). The domain packs pull from distinct collections (courses / products / members / fleet), so per the spec they stay schema-distinct — this is metadata, not consolidation. - **Authored usage/intent metadata** on all 25 domain blocks: LMS (course-card, lesson-list, progress-bar, quiz-summary, instructor-card, enrollment-cta), Catalog (product-card, product-grid, category-strip, price-table, inventory-badge, featured-product), Org (division-card, member-card, member-grid, event-calendar, event-list-item, org-chart, document-link, campaign-banner), SC (fleet-summary, signal-hero-sc, task-force-roster, op-briefing-panel, rsi-handle-card). Also authored usage on the 9 free `extras-primitives` (section, spacer, grid, stacking-wrapper, content, text-block, code, section-header, content-two-column — in @wabbit/tome-blocks-extras, already bumping). - **Audit (clean):** no dual-mechanism drift, no slug-splits, and the card-vs-grid / item-vs-calendar pairs are genuine single-object-vs-array shape differences (NOT layout variants) — correctly kept as separate blocks. The relationship is encoded in each block's `usage.pairsWith`/`avoidWhen` so an assembling agent picks the right one. - **`buildUsageManifest` verified end-to-end** (@wabbit/tome-blocks-core, Batch 0): builds a sane manifest from the now-authored descriptors — `byRegister` (application / editorial / marketing-landing / structural / dossier), `byPageType`, variant flow-through, and `unauthored` tracking. The consumer-side manifest generation + exposure to assembling agents is a live-run wiring step. Note: the domain packs use the inline-meta pattern (BlockMeta passed to `defineBlock` in each block's index.ts), so `usage` was added there. Ships in the linked family's 0.8.0 minor.
  • Updated dependencies [249b670] - @wabbit/tome-blocks-core@0.8.0
v0.7.0patch

Updated dependencies [66c611c] - @wabbit/tome-blocks-core@0.7.0

  • Updated dependencies [66c611c] - @wabbit/tome-blocks-core@0.7.0
v0.6.2patch

Updated dependencies [a9801fe]

  • Updated dependencies [a9801fe]
  • Updated dependencies [baf401e]
  • Updated dependencies [4b2f368] - @wabbit/tome-core@1.1.0 - @wabbit/tome-blocks-core@0.6.2
v0.5.9patch

Updated dependencies [8947ff1] - @wabbit/tome-core@1.0.12 - @wabbit/tome-blocks-core@0.5.9

  • Updated dependencies [8947ff1] - @wabbit/tome-core@1.0.12 - @wabbit/tome-blocks-core@0.5.9
v0.5.7patch

Updated dependencies [36dc023]

  • Updated dependencies [36dc023]
  • Updated dependencies [2612799] - @wabbit/tome-core@1.0.11 - @wabbit/tome-blocks-core@0.5.7
v0.5.0minor

Linked cohort version alignment (no functional change in this package).

  • Linked cohort version alignment (no functional change in this package).
v0.4.2patch

**Re-publish: rewrite `workspace:*` to actual semver in dependencies.** Earlier 0.4.0/0.4.1 publishes used `npm publish` directly, which doesn't rewrite `workspace:*` deps. Verdaccio captured the literal `"workspace:*"` strings in the published `package.json` `dependencies` fields, so npm consumers (e.g. wabbit-site-core) failed to install with `EUNSUPPORTEDPROTOCOL workspace:*`. This changeset triggers a coordinated patch bump across the linked blocks-_ group (already 0.4.1 → 0.4.2) plus motion, lms-pack, catalog-pack, and the previously-untouched blocks-core. Re-publish flow uses `pnpm publish` which rewrites `workspace:_` to the actual version of the workspace dep at publish time. No source changes — purely a publish-pipeline correction.

  • **Re-publish: rewrite `workspace:*` to actual semver in dependencies.** Earlier 0.4.0/0.4.1 publishes used `npm publish` directly, which doesn't rewrite `workspace:*` deps. Verdaccio captured the literal `"workspace:*"` strings in the published `package.json` `dependencies` fields, so npm consumers (e.g. wabbit-site-core) failed to install with `EUNSUPPORTEDPROTOCOL workspace:*`. This changeset triggers a coordinated patch bump across the linked blocks-_ group (already 0.4.1 → 0.4.2) plus motion, lms-pack, catalog-pack, and the previously-untouched blocks-core. Re-publish flow uses `pnpm publish` which rewrites `workspace:_` to the actual version of the workspace dep at publish time. No source changes — purely a publish-pipeline correction.
  • Updated dependencies - @wabbit/tome-blocks-core@0.4.2
v0.4.0patch

Updated dependencies [b76f684] - @wabbit/tome-blocks-core@0.4.0

  • Updated dependencies [b76f684] - @wabbit/tome-blocks-core@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