Org

Capabilities
@wabbit/tome-orgv0.3.3

Tome organization layer — divisions, teams, squads, members, ranks, events, documents, with configurable terminology and 40+ permission grants.

Installnpm install @wabbit/tome-org

Overview

@wabbit/tome-org

Tome organization layer — divisions, teams, squads, members, ranks, events, documents, with configurable terminology and 40+ permission grants. Description copied verbatim from package.json.

Layer: domain (per ARCHITECTURE.md). @wabbit/tome-lms and @wabbit/tome-catalog both detect it at runtime (via the layer registry) to unlock vendor/scoped-org relationships; @wabbit/tome-sc depends on it directly.

Install

pnpm add @wabbit/tome-org

Peer ranges, copied from package.json (v0.3.2):

| Peer | Range | Optional? | |---|---|---| | payload | >=3.67.0 | no | | @payloadcms/richtext-lexical | >=3.67.0 | no | | @wabbit/tome-core | >=1.2.0 <2.0.0 | no | | lucide-react | >=0.460.0 | yes |

60-second quickstart

The current API is a single createOrgLayer(config) call that returns all 13 MVP collections:

import { buildConfig } from 'payload'
import { createOrgLayer } from '@wabbit/tome-org'

export default buildConfig({
  collections: [
    ...existingCollections, // incl. users + a media collection
    ...createOrgLayer({
      userCollection: 'users',
      mediaCollection: 'media',
      terminology: { division: { singular: 'Wing', plural: 'Wings' } },
    }),
  ],
})

createOrgLayer also registers org's permission namespace into the converged @wabbit/tome-core/auth/permissions engine (registerOrgPermissions() — there is no parallel org resolver; checkOrgRole/hasPermission delegate to the shared engine) and calls claimSlot('member-collection', '@wabbit/tome-org') so tome-core init can detect org owns member identity and skip any fallback.

API surface

Single export subpath (. only).

| Group | Exports | |---|---| | Layer factory | createOrgLayer | | Collection factories (13) | createDivisionCollection, createTeamCollection, createSquadCollection, createPositionCollection, createMembershipCollection, createMemberCollection (+isActiveMember), createRankCollection, createPromotionCollection, createEventCollection, createEventAttendanceCollection, createEventTypeCollection, createCampaignCollection, createDocumentCollection | | Terminology | DEFAULT_ORG_TERMINOLOGY, mergeTerminology | | Permissions | ORG_PERMISSIONS, ORG_SUPER_PERMISSIONS, OrgPermissionKey, OrgPermissionValue, registerOrgPermissions, buildOrgSuperPermissionMap, ORG_OVERRIDABLE_PERMISSIONS | | Access helpers | hasPermission, hasAnyOrgPermission, hasPermissionAsync, checkOrgRole, selfOrPermission, authenticatedReadOwnOr, OrgUserLike | | Hooks | autoSlugHook, createMembershipAfterChange, createMembershipAfterDelete, promotionFourEyesHook, createAttendanceUniqueHook (+ MembershipCountHookConfig, AttendanceUniqueHookConfig) | | Types | OrgTerminology, OrgSlugs, BaseOrgCollectionConfig, OrgIdentityConfig, OrgLayerConfig, and one *CollectionConfig type per collection above; ORG_DEFAULT_SLUGS |

Server / client posture

Fully server-side: Payload collection factories, access predicates, and permission registration — no React. sideEffects: ["./dist/registerOrgPermissions.*"] — the permission-registration module has import-side-effect behavior (it self-registers on import; createOrgLayer calling registerOrgPermissions() explicitly is a belt-and-suspenders idempotent call, not the only trigger) and must survive tree-shaking.

Links

  • Design spec: docs/superpowers/specs/2026-04-07-tome-org-layer-design.md (MVP slice, cited directly in src/index.ts)
  • Deferred collections: docs/superpowers/specs/2026-04-14-tome-org-deferred-collections.md
  • CHANGELOG — the 0.3.2 entry documents a @wabbit/tome-core peer-floor raise to >=1.2.0 because checkPermissionHierarchical et al. are runtime-imported from core's permission engine and don't exist pre-1.2.0

Extending this package

Individual collection factories are exported specifically so a site can call one in isolation instead of createOrgLayer wholesale (e.g. to add a division-only slice without the full 13-collection MVP surface). The permission model is a registration into the shared tome-core engine, not a parallel resolver — a site adding custom org permissions should extend via registerOrgPermissions's pattern rather than building a second permission path.

Exports

  • @wabbit/tome-org

Changelog

v0.3.3patch

36e537a: `registerLayer` is now statically imported (forms/intake pattern) instead of lazily `require()`d in ten layer packages' init/register paths. The lazy pattern silently no-ops under Payload's native-ESM CLI (`generate:types` / `generate:importmap`), so layer registration could vanish without error. Packages whose tome-core peer is genuinely optional (economy, ai, gamification) deliberately keep the guarded lazy path; tome-core's `admin-nav/self-register.ts` deliberately keeps its subpath `require()` (documented ESM/CJS dual-cache fix — do not convert).

  • 36e537a: `registerLayer` is now statically imported (forms/intake pattern) instead of lazily `require()`d in ten layer packages' init/register paths. The lazy pattern silently no-ops under Payload's native-ESM CLI (`generate:types` / `generate:importmap`), so layer registration could vanish without error. Packages whose tome-core peer is genuinely optional (economy, ai, gamification) deliberately keep the guarded lazy path; tome-core's `admin-nav/self-register.ts` deliberately keeps its subpath `require()` (documented ESM/CJS dual-cache fix — do not convert).
  • 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.
  • 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).
v0.3.2patch

dca85a3: Core runtime-floor sweep: each package's `@wabbit/tome-core` peer floor now matches the newest core runtime export it actually imports, instead of the platform-wide `>=1.0.0` baseline from the original peer-range sweep. The stale floors let npm silently install a package next to a core version missing a module it runtime-imports, producing a hard `next build` failure at import time (reproduced 2026-07-11: tome-starter locked core 1.0.12 + admin 0.6.3 — `isAdminNavDomain` does not exist in core 1.0.x, where `registry/adminNav` was type-only). - `@wabbit/tome-admin` → `>=1.3.0 <2.0.0` — `nav/manifestResolver` runtime-imports `isAdminNavDomain` from `registry/adminNav`, first shipped as a runtime export in core 1.3.0 (Sidebar v2 Wave 0, d8ff1b2). - `@wabbit/tome-deals` → `>=1.1.0 <2.0.0` — runtime-imports `auth/repScoping` (`buildRepWhereClause` et al.) and `utilities/normalize` (`normalizeEmail`), both introduced in core 1.1.0 (consolidation pass, a9801fe). - `@wabbit/tome-accounts` → `>=1.2.0 <2.0.0` — runtime-imports `auth/permissions` (`roleSatisfiesPermission`, permission registration), introduced in core 1.2.0 (platform permission engine, 9238072). - `@wabbit/tome-org` → `>=1.2.0 <2.0.0` — runtime-imports `auth/permissions` (`checkPermissionHierarchical` et al.). - `@wabbit/tome-sc` → `>=1.2.0 <2.0.0` — runtime-imports `auth/permissions` across access helpers and military collections. Same defect class as the `tome-crm` floor raise to `>=1.1.0` (b027075); `tome-crm` is already correct and unchanged here.

  • dca85a3: Core runtime-floor sweep: each package's `@wabbit/tome-core` peer floor now matches the newest core runtime export it actually imports, instead of the platform-wide `>=1.0.0` baseline from the original peer-range sweep. The stale floors let npm silently install a package next to a core version missing a module it runtime-imports, producing a hard `next build` failure at import time (reproduced 2026-07-11: tome-starter locked core 1.0.12 + admin 0.6.3 — `isAdminNavDomain` does not exist in core 1.0.x, where `registry/adminNav` was type-only). - `@wabbit/tome-admin` → `>=1.3.0 <2.0.0` — `nav/manifestResolver` runtime-imports `isAdminNavDomain` from `registry/adminNav`, first shipped as a runtime export in core 1.3.0 (Sidebar v2 Wave 0, d8ff1b2). - `@wabbit/tome-deals` → `>=1.1.0 <2.0.0` — runtime-imports `auth/repScoping` (`buildRepWhereClause` et al.) and `utilities/normalize` (`normalizeEmail`), both introduced in core 1.1.0 (consolidation pass, a9801fe). - `@wabbit/tome-accounts` → `>=1.2.0 <2.0.0` — runtime-imports `auth/permissions` (`roleSatisfiesPermission`, permission registration), introduced in core 1.2.0 (platform permission engine, 9238072). - `@wabbit/tome-org` → `>=1.2.0 <2.0.0` — runtime-imports `auth/permissions` (`checkPermissionHierarchical` et al.). - `@wabbit/tome-sc` → `>=1.2.0 <2.0.0` — runtime-imports `auth/permissions` across access helpers and military collections. Same defect class as the `tome-crm` floor raise to `>=1.1.0` (b027075); `tome-crm` is already correct and unchanged here.
v0.3.1patch

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.3.0minor

1a5e085: Converge onto the single platform permission engine. `tome-org` now registers `ORG_PERMISSIONS` and its super-permission map (translated to value-space) into `@wabbit/tome-core/auth/permissions` and delegates `checkOrgRole`/`hasPermission` to the shared engine — the parallel org permission resolver is removed (no more two-systems redundancy). Public API is unchanged and verified to resolve identically to the prior implementation across representative role fixtures (Vngd-shape `role.permissions` maps, multi-role, unpopulated/string-id roles, null user) plus super-permission implication. Adds exports `registerOrgPermissions`, `buildOrgSuperPermissionMap`, `ORG_OVERRIDABLE_PERMISSIONS`.

  • 1a5e085: Converge onto the single platform permission engine. `tome-org` now registers `ORG_PERMISSIONS` and its super-permission map (translated to value-space) into `@wabbit/tome-core/auth/permissions` and delegates `checkOrgRole`/`hasPermission` to the shared engine — the parallel org permission resolver is removed (no more two-systems redundancy). Public API is unchanged and verified to resolve identically to the prior implementation across representative role fixtures (Vngd-shape `role.permissions` maps, multi-role, unpopulated/string-id roles, null user) plus super-permission implication. Adds exports `registerOrgPermissions`, `buildOrgSuperPermissionMap`, `ORG_OVERRIDABLE_PERMISSIONS`.
v0.2.6patch

a9801fe: Consolidation pass (2026-06-10 audit dialect-drift findings) — the platform stops forking its own conventions: **tome-core (minor — new public APIs):** - `./auth/repScoping` — `buildRepWhereClause({ adminCapability, repField })` + `buildCapabilityScopedRead({ readCapability, adminCapability, repField })` + `sessionHasCapabilityOrLegacyAdmin` + `DENY_ALL_WHERE`. The canonical "rows I own" access primitive, promoted from crm/deals' ~90%-identical copies (266 LOC → one parameterized implementation). - `./utilities/normalize` — `normalizeEmail` (trim + lowercase). Email is the cross-layer join key; one normalizer, everywhere. - `./fields/slug` — `formatSlug` upgraded to the canonical algorithm (promoted from catalog's strictly-more-robust slugify: collapses whitespace/hyphen runs, trims edge hyphens); new `buildAutoSlugHook(sourceField, slugField)` collection-level variant. Stored slugs untouched; only future generations on irregular-whitespace inputs differ. **catalog / org / crm / deals (patch):** local copies replaced with delegations to the core primitives. Public names and signatures unchanged (`slugify`, `autoSlugHook`, `buildNormalizeEmailHook`, `normalizeDealEmail`, `repWhereClause`, `accountRepWhereClause`, `dealsRepWhereClause`, `dealsRepOrAdminWhereClause`). Notably, org's auto-slug header had _claimed_ to wrap core's slugifier while carrying a divergent local copy — now it actually does.

  • a9801fe: Consolidation pass (2026-06-10 audit dialect-drift findings) — the platform stops forking its own conventions: **tome-core (minor — new public APIs):** - `./auth/repScoping` — `buildRepWhereClause({ adminCapability, repField })` + `buildCapabilityScopedRead({ readCapability, adminCapability, repField })` + `sessionHasCapabilityOrLegacyAdmin` + `DENY_ALL_WHERE`. The canonical "rows I own" access primitive, promoted from crm/deals' ~90%-identical copies (266 LOC → one parameterized implementation). - `./utilities/normalize` — `normalizeEmail` (trim + lowercase). Email is the cross-layer join key; one normalizer, everywhere. - `./fields/slug` — `formatSlug` upgraded to the canonical algorithm (promoted from catalog's strictly-more-robust slugify: collapses whitespace/hyphen runs, trims edge hyphens); new `buildAutoSlugHook(sourceField, slugField)` collection-level variant. Stored slugs untouched; only future generations on irregular-whitespace inputs differ. **catalog / org / crm / deals (patch):** local copies replaced with delegations to the core primitives. Public names and signatures unchanged (`slugify`, `autoSlugHook`, `buildNormalizeEmailHook`, `normalizeDealEmail`, `repWhereClause`, `accountRepWhereClause`, `dealsRepWhereClause`, `dealsRepOrAdminWhereClause`). Notably, org's auto-slug header had _claimed_ to wrap core's slugifier while carrying a divergent local copy — now it actually does.
  • 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.2.5patch

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

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

Updated dependencies [36dc023]

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

52c6bcb: **Publish pipeline setup — first-publish prep.** Adds tsup config, dist build script with NODE_OPTIONS heap bump, publishConfig (restricted, npm.wabbit.com), main/module/types fields, files allowlist (`dist`, `README.md`, `LICENSE.md`), and exports map pointing at `dist/`. Mirrors the canonical `@wabbit/tome-*` publish template (catalog/economy/admin shape). **Peer-dep correction:** moves `@wabbit/tome-core` from `dependencies` (which was incorrect for a peer) to `peerDependencies` (`workspace:*`). Also keeps it in `devDependencies` so workspace install still resolves it at build time. Existing `peerDependenciesMeta` block was already declaring `@wabbit/tome-core` as a peer, so this fixes the orphan declaration. devDeps gains `cross-env`, `rimraf`, and `tsup` to match the canonical template. **One source change to make the published `.d.ts` consumable.** Annotated `createMemberCollection` return type as `CollectionConfig` (it was the lone factory without an explicit return type — the other 12 already had it). Without the annotation, tsup's dts rollup couldn't resolve some Payload internal subpath types referenced by the inferred wide return type and emitted literal `import 'node_modules/payload/dist/...'` paths in the d.ts that would 404 from a published consumer. This is the same pattern catalog/economy already follow; matches the discipline in `feedback_payload_config_typed_for_callback_inference`. Public API surface (collections, access helpers, hooks, factory, terminology, permissions) is otherwise unchanged. **Why now:** unblocks the agency-stack roadmap (`@wabbit/tome-crm`, `@wabbit/tome-deals`) — those layers depend on `tome-org` and consumer registry consumption requires `tome-org` to be on Verdaccio. Same template that catalog and economy got on 2026-04-27.

  • 52c6bcb: **Publish pipeline setup — first-publish prep.** Adds tsup config, dist build script with NODE_OPTIONS heap bump, publishConfig (restricted, npm.wabbit.com), main/module/types fields, files allowlist (`dist`, `README.md`, `LICENSE.md`), and exports map pointing at `dist/`. Mirrors the canonical `@wabbit/tome-*` publish template (catalog/economy/admin shape). **Peer-dep correction:** moves `@wabbit/tome-core` from `dependencies` (which was incorrect for a peer) to `peerDependencies` (`workspace:*`). Also keeps it in `devDependencies` so workspace install still resolves it at build time. Existing `peerDependenciesMeta` block was already declaring `@wabbit/tome-core` as a peer, so this fixes the orphan declaration. devDeps gains `cross-env`, `rimraf`, and `tsup` to match the canonical template. **One source change to make the published `.d.ts` consumable.** Annotated `createMemberCollection` return type as `CollectionConfig` (it was the lone factory without an explicit return type — the other 12 already had it). Without the annotation, tsup's dts rollup couldn't resolve some Payload internal subpath types referenced by the inferred wide return type and emitted literal `import 'node_modules/payload/dist/...'` paths in the d.ts that would 404 from a published consumer. This is the same pattern catalog/economy already follow; matches the discipline in `feedback_payload_config_typed_for_callback_inference`. Public API surface (collections, access helpers, hooks, factory, terminology, permissions) is otherwise unchanged. **Why now:** unblocks the agency-stack roadmap (`@wabbit/tome-crm`, `@wabbit/tome-deals`) — those layers depend on `tome-org` and consumer registry consumption requires `tome-org` to be on Verdaccio. Same template that catalog and economy got on 2026-04-27.
v0.1.1patch

Updated dependencies - @wabbit/tome-core@0.2.0

  • Updated dependencies - @wabbit/tome-core@0.2.0