DevelopersSDK@phimajor-solutions/pwfabric-phico

@pwfabric/phico — composer kernel

@pwfabric/phico is the surface authoring kernel that lives behind the PhiCo Studio. It bundles the Zustand-based editor store, the block and capability registries, the composer mutation executor and stream client, plus the React hooks that wire drag-and-drop, clipboard, keyboard shortcuts, validation, and capability detection into a consuming app shell. AI runtime adapters (assistant-ui chat model, feedback adapter, history compaction) ship under the ./ai subpath.

Every export listed here is intentional — the package barrel is an explicit named-export list (see ADR-152). Internal helpers stay file-scoped and are not part of the public API.

Installation

pnpm add @pwfabric/phico

@pwfabric/phico requires React 19 and Node ^22. It peer-depends on @pwfabric/contracts, @pwfabric/core, and @pwfabric/runtime.

Editor store

The store is a Zustand store holding the surface document, current selection, viewport, composer phase, and the explorer / right-panel UI state. Mutate it through the action methods on the store; never reach into the state shape directly.

import { useEditorStore, type SurfaceDefinition } from '@pwfabric/phico'
 
function loadSurface(surface: SurfaceDefinition): void {
  useEditorStore.getState().setSurface(surface)
}
 
function MyComponent() {
  const surface = useEditorStore((s) => s.surface)
  const isDirty = useEditorStore((s) => s.isDirty)
  return <div>{surface?.name} {isDirty && '*'}</div>
}

Selectors

These hooks return derived state with shallow comparison so consumers re-render only when the relevant slice changes.

HookReturns
useSelectedBlock()The single selected Block or null.
useSelectedBlocks()Array of selected blocks (multi-select aware).
useIsBlockSelected(blockId)true when the block id is in the current selection.
useSelectionCount()Number of selected blocks.
useIsCanvasLocked()true when the canvas is in a non-editable phase (composing / review).
useIsComposing()true while an AI composer turn is in flight.
useBlockStyleValue(blockId, path)Resolves a style value with source attribution (override, token-bound, theme, default).

Mutation sink

setMutationSink() registers a callback that fires every time the store applies a SurfaceMutation. Pair it with useClientMutationBuffer (or your own buffer) to ship mutations to the backend.

import { setMutationSink } from '@pwfabric/phico'
 
setMutationSink((mutation) => {
  buffer.enqueue(mutation)
})

Block registry

The block registry maps block types (heading, text, container, …) to their renderer, property schema, and category metadata. Both a class (for explicit setup) and a singleton getter are exposed.

import { getBlockRegistry, useBlockRegistry } from '@pwfabric/phico'
 
// Imperative (outside React):
const registry = getBlockRegistry()
const headingEntry = registry.get('heading')
 
// React hook:
function BlockTypePicker() {
  const registry = useBlockRegistry()
  return <ul>{registry.types().map(t => <li key={t}>{t}</li>)}</ul>
}
ExportShape
getBlockRegistry()Returns the shared BlockRegistry singleton.
useBlockRegistry()React hook returning the same singleton, stable across renders.
BlockRegistry (class)Construct your own registry; useful in tests.
StudioBlockEntryRegistry entry shape — renderer + schema + category metadata.
PropertySchema, PropertyTypeProperty metadata describing how the inspector renders a field.
BlockCategory, CategoryDefinitionBlock grouping in the palette.

Composer executor

executeSingleMutation and executePlan apply composer-emitted mutations to the live editor store. They are the receipt-lifecycle sink for the streaming composer.

import {
  executeSingleMutation,
  executePlan,
  resetStreamingState,
  streamingSeenIds,
  type ComposerMutation,
  type ComposerPlan,
  type ExecutionResult,
} from '@pwfabric/phico'
 
// Apply a single streamed mutation (idempotent via streamingSeenIds):
function onMutation(m: ComposerMutation): void {
  const store = useEditorStore.getState()
  executeSingleMutation(m, store, streamingSeenIds)
}
 
// Apply a full plan (e.g. composer returned the final receipt):
function onPlan(plan: ComposerPlan): ExecutionResult {
  return executePlan(plan)
}
 
// Reset between composer sessions:
resetStreamingState()
ExportPurpose
executeSingleMutation(mutation, store, seenIds)Apply one mutation, deduplicating via seenIds.
executePlan(plan)Apply every mutation in plan.mutations. Returns ExecutionResult.
resetStreamingState()Clear the dedupe set between composer turns.
streamingSeenIdsThe shared Set<string> used for dedupe (exposed so consumers and tests can inspect it).
BlockSnapshot, DataSourceSnapshotComposer wire types — un-normalised inputs the executor sanitises.
ComposerMutation, ComposerPlanThe receipt-shaped wire format the composer emits.
ExecutionResultOutcome of applying a plan (touched ids, errors).

Composer stream client

streamComposerRequest opens an SSE stream to the composer endpoint and dispatches structured events through StreamCallbacks. The returned promise resolves once the stream is fully drained.

import {
  streamComposerRequest,
  type StreamCallbacks,
  type ComposerStreamRequest,
} from '@pwfabric/phico'
 
const callbacks: StreamCallbacks = {
  onMutation: (m) => executeSingleMutation(m, store, streamingSeenIds),
  onPlan: (p) => onPlan(p),
  onSuggestions: (s) => setSuggestions(s),
  onValidationRejection: (r) => showRejection(r),
  onCreditMeta: (meta) => updateCreditBadge(meta),
}
 
const request: ComposerStreamRequest = { intent, context, history }
await streamComposerRequest({ baseUrl, headers, request, callbacks })

CompletionMeta, ComposerSuggestion, and ValidationRejection are the discriminated payload shapes carried by the stream.

Block tree utilities

Pure helpers for traversing the block tree — useful when consumers need to compute things outside the store or write tests.

import {
  findBlockById,
  findBlockParent,
  getBlockDepth,
  getMaxSubtreeDepth,
} from '@pwfabric/phico'
 
const block = findBlockById(blocks, 'block-42')
const parent = findBlockParent(blocks, 'block-42')
const depth = getBlockDepth(blocks, 'block-42') // 0 for root
const maxDepth = getMaxSubtreeDepth(block!) // longest descendant chain

Surface paths (ADR-124)

Helpers that read and ensure the consolidated surface shape — theme, style variables, persistence config, and data sources live under surface.appearance.config.* and inside the platform/persistence capability config respectively.

import {
  ensureAppearanceConfig,
  getTheme,
  getStyleVariables,
  ensurePersistenceConfig,
  getDataSources,
  getInitialState,
  emptyAppearance,
  DEFAULT_APPEARANCE_ATOM_ID,
} from '@pwfabric/phico'
 
// Read:
const theme = getTheme(surface)
const variables = getStyleVariables(surface)
const dataSources = getDataSources(surface)
 
// Mutate (within a draft):
const cfg = ensureAppearanceConfig(draft)
cfg.theme.colors.primary = '#6366f1'
 
const persistence = ensurePersistenceConfig(draft)
persistence.dataSources = [...]

Layout utilities (ADR-025)

import {
  resolveSpan,
  getEffectiveColumns,
  getDefaultBlockLayout,
  getBreakpointDefinition,
  getParentColumns,
} from '@pwfabric/phico'
 
// How many columns does this block span at the current breakpoint?
const span = resolveSpan(block, breakpoint)
 
// How many columns does its parent contribute?
const cols = getParentColumns(blocks, block.id)

Drag and drop

import {
  useDragStore,
  isValidDropTarget,
  calculateDropPosition,
  getDropIndex,
  setDragData,
  getDragData,
  hasDragData,
} from '@pwfabric/phico'
 
function handleDragOver(event: DragEvent, target: DropTarget) {
  if (isValidDropTarget(useDragStore.getState().dragItem, target)) {
    event.preventDefault()
  }
}

DragItem / DropTarget / DragState / DragActions / DragStore are the corresponding types.

Clipboard

import {
  useClipboardStore,
  useHasClipboard,
  useClipboardCount,
} from '@pwfabric/phico'
 
function ToolbarButton() {
  const hasClipboard = useHasClipboard()
  const count = useClipboardCount()
  return <button disabled={!hasClipboard}>Paste ({count})</button>
}

Keyboard

import {
  KeyboardManager,
  getKeyboardManager,
  defaultShortcuts,
} from '@pwfabric/phico'
 
useEffect(() => {
  const manager = getKeyboardManager()
  return manager.bind(defaultShortcuts, handlers)
}, [handlers])

Validation

Block-level + surface-level validators producing structured ValidationIssues the panel renders.

import {
  validateBlock,
  validateSurface,
  getBlockIssues,
  getSeverityIcon,
} from '@pwfabric/phico'
 
const issues = validateSurface(surface).issues
const blockIssues = getBlockIssues(issues, block.id)

Capability hooks

Detect which capabilities a surface implicitly needs (based on its blocks) and validate the user-supplied config against each capability’s schema.

import {
  useCapabilityDetection,
  detectCapabilitiesFromBlocks,
  useCapabilityValidation,
  getCapabilityConfigSchema,
  getCapabilityDisplayInfo,
} from '@pwfabric/phico'
 
function CapabilityPanel() {
  const suggestions = useCapabilityDetection()
  const validation = useCapabilityValidation()
  return (
    <ul>
      {suggestions.map((s) => (
        <li key={s.capability}>
          {getCapabilityDisplayInfo(s.capability).label}
        </li>
      ))}
    </ul>
  )
}

Command history (undo / redo)

import {
  useCommandHistory,
  createAddBlockCommand,
  createRemoveBlockCommand,
  createUpdateBlockCommand,
  createMoveBlockCommand,
  createUpdateNameCommand,
} from '@pwfabric/phico'
 
const history = useCommandHistory()
history.push(createAddBlockCommand({ block, parentId, index }))
history.undo()
history.redo()

Editor commands

Convenience commands for common toolbar actions and a useEditorCommands hook that wires them to keyboard shortcuts.

import {
  deleteSelectedBlock,
  duplicateSelectedBlocks,
  selectAllBlocks,
  clearSelection,
  zoomIn,
  zoomOut,
  zoomReset,
  toggleExplorer,
  useEditorCommands,
  createSaveHandler,
} from '@pwfabric/phico'
 
useEditorCommands({
  onSave: createSaveHandler(saveSurface),
})

Theme presets (ADR-041)

Ready-to-use surface theme presets and the corresponding type.

import { SURFACE_THEME_PRESETS, type SurfaceThemePreset } from '@pwfabric/phico'
 
const preset: SurfaceThemePreset = SURFACE_THEME_PRESETS[0]
applyPreset(preset)

AI subpath (@pwfabric/phico/ai)

The ./ai subpath ships the assistant-ui chat-model adapter plus the streaming and history utilities the Studio uses.

import {
  createComposerAdapter,
  type ComposerApiClient,
  type ComposerRuntimeCallbacks,
  createFeedbackAdapter,
  createLocalStorageHistoryAdapter,
  clearLocalStorageHistory,
  compactHistory,
  streamToAssistantParts,
  fetchWithRetry,
  useKeyboardAvoidance,
  pushComposerAcceptCommand,
} from '@pwfabric/phico/ai'
 
const adapter = createComposerAdapter(
  runtimeCallbacks,
  () => selectedModelId,
  () => availableBlockTypes,
  () => getApiClient(), // inject your app-shell api client
)

createComposerAdapter accepts a getApiClient getter typed as the minimal ComposerApiClient interface ({ url, getHeaders() }). This keeps the package independent of any specific app-shell library.

useKeyboardAvoidance reports the on-screen keyboard offset on mobile so the composer input can re-position itself. pushComposerAcceptCommand records an accept event into the command history so users can undo it.

TypeScript types

Every export above ships with TypeScript types. Common surface types are re-exported through the editor-store barrel:

import type {
  Block,
  SurfaceDefinition,
  SurfaceTheme,
  SurfaceThemeColors,
  SurfaceThemeBorder,
  SurfaceThemeTypography,
  SurfaceThemeSpacing,
  SurfaceThemeShadows,
  SurfaceThemeMotion,
  SurfaceThemeLayout,
  LayoutConfig,
  ExplorerTab,
  StudioMode,
  ComposerPhase,
  CapabilityRequirement,
  Selection,
  SelectionModifier,
  EditorState,
  EditorActions,
  EditorStore,
  StyleSource,
  SurfaceDataSource,
} from '@pwfabric/phico'

See also

  • defineBlock() — declare a custom block type the registry can render.
  • defineCapability() — declare a capability the composer can wire into a surface.
  • defineFactory() — bundle a set of blocks and capabilities as a single deployable atom.
  • Surface OS layers — where the composer kernel fits in the 14-layer model (ADR-153).