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 reactReact 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.
| Group | Export | What it is |
|---|---|---|
| Block | defineBlock(options) | A frozen, typed block definition: type, name, Zod schema, defaultProps, React component |
| Block | validateBlockDefinition({ type, name }) | The gate’s field check; returns BlockDefinitionIssue[], never throws |
| Block | isCustomBlockDefinition(value) | Type guard for a definition |
| Block | createBlockInstance(definition, id, props?, children?) | A block instance with defaults merged and props parsed; throws on invalid props |
| Factory | defineFactory(options) | A frozen factory: build(config) returns blocks with generated ids |
| Factory | isFactoryDefinition(value) | Type guard for a factory |
| Factory | composeFactoryOutputs(...arrays) | Flattens several build() results into one block array |
| Factory | wrapInContainer(blocks, props?) | Wraps blocks in a container block |
| Factory | resetIdCounter() | Resets the generated-id counter (tests) |
| Capability | defineCapability() | A fluent CapabilityBuilder; re-exported from pwfabric-core |
| Capability | CapabilityBuilder | The builder class itself |
| Registry | createCustomBlockRegistry() | A fresh CustomBlockRegistry |
| Registry | getCustomBlockRegistry() · resetGlobalRegistry() | A process-wide singleton registry, and its reset |
| Registry | CustomBlockRegistry | register, registerMany, get, has, list, getByCategory, types, unregister, clear, size, getComponents() |
| Registry | mergeRegistries(...registries) | A new registry with every definition; throws on a duplicate type |
| Registry | mergeWithBaseComponents(base, registry) | A component map: base spread first, the registry’s components on top |
| Validation | validateProps(props, schema) | { valid, props?, errors } from a Zod schema |
| Validation | validatePropsWithDefaults(props, schema, defaults) | Parsed props, or defaults when parsing fails |
| Validation | validateBlock(block, definition) | Checks type, props, canHaveChildren, allowedChildren |
| Validation | validateBlockTree(blocks, registry) | Walks a tree; blocks the registry does not know are skipped |
| Validation | createBlockValidator(definition) · createPropsValidator(schema) | Curried forms of the two above |
| Validation | formatValidationErrors(errors) · hasErrors(result) | [CODE] at path: message lines; a boolean |
| Validation | isBlock(value) · isBlockArray(value) | Structural guards: id, type, props |
| Types | CustomBlockOptions, CustomBlockDefinition, CustomBlockComponent, CustomBlockComponentProps, CustomBlockContext | The block shapes |
| Types | FactoryOptions, FactoryDefinition, FactoryConfig, BlockSpec, FactoryValidationResult, FactoryValidationError | The factory shapes |
| Types | ICustomBlockRegistry, BlockValidationResult, BlockValidationError, PropsValidationResult, BlockDefinitionIssue | Registry and validation shapes |
| Types | InferBlockProps<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).