AuthoringOverview

Authoring

@phimajor-solutions/pwfabric-authoring is the package a block author writes against. It exports defineBlock, defineFactory and defineCapability, a registry that turns block definitions into renderer components, and the validators the publish gate runs. It is Apache-2.0: what you author against is an interface, and an interface must be free to adopt (see Licensing).

pnpm add @phimajor-solutions/pwfabric-authoring zod react

React 19 is a peer dependency; zod 3 is what schemas are written in. The package depends on pwfabric-contracts, pwfabric-core and pwfabric-runtime and re-exports everything pwfabric-runtime exports, so a block module needs one import.

Source: pwfabric-core/packages/authoring/package.json, pwfabric-core/packages/authoring/src/index.ts.

The export surface

Everything below is a named export of @phimajor-solutions/pwfabric-authoring.

GroupExportWhat it is
BlockdefineBlock(options)A frozen, typed block definition: type, name, Zod schema, defaultProps, React component
BlockvalidateBlockDefinition({ type, name })The gate’s field check; returns BlockDefinitionIssue[], never throws
BlockisCustomBlockDefinition(value)Type guard for a definition
BlockcreateBlockInstance(definition, id, props?, children?)A block instance with defaults merged and props parsed; throws on invalid props
FactorydefineFactory(options)A frozen factory: build(config) returns blocks with generated ids
FactoryisFactoryDefinition(value)Type guard for a factory
FactorycomposeFactoryOutputs(...arrays)Flattens several build() results into one block array
FactorywrapInContainer(blocks, props?)Wraps blocks in a container block
FactoryresetIdCounter()Resets the generated-id counter (tests)
CapabilitydefineCapability()A fluent CapabilityBuilder; re-exported from pwfabric-core
CapabilityCapabilityBuilderThe builder class itself
RegistrycreateCustomBlockRegistry()A fresh CustomBlockRegistry
RegistrygetCustomBlockRegistry() · resetGlobalRegistry()A process-wide singleton registry, and its reset
RegistryCustomBlockRegistryregister, registerMany, get, has, list, getByCategory, types, unregister, clear, size, getComponents()
RegistrymergeRegistries(...registries)A new registry with every definition; throws on a duplicate type
RegistrymergeWithBaseComponents(base, registry)A component map: base spread first, the registry’s components on top
ValidationvalidateProps(props, schema){ valid, props?, errors } from a Zod schema
ValidationvalidatePropsWithDefaults(props, schema, defaults)Parsed props, or defaults when parsing fails
ValidationvalidateBlock(block, definition)Checks type, props, canHaveChildren, allowedChildren
ValidationvalidateBlockTree(blocks, registry)Walks a tree; blocks the registry does not know are skipped
ValidationcreateBlockValidator(definition) · createPropsValidator(schema)Curried forms of the two above
ValidationformatValidationErrors(errors) · hasErrors(result)[CODE] at path: message lines; a boolean
ValidationisBlock(value) · isBlockArray(value)Structural guards: id, type, props
TypesCustomBlockOptions, CustomBlockDefinition, CustomBlockComponent, CustomBlockComponentProps, CustomBlockContextThe block shapes
TypesFactoryOptions, FactoryDefinition, FactoryConfig, BlockSpec, FactoryValidationResult, FactoryValidationErrorThe factory shapes
TypesICustomBlockRegistry, BlockValidationResult, BlockValidationError, PropsValidationResult, BlockDefinitionIssueRegistry and validation shapes
TypesInferBlockProps<T>, InferFactoryConfig<T>, InferSchemaProps<T>Inference helpers

Source: pwfabric-core/packages/authoring/src/index.ts, pwfabric-core/packages/authoring/src/registry.ts, pwfabric-core/packages/authoring/src/validation.ts, pwfabric-core/packages/authoring/src/types.ts.

How a block reaches a page

A page never imports your module. It reaches your block through two files that travel with the app, and both of their shapes are defined in the open engine.

1. Source. In a pwpack source tree a block is one folder: blocks/<name>/component.tsx (the defineBlock module) with a manifest.json beside it. An already-compiled block may ship as blocks/<name>/bundle.mjs instead of the .tsx. The manifest is an AtomFileManifest: id, kind: "block", version, name, state (stable · beta · alpha · deprecated · experimental), category, optional description and tags, a knowledge entry, and definition — the block’s authoring metadata (props, defaultProps, canHaveChildren, and optional allowedParents, icon, traits, allowedChildTraits).

2. Bundle. Publishing compiles the source into one ES module per block type. The renderer’s loader looks for the exports Component and definition; it also accepts a default, component or render export for the component. react, zod and @phimajor-solutions/* stay external and are resolved by the page’s import map.

3. Manifest. The app’s blocks/manifest.json is a list of DynamicBlockDescriptor entries — blockType, moduleUrl, integrity (SHA-256 of the bundle bytes), publisher, publisherVerified, and a definition (name, optional icon, category, defaultProps, canHaveChildren, propertySchema). Before evaluating a bundle the loader fetches it, hashes it and compares; a missing or mismatching hash is a hard failure, not a warning.

The engine defines the shapes on both sides of that step. The tooling that turns a source tree into the bundle and manifest ships with PhiCo and is documented with it; it is not part of the open engine. What the finished files look like inside a downloaded app, and how the runner reads them, is in The envelope.

Source: pwfabric-core/packages/contracts/src/fabric/atom.ts (ATOM_KIND_META.block.pathConvention, AtomFileManifest, AtomBlockDefinitionMetadata), pwfabric-core/packages/contracts/src/fabric/dynamic-artifact.ts (DynamicBlockDescriptor, DynamicBlockDefinition), pwfabric-core/packages/runtime/src/render/dynamic-block-loader.ts (BlockModule, the export lookup order).

Rendering a block locally

None of the above is needed to see a block render in your own React tree. A registry turns definitions into the component map the renderer takes, and the renderer is exported by pwfabric-runtime as BlockSurfaceRenderer (and so by this package too):

import {
  createCustomBlockRegistry,
  BlockSurfaceRenderer,
} from '@phimajor-solutions/pwfabric-authoring'
import { PricingCard } from './blocks/pricing-card/component'
 
const registry = createCustomBlockRegistry()
registry.register(PricingCard)
 
export function Preview() {
  const surface = {
    blocks: [
      { id: 'card-1', type: 'pricing-card', props: { title: 'Pro', price: 29, features: ['Everything'] } },
    ],
  }
  return <BlockSurfaceRenderer surface={surface} components={registry.getComponents()} />
}

getComponents() wraps each definition: it runs the block’s Zod schema over the incoming props and, when parsing fails, renders with defaultProps instead of throwing. The component receives props, blockId, children and context, where context is narrowed to theme, viewport and onEvent.

Source: pwfabric-core/packages/authoring/src/registry.ts (getComponents, createBlockComponentWrapper, mapToCustomBlockContext), pwfabric-core/packages/runtime/src/index.ts (SurfaceRenderer as BlockSurfaceRenderer), pwfabric-core/packages/runtime/src/render/types.ts (SurfaceRendererProps.components).