Learning Blocks

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

Pro-tier LMS blocks — course card, lesson list, progress bar, quiz summary, instructor card, and enrollment CTA, with optional live-data hydration from @wabbit/tome-lms.

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

Overview

@wabbit/tome-blocks-lms-pack

Pro-tier Payload block bundle for LMS (Learning Management System) use cases. Designed to work with @wabbit/tome-lms for rich integrations, with graceful fallback to static props when @wabbit/tome-lms is absent.

All six blocks (Course Card, Lesson List, Progress Bar, Quiz Summary, Instructor Card, Enrollment CTA) are built, registered, and rendered; Course Card and Lesson List additionally support v1 server-side hydration from @wabbit/tome-lms.

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-lms-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 { courseCardBlock, lessonListBlock } from '@wabbit/tome-blocks-lms-pack'
// blocks: [...existingBlocks, courseCardBlock.block(), lessonListBlock.block()]
// blockComponents.ts
import { renderers as lmsRenderers } from '@wabbit/tome-blocks-lms-pack/render/register'
import { adaptRenderersForPayload } from '@wabbit/tome-blocks-core/render'
// blockComponents: { ...adaptRenderersForPayload(lmsRenderers) }
@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-lms-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 | |---|---|---| | 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)

Every block in this pack renders exactly the authored props by default — no @wabbit/tome-lms peer dependency required. Two blocks additionally support server-side hydration: when an author sets the block's courseSlug field AND a consumer wires @wabbit/tome-blocks-lms-pack/server into their components map, a SERVER wrapper overlays live @wabbit/tome-lms data onto the authored props before the static renderer runs. The static renderer itself (./render) never changes — it stays pure, unaware of hydration, and receives only merged props.

// payload.config.ts or wherever your components map is built
import { renderers as staticComponents } from '@wabbit/tome-blocks-lms-pack/render/register'
import { createHydratedRenderers } from '@wabbit/tome-blocks-lms-pack/server'
import { getPayload } from 'payload'
import config from './payload.config'

const components = {
  ...staticComponents,
  ...createHydratedRenderers({ getPayload: () => getPayload({ config }) }),
}

Design principles: hydration is a SERVER concern (a component wrapping the Payload Local API — the static renderer stays pure); authored props are the contract's floor (layer absent, entity missing, or query-throws → renders exactly what the author entered, never a crash, never an empty hole); the components-map is the integration point (no new rendering pipeline); every query runs overrideAccess: false with no user — anonymous-visible data only (personalized/per-user hydration is v2); per-request query dedup via React.cache() (no cache/tag machinery in this pack — cross-request freshness is the consumer's ISR concern).

Verified v1 hydration matrix

Verified against the real courses / course-items / lessons collection schemas (@wabbit/tome-lms/src/collections/*) — the original design guess (a pre-existing "course slug/relationship" lookup field, an excerpt field, a capacity/seats concept) did not survive verification unchanged. None of the four blocks below had ANY course lookup field before this wave; courseSlug (optional text, following this repo's own "text-stub now, relationship later" idiom used for Course.vendor) was added to course-card and lesson-list.

| Block | Lookup key | Live fields overlaid | Authored fallback | |---|---|---|---| | course-card | courseSlug (new optional field) | title, level (raw passthrough — Course's foundational/intermediate/advanced/specialist taxonomy differs from the authored beginner/intermediate/advanced enum; both are display-only strings, never validated against the enum at render time), imageUrl/imageAlt (from featuredImage), instructor (first of Course.instructors, name ?? email) | Everything, when courseSlug is unset, the course is missing/unpublished, or any query throws | | lesson-list | courseSlug (new optional field) | A flattened, ordered single module of live lessons (title, free-preview from visibility === 'free', type — raw lessonType passthrough, same taxonomy-differs rationale as level above, duration formatted from estimatedTime), sourced from course-items (always anonymously readable) | The authored modules array, untouched, under the same conditions as above | | enrollment-cta | — (none added) | Not hydrated in v1 | Always authored | | progress-bar | — | Not hydrated in v1 (per-user data — v2) | Always authored |

Dropped from the spec's original guess, and why:

  • description/excerpt (course-card): Course.description is Lexical richText; the authored field is a plain textarea string. Converting richText → plaintext needs a Lexical-aware helper this pack doesn't depend on (@payloadcms/richtext-lexical is not a dependency here) — assigning the raw richText object into a string prop would render [object Object] or throw. Deferred until a shared plaintext-extraction helper exists (a candidate for @wabbit/tome-blocks-core or @wabbit/tome-lms/server, not this pack).
  • price (course-card, enrollment-cta): lives in @wabbit/tome-catalog's catalog-products (linked via metadata.courseId, see @wabbit/tome-lms/src/integration/catalog.ts) — a THIRD optional composition layer beyond @wabbit/tome-lms itself. Wiring it would require this pack to detect @wabbit/tome-catalog too, out of v1's single-layer scope. Trigger for v2: when a consumer needs it, detect catalog composition the way @wabbit/tome-lms's detectCatalog does, and read the linked product via getCourseProduct.
  • enrollment count (course-card): course-enrollments' read access (enrollmentRead = ownOrInstructor('student')) only exposes a student's own rows or instructor+. An anonymous, overrideAccess: false query with no user always returns zero rows — hydrating this would silently show a permanently-wrong "0 enrolled" forever, which is worse than not hydrating it at all.
  • enrollment state copy: open/closed/full (enrollment-cta): courses' read access (courseRead = publicWithStatusFilter('status', 'published')) already narrows anonymous reads to status: 'published' — an unpublished/draft/archived course is indistinguishable from "not found." No anonymous query can ever positively detect a "closed" course (it just falls back, same as missing), and there is no capacity/seats field on Course to derive "full" from. Trigger for v2: an authenticated or elevated-access read path that can see non-published courses without exposing that distinction to anonymous visitors.
  • module/topic grouping (lesson-list): v1 flattens the live curriculum into a single ordered list rather than reconstructing @wabbit/tome-lms's topic/module tree (which its private buildCourseItemTree builds internally by walking CourseItem.parent — not exported from the public barrel). Trigger for v2: when a consumer asks for real module boundaries in the hydrated list, either @wabbit/tome-lms exports that helper (or an equivalent) for reuse, or this pack grows its own topic-aware grouping on top of the same course-items query (grouping by parent).
  • `progress-bar` is excluded entirely: a learner's real progress (CourseEnrollment.overallProgress) is per-user data, and v1 hydration only ever runs anonymous, unauthenticated queries. Trigger for v2: when the hydration wrapper contract grows a way to thread an authenticated principal through to the resolver.

Anonymous-access boundary

Every hydration query in /server runs with overrideAccess: false and no user — exactly what an anonymous site visitor could read via Payload's own access-control layer, never bypassed. course-items' read access is unconditionally public, so lesson metadata (title/order/lock-state) is always queryable; the populated lessons documents are still subject to lessons' own read access (public by default, or enrollment-gated if a site opts in via lessonReadAccess: 'enrollment-gated') — in the gated case, a locked lesson the anonymous caller can't reach resolves to nothing and is silently skipped. Either way, this pack never reads a lesson's content/blocks/video fields — only title/order/lock-state metadata ever leaves the resolver. Enrollment-gated lesson content stays gated, always.

Server-only guard

@wabbit/tome-blocks-lms-pack/server and every module under src/server/ import 'server-only' at the top, mirroring @wabbit/tome-lms's own established pattern exactly (server-only declared as a plain dependency in package.json, 'server-only' listed in tsup's external) — accidental client-bundle inclusion fails fast. Payload's Payload type is imported type-only (import type { Payload } from 'payload'); payload was already a required peer dependency for this pack (block configs use Block from payload), so no peerDependenciesMeta change was needed here.

Detection mechanism (legacy, . subpath)

The root . subpath still exposes hasLayer('@wabbit/tome-lms') detection via @wabbit/tome-core's layerRegistry (isLmsLayerPresent/resolveLmsLayer in src/index.ts), using a dynamic import() wrapped in a try/catch so the package boots cleanly even when @wabbit/tome-core itself is not installed. The v1 hydration wrappers do not consult this flag — it's populated by a fire-and-forget async detection with no ordering guarantee relative to first render, so relying on it would be race-prone. The wrappers instead rely on the query itself failing closed (collection not registered → Payload throws → caught → authored fallback), which is race-free.

Compatibility matrix

Required peers:

| Payload | React | React DOM | |---------|-------|-----------| | >=3.67.0 | >=18.0.0 | >=18.0.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-lms') detection via a try/catch dynamic import; the pack boots cleanly if it's absent or fails to install.
  • @wabbit/tome-lms — optional peer (peerDependenciesMeta only; no enforced version range). This pack never imports it directly — hydration queries the Payload collections it registers (courses, course-items, lessons) by name and falls back to authored props if the collections aren't registered.

Block list

All six blocks are built and registered:

| Block | Description | Variants | |---|---|---| | course-card | A single course tile — image, title, instructor, level, rating, and an enroll CTA | default, minimal, featured | | lesson-list | Ordered list of course lessons with status, duration, and module grouping | default, compact, accordion | | progress-bar | Displays learner progress through a course or module | default, minimal, circular | | quiz-summary | Displays quiz results with score, pass/fail status, and optional breakdown | default, minimal, detailed | | instructor-card | Displays an instructor profile with avatar, bio, credentials, and social links | default, minimal, editorial | | enrollment-cta | High-conversion enrollment section with price, features, and enroll button | default, minimal, dark |

Public API

| Export | Subpath | Description | |---|---|---| | Block descriptors (CourseCard, EnrollmentCta, InstructorCard, LessonList, ProgressBar, QuizSummary configs) + register(blockRegistry, bundleRegistry) + isLmsLayerPresent() | . | Payload block config descriptors, bundle registration, and the @wabbit/tome-lms layer-detection flag | | CourseCard, EnrollmentCta, InstructorCard, LessonList, ProgressBar, QuizSummary | ./render | Legacy self-registering render barrel | | renderers map + registerRenderers() | ./render/register | Explicit registration (server-safe adapter contract) — no side effects on import | | createHydratedRenderers({ getPayload }), resolveCourseCardData, resolveLessonListData, wrapHydrated, fetchCourseBySlug, fetchCourseLessons, safeCache | ./server | v1 hydration contract — see "Hydration (v1)" above | | 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 | | Global CSS token surface | ./styles.css | Import once at the app root | | Bundle/block metadata (slugs + variants) | ./meta | Payload-free surface for the gallery storefront |

Server / client posture

5 of the pack's 6 renderers under src/render/ (EnrollmentCta, InstructorCard, LessonList, ProgressBar, QuizSummary) are plain static components with no 'use client' directive. CourseCard alone is a client component (verified 2026-07-12; scripts/assert-rsc-boundaries.mjs MANIFEST: blocks-lms-pack: 1). 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.

./server's hydration wrappers add ZERO 'use client' files — they live under src/server/, not src/render/, so the MANIFEST count above is unaffected. createHydratedRenderers's wrappers import CourseCard (a client component) and LessonList (a static one) directly, same as ./render/register does — a server component composing a client component as a JSX child is the standard, supported RSC pattern; the wrapper itself carries no directive and never executes in a client bundle.

Blocks

course-card

A single course tile — image, title, instructor, level, rating, and an enroll CTA. Fully static — renders the authored props; no @wabbit/tome-lms peer required. Three variants (default, minimal title-and-CTA, featured hero with rating) cover the range from catalog row to spotlight.

When to use
  • Surfacing one course on a landing, about, or program page where the course is the call to action
  • A "featured course" or "start here" highlight on a course-catalog or homepage
  • Cross-linking a related course from inside an article or lesson page
Page types
  • landing
  • about
  • docs
  • marketing
How to use

Author title (required), instructor, duration, level, rating, image, and enroll URL/label — these are the authored floor, always rendered as-is when hydration is absent. Optionally set `courseSlug` to a live @wabbit/tome-lms course: when a consumer wires `@wabbit/tome-blocks-lms-pack/server`'s createHydratedRenderers into their components map, live title/level/featuredImage/instructor overlay these fields at render time (v1 hydration matrix — see the pack README). Description, price, and enrollment count are NOT hydrated in v1 (richText/enum mismatch, third-layer catalog dependency, and anonymous-access limits respectively — see README). Pick a variant via the `_variant` axis: default for a full card with image and description, minimal when you only need title + instructor + CTA in a tight space, featured for a large hero tile that leads with image and rating. For a wall of courses, repeat the card or reach for a grid block rather than stacking many featured tiles.

Pairs with
  • lesson-list
  • instructor-card
  • enrollment-cta
  • progress-bar
Precedes
  • lesson-list
  • instructor-card
  • enrollment-cta
Avoid when
  • Listing many courses at once — repeat the card in a grid; a single Course Card is for one course
  • You need the full curriculum breakdown — that is `lesson-list`, not the card
  • The page goal is the purchase decision with price and guarantee — use `enrollment-cta`
Register in

application

lesson-list

The course curriculum — modules and their lessons with per-lesson duration, type (video/article/quiz/assignment), and free-preview flags, plus an optional total-duration readout. Fully static — renders the authored module/lesson arrays; no @wabbit/tome-lms peer required. Three variants (numbered default, compact title-and-duration rows, accordion grouped by module).

When to use
  • A course landing or sales page that needs to show "what you will learn" as a structured syllabus
  • A docs-style curriculum page where lessons group under modules
  • Marking certain lessons as free-preview to pull prospects into enrollment
Page types
  • landing
  • docs
  • about
  • marketing
How to use

Author a `modules` array, each with a title and a nested `lessons` array (title required; optional duration, type, and a free-preview checkbox); set the heading and toggle the total-duration readout — this structure is the authored floor, always rendered as-is when hydration is absent. Optionally set `courseSlug` to a live @wabbit/tome-lms course: when a consumer wires `@wabbit/tome-blocks-lms-pack/server`'s createHydratedRenderers into their components map, the live curriculum (lesson titles, order, lock state) replaces `modules` at render time, flattened into a single ordered list in v1 (topic/module grouping is a v2 trigger — see the pack README). Only anonymous-visible lesson metadata is ever read — never gated lesson content. Choose the variant by length: default for a numbered overview, compact for a dense long syllabus, accordion when there are many modules and you want collapsible sections. Sits naturally below a course-card or hero and above the enrollment CTA.

Pairs with
  • course-card
  • progress-bar
  • instructor-card
  • enrollment-cta
Follows
  • course-card
Precedes
  • enrollment-cta
  • instructor-card
Avoid when
  • You only need a one-line "12 lessons" stat — that is a metric, not the full list
  • The content is a flat article rather than a structured course — use an editorial list
  • Showing learner progress percentage — that is `progress-bar`
Register in

application

progress-bar

A single learner-progress indicator — a labeled bar with an optional percentage and status line ("3 of 12 lessons complete"). Fully static — always pinned to the authored percent; no @wabbit/tome-lms peer required. Three variants: horizontal default, thin minimal bar, and a circular donut with a central percentage.

When to use
  • An authenticated course or dashboard page showing how far a learner has gotten
  • A "resume where you left off" prompt that needs a visual completion cue
  • A module header that shows progress through that module
Page types
  • docs
  • about
  • landing
How to use

Set a label, the percent to show (0-100), and an optional status string; toggle whether the label and percentage show — the authored percent is the only figure shown today, and stays that way in v1. Deliberately excluded from the v1 hydration wave (`@wabbit/tome-blocks-lms-pack/server`): a learner's real progress (`CourseEnrollment.overallProgress`) is per-user data, and v1 hydration only ever runs anonymous, unauthenticated queries — see the pack README's "Hydration (v1)" section for the v2 trigger (an authenticated principal threaded through the resolver contract). Choose the variant by emphasis: default for a labeled inline bar, minimal for an unobtrusive thin bar inside a list or header, circular when progress is the focal stat (e.g. a dashboard ring). This is a status readout, not a navigation or content block — keep it small and near the thing it measures.

Pairs with
  • lesson-list
  • course-card
  • quiz-summary
Follows
  • course-card
Precedes
  • lesson-list
Avoid when
  • On a marketing/sales page to a logged-out visitor — there is no learner to measure
  • You want the lesson-by-lesson breakdown — that is `lesson-list`
  • Reporting a quiz score rather than course completion — use `quiz-summary`
Register in

application

quiz-summary

A quiz-result panel — the score, a pass/fail verdict against a passing threshold, tailored pass/fail messages, an optional category breakdown, and a retake link on fail. Fully static — always shows the authored score; no @wabbit/tome-lms peer required. Three variants: default (score + badge + breakdown), minimal (score and verdict only), detailed (question-by-question review).

When to use
  • An assessment-results page shown after a learner submits a quiz
  • A module checkpoint that reports the learner's standing and offers a retake
  • A certification gate that needs to show pass/fail clearly
Page types
  • docs
  • about
How to use

Set the quiz title, the authored score, a passing-score threshold, and the pass/fail messages — the authored score is the only source shown today. @wabbit/tome-lms is not wired in yet; the isLmsLayerPresent/resolveLmsLayer seam in src/index.ts is scaffolding for a future hydration wave, to be wired when a consumer needs this panel to show the learner's actual attempt. Choose the variant by depth: minimal for a compact verdict, default for score plus category breakdown, detailed for a full per-question review. This is an outcome block — show it after an attempt, not as marketing.

Pairs with
  • progress-bar
  • lesson-list
  • enrollment-cta
Follows
  • lesson-list
Avoid when
  • There is no quiz attempt to report — this block needs a result, real or static
  • Tracking overall course completion rather than a single quiz — use `progress-bar`
  • Marketing a course to logged-out visitors — nothing to summarize yet
Register in

application

instructor-card

A single instructor profile — avatar, name, title, bio, a list of credentials, and social links. Pure authored content (no live LMS dependency). Three variants: default horizontal layout, minimal (avatar, name, title only), and editorial (large avatar with full bio in a magazine column).

When to use
  • A "meet your instructor" section on a course landing or sales page
  • An about or team page introducing the people behind a program
  • Establishing authority/credibility next to the curriculum and enrollment ask
Page types
  • about
  • landing
  • marketing
  • docs
How to use

Author name (required), title, bio, a credentials array (up to 6), and social links (platform + URL, up to 5). Choose the variant by role on the page: minimal for a compact byline, default for a standard profile beside other content, editorial when the instructor's story carries the section and deserves a full bio column. For multiple instructors, repeat the card. Works well as a trust beat between the curriculum and the enrollment CTA.

Pairs with
  • course-card
  • lesson-list
  • enrollment-cta
Follows
  • lesson-list
Precedes
  • enrollment-cta
Avoid when
  • Listing a whole faculty roster compactly — repeat the minimal variant or use a team grid
  • The page is about the course outcome, not the people — lead with curriculum and CTA
  • You need course content rather than a person — use `course-card` or `lesson-list`
Register in

dossier

enrollment-cta

The enroll-now conversion block — heading, price with note, an included-features list, the enroll button, and an optional trust/guarantee line. Fully static — the button always targets the authored URL; no @wabbit/tome-lms peer required. Three variants: centered default, minimal banner (heading + price + button), and dark for high-contrast emphasis.

When to use
  • The decision point on a course sales/landing page where the visitor enrolls
  • A closing band after the curriculum, instructor, and proof have been presented
  • A standalone "ready to start?" banner mid-page or in a sticky footer
Page types
  • landing
  • marketing
  • about
How to use

Author the heading (required), price and price-note, the included-features array (up to 10), the CTA label/URL (label required), and an optional guarantee line — the URL is the only enrollment target today, and stays that way in v1. Evaluated for the v1 hydration wave (`@wabbit/tome-blocks-lms-pack/server`) and found not hydratable yet: live price lives in @wabbit/tome-catalog's catalog-products, a third optional layer beyond @wabbit/tome-lms itself, out of v1's single-layer scope; and an anonymous, unauthenticated query can never distinguish a "closed" course from a merely-unpublished one (Payload's own course-read access collapses both to "not found"), so no live enrollment-state copy is derivable without either leaking draft state or fabricating capacity data that doesn't exist. See the pack README's "Hydration (v1)" section for the full reasoning and the v2 trigger. Choose the variant by weight: default for a full feature-listed offer, minimal for a compact banner where the offer is already understood, dark to make the close pop against the page. This is the closer — place it after the value has been built, and give it one clear action.

Pairs with
  • course-card
  • lesson-list
  • instructor-card
  • quiz-summary
Follows
  • lesson-list
  • instructor-card
  • course-card
Avoid when
  • Early on the page before any value is established — earn the ask first
  • You are comparing multiple plans/tiers — that is a pricing table, not a single CTA
  • There is no enrollment action yet (content still in development) — use a content block
Register in

marketing-landing

Exports

  • @wabbit/tome-blocks-lms-pack
  • @wabbit/tome-blocks-lms-pack/render
  • @wabbit/tome-blocks-lms-pack/render/register
  • @wabbit/tome-blocks-lms-pack/server
  • @wabbit/tome-blocks-lms-pack/styles.css
  • @wabbit/tome-blocks-lms-pack/demo
  • @wabbit/tome-blocks-lms-pack/meta

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

196d642: Remove a Star Citizen reference from the `instructor-card` editorial demo bio. Tome-native packages use domain-neutral language; domain vocabulary belongs in the sanctioned adaptation layer (`@wabbit/tome-sc`, `@wabbit/tome-blocks-sc-pack`), where consumers adapt neutral mechanisms to their own domain. `blocks-lms-pack` is a general-purpose LMS pack, so its shipped demo copy should not reference a specific game. Note the Star Citizen strings in `blocks-core`'s `BLOCK_CATALOG.ts` and `thumbnail-index.ts` are deliberately untouched: both are generated from the packs' own `meta.ts` descriptors and guarded by `scripts/assert-block-catalog.mjs`, and the strings originate in `blocks-sc-pack` where they are correct. An aggregation artifact is not a leak — what matters is where vocabulary is authored, not where it is compiled to. - @wabbit/tome-blocks-core@0.15.9

  • 196d642: Remove a Star Citizen reference from the `instructor-card` editorial demo bio. Tome-native packages use domain-neutral language; domain vocabulary belongs in the sanctioned adaptation layer (`@wabbit/tome-sc`, `@wabbit/tome-blocks-sc-pack`), where consumers adapt neutral mechanisms to their own domain. `blocks-lms-pack` is a general-purpose LMS pack, so its shipped demo copy should not reference a specific game. Note the Star Citizen strings in `blocks-core`'s `BLOCK_CATALOG.ts` and `thumbnail-index.ts` are deliberately untouched: both are generated from the packs' own `meta.ts` descriptors and guarded by `scripts/assert-block-catalog.mjs`, and the strings originate in `blocks-sc-pack` where they are correct. An aggregation artifact is not a leak — what matters is where vocabulary is authored, not where it is compiled to. - @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

8100b6f: Adds a new `@wabbit/tome-blocks-lms-pack/server` subpath: the v1 hydration contract. When @wabbit/tome-lms is present and an author sets a course-card or lesson-list block's new optional `courseSlug` field, a server component wrapper overlays live course data over the authored props before the static renderer runs — the renderer itself stays pure, unaware of hydration, and unchanged. Exports `createHydratedRenderers({ getPayload })`, which produces a drop-in `{ slug: Component }` map to spread over a static components map (`{ ...staticComponents, ...createHydratedRenderers({ getPayload }) }`), plus individually-exported per-block resolvers (`resolveCourseCardData`, `resolveLessonListData`) for hydrating outside the block pipeline. The v1 matrix (verified against the real `courses`/`course-items`/`lessons` schemas — the original design guess did not survive verification unchanged): - `course-card`: overlays `title`, `level`, `imageUrl`/`imageAlt` (from `featuredImage`), `instructor` (first of `Course.instructors`, name-or-email). Dropped: description/excerpt (Course.description is Lexical richText, incompatible with the authored plain-string field without a new richText-to-plaintext dependency this pack doesn't carry), price (lives in @wabbit/tome-catalog's `catalog-products`, a third optional composition layer beyond @wabbit/tome-lms itself — out of v1's single-layer scope), enrollment count (anonymous, `overrideAccess: false` reads of `course-enrollments` always return zero rows under that collection's access rules — hydrating it would show a permanently-wrong "0 enrolled"). - `lesson-list`: overlays a flattened, ordered list of live lesson titles, free-preview flags, and lesson types, sourced from `course-items` (always anonymously readable) with `lessons` populated at depth 1. Topic/module grouping is deferred to v2 (see the pack README). - `enrollment-cta` and `progress-bar` are NOT hydrated in v1. No anonymous-safe live field survived verification for the former (see the price/enrollment-state reasoning above — an anonymous course-read collapses "closed" and "not found" into the same result, so no live enrollment-state copy is derivable either). `progress-bar` needs per-user data, explicitly out of v1's anonymous-only scope (v2). Anonymous-access boundary: every hydration query runs with `overrideAccess: false` and no `user` — exactly what an anonymous site visitor could read. Enrollment-gated lesson content is never read (only title/order/lock-state metadata, never `content`/`blocks`/`video`). Per-request query dedup via `React.cache()` — no new cache/tag machinery in this pack. Static usage is completely unchanged: every block still renders exactly the authored props when `courseSlug` is unset, when @wabbit/tome-lms is absent, when the referenced course/lessons are missing, or if any query throws. Zero new `'use client'` directives were added (all new files live under `src/server/`, not `src/render/`).

  • 8100b6f: Adds a new `@wabbit/tome-blocks-lms-pack/server` subpath: the v1 hydration contract. When @wabbit/tome-lms is present and an author sets a course-card or lesson-list block's new optional `courseSlug` field, a server component wrapper overlays live course data over the authored props before the static renderer runs — the renderer itself stays pure, unaware of hydration, and unchanged. Exports `createHydratedRenderers({ getPayload })`, which produces a drop-in `{ slug: Component }` map to spread over a static components map (`{ ...staticComponents, ...createHydratedRenderers({ getPayload }) }`), plus individually-exported per-block resolvers (`resolveCourseCardData`, `resolveLessonListData`) for hydrating outside the block pipeline. The v1 matrix (verified against the real `courses`/`course-items`/`lessons` schemas — the original design guess did not survive verification unchanged): - `course-card`: overlays `title`, `level`, `imageUrl`/`imageAlt` (from `featuredImage`), `instructor` (first of `Course.instructors`, name-or-email). Dropped: description/excerpt (Course.description is Lexical richText, incompatible with the authored plain-string field without a new richText-to-plaintext dependency this pack doesn't carry), price (lives in @wabbit/tome-catalog's `catalog-products`, a third optional composition layer beyond @wabbit/tome-lms itself — out of v1's single-layer scope), enrollment count (anonymous, `overrideAccess: false` reads of `course-enrollments` always return zero rows under that collection's access rules — hydrating it would show a permanently-wrong "0 enrolled"). - `lesson-list`: overlays a flattened, ordered list of live lesson titles, free-preview flags, and lesson types, sourced from `course-items` (always anonymously readable) with `lessons` populated at depth 1. Topic/module grouping is deferred to v2 (see the pack README). - `enrollment-cta` and `progress-bar` are NOT hydrated in v1. No anonymous-safe live field survived verification for the former (see the price/enrollment-state reasoning above — an anonymous course-read collapses "closed" and "not found" into the same result, so no live enrollment-state copy is derivable either). `progress-bar` needs per-user data, explicitly out of v1's anonymous-only scope (v2). Anonymous-access boundary: every hydration query runs with `overrideAccess: false` and no `user` — exactly what an anonymous site visitor could read. Enrollment-gated lesson content is never read (only title/order/lock-state metadata, never `content`/`blocks`/`video`). Per-request query dedup via `React.cache()` — no new cache/tag machinery in this pack. Static usage is completely unchanged: every block still renders exactly the authored props when `courseSlug` is unset, when @wabbit/tome-lms is absent, when the referenced course/lessons are missing, or if any query throws. Zero new `'use client'` directives were added (all new files live under `src/server/`, not `src/render/`).
  • 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.1patch

Add a `./styles.css` export to the package `exports` map so registry consumers can import the pack's compiled stylesheet (`@wabbit/tome-blocks-lms-pack/styles.css`). Under path-alias consumption the file resolved directly; the `exports` map enforces the subpath under registry consumption. Surfaced by tome-starter's `(frontend)/layout.tsx` during the move to registry consumption.

  • Add a `./styles.css` export to the package `exports` map so registry consumers can import the pack's compiled stylesheet (`@wabbit/tome-blocks-lms-pack/styles.css`). Under path-alias consumption the file resolved directly; the `exports` map enforces the subpath under registry consumption. Surfaced by tome-starter's `(frontend)/layout.tsx` during the move to registry consumption.
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