Org Blocks
Blocks & ThemesRender-ready organization/community blocks — division card, member card, member grid, event calendar, event list item, org chart, document link, and campaign banner; static today, scaffolded for future @wabbit/tome-org hydration.
npm install @wabbit/tome-blocks-org-packOverview
@wabbit/tome-blocks-org-pack
Organization block pack for the Tome platform — render-ready blocks (DivisionCard, MemberCard, MemberGrid, EventCalendar, EventListItem, OrgChart, DocumentLink, CampaignBanner) for org/community surfaces.
Config descriptors are exported from .; React render components from ./render. All blocks are fully static today — they render exactly the props an author enters, and carry no @wabbit/tome-org peer dependency. The pack does detect whether @wabbit/tome-org is registered (isOrgLayerPresent/resolveOrgLayer in src/index.ts) and each block's config retains fields shaped to match the org layer's records — this is scaffolding for a future hydration wave, not a live integration. Trigger to build it: when a consumer needs a block in this pack to render live @wabbit/tome-org data, wire the resolver into a server-side wrapper at that seam.
Install
On a Tome site (meta-package path)
Not currently bundled by @wabbit/tome-blocks' registerAll() (a pre-existing scope decision — see that package's README, "Public API" section). Install this pack directly even on a Tome site.
On a stock Payload site — add to an existing blocks field
npm install @wabbit/tome-blocks-org-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 { memberCardBlock, eventCalendarBlock } from '@wabbit/tome-blocks-org-pack'
// blocks: [...existingBlocks, memberCardBlock.block(), eventCalendarBlock.block()]// blockComponents.ts
import { renderers as orgRenderers } from '@wabbit/tome-blocks-org-pack/render/register'
import { adaptRenderersForPayload } from '@wabbit/tome-blocks-core/render'
// blockComponents: { ...adaptRenderersForPayload(orgRenderers) }@import '@wabbit/tome-blocks-core/styles.css';Registering everything from scratch instead:
// payload.config.ts
import { BlockRegistry, BundleRegistry } from '@wabbit/tome-blocks-core/registry'
import { register } from '@wabbit/tome-blocks-org-pack'
const blockRegistry = new BlockRegistry()
const bundleRegistry = new BundleRegistry()
register(blockRegistry, bundleRegistry)
// Use blockRegistry.resolveAll() to get Payload Block configs| 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 |
@wabbit/tome-org is an optional peer (peerDependenciesMeta) — see the detection note above.
Block list
| Slug | Name | Notes | |---|---|---| | division-card | Division Card | Org sub-unit/division summary card | | member-card | Member Card | Individual member profile card | | member-grid | Member Grid | Grid of member cards | | event-calendar | Event Calendar | Calendar view of org events | | event-list-item | Event List Item | Single event row (list context) | | org-chart | Org Chart | Hierarchical org structure display | | document-link | Document Link | Linked document/resource reference | | campaign-banner | Campaign Banner | Announcement/campaign call-out banner |
Public API
// config (Payload collections / Pages block list)
import { divisionCardBlock } from '@wabbit/tome-blocks-org-pack'
// render — DivisionCard (and every other block component) is exported from
// the `./render` barrel; there is no per-component subpath in the exports map
import { DivisionCard } from '@wabbit/tome-blocks-org-pack/render'| Export | Subpath | Description | |---|---|---| | Block descriptors (divisionCardBlock, memberCardBlock, memberGridBlock, eventCalendarBlock, eventListItemBlock, orgChartBlock, documentLinkBlock, campaignBannerBlock) + register(blockRegistry, bundleRegistry) + isOrgLayerPresent() | . | Payload block config descriptors, bundle registration, and the @wabbit/tome-org layer-detection flag | | DivisionCard, MemberCard, MemberGrid, EventCalendar, EventListItem, OrgChart, DocumentLink, CampaignBanner + orgPackRenders map | ./render | Legacy self-registering render barrel | | renderers map + registerRenderers() | ./render/register | Explicit registration (server-safe adapter contract) — imports each component directly, no side effects on import | | getDemoProps(blockSlug, variant, ctx?) (plus tree-shakeable per-block demo functions) | ./demo | Pack-level demo-props dispatcher feeding the auto-gallery route; returns null for unknown slugs | | orgPackBlockMeta (per-block meta + variants, in registration order) | ./meta | Payload-free surface for the gallery storefront |
Server / client posture
All 8 renderers under src/render/ are plain static components — zero carry a 'use client' directive (verified 2026-07-12; scripts/assert-rsc-boundaries.mjs MANIFEST: blocks-org-pack: 0). They render safely from either a server or client component graph. 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.
Built with tsup (bundle: false, dual ESM/CJS, CSS mirrored to dist/).
Blocks
division-card
A single division/department display — logo, name, description, member count, and named leader. The unit-level companion to member-card, summarizing a sub-org rather than a person. Fully static — renders the authored fields; no @wabbit/tome-org peer required.
- Introducing a division, department, or regional sub-unit on an about/structure page
- A "Our divisions" section where each unit gets a summary card with its leader and headcount
- Leading into a member-grid or org-chart that drills into that division
- about
- docs
- dossier
Provide name (required) plus description, memberCount, logoUrl, and leaderName — these are the only data source today, not a static fallback. @wabbit/tome-org is not wired in yet; the isOrgLayerPresent/resolveOrgLayer seam in src/index.ts is scaffolding for a future hydration wave, to be wired when a consumer needs this card to render a live Division record. Use one card per unit, or several side by side, as the header for the people that belong to it. One variant (default).
- member-grid
- member-card
- org-chart
- document-link
- org-chart
- member-grid
- member-card
- Profiling an individual rather than a unit — use member-card
- You need the full three-level structure tree — use org-chart
application
member-card
A single member profile card — avatar, display name, role, and position for one person. Fully static — renders the authored fields; no @wabbit/tome-org peer required.
- Spotlighting one person on an about/leadership page (a director, a guild leader, a founder)
- A "meet the director" callout where a single profile carries the section
- Inside a custom layout where you place individual cards by hand rather than a generated grid
- about
- portfolio
- dossier
Provide one member: displayName (required), plus avatarUrl, rank, and position — these are the only data source today. @wabbit/tome-org is not wired in yet; the isOrgLayerPresent/resolveOrgLayer seam in src/index.ts is scaffolding for a future hydration wave, to be wired when a consumer needs this card to render a live Member record. This is the single-member primitive — to lay out many people in a generated grid use member-grid, which renders the same card shape from an array. One variant (default).
- member-grid
- division-card
- org-chart
- division-card
- member-grid
- Showing a team or roster — use member-grid (array-driven) instead of hand-placing cards
- You need the org hierarchy itself rather than individuals — use org-chart
application
member-grid
A responsive grid of member cards under an optional heading — the team/roster directory block. Renders the same card shape as member-card from a list, with column count and an item cap. Fully static — renders the authored members array; no @wabbit/tome-org peer required.
- A "Meet the team" / "Our people" / "Leadership" section listing many members at once
- A company or community directory page where you want every member tiled in a grid
- Any directory of people you want generated from data rather than placed card by card
- about
- portfolio
- dossier
Set a heading, pick columns (2, 3, or 4), and cap with maxItems. Supply the members array — it is the only data source today, not a static fallback. @wabbit/tome-org is not wired in yet; the isOrgLayerPresent/resolveOrgLayer seam in src/index.ts is scaffolding for a future hydration wave, to be wired when a consumer needs this grid to render a live Member collection. This is the array/layout counterpart to member-card — same per-person card, generated in bulk. One variant (default).
- member-card
- division-card
- org-chart
- campaign-banner
- division-card
- org-chart
- event-calendar
- Featuring a single person — use member-card
- You want the reporting hierarchy rather than a flat people grid — use org-chart
application
event-calendar
An upcoming-events list under an optional heading — each row carries a title, start time, status (scheduled/live/completed/cancelled), and location. The array/section counterpart to event-list-item. Fully static — renders the authored events array; no @wabbit/tome-org peer required.
- An "Upcoming events" / "What's on" section on an org, community, or company page
- A schedule of workshops, meetups, or streams where each entry needs date and status
- Any place you want the next N events generated from data with a single block
- about
- docs
- dossier
Set a heading and cap with maxItems, then supply the events array (title required, plus startsAt ISO string, status, location) — it is the only data source today. @wabbit/tome-org is not wired in yet; the isOrgLayerPresent/resolveOrgLayer seam in src/index.ts is scaffolding for a future hydration wave, to be wired when a consumer needs this block to render the live Event collection. Use this for the full list; drop a single event-list-item into a sidebar or feed when you only need one row. One variant (default).
- event-list-item
- campaign-banner
- member-grid
- campaign-banner
- member-grid
- document-link
- You only need one event row in a sidebar or feed — use event-list-item
- Promoting a single flagship campaign rather than a dated list — use campaign-banner
application
event-list-item
A single event row — title, start time, status, and an optional click-through — sized for feeds, sidebars, and inline placement. The single-item primitive behind event-calendar. Fully static — renders the authored fields; no @wabbit/tome-org peer required.
- Surfacing one upcoming event in a sidebar, footer, or "next event" callout
- Inline within editorial content where a full calendar would be too heavy
- Hand-placing individual event rows rather than generating a list
- about
- docs
- blog
Provide title (required) plus startsAt (ISO string), status, and an optional href — these are the only data source today. @wabbit/tome-org is not wired in yet; the isOrgLayerPresent/resolveOrgLayer seam in src/index.ts is scaffolding for a future hydration wave, to be wired when a consumer needs this row to render a live Event record. This is the single-row counterpart to event-calendar — same row shape, one at a time. One variant (default).
- event-calendar
- member-card
- document-link
- event-calendar
- Listing several upcoming events — use event-calendar (array-driven) instead of stacking rows
- You need date, status, and location laid out as a section header — use event-calendar
application
org-chart
A simple three-level hierarchy display — a top-level unit, its teams, and their groups — the structure-of-the-org block, showing reporting lines rather than individual profiles. Fully static — renders the authored teams array; no @wabbit/tome-org peer required.
- An "Org structure" / "How we are organized" section showing the levels from department down to working groups
- Orienting a reader before they meet the people — the map that member-grid then fills in
- A dossier page where the unit hierarchy itself is the information
- about
- docs
- dossier
Set divisionName (required) and supply the teams array (each team carries its squads) — it is the only data source today. @wabbit/tome-org is not wired in yet; the isOrgLayerPresent/resolveOrgLayer seam in src/index.ts is scaffolding for a future hydration wave, to be wired when a consumer needs this chart to render live org-structure records. Use it once near the top of a structure page to frame the divisions and people that follow. One variant (default).
- division-card
- member-grid
- member-card
- division-card
- member-grid
- Showing people rather than structure — use member-grid or member-card
- Summarizing one unit with its leader and headcount — use division-card
application
document-link
A linked document card — title, category label (SOP, Policy, Handbook), and last-updated timestamp pointing at a document URL. The resource/reference primitive for an org. Fully static — renders the authored fields; no @wabbit/tome-org peer required.
- A "Resources" / "Documents" / "Policies" section linking out to SOPs, handbooks, or forms
- A knowledge-base or onboarding page where members find the canonical documents
- Anywhere you want a clean, dated link card instead of a bare hyperlink
- docs
- about
- dossier
Provide title (required) and href (required) plus category and updatedAt (ISO string) — these are the only data source today. @wabbit/tome-org is not wired in yet; the isOrgLayerPresent/resolveOrgLayer seam in src/index.ts is scaffolding for a future hydration wave, to be wired when a consumer needs this card to render a live Document record. Stack several to form a resource list, often under a division-card or near an org-chart on a structure/docs page. One variant (default).
- division-card
- org-chart
- event-calendar
- division-card
- org-chart
- Linking to an event rather than a document — use event-list-item
- You want rich body content inline rather than a link out — use a richText/content block
application
campaign-banner
A full-width campaign hero — campaign name, tagline, full-bleed banner image, objective count, and a CTA. The promotional top-of-page block for an org campaign or initiative. Fully static — renders the authored fields; no @wabbit/tome-org peer required.
- Topping a campaign or initiative page with a cinematic banner and a single call to action
- Announcing an active campaign on a community/org home or landing section
- Driving members toward a campaign detail view or sign-up via the CTA
- about
- portfolio
- dossier
Set codename (the campaign's display name, required) plus tagline, bannerUrl, objectiveCount, and the ctaLabel/ctaUrl pair — these are the only data source today. @wabbit/tome-org is not wired in yet; the isOrgLayerPresent/resolveOrgLayer seam in src/index.ts is scaffolding for a future hydration wave, to be wired when a consumer needs this banner to render a live Campaign record. Place it at the very top of the page as the hero, with the objective list, member-grid, or event-calendar following beneath. One variant (default).
- event-calendar
- member-grid
- org-chart
- event-calendar
- member-grid
- You need a generic marketing hero outside the org-campaign context — use a marketing hero block
- Listing dated events rather than promoting one campaign — use event-calendar
application
Exports
@wabbit/tome-blocks-org-pack@wabbit/tome-blocks-org-pack/render@wabbit/tome-blocks-org-pack/render/register@wabbit/tome-blocks-org-pack/demo@wabbit/tome-blocks-org-pack/meta
Changelog
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.
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.
@wabbit/tome-blocks-core@0.16.0
- @wabbit/tome-blocks-core@0.16.0
@wabbit/tome-blocks-core@0.16.0
- @wabbit/tome-blocks-core@0.16.0
@wabbit/tome-blocks-core@0.16.0
- @wabbit/tome-blocks-core@0.16.0
@wabbit/tome-blocks-core@0.16.0
- @wabbit/tome-blocks-core@0.16.0
c1f3830: Demo imagery now resolves without a network. Every org-pack image field is a plain-text URL (no Payload upload), and the demos pointed at `https://cdn.wabbit.example/…`, a domain that does not exist — so member-card, member-grid, division-card and campaign-banner rendered broken-image glyphs in any gallery preview or thumbnail capture. New `demo-media.ts` emits a self-contained SVG data URI per subject (neutral tonal field + initials; no brand colour), and `DemoContext` gains `placeholderImageUrl` so a host can substitute real imagery. Found while registering Org Blocks on wabbit.com/blocks (2026-09-05).
- c1f3830: Demo imagery now resolves without a network. Every org-pack image field is a plain-text URL (no Payload upload), and the demos pointed at `https://cdn.wabbit.example/…`, a domain that does not exist — so member-card, member-grid, division-card and campaign-banner rendered broken-image glyphs in any gallery preview or thumbnail capture. New `demo-media.ts` emits a self-contained SVG data URI per subject (neutral tonal field + initials; no brand colour), and `DemoContext` gains `placeholderImageUrl` so a host can substitute real imagery. Found while registering Org Blocks on wabbit.com/blocks (2026-09-05).
- Updated dependencies - @wabbit/tome-blocks-core@0.16.0
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.
- Updated dependencies [57875ba]
- Updated dependencies [b01ca1f]
- Updated dependencies [0836ef5]
- Updated dependencies [73081e6]
- Updated dependencies [090e984]
- Updated dependencies [73081e6] - @wabbit/tome-blocks-core@0.16.0
@wabbit/tome-blocks-core@0.15.9
- @wabbit/tome-blocks-core@0.15.9
@wabbit/tome-blocks-core@0.15.9
- @wabbit/tome-blocks-core@0.15.9
@wabbit/tome-blocks-core@0.15.9
- @wabbit/tome-blocks-core@0.15.9
@wabbit/tome-blocks-core@0.15.9
- @wabbit/tome-blocks-core@0.15.9
@wabbit/tome-blocks-core@0.15.9
- @wabbit/tome-blocks-core@0.15.9
@wabbit/tome-blocks-core@0.15.9
- @wabbit/tome-blocks-core@0.15.9
@wabbit/tome-blocks-core@0.15.9
- @wabbit/tome-blocks-core@0.15.9
@wabbit/tome-blocks-core@0.15.9
- @wabbit/tome-blocks-core@0.15.9
@wabbit/tome-blocks-core@0.15.9
- @wabbit/tome-blocks-core@0.15.9
@wabbit/tome-blocks-core@0.15.9
- @wabbit/tome-blocks-core@0.15.9
@wabbit/tome-blocks-core@0.15.9
- @wabbit/tome-blocks-core@0.15.9
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.
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).
Updated dependencies [71d3b09] - @wabbit/tome-blocks-core@0.15.9
- Updated dependencies [71d3b09] - @wabbit/tome-blocks-core@0.15.9
6779aa1: Neutralize gaming/military-flavored language across the org surfaces — labels, descriptions, and demo content only; zero schema changes (all field names, collection slugs, and enum/select VALUES are byte-identical, so no consumer data migration). - **blocks-org-pack:** CampaignBanner's `codename` field is now labeled "Name" with a business example ("Spring Launch" demo replaces "Operation Nightfall … contested systems"); MemberCard/MemberGrid `rank` fields labeled "Role" with business-ladder demo values (Principal/Staff/Senior replace Captain/Lieutenant/Sergeant, "Fleet Commander" → "Design Lead"); EventCalendar demo uses business events (workshop, hiring open house, quarterly business review — "Upcoming Operations" heading → "Upcoming Events"); OrgChart meta/variants describe a generic three-level hierarchy instead of Division → Teams → Squads (render output was already 100% data-driven — level headings come from the authored rows, so no new props were needed); block meta descriptions/usage neutralized throughout. - **tome-org:** flavored admin LABELS get neutral text while stored values stay put — Event status `boarding`/`debrief` labeled "Check-In"/"Wrap-Up"; eventType `operation`/`patrol`/`exam` labeled "Initiative"/"Outreach"/"Assessment"; `securityLevel` labeled "Access"; Campaign `codename` labeled "Internal Name" and campaignType `recurring_op`/`special_operation`/`deployment` labeled "Recurring Series"/"Special Initiative"/"Rollout"; Member `classification` labeled "Directory Visibility" with `classified` labeled "Private", "Chain of command" → "Reporting line"; Rank `securityClearance` labeled "Access Level" and category `command` labeled "Management"; Squad squadType `fire_team`/`flight` labeled "Crew"/"Pod", `callsign` labeled "Nickname"; Membership `squadron` labeled "Unit", role example "Pointman, Medic" → "Coordinator, Facilitator"; Position abbreviation example "CO, XO" → "COO, PM", category `command` labeled "Executive". The configurable `DEFAULT_ORG_TERMINOLOGY` (Division/Team/Squad/Rank) is deliberately unchanged — it is the documented override seam and `@wabbit/tome-sc` inherits it for its themed collections. - **blocks-core:** BLOCK_CATALOG entries for campaign-banner, member-card, and org-chart re-mirror the updated pack meta descriptions (catalog is generated from pack meta; only the entries owned by this change were refreshed).
- 6779aa1: Neutralize gaming/military-flavored language across the org surfaces — labels, descriptions, and demo content only; zero schema changes (all field names, collection slugs, and enum/select VALUES are byte-identical, so no consumer data migration). - **blocks-org-pack:** CampaignBanner's `codename` field is now labeled "Name" with a business example ("Spring Launch" demo replaces "Operation Nightfall … contested systems"); MemberCard/MemberGrid `rank` fields labeled "Role" with business-ladder demo values (Principal/Staff/Senior replace Captain/Lieutenant/Sergeant, "Fleet Commander" → "Design Lead"); EventCalendar demo uses business events (workshop, hiring open house, quarterly business review — "Upcoming Operations" heading → "Upcoming Events"); OrgChart meta/variants describe a generic three-level hierarchy instead of Division → Teams → Squads (render output was already 100% data-driven — level headings come from the authored rows, so no new props were needed); block meta descriptions/usage neutralized throughout. - **tome-org:** flavored admin LABELS get neutral text while stored values stay put — Event status `boarding`/`debrief` labeled "Check-In"/"Wrap-Up"; eventType `operation`/`patrol`/`exam` labeled "Initiative"/"Outreach"/"Assessment"; `securityLevel` labeled "Access"; Campaign `codename` labeled "Internal Name" and campaignType `recurring_op`/`special_operation`/`deployment` labeled "Recurring Series"/"Special Initiative"/"Rollout"; Member `classification` labeled "Directory Visibility" with `classified` labeled "Private", "Chain of command" → "Reporting line"; Rank `securityClearance` labeled "Access Level" and category `command` labeled "Management"; Squad squadType `fire_team`/`flight` labeled "Crew"/"Pod", `callsign` labeled "Nickname"; Membership `squadron` labeled "Unit", role example "Pointman, Medic" → "Coordinator, Facilitator"; Position abbreviation example "CO, XO" → "COO, PM", category `command` labeled "Executive". The configurable `DEFAULT_ORG_TERMINOLOGY` (Division/Team/Squad/Rank) is deliberately unchanged — it is the documented override seam and `@wabbit/tome-sc` inherits it for its themed collections. - **blocks-core:** BLOCK_CATALOG entries for campaign-banner, member-card, and org-chart re-mirror the updated pack meta descriptions (catalog is generated from pack meta; only the entries owned by this change were refreshed).
- Updated dependencies [6779aa1] - @wabbit/tome-blocks-core@0.15.8
@wabbit/tome-blocks-core@0.15.0
- @wabbit/tome-blocks-core@0.15.0
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.
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
Updated dependencies - @wabbit/tome-blocks-core@0.14.0
- Updated dependencies - @wabbit/tome-blocks-core@0.14.0
Updated dependencies [f4d55c9] - @wabbit/tome-blocks-core@0.13.0
- Updated dependencies [f4d55c9] - @wabbit/tome-blocks-core@0.13.0
Updated dependencies [e11d5a2] - @wabbit/tome-blocks-core@0.11.2
- Updated dependencies [e11d5a2] - @wabbit/tome-blocks-core@0.11.2
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.
- 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: 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.
- aef2725: DRY adoption sweep (the audit's "adoption, not extraction" rule): crm/deals capability presets delegate to core's `sessionHasCapabilityOrLegacyAdmin`; new core `buildOwnershipWhere`/`ownershipOrBypass` (via `./access`) adopted by core's vendorScoped, catalog's vendor-scoping, and org's ownOrScoped (public APIs unchanged); `slugField()` adopted at 7 sites where semantics matched exactly (core lms collections + createMemberCollection — replacing a third independent slugify), with ~25 sites honestly skipped for named semantic divergences (auto-regenerate-on-clear vs allow-empty, collection-level hook pattern) now listed as core-enhancement candidates; new `formatDisplayDate` in blocks-core utilities (UTC-pinned, hydration-safe) adopted at 5 verified-identical sites; lms-ui consolidates its two certificate date formatters locally; `useMediaQuery`/`useIsMobile` published from tome-ui and adopted by AppShell + admin's SidebarProvider; gamification's `awardPoints` now uses the authoritative `getPointsBalance` (fixes a divergent 1000-row scan cap vs the correct 10000).
- 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
bbe8945: 0.2.5 published to npm.wabbit.com without its `dist/` output: the tarball contained only `package.json`/`README.md`/`CHANGELOG.md`/`LICENSE.md` (4.5 kB) even though `files` declares `dist` and `main`/`module`/`exports` all point into it, breaking every consumer's install (missing entry point). Root cause: 0.2.5 was a pure dependency-ripple patch (blocks-core 0.10.0 bump, no source change to this package) published via `pnpm publish` with no lifecycle guard verifying `dist/` existed at pack time — `pnpm pack`/`publish` silently omits a listed `files` entry when the path is absent rather than erroring, and this package (like its blocks-family siblings) had no `prepublishOnly` build guard. Reproduced locally: `rm -rf dist && pnpm pack` produced the identical 4-file/~1.6kB-unpacked artifact; with `dist/` present, `pnpm pack` correctly emits 276 files / 63.9 kB, matching the healthy 0.2.4 baseline. Fix: added `"prepublishOnly": "pnpm run build"` to this package's scripts. tsup's own config already sets `clean: true`, so a plain build both clears stale output and guarantees `dist/` exists before `pnpm publish` packs the tarball — verified via `pnpm publish --dry-run --no-git-checks` with `dist/` deleted beforehand: the hook rebuilt dist and the resulting dry-run tarball matched the 276-file/63.9 kB healthy shape. This is a republish, not a code change — no runtime behavior differs from what 0.2.4/0.2.5-intended shipped. Note (not fixed here — out of scope for this package's patch): this dist-less-publish class of defect is repo-wide, since no package in the monorepo has a `prepublishOnly`/`prepack` build guard prior to this change (confirmed via `grep -l prepublishOnly packages/*/package.json` returning nothing). Any package published without a preceding fresh build is equally exposed. Recommend a shared guard (e.g. a `scripts/verify-dist-before-publish.mjs` invoked from each package's `prepublishOnly`, or a root `pnpm publish` wrapper that runs `pnpm --filter <pkg>... build` first) rather than hand-adding `prepublishOnly: "pnpm run build"` to all ~30 packages individually. Separately (also not fixed here): this package's pre-existing `"clean": "rimraf dist"` script is independently broken — `rimraf` is not declared in this package's `devDependencies` (nor at the workspace root), so `pnpm run clean` fails with "'rimraf' is not recognized" if invoked directly. The same gap exists across the whole blocks family (blocks, blocks-core, blocks-extras, and all 8 sibling packs) plus `core`, `ui`, `motion`, `lms`, `lms-ui`, `gamification` — none declare `rimraf` even though their `clean` script calls it; sibling packages like `admin`, `crm`, `marketing`, `accounts`, `org`, `sc` do declare it (`^5.0.0`). This guard's `prepublishOnly` avoids the gap by not calling `clean` at all (relying on tsup's own `clean: true`), so it is unaffected, but the standalone `clean` script remains latent-broken for these ~18 packages. - @wabbit/tome-blocks-core@0.10.0
- bbe8945: 0.2.5 published to npm.wabbit.com without its `dist/` output: the tarball contained only `package.json`/`README.md`/`CHANGELOG.md`/`LICENSE.md` (4.5 kB) even though `files` declares `dist` and `main`/`module`/`exports` all point into it, breaking every consumer's install (missing entry point). Root cause: 0.2.5 was a pure dependency-ripple patch (blocks-core 0.10.0 bump, no source change to this package) published via `pnpm publish` with no lifecycle guard verifying `dist/` existed at pack time — `pnpm pack`/`publish` silently omits a listed `files` entry when the path is absent rather than erroring, and this package (like its blocks-family siblings) had no `prepublishOnly` build guard. Reproduced locally: `rm -rf dist && pnpm pack` produced the identical 4-file/~1.6kB-unpacked artifact; with `dist/` present, `pnpm pack` correctly emits 276 files / 63.9 kB, matching the healthy 0.2.4 baseline. Fix: added `"prepublishOnly": "pnpm run build"` to this package's scripts. tsup's own config already sets `clean: true`, so a plain build both clears stale output and guarantees `dist/` exists before `pnpm publish` packs the tarball — verified via `pnpm publish --dry-run --no-git-checks` with `dist/` deleted beforehand: the hook rebuilt dist and the resulting dry-run tarball matched the 276-file/63.9 kB healthy shape. This is a republish, not a code change — no runtime behavior differs from what 0.2.4/0.2.5-intended shipped. Note (not fixed here — out of scope for this package's patch): this dist-less-publish class of defect is repo-wide, since no package in the monorepo has a `prepublishOnly`/`prepack` build guard prior to this change (confirmed via `grep -l prepublishOnly packages/*/package.json` returning nothing). Any package published without a preceding fresh build is equally exposed. Recommend a shared guard (e.g. a `scripts/verify-dist-before-publish.mjs` invoked from each package's `prepublishOnly`, or a root `pnpm publish` wrapper that runs `pnpm --filter <pkg>... build` first) rather than hand-adding `prepublishOnly: "pnpm run build"` to all ~30 packages individually. Separately (also not fixed here): this package's pre-existing `"clean": "rimraf dist"` script is independently broken — `rimraf` is not declared in this package's `devDependencies` (nor at the workspace root), so `pnpm run clean` fails with "'rimraf' is not recognized" if invoked directly. The same gap exists across the whole blocks family (blocks, blocks-core, blocks-extras, and all 8 sibling packs) plus `core`, `ui`, `motion`, `lms`, `lms-ui`, `gamification` — none declare `rimraf` even though their `clean` script calls it; sibling packages like `admin`, `crm`, `marketing`, `accounts`, `org`, `sc` do declare it (`^5.0.0`). This guard's `prepublishOnly` avoids the gap by not calling `clean` at all (relying on tsup's own `clean: true`), so it is unaffected, but the standalone `clean` script remains latent-broken for these ~18 packages. - @wabbit/tome-blocks-core@0.10.0
@wabbit/tome-blocks-core@0.10.0
- @wabbit/tome-blocks-core@0.10.0
Updated dependencies - @wabbit/tome-blocks-core@0.10.0
- Updated dependencies - @wabbit/tome-blocks-core@0.10.0
bed3f90: Docs-manifest emitter pipeline (W3 ship-readiness). `@wabbit/tome-blocks-core` now ships a standalone Node ESM CLI at `scripts/emit-docs-manifests.mjs` that emits per-package documentation manifests (index.json, packages/<slug>.json, changelog.json) by reading what packages already carry — READMEs, the payload-free `<pkg>/meta` block-usage barrels, package.json exports maps, and CHANGELOG.md. It is the docs-pipeline sibling of the gallery source extractor and is consumed by host sites at prebuild: `node node_modules/@wabbit/tome-blocks-core/scripts/emit-docs-manifests.mjs --output-dir <dir> --scope <scope.json>`. To let the emitter import block metadata uniformly without dragging Payload config into a build script, the `./meta` payload-free subpath (BlockMetaEntry[]) is extended to the remaining offered blocks packs — agency-essentials, catalog-pack, lms-pack, org-pack, and signal-theme — mirroring the existing editorial-pack / marketing-starter / content-writer / extras barrels. Each block's `BlockMeta` was relocated verbatim into a payload-free sibling meta module and re-imported by its block config; no meta values changed. Every supported-core package additionally adds `CHANGELOG.md` to its published `files` array so the next publish cascade ships changelogs the emitter can read from installed tarballs at prebuild.
- bed3f90: Docs-manifest emitter pipeline (W3 ship-readiness). `@wabbit/tome-blocks-core` now ships a standalone Node ESM CLI at `scripts/emit-docs-manifests.mjs` that emits per-package documentation manifests (index.json, packages/<slug>.json, changelog.json) by reading what packages already carry — READMEs, the payload-free `<pkg>/meta` block-usage barrels, package.json exports maps, and CHANGELOG.md. It is the docs-pipeline sibling of the gallery source extractor and is consumed by host sites at prebuild: `node node_modules/@wabbit/tome-blocks-core/scripts/emit-docs-manifests.mjs --output-dir <dir> --scope <scope.json>`. To let the emitter import block metadata uniformly without dragging Payload config into a build script, the `./meta` payload-free subpath (BlockMetaEntry[]) is extended to the remaining offered blocks packs — agency-essentials, catalog-pack, lms-pack, org-pack, and signal-theme — mirroring the existing editorial-pack / marketing-starter / content-writer / extras barrels. Each block's `BlockMeta` was relocated verbatim into a payload-free sibling meta module and re-imported by its block config; no meta values changed. Every supported-core package additionally adds `CHANGELOG.md` to its published `files` array so the next publish cascade ships changelogs the emitter can read from installed tarballs at prebuild.
- Updated dependencies [bed3f90] - @wabbit/tome-blocks-core@0.9.4
Updated dependencies - @wabbit/tome-blocks-core@0.9.2
- Updated dependencies - @wabbit/tome-blocks-core@0.9.2
Updated dependencies [c7d0afc] - @wabbit/tome-blocks-core@0.9.0
- Updated dependencies [c7d0afc] - @wabbit/tome-blocks-core@0.9.0
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
Updated dependencies [66c611c] - @wabbit/tome-blocks-core@0.7.0
- Updated dependencies [66c611c] - @wabbit/tome-blocks-core@0.7.0
Updated dependencies [4b2f368] - @wabbit/tome-blocks-core@0.6.2
- Updated dependencies [4b2f368] - @wabbit/tome-blocks-core@0.6.2
@wabbit/tome-blocks-core@0.5.9
- @wabbit/tome-blocks-core@0.5.9
@wabbit/tome-blocks-core@0.5.7
- @wabbit/tome-blocks-core@0.5.7
Onboard to the dist-publish pipeline — **first registry publish** to npm.wabbit.com (the pack was previously source-only / path-alias consumption). Adds a `tsup` build (`bundle: false`, dual ESM/CJS, `'use client'` preserved, CSS mirrored to `dist/`), dist-pointing `exports` (`.` config + `./render` components), and `files` / `publishConfig` / `license` / `author` / `repository` metadata. No source or behavior change. Enables registry consumption (e.g. tome-starter moving off path-alias-to-source).
- Onboard to the dist-publish pipeline — **first registry publish** to npm.wabbit.com (the pack was previously source-only / path-alias consumption). Adds a `tsup` build (`bundle: false`, dual ESM/CJS, `'use client'` preserved, CSS mirrored to `dist/`), dist-pointing `exports` (`.` config + `./render` components), and `files` / `publishConfig` / `license` / `author` / `repository` metadata. No source or behavior change. Enables registry consumption (e.g. tome-starter moving off path-alias-to-source).
- Updated dependencies - @wabbit/tome-blocks-core@0.4.2
Updated dependencies [b76f684] - @wabbit/tome-blocks-core@0.4.0
- Updated dependencies [b76f684] - @wabbit/tome-blocks-core@0.4.0
Updated dependencies [f2202cd] - @wabbit/tome-blocks-core@0.3.0
- Updated dependencies [f2202cd] - @wabbit/tome-blocks-core@0.3.0