Living Document Notice
Published 2026-09-15. The evolving architecture, revisions, and connected notes for this dispatch live in the Stax Digital Garden.

Schema-Driven UI Generation

Schema-Driven UI Generation: Stark monochrome P4 paper white vector CRT macro showing modular three-column wireframe UI schema layout

Summary

Building dedicated frontend forms for every note type and metadata property leads to sprawling UI codebases. As knowledge collections diversify into project tracking, literature reviews, and contact indexes, client applications frequently require custom React components, bespoke validation handlers, and specialized REST endpoints for each schema iteration.

FreeNext removes bespoke UI glue code by driving client interface generation directly from declarative JSON Schema specifications. The frontend rendering engine inspects the schema contract of a document and generates corresponding input widgets, validation rules, and layout structures at runtime.

Dynamic Component Generation Pipeline

When a user opens a note or configures a template, the FreeNext lens evaluates the schema definition embedded in the vault configuration. The schema serves as the sole contract between storage and layout.

+-------------------------------------------------------------+
|               Declarative Document Schema                   |
|               (JSON Schema / TypeBox Spec)                  |
+-------------------------------------------------------------+
                               |
                               | Ingest & AST Parse
                               v
+-------------------------------------------------------------+
|                  Schema Resolver & Validator                |
|           (Validates Frontmatter Values Against Spec)       |
+-------------------------------------------------------------+
                               |
                               | Component AST Stream
                               v
+-------------------------------------------------------------+
|                  Dynamic Form Renderer                      |
|      [Text Input]   [Date Picker]   [Tag Multi-Select]      |
+-------------------------------------------------------------+
                               |
                               | Emits Synchronized Patch
                               v
+-------------------------------------------------------------+
|              CommonMark Frontmatter Serializer              |
+-------------------------------------------------------------+

Because component rendering is algorithmic, introducing a new field type requires updating a single JSON schema definition rather than deploying new client application builds.

Schema to Interface Component Mapping

The rendering engine maps standard JSON Schema primitive types and semantic constraints directly into interactive interface controls.

Schema Primitive TypeFormat / ConstraintRendered UI ControlValidation Execution
stringformat: "date"Native Date Picker WidgetISO 8601 validation regex
stringenum: [...]Single-Select DropdownMember inclusion check
arrayitems: { type: "string" }Chip Tag Multi-InputArray element uniqueness
integerminimum: 0, maximum: 100Stepper / Slider InputRange boundary assertion
booleandefaultToggle SwitchStrict boolean truthiness
objectnested propertiesCollapsible Fieldset PanelRecursive schema evaluator

This deterministic mapping ensures consistent interaction patterns across diverse document types while keeping validation logic synchronized between disk storage and form fields.

Declarative Spec and Renderer Implementation

The following code illustrates a note property schema and the lightweight JavaScript generator that maps it to native Web Components:

// schema.ts: Declarative Note Property Schema
export interface NotePropertySchema {
  $id: string;
  title: string;
  type: 'object';
  properties: Record<string, SchemaProperty>;
  required?: string[];
}
 
export interface SchemaProperty {
  type: 'string' | 'number' | 'boolean' | 'array';
  title: string;
  description?: string;
  enum?: string[];
  format?: string;
  items?: { type: string };
}
 
export const DispatchSchema: NotePropertySchema = {
  $id: 'https://bosunpkm.com/schemas/dispatch.json',
  title: 'Dispatch Note Contract',
  type: 'object',
  properties: {
    title: { type: 'string', title: 'Dispatch Title' },
    date: { type: 'string', format: 'date', title: 'Publication Date' },
    draft: { type: 'boolean', title: 'Draft Status' },
    tags: {
      type: 'array',
      title: 'Classification Tags',
      items: { type: 'string' },
    },
  },
  required: ['title', 'date', 'draft'],
};
 
// renderer.ts: Web Component Form Builder
export class SchemaFormBuilder {
  buildForm(
    schema: NotePropertySchema,
    initialValues: Record<string, unknown>,
    onChange: (values: Record<string, unknown>) => void
  ): HTMLElement {
    const container = document.createElement('form');
    container.className = 'schema-form-root';
    const state = { ...initialValues };
 
    for (const [key, prop] of Object.entries(schema.properties)) {
      const fieldWrapper = document.createElement('div');
      fieldWrapper.className = 'field-row';
 
      const label = document.createElement('label');
      label.textContent = prop.title || key;
      label.htmlFor = `field-${key}`;
      fieldWrapper.appendChild(label);
 
      if (prop.type === 'boolean') {
        const input = document.createElement('input');
        input.type = 'checkbox';
        input.id = `field-${key}`;
        input.checked = Boolean(state[key]);
        input.addEventListener('change', () => {
          state[key] = input.checked;
          onChange(state);
        });
        fieldWrapper.appendChild(input);
      } else if (prop.type === 'string' && prop.format === 'date') {
        const input = document.createElement('input');
        input.type = 'date';
        input.id = `field-${key}`;
        input.value = String(state[key] || '');
        input.addEventListener('input', () => {
          state[key] = input.value;
          onChange(state);
        });
        fieldWrapper.appendChild(input);
      } else {
        const input = document.createElement('input');
        input.type = 'text';
        input.id = `field-${key}`;
        input.value = String(state[key] || '');
        input.addEventListener('input', () => {
          state[key] = input.value;
          onChange(state);
        });
        fieldWrapper.appendChild(input);
      }
 
      container.appendChild(fieldWrapper);
    }
 
    return container;
  }
}

By decoupling interface controls from hardcoded application components, schema-driven generation keeps frontmatter structures flexible and user-defined.


  • Directus Target: freenext
  • Garden Source Reference: NXT-1001 - The Application Is Just a Lens, REC-1001 - Recipe Schema Engine, MOC - Data Liberation Workbenches, MOC - The Plain-Text Longevity Standard, MOC - Local-First Systems and Synchronization, MOC - Bosun PKM Tools