Blocks Gallery

FoundationStable
@wabbit/tome-blocks-galleryv1.10.0

Shared browse + detail + code-viewer + entitlement-aware gallery for @wabbit/tome-blocks-* packs. Ships NO Payload collections. The /components subpath imports CSS modules and is browser/Next.js-only — do NOT import it from Payload config or generate:types evaluation paths.

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-gallery

Overview

@wabbit/tome-blocks-gallery

Shared browse + detail + code-viewer + entitlement-aware storefront gallery for @wabbit/tome-blocks-* packs. blocks layer per root ARCHITECTURE.md, but deliberately excluded from the linked-versioning blocks family — it versions independently. Depends only on @wabbit/tome-blocks-core.

Install

pnpm add @wabbit/tome-blocks-gallery

| Peer | Range | Notes | |---|---|---| | @wabbit/tome-blocks-core | >=0.11.0 <1 | | | next | >=14 | required (peerDependenciesMeta.next.optional: false — explicitly non-optional despite the field existing) | | react / react-dom | >=19.0.0 | |

shiki (syntax highlighting) is a regular dependency, not a peer.

Payload-free storefront posture

This package ships no Payload collections. Its own package description states the boundary directly: "Do NOT import this package (or any subpath) into a Payload `blocks` array or from a Payload collection config. This package ships NO Payload collections. Importing it into Payload config evaluation paths that touch the `/components` subpath will crash with a CSS parse error." It exists purely as a browse/detail UI layer that sits in front of the real block packs (tome-blocks-marketing-starter, tome-blocks-lms-pack, etc.) — those packs are the actual Payload block registrations; this package renders a catalog of them.

60-second quickstart

// A blocks-*-pack registers itself with the gallery at build/manifest time
import { defineGalleryPack } from '@wabbit/tome-blocks-gallery'
// Server (build script or RSC) — build the manifest, CSS-free.
// Importing each blocks-*-pack's defineGalleryPack() call registers it as a
// side effect; getRegisteredPacks() then returns every pack registered so far.
import { buildGalleryManifest } from '@wabbit/tome-blocks-gallery/server'
import { buildEntitlementState, getRegisteredPacks } from '@wabbit/tome-blocks-gallery'

const manifest = buildGalleryManifest(getRegisteredPacks())
const entitlementState = buildEntitlementState(await getEntitlements(session)) // [] in demo/starter mode
// Client/Next.js — render the browse index (verified pattern from wabbit-site-core's
// src/marketplace/gallery/GalleryApp.tsx). GalleryIndex reads entitlements from
// context, not props — wrap it in TomeBlocksGalleryProvider and set them there.
import { GalleryIndex, TomeBlocksGalleryProvider } from '@wabbit/tome-blocks-gallery/components'

<TomeBlocksGalleryProvider config={{ manifest, entitlementState, AcquireCTA }}>
  <GalleryIndex manifest={manifest} />
</TomeBlocksGalleryProvider>

`GalleryIndex`'s own `entitlementState` and `AcquireCTA` props are currently ignored — it throws if mounted without an enclosing TomeBlocksGalleryProvider, and always reads entitlements/CTA from that provider's config, not from these props. manifest is still required directly on GalleryIndex (it is not read from context).

Rich text in previews. Set config.RichTextAdapter (a blocks-core RichTextAdapter) and the provider mounts blocks-core's RichTextAdapterProvider around the gallery tree, so previewed blocks render rich text with your adapter. Leave it out and they use an adapter you registered with registerBlockAdapters(), else blocks-core's built-in Lexical renderer. Pass a stable object, since the Provider also writes it into the process-wide adapter registry.

Public API

| Export | Subpath | Description | |---|---|---| | defineGalleryPack, variantsFromValues | root | Pack registration API — a blocks-*-pack calls this to list itself in the gallery | | deriveGalleryCategory, GALLERY_CATEGORY_RULES | root | Catalog browse-axis category derivation | | galleryCategoryCounts, categoriesAtLeast, orderCategoriesBySize, pickCategoryRepresentative | root | Category-level helpers for the Grouped view and per-category pages | | parseCatalogUrlState, serializeCatalogUrlState | root | Catalog URL state (view, category) | | registerGalleryPack, getRegisteredPacks, clearRegistry | root | Package-level registry | | buildGalleryManifest(packs, options?) | root + ./server | Manifest builder — packs is a GalleryPackRegistration[] (typically getRegisteredPacks()), options.thumbsManifest is optional; root re-export is for convenience, ./server is the explicit server-import path | | buildEntitlementState(allowedBundleSlugs) | root | Factory: string[] → EntitlementState | | extractGallerySource, resolveBlockSource, getBlockSourceFull | ./server | Build-time source-code snapshot + entitlement-gated reveal | | GalleryIndex, browse/bundle/detail/variant-card/preview/badge components | ./components | Browse surfaces | | GalleryCodeViewer, GalleryCodeTabs, highlightCode | ./components | Shiki-highlighted code viewer with lock/teaser states | | TomeBlocksGalleryProvider, useGalleryEntitlements, resolveLockState, component render-map registry | ./components | Entitlement context provider (required ancestor of GalleryIndex/GalleryBlockDetail/GalleryBundlePage) + registry | | GalleryTier, GalleryPackRegistration, GalleryBlockEntry, GalleryVariant, GalleryManifest, EntitlementState, GalleryLockState, GalleryAcquireCTAProps, GalleryAcquireCTAComponent, TomeBlocksGalleryConfig, etc. | ./types (re-exported from root) | Full type surface |

Uniform catalog, Grouped view and category pages

Everything here is opt-in. A host that passes none of these props renders the classic catalog as before; the one change for every host is that a card no longer swaps in the live block on hover.

GalleryIndex props:

| Prop | Default | What it does | |---|---|---| | variant | 'classic' | 'uniform': equal-size tiles (tinted 16:10 preview, name, pack line, tier badge in the corner), no featured plate, no description; a column grid of 4 / 3 / 2 / 1 at ≥1800 / ≥1280 / ≥768 / below; the result count inside the search field; a Columns control (−1 / 0 / +1 from the base, labelled with the real counts, saved in localStorage, disabled under 1280px and hidden under 768px) | | grouping | false | Uniform: an All / Grouped toggle. Grouped shows one tile per category: representative preview, label, "N blocks" | | categorySlug | — | One category only: the same toolbar and grid, no category sections, no view toggle. For a host's per-category page; the host renders the header and anything around the grid | | categoryHref | — | (slug, count) => string, the href of a Grouped tile. Without it a tile switches to the All view filtered to that category | | minCategorySize | 3 | Grouped order: categories with at least this many blocks first, then the rest, each in manifest order | | categoryRepresentatives | — | { [categorySlug]: blockSlug \| 'bundle/blockSlug' \| null }, overriding the Grouped preview; null shows a labelled empty frame | | searchPlaceholder | classic text, or "Search N blocks" (uniform) | The search field's placeholder and accessible name | | columnsStorageKey | 'tome-gallery-columns' | localStorage key for the Columns choice | | pendingPreviewLabel | 'Preview pending' | Text in a Grouped tile with no preview |

GalleryCatalogProvider props: defaultView ('all', or 'grouped' to open on Grouped; it is also the view a bare URL means), initialView and initialCategory (seeds, e.g. from server searchParams), and syncUrl (default false). With syncUrl the provider reads view (all / grouped / bundle) and category from the URL on mount and on back/forward, and writes them with history.replaceState when they change: no navigation, no scroll jump, other params kept. With a Grouped default, /blocks is Grouped and /blocks?view=all&category=faq is the All view filtered to FAQ.

GalleryRail props: categoryHref renders the category rows as links (the active row gets aria-current="page"), and allHref does the same for "All blocks". A link to the page already on screen also applies its view / category to the catalog, so the click lands without a remount. The rail renders no title of its own; the host's sidebar header owns that.

Helpers (root, CSS-free): galleryCategoryCounts, categoriesAtLeast(categories, n), orderCategoriesBySize(categories, minSize), pickCategoryRepresentative(category, overrides?), parseCatalogUrlState(search, { defaultView, validCategories }) and serializeCatalogUrlState(search, state, { defaultView }). From ./components: the column helpers (baseColumnsForWidth, columnControlState, columnOptions, columnsForWidth, effectiveColumnDelta, parseColumnDelta, GALLERY_COLUMN_BREAKPOINTS).

Theming: --tome-gallery-tint-1 … --tome-gallery-tint-6 (the six preview grounds; defaults are color-mix blends of the theme's secondary, chart and primary tokens with the surface), --tome-gallery-plate-inset (9%), --tome-gallery-search-height, --tome-gallery-tile-hover-color (accent).

import { categoriesAtLeast, parseCatalogUrlState } from '@wabbit/tome-blocks-gallery'
import { GalleryCatalogProvider, GalleryIndex, GalleryRail } from '@wabbit/tome-blocks-gallery/components'

const categoryHref = (slug: string, count: number) =>
  count >= 3 ? `/blocks/category/${slug}` : `/blocks?view=all&category=${slug}`

// Index: Grouped by default, URL-backed.
const seed = parseCatalogUrlState(searchParams, { defaultView: 'grouped' })
<GalleryCatalogProvider manifest={manifest} defaultView="grouped" initialView={seed.view} initialCategory={seed.category} syncUrl>
  <GalleryRail categoryHref={categoryHref} allHref="/blocks" />
  <GalleryIndex manifest={manifest} rail="external" variant="uniform" grouping categoryHref={categoryHref} />
</GalleryCatalogProvider>

// Category page: one category, no URL sync.
<GalleryCatalogProvider manifest={manifest} initialCategory="hero">
  <GalleryRail categoryHref={categoryHref} allHref="/blocks" />
  <GalleryIndex manifest={manifest} rail="external" variant="uniform" categorySlug="hero" searchPlaceholder="Search 14 hero blocks" />
</GalleryCatalogProvider>

// Which categories get a page: categoriesAtLeast(manifest.categories, 3)

Server / client posture

Three-way split, enforced by both convention and a runtime crash if violated:

  • root (`.`) — "CSS-FREE. Safe to import from server components and build scripts" (package's own header comment). Pack registration, category derivation, the package-level registry, and buildEntitlementState — no React, no CSS.
  • `./server` — CSS-free, safe in Payload config evaluation paths and build scripts. Manifest building + build-time source extraction.
  • `./components` — "imports CSS modules and is browser/Next.js-only." 15 of the package's 22 .tsx files declare 'use client'; the remainder (GalleryDetailChrome, GalleryPropsTable, GalleryVariantCard, SourceBadge, TierBadge, GalleryCodeViewer, GalleryCodeViewerHydrated) are presentational — GalleryCodeViewer, for example, mounts the 'use client' CopyButton as its only interactive island. Never import `./components` from a Payload `blocks` array or collection config — the package's own docs call this out as a crash, not a lint warning: CSS Module imports fail Payload's config-evaluation loader with a raw parse error.

The entitlement model

The gallery does not compute entitlements itself — the host computes them and passes an EntitlementState prop in:

interface EntitlementState {
  allowedBundles: Set<string>  // empty/omitted = anonymous / unsubscribed
  loading?: boolean            // true → gallery renders a skeleton
}

Internally, each block's GalleryLockState is derived from EntitlementState + the bundle's GalleryTier:

| State | Trigger | Preview | Code | CTA | |---|---|---|---|---| | free | bundle.tier === 'free' | yes | full | no | | unlocked | allowedBundles.has(bundleSlug) | yes | full | no | | locked | paid bundle, not in allowedBundles | yes | teaser | yes | | loading | entitlementState.loading === true | yes | skeleton | no |

The locked-state call-to-action is itself a host-supplied slot (AcquireCTA?: GalleryAcquireCTAComponent, receiving { bundleSlug, tier, blockSlug? }) — the gallery renders "Available in paid plan" generically when AcquireCTA is undefined (the mode tome-starter runs in, per buildEntitlementState([])'s own usage comment), and wires the real checkout flow when a host (wabbit-site-core's storefront) supplies one.

Extending

A new block pack lists itself via defineGalleryPack() (category, tier, variants, demo-props getter); the gallery's browse/detail surfaces pick it up through getRegisteredPacks() with no gallery-side code change.

Design history

  • Original design established the entitlement model (unlocked/locked/loading states, tier-gated teaser rendering) and the source-reveal contract for showing a block's code to an unlocked viewer.
  • docs/claude-gotchas.md → Module / Exports Contracts section — "Block-pack barrels must never pull React components" and "Config-time vs view split for tsx-evaluated modules" are the general form of this package's root-vs-./components split

Exports

  • @wabbit/tome-blocks-gallery
  • @wabbit/tome-blocks-gallery/types
  • @wabbit/tome-blocks-gallery/server
  • @wabbit/tome-blocks-gallery/components

Changelog

v1.10.0minor

4375c7f: The catalog gains an opt-in uniform layout, a Grouped view, per-category rendering and URL state, for hosts that build block category pages. - **`GalleryIndex variant="uniform"`**: equal-size tiles (a 16:10 preview on a tinted ground, the name, the pack line and a tier badge in the corner), with no featured plate and no description. The grid has 4 / 3 / 2 / 1 columns at ≥1800 / ≥1280 / ≥768 / below. A Columns control shifts that by −1 / 0 / +1, labelled with the real counts; the choice is saved in `localStorage`, and the control is disabled under 1280px and hidden under 768px. The result count sits inside the search field. Hover lifts and slightly enlarges the preview and colours the name with the accent. - **`grouping`** adds an All / Grouped toggle. Grouped shows one tile per category (representative preview, label, "N blocks"), with categories of at least `minCategorySize` (default 3) first. Each tile links through the host's `categoryHref(slug, count)`; `categoryRepresentatives` overrides the preview. - **`categorySlug`** renders a single category as one flat grid with the same toolbar, for a host's category page. `searchPlaceholder`, `columnsStorageKey` and `pendingPreviewLabel` are host-supplied text and keys. - **`GalleryCatalogProvider`** takes `defaultView`, `initialView`, `initialCategory` and an opt-in `syncUrl`, which reads `view` and `category` from the URL and writes them back with `history.replaceState` (no navigation, no scroll jump). `GalleryCatalogView` now includes `'grouped'`. - **`GalleryRail`** renders category rows as links when given `categoryHref` (and "All blocks" with `allHref`). - **New helpers**: `galleryCategoryCounts`, `categoriesAtLeast`, `orderCategoriesBySize`, `pickCategoryRepresentative`, `parseCatalogUrlState` and `serializeCatalogUrlState` from the root, plus the column helpers from `./components`. One change applies to every host: a catalog card no longer mounts the live block over its screenshot on hover. Everything else is opt-in; without the new props the catalog renders as before.

  • 4375c7f: The catalog gains an opt-in uniform layout, a Grouped view, per-category rendering and URL state, for hosts that build block category pages. - **`GalleryIndex variant="uniform"`**: equal-size tiles (a 16:10 preview on a tinted ground, the name, the pack line and a tier badge in the corner), with no featured plate and no description. The grid has 4 / 3 / 2 / 1 columns at ≥1800 / ≥1280 / ≥768 / below. A Columns control shifts that by −1 / 0 / +1, labelled with the real counts; the choice is saved in `localStorage`, and the control is disabled under 1280px and hidden under 768px. The result count sits inside the search field. Hover lifts and slightly enlarges the preview and colours the name with the accent. - **`grouping`** adds an All / Grouped toggle. Grouped shows one tile per category (representative preview, label, "N blocks"), with categories of at least `minCategorySize` (default 3) first. Each tile links through the host's `categoryHref(slug, count)`; `categoryRepresentatives` overrides the preview. - **`categorySlug`** renders a single category as one flat grid with the same toolbar, for a host's category page. `searchPlaceholder`, `columnsStorageKey` and `pendingPreviewLabel` are host-supplied text and keys. - **`GalleryCatalogProvider`** takes `defaultView`, `initialView`, `initialCategory` and an opt-in `syncUrl`, which reads `view` and `category` from the URL and writes them back with `history.replaceState` (no navigation, no scroll jump). `GalleryCatalogView` now includes `'grouped'`. - **`GalleryRail`** renders category rows as links when given `categoryHref` (and "All blocks" with `allHref`). - **New helpers**: `galleryCategoryCounts`, `categoriesAtLeast`, `orderCategoriesBySize`, `pickCategoryRepresentative`, `parseCatalogUrlState` and `serializeCatalogUrlState` from the root, plus the column helpers from `./components`. One change applies to every host: a catalog card no longer mounts the live block over its screenshot on hover. Everything else is opt-in; without the new props the catalog renders as before.
v1.9.8patch

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.
v1.9.7patch

The source viewer finds a block's stylesheet again. Packs now ship per-block sheets as `.tome-css` (or `.tome-scss`), but the source extractor only looked for `.module.css`, so the Styles tab was empty for every converted block. It now looks for the per-block sheet first and highlights it as CSS.

  • The source viewer finds a block's stylesheet again. Packs now ship per-block sheets as `.tome-css` (or `.tome-scss`), but the source extractor only looked for `.module.css`, so the Styles tab was empty for every converted block. It now looks for the per-block sheet first and highlights it as CSS.
v1.9.6patch

9ac8d3d: The bundle page's block list now has a gap between blocks, and the block preview frame has a surface colour when the site sets no `--tome-gallery-preview-bg`. The list read the undeclared `--tome-space-20` (so the gap was zero); it now reads `--tome-space-11` (80px). The preview frame read `--tome-gallery-preview-bg` with no fallback; it now falls back to `--tome-color-surface`, like the other gallery preview surfaces.

  • 9ac8d3d: The bundle page's block list now has a gap between blocks, and the block preview frame has a surface colour when the site sets no `--tome-gallery-preview-bg`. The list read the undeclared `--tome-space-20` (so the gap was zero); it now reads `--tome-space-11` (80px). The preview frame read `--tome-gallery-preview-bg` with no fallback; it now falls back to `--tome-color-surface`, like the other gallery preview surfaces.
v1.9.5patch

765b692: Catalog cards stop covering their own artwork, and small components stop previewing as a strip above a blank band. - **Lock:** the lock moved off the thumbnail and into the card's tier pill. It used to float over the artwork's top-left corner, where it covered headlines and table headers on every locked card (58 of 116 live cards on the starter). It is now an inline padlock left of the tier word ("PRO"), drawn in the pill's color. The featured plate gains the same pill, so a locked featured block still shows its lock. The card's name link still announces "(locked)". - **Paid label:** paid tier labels now read in `--tome-color-primary`. They used `--tome-color-accent`, a surface tint in the theme contract, which rendered near-invisible on tinted-accent themes. - **Small components:** live thumbnails gain a 360 rung (`THUMB_WIDTH_LADDER` is now 1280/960/640/480/360). The preview frame's 200px minimum no longer pads blocks inside thumbnails. A block still shorter than the frame at 360 (callouts, tables, stat strips) sits centered on the surface-tint mat instead of pinned to the top above a blank band.

  • 765b692: Catalog cards stop covering their own artwork, and small components stop previewing as a strip above a blank band. - **Lock:** the lock moved off the thumbnail and into the card's tier pill. It used to float over the artwork's top-left corner, where it covered headlines and table headers on every locked card (58 of 116 live cards on the starter). It is now an inline padlock left of the tier word ("PRO"), drawn in the pill's color. The featured plate gains the same pill, so a locked featured block still shows its lock. The card's name link still announces "(locked)". - **Paid label:** paid tier labels now read in `--tome-color-primary`. They used `--tome-color-accent`, a surface tint in the theme contract, which rendered near-invisible on tinted-accent themes. - **Small components:** live thumbnails gain a 360 rung (`THUMB_WIDTH_LADDER` is now 1280/960/640/480/360). The preview frame's 200px minimum no longer pads blocks inside thumbnails. A block still shorter than the frame at 360 (callouts, tables, stat strips) sits centered on the surface-tint mat instead of pinned to the top above a blank band.
v1.9.4patch

0afc071: Live (no-capture) catalog thumbnails now fill their frame. `BlockThumb` rendered every block at a fixed 1280px virtual width scaled into the 16:10 card, so a short block such as a hero (about 300px tall at 1280) filled a quarter of the frame in about 3px type. It now steps the render width down `THUMB_WIDTH_LADDER` (1280, 960, 640, 480) until the block's height fills the frame's own aspect. Short blocks reflow taller with legible type, and the wider 21:9 featured frame keeps more blocks wide. Blocks taller than the frame crop at the bottom behind a short fade. The layer stays hidden over the placeholder until the width settles, so the step-down reflows are never seen. The rule is `nextThumbRung` in `galleryHelpers`: it only steps down and is independent of card size, so it converges and card resizes never re-trigger it. Captured thumbnails and their hover overlay are unchanged. `GalleryDemoContext.placeholderMediaId` is now documented as any resource the host's media adapter renders; `resolveMedia` treats a string as a URL, so a bare ID renders an empty slot in a Payload-less preview.

  • 0afc071: Live (no-capture) catalog thumbnails now fill their frame. `BlockThumb` rendered every block at a fixed 1280px virtual width scaled into the 16:10 card, so a short block such as a hero (about 300px tall at 1280) filled a quarter of the frame in about 3px type. It now steps the render width down `THUMB_WIDTH_LADDER` (1280, 960, 640, 480) until the block's height fills the frame's own aspect. Short blocks reflow taller with legible type, and the wider 21:9 featured frame keeps more blocks wide. Blocks taller than the frame crop at the bottom behind a short fade. The layer stays hidden over the placeholder until the width settles, so the step-down reflows are never seen. The rule is `nextThumbRung` in `galleryHelpers`: it only steps down and is independent of card size, so it converges and card resizes never re-trigger it. Captured thumbnails and their hover overlay are unchanged. `GalleryDemoContext.placeholderMediaId` is now documented as any resource the host's media adapter renders; `resolveMedia` treats a string as a URL, so a bare ID renders an empty slot in a Payload-less preview.
v1.9.3patch

786853e: The gallery's source view now finds a block's config when its directory name differs from its slug, so the pricing block (`pricing-plans`) shows its Config tab again. `resolveBlockFiles` falls back to the block directory whose own `meta` declares the slug, in both the source and the installed `dist/` layouts. The pricing block's "added" date follows the new `pricing-plans` slug.

  • 786853e: The gallery's source view now finds a block's config when its directory name differs from its slug, so the pricing block (`pricing-plans`) shows its Config tab again. `resolveBlockFiles` falls back to the block directory whose own `meta` declares the slug, in both the source and the installed `dist/` layouts. The pricing block's "added" date follows the new `pricing-plans` slug.
v1.9.2patch

dfe59e3: `TomeBlocksGalleryProvider` now passes `config.RichTextAdapter` to previewed blocks instead of storing it where no block reads it. When the adapter is set, the provider mounts blocks-core's `RichTextAdapterProvider` around the gallery tree; when it is omitted, blocks fall back to a registered adapter and then blocks-core's built-in Lexical renderer. `RichTextAdapter` is now typed as blocks-core's `RichTextAdapter` rather than `unknown`, and the `@wabbit/tome-blocks-core` peer range starts at `0.11.0`, the first release with the `./adapters` subpath this uses.

  • dfe59e3: `TomeBlocksGalleryProvider` now passes `config.RichTextAdapter` to previewed blocks instead of storing it where no block reads it. When the adapter is set, the provider mounts blocks-core's `RichTextAdapterProvider` around the gallery tree; when it is omitted, blocks fall back to a registered adapter and then blocks-core's built-in Lexical renderer. `RichTextAdapter` is now typed as blocks-core's `RichTextAdapter` rather than `unknown`, and the `@wabbit/tome-blocks-core` peer range starts at `0.11.0`, the first release with the `./adapters` subpath this uses.
v1.9.1patch

205ecfc: Fix GalleryIndex causing a horizontal-scroll page overflow at phone widths (verified live on starter.wabbit.com/blocks at 390px: page 2,778px wide, `.aside` and `.main` each 2,762px). Root cause: the narrow-viewport (`max-width: 860px`) `.shell` rule collapsed the sidebar/content grid to a bare `1fr` track. A single grid track sized with only `1fr` (no `minmax`) defaults its automatic minimum to its content's min-content size rather than 0, so `.aside`'s unwrapped category/bundle `.navList` chip row inflated the one shared track — and, since it's a single column, both stacked children (`.aside` and `.main`) inherited that oversized track and overflowed the viewport. Fix: the mobile `.shell` track is now `minmax(0, 1fr)` (mirrors the desktop two-column rule's own `minmax(0, 1fr)` main track), and `.aside` picks up a `min-width: 0` grid-item override in that same media block (the companion `.main` already had unconditionally) so it can shrink to the track instead of forcing it wide. `.navList` remains internally horizontally scrollable (`overflow-x: auto`) — this only stops that scroll from leaking into the page. No change to the >=861px two-column layout. Verified via a Playwright repro harness measuring `document.documentElement.scrollWidth` at 390/768/1023/1440: 390px and 768px went from overflowing (scrollWidth 1553px against a 390/768px viewport) to matching the viewport exactly; 1023px and 1440px were unaffected before and after. Adds `tests/gallery-index-responsive.test.ts`, a static assertion on the two declarations, confirmed to fail against the pre-fix CSS and pass against the fix.

  • 205ecfc: Fix GalleryIndex causing a horizontal-scroll page overflow at phone widths (verified live on starter.wabbit.com/blocks at 390px: page 2,778px wide, `.aside` and `.main` each 2,762px). Root cause: the narrow-viewport (`max-width: 860px`) `.shell` rule collapsed the sidebar/content grid to a bare `1fr` track. A single grid track sized with only `1fr` (no `minmax`) defaults its automatic minimum to its content's min-content size rather than 0, so `.aside`'s unwrapped category/bundle `.navList` chip row inflated the one shared track — and, since it's a single column, both stacked children (`.aside` and `.main`) inherited that oversized track and overflowed the viewport. Fix: the mobile `.shell` track is now `minmax(0, 1fr)` (mirrors the desktop two-column rule's own `minmax(0, 1fr)` main track), and `.aside` picks up a `min-width: 0` grid-item override in that same media block (the companion `.main` already had unconditionally) so it can shrink to the track instead of forcing it wide. `.navList` remains internally horizontally scrollable (`overflow-x: auto`) — this only stops that scroll from leaking into the page. No change to the >=861px two-column layout. Verified via a Playwright repro harness measuring `document.documentElement.scrollWidth` at 390/768/1023/1440: 390px and 768px went from overflowing (scrollWidth 1553px against a 390/768px viewport) to matching the viewport exactly; 1023px and 1440px were unaffected before and after. Adds `tests/gallery-index-responsive.test.ts`, a static assertion on the two declarations, confirmed to fail against the pre-fix CSS and pass against the fix.
v1.9.0minor

b01ca1f: Raise the `react` / `react-dom` peer floor to `>=19.0.0` (ruled 2026-09-01). The platform declared React peers in five different shapes — `>=18.0.0`, `>=18`, `^18 || ^19`, `^18.3.0 || ^19.0.0`, `^19.0.0` — while its kernel (`@wabbit/tome-core`) and five app-layer packages already required `>=19`. Any package advertising React 18 was advertising a configuration that could not be installed alongside the kernel, so the split was never a supported matrix; it was drift. One shape now, and it is the honest one. These nine version independently of the `linked` blocks family (which gets its own coordinated bump), so they are listed here: - `@wabbit/tome-admin`, `@wabbit/tome-admin-pro` — from `^18.3.0 || ^19.0.0` - `@wabbit/tome-blocks-gallery` — from `^18 || ^19`; devDeps `react`/`@types/react` `^18.0.0` → `^19.0.0` - `@wabbit/tome-blocks-org-pack` — from `>=18.0.0`; same devDep correction - `@wabbit/tome-engine`, `@wabbit/tome-motion`, `@wabbit/tome-rpg`, `@wabbit/tome-webgl` — from `>=18` - `@wabbit/tome-ui` — from `>=18.0.0` The `^18` devDependency pins on the two block-shaped packages were already fiction: the root `pnpm.overrides` pins `@types/react` to `19.2.14`, so both have been building against React 19 types regardless. Correcting them changes the manifest, not the resolved tree. Consumer impact: a React 18 consumer can no longer install these. That install was already impossible with the kernel in the graph.

  • b01ca1f: Raise the `react` / `react-dom` peer floor to `>=19.0.0` (ruled 2026-09-01). The platform declared React peers in five different shapes — `>=18.0.0`, `>=18`, `^18 || ^19`, `^18.3.0 || ^19.0.0`, `^19.0.0` — while its kernel (`@wabbit/tome-core`) and five app-layer packages already required `>=19`. Any package advertising React 18 was advertising a configuration that could not be installed alongside the kernel, so the split was never a supported matrix; it was drift. One shape now, and it is the honest one. These nine version independently of the `linked` blocks family (which gets its own coordinated bump), so they are listed here: - `@wabbit/tome-admin`, `@wabbit/tome-admin-pro` — from `^18.3.0 || ^19.0.0` - `@wabbit/tome-blocks-gallery` — from `^18 || ^19`; devDeps `react`/`@types/react` `^18.0.0` → `^19.0.0` - `@wabbit/tome-blocks-org-pack` — from `>=18.0.0`; same devDep correction - `@wabbit/tome-engine`, `@wabbit/tome-motion`, `@wabbit/tome-rpg`, `@wabbit/tome-webgl` — from `>=18` - `@wabbit/tome-ui` — from `>=18.0.0` The `^18` devDependency pins on the two block-shaped packages were already fiction: the root `pnpm.overrides` pins `@types/react` to `19.2.14`, so both have been building against React 19 types regardless. Correcting them changes the manifest, not the resolved tree. Consumer impact: a React 18 consumer can no longer install these. That install was already impossible with the kernel in the graph.
  • 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.
v1.8.2patch

73081e6: Decompose `GalleryBlockDetail`, and give the package its first tests. The component had grown from 494 to 783 LOC with 7 `useState` calls — July's decomposition target, flagged again in the 2026-09-01 audit (§4.2) — while its sibling `GalleryIndex` shrank from ~1000 to 572 over the same period by splitting into `BlockCard`/`BlockThumb`/`useDebouncedValue`. This applies the same move. No behaviour change: every value, transition and rendered element is what the inline version produced. Three new sibling files, following the package's own convention: - **`usePreviewSurface.ts`** — view / variant / viewport / theme / fullscreen, plus the live pixel readout. These five travel together because they are not independent: the readout is meaningless in `code` view, `desktop` is deliberately fluid (measured via ResizeObserver on the content box) while tablet and mobile are fixed, and the Escape-exits-fullscreen listener must exist only while fullscreen. Those couplings were previously enforced by scattered conditions in the JSX; now they have one home. - **`useCopyToClipboard.ts`** — the copy-then-confirm flow. Worth extracting for the three things a copy button gets wrong independently: the flag auto-clears on a timer, the timer is cancelled on unmount, and a REJECTED write leaves `copied` false rather than telling the reader to paste something that is not on their clipboard. - **`GalleryDetailChrome.tsx`** — `Breadcrumb`, `AdjacentNav`, `SectionRule`, `RelatedBlockCard`. Stateless page chrome, so no `'use client'` directive: the directive travels with hooks, and the parent already carries it. Adding one that nothing needs is how a package's client-file count drifts from its actual client surface. `GalleryBlockDetail.tsx` is 783 → 617 lines and now owns no `useState` of its own. Nothing new is exported from the package barrel — these are internals of the detail page. **First test suite** (`tests/detail-hooks.test.tsx`, vitest + jsdom, 10 tests) covering exactly the couplings above: the readout goes quiet in `code` view and returns in `split`, tablet/mobile report their fixed widths without measuring, desktop rounds its measured width, the keydown listener is bound only while fullscreen and removed on exit, the ResizeObserver disconnects on unmount, and a rejected clipboard write leaves `copied` false. `@wabbit/tome-blocks-gallery` is removed from the `assert:test-floor` allowlist in the same change — the ratchet's entries are removed, never added.

  • 73081e6: Decompose `GalleryBlockDetail`, and give the package its first tests. The component had grown from 494 to 783 LOC with 7 `useState` calls — July's decomposition target, flagged again in the 2026-09-01 audit (§4.2) — while its sibling `GalleryIndex` shrank from ~1000 to 572 over the same period by splitting into `BlockCard`/`BlockThumb`/`useDebouncedValue`. This applies the same move. No behaviour change: every value, transition and rendered element is what the inline version produced. Three new sibling files, following the package's own convention: - **`usePreviewSurface.ts`** — view / variant / viewport / theme / fullscreen, plus the live pixel readout. These five travel together because they are not independent: the readout is meaningless in `code` view, `desktop` is deliberately fluid (measured via ResizeObserver on the content box) while tablet and mobile are fixed, and the Escape-exits-fullscreen listener must exist only while fullscreen. Those couplings were previously enforced by scattered conditions in the JSX; now they have one home. - **`useCopyToClipboard.ts`** — the copy-then-confirm flow. Worth extracting for the three things a copy button gets wrong independently: the flag auto-clears on a timer, the timer is cancelled on unmount, and a REJECTED write leaves `copied` false rather than telling the reader to paste something that is not on their clipboard. - **`GalleryDetailChrome.tsx`** — `Breadcrumb`, `AdjacentNav`, `SectionRule`, `RelatedBlockCard`. Stateless page chrome, so no `'use client'` directive: the directive travels with hooks, and the parent already carries it. Adding one that nothing needs is how a package's client-file count drifts from its actual client surface. `GalleryBlockDetail.tsx` is 783 → 617 lines and now owns no `useState` of its own. Nothing new is exported from the package barrel — these are internals of the detail page. **First test suite** (`tests/detail-hooks.test.tsx`, vitest + jsdom, 10 tests) covering exactly the couplings above: the readout goes quiet in `code` view and returns in `split`, tablet/mobile report their fixed widths without measuring, desktop rounds its measured width, the keydown listener is bound only while fullscreen and removed on exit, the ResizeObserver disconnects on unmount, and a rejected clipboard write leaves `copied` false. `@wabbit/tome-blocks-gallery` is removed from the `assert:test-floor` allowlist in the same change — the ratchet's entries are removed, never added.
  • 3a260e1: `buildGalleryManifest` annotates per-build copies of the registered block entries instead of writing `thumbs`, `addedAt` and `categorySlug` onto the pack registry's own objects. The registry is a module-level singleton that outlives a request on the server, so a thumbs annotation from one build leaked into the next: on wabbit.com the index page built with one thumbs manifest and the detail page with another, the server kept the index's thumbs on a block the detail manifest never listed, the client (building from its own props) did not, and the detail page threw React #418 — a related-block `<img>` present in the server HTML and absent on the client. Every build now derives `thumbs` from its own manifest only (`undefined` when absent). Also records that 1.8.1 shipped the `GalleryBlockDetail` decomposition (usePreviewSurface / useCopyToClipboard / GalleryDetailChrome) whose changeset had been pending on main.
v1.8.1patch

93ecd9e: GalleryIndex: the catalog container now sets `width: 100%`, so a host that lifts `--tome-gallery-catalog-max-width` (sets it to `none`) inside its own grid gets a catalog that fills the track instead of shrink-wrapping to its content. Auto margins on a grid item switch it from stretch to fit-content; measured on wabbit.com/blocks at 1920 the catalog rendered 973px wide in a 1560px track with three card columns. No change for hosts that keep the default cap.

  • 93ecd9e: GalleryIndex: the catalog container now sets `width: 100%`, so a host that lifts `--tome-gallery-catalog-max-width` (sets it to `none`) inside its own grid gets a catalog that fills the track instead of shrink-wrapping to its content. Auto margins on a grid item switch it from stretch to fit-content; measured on wabbit.com/blocks at 1920 the catalog rendered 973px wide in a 1560px track with three card columns. No change for hosts that keep the default cap.
v1.8.0minor

c076d3b: Fix dark-theme thumbnails never revealing, and add host-tunable catalog chrome tokens. **Fix — dark pages never showed a single thumbnail.** BlockThumb's load-state `ref`/`onLoad` rode the LIGHT image only. On a dark page the light `<img>` is `display: none`, and a `loading="lazy"` image with no box never fetches — so `imgLoaded` stayed `false` forever and the dark image (fully downloaded) sat at `opacity: 0` behind the skeleton shimmer. Measured on wabbit.com/blocks 2026-08-31: 180/180 thumb frames stuck at `data-loaded="false"`. Both theme images now carry the ref + `onLoad`; only the theme-visible one ever loads, and whichever loads clears the shimmer. **New — catalog chrome tokens** (all default to the current look; no consumer change without opting in): - `--tome-gallery-card-border-color` — card, featured-plate, and thumb-viewport hairlines (default `--tome-color-border`) - `--tome-gallery-card-radius` — card / plate / viewport corner radius (default `--tome-radius-sm`) - `--tome-gallery-card-surface` — card + plate surface (default `--tome-color-surface`) - `--tome-gallery-input-surface` / `--tome-gallery-input-border-color` / `--tome-gallery-input-radius` — search well restyling (defaults preserve the outlined input) - `--tome-gallery-active-bg` / `--tome-gallery-active-fg` — the selected-state fill shared by the index view toggle, filter chips, and the detail page's Preview/Code/Split segmented control (defaults preserve the inverse-ink fill) Motivation: hosts skinning the catalog (e.g. borderless elevated cards with a larger radius, accent-colored active states) previously had no stable hooks — module class names are hashed. **Changed — segmented controls move to the recessed-track idiom.** The detail page's Preview/Code/Split toggle and the index's view toggle drop the cramped outlined strip (per-segment hairline dividers, edge-to-edge segments) for a muted track holding spaced pills with a filled active pill. The selected variant card's ring also rides `--tome-gallery-active-bg` instead of hardcoded on-surface.

  • c076d3b: Fix dark-theme thumbnails never revealing, and add host-tunable catalog chrome tokens. **Fix — dark pages never showed a single thumbnail.** BlockThumb's load-state `ref`/`onLoad` rode the LIGHT image only. On a dark page the light `<img>` is `display: none`, and a `loading="lazy"` image with no box never fetches — so `imgLoaded` stayed `false` forever and the dark image (fully downloaded) sat at `opacity: 0` behind the skeleton shimmer. Measured on wabbit.com/blocks 2026-08-31: 180/180 thumb frames stuck at `data-loaded="false"`. Both theme images now carry the ref + `onLoad`; only the theme-visible one ever loads, and whichever loads clears the shimmer. **New — catalog chrome tokens** (all default to the current look; no consumer change without opting in): - `--tome-gallery-card-border-color` — card, featured-plate, and thumb-viewport hairlines (default `--tome-color-border`) - `--tome-gallery-card-radius` — card / plate / viewport corner radius (default `--tome-radius-sm`) - `--tome-gallery-card-surface` — card + plate surface (default `--tome-color-surface`) - `--tome-gallery-input-surface` / `--tome-gallery-input-border-color` / `--tome-gallery-input-radius` — search well restyling (defaults preserve the outlined input) - `--tome-gallery-active-bg` / `--tome-gallery-active-fg` — the selected-state fill shared by the index view toggle, filter chips, and the detail page's Preview/Code/Split segmented control (defaults preserve the inverse-ink fill) Motivation: hosts skinning the catalog (e.g. borderless elevated cards with a larger radius, accent-colored active states) previously had no stable hooks — module class names are hashed. **Changed — segmented controls move to the recessed-track idiom.** The detail page's Preview/Code/Split toggle and the index's view toggle drop the cramped outlined strip (per-segment hairline dividers, edge-to-edge segments) for a muted track holding spaced pills with a filled active pill. The selected variant card's ring also rides `--tome-gallery-active-bg` instead of hardcoded on-surface.
v1.7.1patch

71d3b09: Purge client-specific lore and Star Citizen universe references from all non-SC packages (content and labels only — no schema field names, slugs, or enum values changed). - **dispatch**: demo content rewritten as an incident-war-room / ops-bridge scenario (SEV-1 bridge traffic, failover runbooks, recovered security-report transcript) plus neutral original fiction for inherently fictional variants (Relay Station Aurelia personal log, SV Aurelia ship log). Config field-description examples de-lored (old client- and universe-specific labels → neutral equivalents). - **readout**: all 9 blocks' demo props rewritten as business-operations console data (deployment phases, sprint objectives, service status, perimeter traffic, on-call roster, infrastructure asset cards). Config examples de-lored. - **blocks-signal-theme**: demo props for the 33-block pack rewritten as an original search-and-rescue expedition serial ("Operation Long Wake", SV Aurelia, Meridian Reach) with zero client or SC references; config examples de-lored. Pack positioning (SC-tier bundling per OQ-4) unchanged. - **blocks-extras / blocks-content-writer**: Custom Hero and Post Hero meta descriptions stop name-dropping the client; "Callsign" field descriptions neutralized to "Author name or handle"; provenance comments neutralized. - **blocks-core**: BLOCK_CATALOG mirror entries refreshed for custom-hero and post-hero only; registry comment neutralized. - **blocks-gallery**: SourceBadge label for the `vngd` source value now renders "Legacy" (enum value unchanged). - **accounts / core / lms / ui / org / admin / motion / longform / cop / blocks**: internal provenance comments, shipped CSS comments, and consumer-visible field descriptions that named a specific client replaced with neutral "upstream" phrasing; longform package description de-lored. Historical CHANGELOG entries left untouched.

  • 71d3b09: Purge client-specific lore and Star Citizen universe references from all non-SC packages (content and labels only — no schema field names, slugs, or enum values changed). - **dispatch**: demo content rewritten as an incident-war-room / ops-bridge scenario (SEV-1 bridge traffic, failover runbooks, recovered security-report transcript) plus neutral original fiction for inherently fictional variants (Relay Station Aurelia personal log, SV Aurelia ship log). Config field-description examples de-lored (old client- and universe-specific labels → neutral equivalents). - **readout**: all 9 blocks' demo props rewritten as business-operations console data (deployment phases, sprint objectives, service status, perimeter traffic, on-call roster, infrastructure asset cards). Config examples de-lored. - **blocks-signal-theme**: demo props for the 33-block pack rewritten as an original search-and-rescue expedition serial ("Operation Long Wake", SV Aurelia, Meridian Reach) with zero client or SC references; config examples de-lored. Pack positioning (SC-tier bundling per OQ-4) unchanged. - **blocks-extras / blocks-content-writer**: Custom Hero and Post Hero meta descriptions stop name-dropping the client; "Callsign" field descriptions neutralized to "Author name or handle"; provenance comments neutralized. - **blocks-core**: BLOCK_CATALOG mirror entries refreshed for custom-hero and post-hero only; registry comment neutralized. - **blocks-gallery**: SourceBadge label for the `vngd` source value now renders "Legacy" (enum value unchanged). - **accounts / core / lms / ui / org / admin / motion / longform / cop / blocks**: internal provenance comments, shipped CSS comments, and consumer-visible field descriptions that named a specific client replaced with neutral "upstream" phrasing; longform package description de-lored. Historical CHANGELOG entries left untouched.
v1.7.0minor

5458399: Thumbnails: bind capture geometry to card geometry, and stop serving the wrong theme **Fixed-aspect capture.** `GalleryThumbTarget` now composes every block inside a locked 1280×800 (16:10) frame and the capture CLI screenshots that frame instead of clipping to the block's own bounds. Short blocks centre on their own sampled background, near-fits shrink to 0.75, taller blocks render 1:1 top-anchored with a bottom fade. Every output file is exactly 2560×1600. The browse card's `.thumbViewport` moves 4:3 → 16:10 to match, which makes its `object-fit: cover` an identity transform. These two are ONE contract: the previous block-bounds clip produced files from 2560×672 to 2560×1920 while the card forced them all into 4:3, giving up to 2.47× zoom with 60% of the image cropped away — the only block that rendered correctly was the one whose capture happened to land on 4:3. **Theme pair selected in CSS, not JS.** `BlockThumb` renders both captures and the stylesheet hides one. The previous `useDocumentTheme()`-driven `src` was a hydration dead-zone: the server rendered the light path, the client's state initialiser also resolved `dark`, and the mount effect set that same value — so React never re-rendered and never reconciled the server-rendered attribute. State said dark, the DOM stayed light. Measured on prod: 60/60 thumbs served the light capture on a dark page while all 130 manifest entries had both. Adds `pickThumbPair`; `pickThumb` is retained for callers that still want one src. **QA hard gates** (`gallery-thumbs-qa.ts`, exit 2): DIMENSION (every capture exactly the expected size — the gate that would have caught this regression on day one), PAIR (both themes per variant), COVERAGE (against the enumeration, with `--allow-missing`), CONTRAST (5th–95th percentile ink/ground separation, catching the dark-plum-on-near-black cases). LOW-VARIANCE stays advisory. `--no-gates` reverts to report-only. The featured plate keeps a deliberate 21:9 crop, now centre-anchored to match the harness's centred composition.

  • 5458399: Thumbnails: bind capture geometry to card geometry, and stop serving the wrong theme **Fixed-aspect capture.** `GalleryThumbTarget` now composes every block inside a locked 1280×800 (16:10) frame and the capture CLI screenshots that frame instead of clipping to the block's own bounds. Short blocks centre on their own sampled background, near-fits shrink to 0.75, taller blocks render 1:1 top-anchored with a bottom fade. Every output file is exactly 2560×1600. The browse card's `.thumbViewport` moves 4:3 → 16:10 to match, which makes its `object-fit: cover` an identity transform. These two are ONE contract: the previous block-bounds clip produced files from 2560×672 to 2560×1920 while the card forced them all into 4:3, giving up to 2.47× zoom with 60% of the image cropped away — the only block that rendered correctly was the one whose capture happened to land on 4:3. **Theme pair selected in CSS, not JS.** `BlockThumb` renders both captures and the stylesheet hides one. The previous `useDocumentTheme()`-driven `src` was a hydration dead-zone: the server rendered the light path, the client's state initialiser also resolved `dark`, and the mount effect set that same value — so React never re-rendered and never reconciled the server-rendered attribute. State said dark, the DOM stayed light. Measured on prod: 60/60 thumbs served the light capture on a dark page while all 130 manifest entries had both. Adds `pickThumbPair`; `pickThumb` is retained for callers that still want one src. **QA hard gates** (`gallery-thumbs-qa.ts`, exit 2): DIMENSION (every capture exactly the expected size — the gate that would have caught this regression on day one), PAIR (both themes per variant), COVERAGE (against the enumeration, with `--allow-missing`), CONTRAST (5th–95th percentile ink/ground separation, catching the dark-plum-on-near-black cases). LOW-VARIANCE stays advisory. `--no-gates` reverts to report-only. The featured plate keeps a deliberate 21:9 crop, now centre-anchored to match the harness's centred composition.
v1.6.0minor

a2728f7: Full-bleed browse-card thumbnails + block-bounds capture framing. The card mat (thumbViewport margin/border/tint frame) and inset hairline plate (thumbFrame border/shadow) are gone — the artwork fills the card edge-to-edge above the meta, separated by a single bottom hairline. The capture CLI now shoots under reduced motion (no more mid-entrance-animation frames) and clips each shot to the block's own bounding box instead of the full 1280×960 viewport, so short blocks no longer bake fat stage-background bands into the image. BlockThumb's hover-crossfade math mirrors the new cover composition (height-dominant zoom + horizontal centering for short blocks). Existing 4:3 captures still render correctly; re-run gallery:capture to get the band-free framing.

  • a2728f7: Full-bleed browse-card thumbnails + block-bounds capture framing. The card mat (thumbViewport margin/border/tint frame) and inset hairline plate (thumbFrame border/shadow) are gone — the artwork fills the card edge-to-edge above the meta, separated by a single bottom hairline. The capture CLI now shoots under reduced motion (no more mid-entrance-animation frames) and clips each shot to the block's own bounding box instead of the full 1280×960 viewport, so short blocks no longer bake fat stage-background bands into the image. BlockThumb's hover-crossfade math mirrors the new cover composition (height-dominant zoom + horizontal centering for short blocks). Existing 4:3 captures still render correctly; re-run gallery:capture to get the band-free framing.
v1.5.2patch

510036f: Replace the enumerated `@wabbit/tome-blocks-core` peer range with an open one: `>=0.9.0 <1`. The previous form (`^0.9.0 || … || ^0.14.0`) had to be extended by hand on **every** blocks-core minor. Miss it and the package falls out of range, which — with `onlyUpdatePeerDependentsWhenOutOfRange` — silently resolves this package to a **major** bump as a side effect of a sibling's additive change. That happened last release (caught at 1.5.0 → 2.0.0 and corrected to a patch) and recurred immediately on the next minor, which is the argument for fixing the pattern rather than appending another version. `>=0.9.0 <1` expresses the actual compatibility claim — any pre-1.0 blocks-core from 0.9 up — and does not need editing again until blocks-core reaches 1.0.

  • 510036f: Replace the enumerated `@wabbit/tome-blocks-core` peer range with an open one: `>=0.9.0 <1`. The previous form (`^0.9.0 || … || ^0.14.0`) had to be extended by hand on **every** blocks-core minor. Miss it and the package falls out of range, which — with `onlyUpdatePeerDependentsWhenOutOfRange` — silently resolves this package to a **major** bump as a side effect of a sibling's additive change. That happened last release (caught at 1.5.0 → 2.0.0 and corrected to a patch) and recurred immediately on the next minor, which is the argument for fixing the pattern rather than appending another version. `>=0.9.0 <1` expresses the actual compatibility claim — any pre-1.0 blocks-core from 0.9 up — and does not need editing again until blocks-core reaches 1.0.
v1.5.1patch

Widen the `@wabbit/tome-blocks-core` peer range to include `^0.14.0`. The range is enumerated (`^0.9.0 || … || ^0.13.0`) rather than open-ended, so every blocks-core minor falls out of it. Combined with `onlyUpdatePeerDependentsWhenOutOfRange`, that resolved this release to a **major** bump for this package — 1.5.0 → 2.0.0 — purely as a side effect of a sibling's additive minor. This changeset exists so the widened range actually reaches the registry: without it the package is not republished, and the published `1.5.0` would keep declaring `^0.13.0` as its ceiling while `blocks-core@0.14.0` is the current release, producing a peer warning on install. Worth revisiting whether the enumeration should become `>=0.9.0 <1` so the next minor does not repeat this.

  • Widen the `@wabbit/tome-blocks-core` peer range to include `^0.14.0`. The range is enumerated (`^0.9.0 || … || ^0.13.0`) rather than open-ended, so every blocks-core minor falls out of it. Combined with `onlyUpdatePeerDependentsWhenOutOfRange`, that resolved this release to a **major** bump for this package — 1.5.0 → 2.0.0 — purely as a side effect of a sibling's additive minor. This changeset exists so the widened range actually reaches the registry: without it the package is not republished, and the published `1.5.0` would keep declaring `^0.13.0` as its ceiling while `blocks-core@0.14.0` is the current release, producing a peer warning on install. Worth revisiting whether the enumeration should become `>=0.9.0 <1` so the next minor does not repeat this.
v1.5.0minor

Widened the `@wabbit/tome-blocks-core` peer range to include `^0.12.0 || ^0.13.0` (backward-compatible — the multi-minor union is this package's deliberate compatibility contract; changesets' automated resolution had narrowed it to `^0.13.0` with a major bump, hand-corrected at release preflight).

  • Widened the `@wabbit/tome-blocks-core` peer range to include `^0.12.0 || ^0.13.0` (backward-compatible — the multi-minor union is this package's deliberate compatibility contract; changesets' automated resolution had narrowed it to `^0.13.0` with a major bump, hand-corrected at release preflight).
  • Updated dependencies [f4d55c9] - @wabbit/tome-blocks-core@0.13.0
v1.4.0minor

7efd826: Full-width block detail redesign + index rhythm + props metadata contract. Detail page: the article now defaults to full width (`--tome-gallery-detail-max-width` default changed 1120px → none; set the token to re-cap). The 260px sticky meta rail is replaced by a horizontal hairline meta band (Pack / Category / Tier / Added / Variants / Tags); the install command / Acquire CTA moved into the header row. New sections: variant thumbnail-card strip (surfaces variant descriptions; falls back to toolbar pills when no thumbs), "In both themes" light/dark screenshot pair, props reference table, and related blocks (category + bundle siblings). Toolbar gains a Split view (code docks beside the preview at ≥1600px) and a live pixel-width readout on the viewport toggle. Index: category sections with ≥3 blocks lead with a full-width featured plate; the card grid default min-width is now responsive (`clamp(240px, 18vw, 300px)`, token override unchanged). New contract (additive): `GalleryBlockEntry.fields?: GalleryFieldMeta[]` — derived automatically in `defineGalleryPack` by introspecting `descriptor.block()` (pure; degrades to undefined on failure). New export: `GalleryPropsTable`.

  • 7efd826: Full-width block detail redesign + index rhythm + props metadata contract. Detail page: the article now defaults to full width (`--tome-gallery-detail-max-width` default changed 1120px → none; set the token to re-cap). The 260px sticky meta rail is replaced by a horizontal hairline meta band (Pack / Category / Tier / Added / Variants / Tags); the install command / Acquire CTA moved into the header row. New sections: variant thumbnail-card strip (surfaces variant descriptions; falls back to toolbar pills when no thumbs), "In both themes" light/dark screenshot pair, props reference table, and related blocks (category + bundle siblings). Toolbar gains a Split view (code docks beside the preview at ≥1600px) and a live pixel-width readout on the viewport toggle. Index: category sections with ≥3 blocks lead with a full-width featured plate; the card grid default min-width is now responsive (`clamp(240px, 18vw, 300px)`, token override unchanged). New contract (additive): `GalleryBlockEntry.fields?: GalleryFieldMeta[]` — derived automatically in `defineGalleryPack` by introspecting `descriptor.block()` (pure; degrades to undefined on failure). New export: `GalleryPropsTable`.
v1.3.1patch

Peer range on `@wabbit/tome-blocks-core` widened to `^0.9.0 || ^0.10.0 || ^0.11.0`. (Changesets' out-of-range-peer rule had force-majored this release to 2.0.0 and narrowed the peer to `^0.11.0` — both artifacts of the old range not admitting 0.11, not a real breaking change; every change below is a patch. Gallery is verified against blocks-core 0.11 by this train's own gates.)

  • Peer range on `@wabbit/tome-blocks-core` widened to `^0.9.0 || ^0.10.0 || ^0.11.0`. (Changesets' out-of-range-peer rule had force-majored this release to 2.0.0 and narrowed the peer to `^0.11.0` — both artifacts of the old range not admitting 0.11, not a real breaking change; every change below is a patch. Gallery is verified against blocks-core 0.11 by this train's own gates.)
  • 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.
  • 36e537a: Small verified fixes: agency-essentials `Contact` gains its missing `'use client'` (it calls the rich-text adapter hook; direct RSC import crashed). chrome `NavGuard` now dev-warns when its capability gate fails to load while a `requiredCapability` is set (the fail-open contract itself is unchanged and now documented). blocks-core `BLOCK_CATALOG.ts` corrupted entries corrected from real block meta (content-two-column, content-with-corner-notch, signal-ship-card names/descriptions; gallery variants filled) + drift-risk header. Stale docstrings fixed (chrome `HeaderLogo`, blocks-gallery registry header, lms-ui payload JSDoc import path). blocks meta-package backcompat suite now asserts the RENDER registry resolves renderers (previously only descriptor registration was tested — a dropped render import shipped silently).
  • a93f478: Gallery browse performance: search input debounced at 80ms via a local-state + `useDebouncedValue` split (instant keystroke echo, one grid re-filter per pause; immune to context-sibling re-render stomps), `BlockCard` wrapped in `React.memo` (props verified referentially stable — unrelated re-renders no longer re-run every card's hover/visibility machinery). blocks-core publishes `./utilities/useDocumentTheme` as a lean subpath (mirroring `useDebouncedResize`) so Payload-free consumers can reach the theme hook without the admin barrel's `@payloadcms/ui` graph; gallery's local MutationObserver copy replaced with a narrowing wrapper over the canonical hook. Also: BlockPicker's artificial `setTimeout(0)` skeleton delay removed; RecentFavorites static data-attributes moved from an effect into JSX.
  • 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] - @wabbit/tome-blocks-core@0.11.0
v1.3.0minor

8f52a6a: Gallery Wave 3 — detail-page upgrades + catalog motion pass. Detail page: opt-in iframe-scoped preview (`TomeBlocksGalleryConfig.previewHarnessPath` + new exported `GalleryPreviewTarget` harness component with a same-origin postMessage height/readiness protocol; the device-width toggle becomes an honest reflow instead of a maxWidth clamp; inline preview unchanged when unconfigured), prev/next sibling navigation (curated category order, bundle-order fallback), a toolbar install-copy control, and an Osmo-pattern meta rail (Pack / Tier / Added / Variants / Tags — replaces the bottom tags section). Context now exposes `manifest` and `previewHarnessPath`. Catalog: CSS-only motion pass — staggered card entrances that replay on structural catalog switches (never on search keystrokes), thumbnail skeleton shimmer + captured-image fade-in (cached-image safe), all gated behind `prefers-reduced-motion: no-preference`. Also fixes the empty-`<h1>` hero stub rendered in external-rail mode (a11y).

  • 8f52a6a: Gallery Wave 3 — detail-page upgrades + catalog motion pass. Detail page: opt-in iframe-scoped preview (`TomeBlocksGalleryConfig.previewHarnessPath` + new exported `GalleryPreviewTarget` harness component with a same-origin postMessage height/readiness protocol; the device-width toggle becomes an honest reflow instead of a maxWidth clamp; inline preview unchanged when unconfigured), prev/next sibling navigation (curated category order, bundle-order fallback), a toolbar install-copy control, and an Osmo-pattern meta rail (Pack / Tier / Added / Variants / Tags — replaces the bottom tags section). Context now exposes `manifest` and `previewHarnessPath`. Catalog: CSS-only motion pass — staggered card entrances that replay on structural catalog switches (never on search keystrokes), thumbnail skeleton shimmer + captured-image fade-in (cached-image safe), all gated behind `prefers-reduced-motion: no-preference`. Also fixes the empty-`<h1>` hero stub rendered in external-rail mode (a11y).
v1.2.0minor

Wave-2 catalog features: hover-live preview crossfade with capture-stage composition matching (150ms intent, lazy persistent mount, measured vertical centering, reduced-motion safe); taxonomy redesign (no "Other", Editorial split, max bucket 10); Sort Curated/Newest plus "New" pills backed by git-derived per-block addedAt dates; external rail mode (GalleryCatalogProvider, GalleryRail, rail="internal"|"external") for app-shell layouts; fixes a latent conditional-hooks violation in BlockThumb. Widens the @wabbit/tome-blocks-core peer range to tolerate the family's 0.10 minor (B1-amendment precedent — prevents a spurious major).

  • Wave-2 catalog features: hover-live preview crossfade with capture-stage composition matching (150ms intent, lazy persistent mount, measured vertical centering, reduced-motion safe); taxonomy redesign (no "Other", Editorial split, max bucket 10); Sort Curated/Newest plus "New" pills backed by git-derived per-block addedAt dates; external rail mode (GalleryCatalogProvider, GalleryRail, rail="internal"|"external") for app-shell layouts; fixes a latent conditional-hooks violation in BlockThumb. Widens the @wabbit/tome-blocks-core peer range to tolerate the family's 0.10 minor (B1-amendment precedent — prevents a spurious major).
v1.1.0minor

Build-time thumbnail pipeline: GalleryThumbTarget capture harness, Playwright capture CLI with DOM-fallback detection and merge-on-write manifest, sharp visual-QA/contact-sheet script, thumbs manifest threading through buildGalleryManifest, and img-based catalog cards with live-preview fallback. New Gallery Plate card chrome: mat + hairline frame, serif block name, description line, tier pills, and a lock pill over fully visible artwork replacing the gray lock wash.

  • Build-time thumbnail pipeline: GalleryThumbTarget capture harness, Playwright capture CLI with DOM-fallback detection and merge-on-write manifest, sharp visual-QA/contact-sheet script, thumbs manifest threading through buildGalleryManifest, and img-based catalog cards with live-preview fallback. New Gallery Plate card chrome: mat + hairline frame, serif block name, description line, tier pills, and a lock pill over fully visible artwork replacing the gray lock wash.
v1.0.0patch

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

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

249b670: Fix the source-extractor's component resolver so code-viewer snapshots stop silently skipping legitimate components (2026-06-27 marketplace audit). The resolver derived only `Pascal(slug)` and probed an exact-case path, which (1) missed blocks whose React component is named independently of the slug and (2) was case-sensitive — the latter a latent **Linux-only** bug masked on Windows NTFS (`checklist` → `CheckList.js`, fine on Windows, skipped on the case-sensitive Coolify deploy host). Now layered: `Pascal(slug)` exact → case-insensitive → a **registration scan** (the `render/` file whose `registerRenderer('<slug>', …)` matches), with the styles sibling resolved from the found component's base name. A null component now factually means no render file registers the slug (structural/config-only block) — recorded as `configOnly` in each block's `manifest.json` and logged as `[no-component] … verify it is structural/config-only and not a slug mismatch` for the audit, rather than silently asserting the absent Component tab is intentional. Verified against the real pack sources (8/8): fixes `content` and the launch-blocking `checklist`; correctly surfaces (does not mask) the kebab-vs-camel gallery/render slug split (`pricing`/`pricingPlans`, `card`/`cardBlock`, …) as a separate audit item. Also widens the `@wabbit/tome-blocks-core` peer range to tolerate `^0.8.0` (the linked family's next minor), so the family bump does not force a spurious gallery major — the B1-amendment precedent.

  • 249b670: Fix the source-extractor's component resolver so code-viewer snapshots stop silently skipping legitimate components (2026-06-27 marketplace audit). The resolver derived only `Pascal(slug)` and probed an exact-case path, which (1) missed blocks whose React component is named independently of the slug and (2) was case-sensitive — the latter a latent **Linux-only** bug masked on Windows NTFS (`checklist` → `CheckList.js`, fine on Windows, skipped on the case-sensitive Coolify deploy host). Now layered: `Pascal(slug)` exact → case-insensitive → a **registration scan** (the `render/` file whose `registerRenderer('<slug>', …)` matches), with the styles sibling resolved from the found component's base name. A null component now factually means no render file registers the slug (structural/config-only block) — recorded as `configOnly` in each block's `manifest.json` and logged as `[no-component] … verify it is structural/config-only and not a slug mismatch` for the audit, rather than silently asserting the absent Component tab is intentional. Verified against the real pack sources (8/8): fixes `content` and the launch-blocking `checklist`; correctly surfaces (does not mask) the kebab-vs-camel gallery/render slug split (`pricing`/`pricingPlans`, `card`/`cardBlock`, …) as a separate audit item. Also widens the `@wabbit/tome-blocks-core` peer range to tolerate `^0.8.0` (the linked family's next minor), so the family bump does not force a spurious gallery major — the B1-amendment precedent.
v0.3.0minor

70d3043: Catalog pagination. Each browse section caps at 8 cards in the unfiltered overview, with a "Show all N blocks" / "Show fewer" toggle; sections auto-expand when the catalog is narrowed by search or a category/pack/tier filter. Keeps the all-categories view scannable as the library grows. Drop-in: no prop or API changes.

  • 70d3043: Catalog pagination. Each browse section caps at 8 cards in the unfiltered overview, with a "Show all N blocks" / "Show fewer" toggle; sections auto-expand when the catalog is narrowed by search or a category/pack/tier filter. Keeps the all-categories view scannable as the library grows. Drop-in: no prop or API changes.
v0.2.0minor

b4bfb1f: Gallery redesign: category-primary browse catalog + unified block-detail surface. - **Detail page**: one unified surface with a toolbar — Preview/Code toggle, variant switcher, viewport (desktop/tablet/mobile), light/dark preview theme, and fullscreen; tier/source badges and Install promoted above the fold. - **Catalog** (`GalleryIndex`): category-primary sections derived from block tags, client-side search, category/pack/tier filters, a "By bundle" view toggle (preserving the bundle-grouped lens), and live mini-preview thumbnail cards (the render engine at reduced scale, lazily mounted as cards scroll into view). - **Category facet**: new `deriveGalleryCategory` + `GALLERY_CATEGORY_RULES` exports; `buildGalleryManifest` now emits `manifest.categories`; `GalleryBlockEntry` carries `category` (formal BlockMeta.category) + derived `categorySlug`. - **Demo context**: new optional `demoContext` on `TomeBlocksGalleryConfig`, threaded into `getDemoProps` calls so previews can resolve placeholder media/href. - **Tokens**: migrated every gallery component stylesheet from undefined token names to canonical `@wabbit/tome-ui` tokens (`--tome-color-*`, `--tome-type-*`). Fixes missing card/surface borders and broken nested dark-theme previews in consumers. Additive and backward-compatible: `GalleryIndex` and `GalleryBlockDetail` public props are unchanged, so consumers need no edits.

  • b4bfb1f: Gallery redesign: category-primary browse catalog + unified block-detail surface. - **Detail page**: one unified surface with a toolbar — Preview/Code toggle, variant switcher, viewport (desktop/tablet/mobile), light/dark preview theme, and fullscreen; tier/source badges and Install promoted above the fold. - **Catalog** (`GalleryIndex`): category-primary sections derived from block tags, client-side search, category/pack/tier filters, a "By bundle" view toggle (preserving the bundle-grouped lens), and live mini-preview thumbnail cards (the render engine at reduced scale, lazily mounted as cards scroll into view). - **Category facet**: new `deriveGalleryCategory` + `GALLERY_CATEGORY_RULES` exports; `buildGalleryManifest` now emits `manifest.categories`; `GalleryBlockEntry` carries `category` (formal BlockMeta.category) + derived `categorySlug`. - **Demo context**: new optional `demoContext` on `TomeBlocksGalleryConfig`, threaded into `getDemoProps` calls so previews can resolve placeholder media/href. - **Tokens**: migrated every gallery component stylesheet from undefined token names to canonical `@wabbit/tome-ui` tokens (`--tome-color-*`, `--tome-type-*`). Fixes missing card/surface borders and broken nested dark-theme previews in consumers. Additive and backward-compatible: `GalleryIndex` and `GalleryBlockDetail` public props are unchanged, so consumers need no edits.
v0.1.3patch

727cf98: Fix gallery code-reveal source extraction for installed (dist-only) packs. The extractor probed `dist/<slug>/index.js` for block config, but the real published layout is `dist/blocks/<slug>/index.js` (nested, purpose-built packs) or `dist/blocks/<slug>.js` (flat, legacy-derived packs like extras). Config reveal was therefore completely broken for every marketplace install (packs ship dist-only, no src). Now probes both layouts plus kebab-case slug variants (camelCase Payload slugs like `compareColumns` map to kebab dirs `compare-columns`), and adds the missing dist styles fallback (`dist/render/<Pascal>.module.css`). Verified in-consumer (wabbit): config reveal 100% across content-writer, editorial-pack, extras, marketing-starter. Known follow-up: ~6 blocks whose React component filename differs from Pascal(slug) (e.g. pricing -> PricingPlans) still skip the standalone component file; needs a pack-emitted slug->file manifest (no safe slug->filename transform exists).

  • 727cf98: Fix gallery code-reveal source extraction for installed (dist-only) packs. The extractor probed `dist/<slug>/index.js` for block config, but the real published layout is `dist/blocks/<slug>/index.js` (nested, purpose-built packs) or `dist/blocks/<slug>.js` (flat, legacy-derived packs like extras). Config reveal was therefore completely broken for every marketplace install (packs ship dist-only, no src). Now probes both layouts plus kebab-case slug variants (camelCase Payload slugs like `compareColumns` map to kebab dirs `compare-columns`), and adds the missing dist styles fallback (`dist/render/<Pascal>.module.css`). Verified in-consumer (wabbit): config reveal 100% across content-writer, editorial-pack, extras, marketing-starter. Known follow-up: ~6 blocks whose React component filename differs from Pascal(slug) (e.g. pricing -> PricingPlans) still skip the standalone component file; needs a pack-emitted slug->file manifest (no safe slug->filename transform exists).
v0.1.2patch

66c611c: B1 — block tiering formalization + blocks-extras free/paid split. - **blocks-core:** add `'addon'` to the `BundleMeta.tier` union (additive; existing `'pro'` values stay valid). - **blocks-extras:** register TWO bundles from one package (Option B) — a free `extras-primitives` sub-bundle (9 structural primitives) + the paid `extras` remainder (39 blocks, was 48). Mechanically additive: no import-path changes, all blocks still exported + registered, existing content keeps rendering, existing `extras` entitlements keep working (the primitives are now free to everyone). The tier-scope change is the only semantic shift. - **signal-theme + sc-pack:** tier `'pro'` → `'addon'` (sold independently of the tiered subscription track). - **blocks-gallery:** widen the `@wabbit/tome-blocks-core` peer to `^0.5.9 || ^0.6.0 || ^0.7.0` so the 0.7.0 bump doesn't force a spurious major (it's a types-only peer). Patch. Release note: the blocks family is `linked`, so this aligns the whole family to **0.7.0**. Minor (not major) is deliberate — 0.7.0 still gates explicit consumer adoption (`^0.6` does not auto-resolve 0.7.0), without declaring a symbolic 1.0.0 before the marketplace launch.

  • 66c611c: B1 — block tiering formalization + blocks-extras free/paid split. - **blocks-core:** add `'addon'` to the `BundleMeta.tier` union (additive; existing `'pro'` values stay valid). - **blocks-extras:** register TWO bundles from one package (Option B) — a free `extras-primitives` sub-bundle (9 structural primitives) + the paid `extras` remainder (39 blocks, was 48). Mechanically additive: no import-path changes, all blocks still exported + registered, existing content keeps rendering, existing `extras` entitlements keep working (the primitives are now free to everyone). The tier-scope change is the only semantic shift. - **signal-theme + sc-pack:** tier `'pro'` → `'addon'` (sold independently of the tiered subscription track). - **blocks-gallery:** widen the `@wabbit/tome-blocks-core` peer to `^0.5.9 || ^0.6.0 || ^0.7.0` so the 0.7.0 bump doesn't force a spurious major (it's a types-only peer). Patch. Release note: the blocks family is `linked`, so this aligns the whole family to **0.7.0**. Minor (not major) is deliberate — 0.7.0 still gates explicit consumer adoption (`^0.6` does not auto-resolve 0.7.0), without declaring a symbolic 1.0.0 before the marketplace launch.