Intake

Capabilities
@wabbit/tome-intakev0.3.0

Tome intake layer — the canonical lead-capture submit pipeline (verify → persist → route → notify) behind a single submitIntakeAction composition seam. Render is owned by @wabbit/tome-forms.

Installnpm install @wabbit/tome-intake

Overview

@wabbit/tome-intake

The canonical Tome lead-capture submit pipeline — verify → persist → route → notify — behind a single submitIntakeAction composition seam. Render is owned by `@wabbit/tome-forms`; intake owns everything downstream of the submit.

See the design spec: docs/superpowers/specs/2026-04-24-tome-intake-layer-design.md (Business repo).

Install

pnpm add @wabbit/tome-intake

Required peer: @wabbit/tome-core. Optional sister layers (lazy, runtime-detected): @wabbit/tome-territory (zip routing), @wabbit/tome-catalog (product routing).

Wire it (payload.config.ts)

initIntake must run before `initForms` so @wabbit/tome-forms' config-time hasLayer('@wabbit/tome-intake') check passes.

import { buildConfig } from 'payload'
import { initIntake, defineIntakeForm } from '@wabbit/tome-intake'

const contactIntake = defineIntakeForm({
  slug: 'contact',
  fields: [
    { name: 'name', type: 'text', required: true },
    { name: 'email', type: 'email', required: true },
    { name: 'message', type: 'textarea' },
    { name: '_hp', type: 'hidden', honeypot: true },
  ],
  verification: { provider: 'none' }, // forms owns anti-fraud upstream
  routing: { strategy: 'static', staticAssigneeId: 'rep-1' },
  notify: { stakeholderEmail: { to: 'sales@acme.com' }, submitterEmail: { subject: 'Thanks!' } },
})

const intake = initIntake({
  forms: [contactIntake],
  // Lazy resolver — submitIntakeAction receives no Payload from its caller.
  getPayload: async () => (await import('payload')).getPayload({ config: (await import('@payload-config')).default }),
})

export default buildConfig({
  collections: [/* ... */ ...intake.collections],
  // ...then initForms(...) for the render layer
})

The composition seam

@wabbit/tome-forms (a form with submission.target: 'intake') renders + validates, then calls:

submitIntakeAction(formSlug: string, data: Record<string, unknown>): Promise<TomeIntakeResult>

This flat two-argument contract is frozen (it's what tome-forms@0.1.3 calls). For a direct HTML <form> without the forms engine, use the createIntakeFormAction(form, getPayload) HOF instead.

Public API (subpaths)

  • . — initIntake, defineIntakeForm, submitIntakeAction, createIntakeFormAction, defineIntakeSubmissionsCollection, withIntakeAccess, types
  • ./verify — verifyHuman (recaptcha-v3 / turnstile / hcaptcha / none adapters)
  • ./routing — routeIntake
  • ./notify — dispatchOrLog, notifyStakeholder, notifySubmitter
  • ./test — createMockIntakePayload harness

This package ships no .tsx — it is a pure server-side submit pipeline (verify → persist → route → notify), so there is no server/client posture to declare; every subpath above is safe to import from any Node/server context, including payload.config.ts.

Smoke gate

pnpm --filter ./packages/intake smoke

Exercises the real pipeline against the mock Payload harness. Non-zero exit = publish gate fail.

Exports

  • @wabbit/tome-intake
  • @wabbit/tome-intake/notify
  • @wabbit/tome-intake/verify
  • @wabbit/tome-intake/routing
  • @wabbit/tome-intake/test

Changelog

v0.3.0minor

6bc419c: R4 rulings #2 + #3 (all additive; every old name keeps working as a `@deprecated` alias until that package's next major). `create*` is canonical for collection/layer factories (`define*` stays reserved for the blocks descriptor system): crm/deals/marketing/intake/forms gain `create*Collection` names for their former `define*Collection` factories. Layer entries converge on `createXLayer(config?) → bundle`: `createCrmLayer`/`createDealsLayer`/`createMarketingLayer`/`createCatalogLayer`/`createEconomyLayer`/`createChromeLayer`/`createLmsLayer`/`createAiLayer` (+ `createFormsLayer`/`createIntakeLayer`), returning bare `CollectionConfig[]` where the layer contributes only collections or an honest named bundle where it hands back more (chrome: `{ globals }`; lms/ai: `{ collections, hooks }`); void-returning `initCatalog`/`initEconomy` stay as the single registration call sites, delegated to internally. Naming note for forms consumers: `createFormsCollection` (singular factory) vs `createFormsCollections` (plural composer) vs `createFormsLayer` (layer entry) — each docblock states the distinction.

  • 6bc419c: R4 rulings #2 + #3 (all additive; every old name keeps working as a `@deprecated` alias until that package's next major). `create*` is canonical for collection/layer factories (`define*` stays reserved for the blocks descriptor system): crm/deals/marketing/intake/forms gain `create*Collection` names for their former `define*Collection` factories. Layer entries converge on `createXLayer(config?) → bundle`: `createCrmLayer`/`createDealsLayer`/`createMarketingLayer`/`createCatalogLayer`/`createEconomyLayer`/`createChromeLayer`/`createLmsLayer`/`createAiLayer` (+ `createFormsLayer`/`createIntakeLayer`), returning bare `CollectionConfig[]` where the layer contributes only collections or an honest named bundle where it hands back more (chrome: `{ globals }`; lms/ai: `{ collections, hooks }`); void-returning `initCatalog`/`initEconomy` stay as the single registration call sites, delegated to internally. Naming note for forms consumers: `createFormsCollection` (singular factory) vs `createFormsCollections` (plural composer) vs `createFormsLayer` (layer entry) — each docblock states the distinction.
  • 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.
v0.1.8patch

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.
v0.1.7patch

4b2f368: Platform-wide peer-range sweep: every `workspace:*`/`workspace:^` entry in `peerDependencies` replaced with an explicit semver range (`@wabbit/tome-core >=1.0.0 <2.0.0`, `tome-ui >=0.9.0 <1.0.0`, `tome-motion >=0.2.0 <1.0.0`, `tome-catalog >=1.1.0 <2.0.0`, `tome-admin >=0.5.0 <1.0.0`; `tome-crm` ranges standardized to `>=0.2.0 <1.0.0`). The workspace protocol publishes as an **exact-version pin**, so every substrate bump stranded installed dependents — the breakage class proven by marketing@0.1.0/deals@0.1.1 requiring `tome-crm@0.2.0` exactly. devDependencies keep `workspace:*` for the local link. (`@wabbit/tome-admin-pro` got the same source fix but is rc-versioned; it carries the change on its next intentional release.) tome-crm additionally gains a once-per-process **production warning when the capability-registry fallback grants access** — the bootstrap heuristic (any authenticated user passes `crm:read`) now announces itself instead of running silently on sites that forgot to seed capability grants (2026-06-10 audit hardening item). Graph-truth additions (same hygiene wave): tome-deals declares its lazy print integration as an optional peer (`@wabbit/tome-print >=0.1.0 <1.0.0`); tome-intake declares its lazy catalog routing strategy (`@wabbit/tome-catalog >=1.1.0 <2.0.0`, optional). These were undeclared dynamic imports — invisible to consumers and to pnpm's build topology.

  • 4b2f368: Platform-wide peer-range sweep: every `workspace:*`/`workspace:^` entry in `peerDependencies` replaced with an explicit semver range (`@wabbit/tome-core >=1.0.0 <2.0.0`, `tome-ui >=0.9.0 <1.0.0`, `tome-motion >=0.2.0 <1.0.0`, `tome-catalog >=1.1.0 <2.0.0`, `tome-admin >=0.5.0 <1.0.0`; `tome-crm` ranges standardized to `>=0.2.0 <1.0.0`). The workspace protocol publishes as an **exact-version pin**, so every substrate bump stranded installed dependents — the breakage class proven by marketing@0.1.0/deals@0.1.1 requiring `tome-crm@0.2.0` exactly. devDependencies keep `workspace:*` for the local link. (`@wabbit/tome-admin-pro` got the same source fix but is rc-versioned; it carries the change on its next intentional release.) tome-crm additionally gains a once-per-process **production warning when the capability-registry fallback grants access** — the bootstrap heuristic (any authenticated user passes `crm:read`) now announces itself instead of running silently on sites that forgot to seed capability grants (2026-06-10 audit hardening item). Graph-truth additions (same hygiene wave): tome-deals declares its lazy print integration as an optional peer (`@wabbit/tome-print >=0.1.0 <1.0.0`); tome-intake declares its lazy catalog routing strategy (`@wabbit/tome-catalog >=1.1.0 <2.0.0`, optional). These were undeclared dynamic imports — invisible to consumers and to pnpm's build topology.
v0.1.6patch

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

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

Updated dependencies [36dc023]

  • Updated dependencies [36dc023]
  • Updated dependencies [2612799] - @wabbit/tome-core@1.0.11