# How Instatic Visual Components Handle Typed Parameters and Slot Synchronization

> Discover how Instatic visual components ensure robust parameter validation with TypeBox and maintain synchronized slots using a deterministic algorithm. Learn more about the CoreBunch Instatic repository.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: internals
- Published: 2026-07-26

---

**Instatic Visual Components use TypeBox schemas for compile-time and runtime parameter validation, coupled with a deterministic slot-sync algorithm that keeps slot instances synchronized with outlet definitions.**

In the CoreBunch/Instatic codebase, Visual Components (VCs) provide a reusable abstraction for building dynamic interfaces. Understanding how Instatic Visual Components handle typed parameters and slot synchronization is essential for developers extending the editor or creating custom component libraries. The system ensures end-to-end type safety through JSON Schema definitions while maintaining tree consistency via a pure, side-effect-free synchronization routine.

## Typed Parameter Handling with TypeBox Schemas

### Schema Definition in schemas.ts

Every Visual Component ships with a TypeBox schema that describes its parameters, types, defaults, and validation rules. In [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts), the `VisualComponent` type defines this contract. The schema generates a TypeScript type via `Static<typeof ParamsSchema>`, which is used throughout the editor, server, and publishing pipeline.

The result is a **strongly typed** plain object that satisfies the generated `VisualComponentParams` type. This guarantees that all callers—from the canvas to the publishing worker—agree on the shape of the data.

### Runtime Validation with propGuards.ts

When a VC is instantiated (for example, when dragged onto the canvas), the engine validates supplied parameter values against the schema. The helper functions in [`src/core/visualComponents/propGuards.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/propGuards.ts) enforce these constraints at runtime, ensuring that only valid data enters the node tree. This validation step acts as a guard between the UI layer and the internal state, preventing malformed props from propagating to the server or static generation pipeline.

## Slot Outlet Architecture and Synchronization Logic

### Defining Slots with base.slot-outlet Nodes

A VC exposes **slots** by placing a `base.slot-outlet` node inside its internal tree. The slot-outlet itself serves as the slot definition—there is no separate parameter list for slots. When the outlet omits a name, it defaults to `"children"`.

When a VC reference (`base.visual-component-ref`) appears on a page, it must maintain **exactly one** `base.slot-instance` child for each slot-outlet declared by the referenced VC. This invariant is enforced by the slot-sync module to ensure the tree structure always mirrors the component definition.

### The Slot Synchronization Algorithm

The synchronization logic lives in [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts) and operates as a **pure, idempotent** function:

1.  **Collect slot names** – `collectSlotOutletNames` walks the VC’s internal tree and returns the ordered list of slot-outlet names.
2.  **Compute diff** – `syncSlotInstances` receives the VC-ref node, the VC schema, and the current node map. It produces a `SyncResult` describing inserts, renames, and deletions needed to align the ref’s children with the VC’s slots.
3.  **Apply changes** – `applySlotSyncResult` mutates the mutable node map (within a Mutative recipe) by adding new `base.slot-instance` nodes, renaming existing ones, deleting stray children, and re-ordering the children array to match the VC’s slot order.

Running the algorithm on an already-synced ref yields an empty operation list, making it safe to invoke repeatedly during reactive updates.

## Integration: Instantiation and Store Updates

The end-to-end flow connects schema validation with slot synchronization through two primary integration points.

In [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/instantiate.ts), the `instantiateVisualComponent` function creates a VC reference node, validates supplied parameters against the VC’s schema, and immediately runs slot-sync so the new reference starts with a proper set of slot-instance children.

At the state management layer, [`src/admin/pages/site/store/slices/visualComponentsSlice.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/store/slices/visualComponentsSlice.ts) invokes `syncSlotInstances` whenever a VC reference is added, moved, or when the underlying VC definition changes. This guarantees that the page tree remains consistent with the component API surface, which is crucial for correct publishing and for plugin-level mutation contracts.

```ts
/* 1️⃣ Define a visual component with typed parameters and a slot outlet */
import { Type, Static } from '@sinclair/typebox'

export const MyCardSchema = Type.Object({
  title: Type.String({ description: 'Card title' }),
  showImage: Type.Boolean({ default: true })
})

export type MyCardParams = Static<typeof MyCardSchema>

export const myCardVc = {
  moduleId: 'my.card',
  paramsSchema: MyCardSchema,
  // the internal tree includes a slot-outlet named “footer”
  tree: {
    rootNodeId: 'root',
    nodes: {
      root: { id: 'root', moduleId: 'base.container', props: {}, children: ['slot'] },
      slot: { id: 'slot', moduleId: 'base.slot-outlet', props: { slotName: 'footer' }, children: [] }
    }
  }
}

```

```ts
/* 2️⃣ Instantiate the component on a page */
import { instantiateVisualComponent } from '@core/visualComponents/instantiate'

const pageNodeMap = { /* mutable map of BaseNode */ }
const vcRefNode = instantiateVisualComponent({
  vc: myCardVc,
  params: { title: 'Welcome', showImage: false },
  treeNodes: pageNodeMap
})
// The function validates `params` against `MyCardSchema` and then runs
// `syncSlotInstances` so `vcRefNode.children` now contains a single
// `base.slot-instance` whose props.slotName === 'footer'.

```

```ts
/* 3️⃣ Updating the VC definition (e.g. adding a new slot) */
import { syncSlotInstances, applySlotSyncResult } from '@core/visualComponents/slotSync'

// Suppose the VC now declares a second slot‑outlet called “badge”.
const result = syncSlotInstances(vcRefNode, updatedVc, pageNodeMap)
applySlotSyncResult(pageNodeMap, result, vcRefNode.id)
// After applying, the ref has two slot‑instance children (footer & badge)
// in the correct order, and any stray child nodes have been removed.

```

## Summary

- **TypeBox schemas** in [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts) provide compile-time TypeScript safety and runtime validation rules for Visual Component parameters.
- **Runtime validation** occurs in [`src/core/visualComponents/propGuards.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/propGuards.ts) when components are instantiated or updated.
- **Slots** are declared via `base.slot-outlet` nodes inside the VC tree, defaulting to the name `"children"` when unspecified.
- The **slot-sync algorithm** (`collectSlotOutletNames`, `syncSlotInstances`, `applySlotSyncResult`) is pure, idempotent, and enforced via [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts).
- **Instantiation logic** in [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/instantiate.ts) and the **store slice** in [`src/admin/pages/site/store/slices/visualComponentsSlice.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/store/slices/visualComponentsSlice.ts) ensure the page tree remains synchronized with component definitions.

## Frequently Asked Questions

### How does Instatic ensure type safety for Visual Component parameters across the entire stack?

Instatic leverages TypeBox schemas defined in [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts) to generate TypeScript types using `Static<typeof ParamsSchema>`. This type propagates through the editor, server, and publishing pipeline, while [`src/core/visualComponents/propGuards.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/propGuards.ts) performs runtime validation to catch mismatches at instantiation time.

### What happens to existing page instances when a Visual Component adds a new slot outlet?

When a VC definition changes (for example, adding a `"badge"` slot), the `syncSlotInstances` function detects the new requirement by comparing the current `base.slot-instance` children against the updated outlet list. It then generates insertion operations, and `applySlotSyncResult` adds the missing `base.slot-instance` nodes to the VC reference, preserving existing content while aligning the tree structure.

### Is the slot synchronization algorithm safe to run redundantly or during rapid state updates?

Yes. According to the implementation in [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts), the algorithm is **pure** (no side effects) and **idempotent**. Running it on an already-synchronized reference produces an empty operation list, making it safe to invoke repeatedly as users drag components or undo/redo mutations.

### Where does parameter validation occur when dragging a component onto the canvas?

Validation happens inside `instantiateVisualComponent` in [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/instantiate.ts). This function checks supplied parameters against the VC’s TypeBox schema before the node is inserted into `pageNodeMap`, ensuring only valid data enters the document tree.