Adding schemaForm() to the DSL looked like a one-liner — and the builder function itself is. But the type it needs, SchemaFormProps, lived in pages-viz, and the builders live in pages-ui. The dependency graph runs pages-ui → pages-component → pages-data. There’s no edge from pages-ui to pages-viz.

So the “one-liner” required moving FieldSchema and SchemaFormProps out of pages-viz into pages-component’s model layer, where every other props type already lives. The pattern was already there — TextInputProps, CheckboxProps, DropdownProps all sit in pages-component/src/model/form-input-types.ts. SchemaFormProps was the exception, not the rule.

The builder itself:

export function schemaForm(props: SchemaFormProps): TypedComponent<"schema-form"> {
  return freeze({ type: "schema-form" as const, props: { ...props } });
}

One line of real logic. Nine files of plumbing to wire it in: type registry, type guard, barrel exports, import updates in pages-viz to consume from pages-component instead of defining locally.

The implicit rule this surfaces: if a type appears in the ComponentTypeRegistry, it must live in pages-component. The registry is the contract between the model layer and everything above it. Types that bypass it create import dead-ends the next time someone needs them upstream.


<
Previous Post
Three designs, one lesson — check what already exists
>
Next Post
Six Units, Three Inputs, One String