Architecture
Overview
Glyph JS transforms Markdown documents with embedded UI components into interactive React applications. The pipeline consists of three main phases: Parse, Compile, and Render.
Pipeline
Markdown → Parser → AST → Compiler → IR → Runtime → React Components1. Parse Phase (@glyphjs/parser)
The parser extends remark with a custom plugin that recognizes ui:* fenced code blocks. Standard Markdown is parsed normally; fenced blocks with the ui: prefix are extracted as GlyphUIBlock AST nodes with their YAML payload preserved. Reserved keys (glyph-id, refs, interactive) are extracted and stripped before the data reaches component schemas.
2. Compile Phase (@glyphjs/compiler)
The compiler walks the AST and produces the Glyph IR (Intermediate Representation). This is a 14-step pipeline:
- Parse markdown via
parseGlyphMarkdown() - Extract frontmatter metadata and layout hints
- Generate document ID (content-addressed)
- Create translation context
- Walk AST and translate each node to IR blocks
- Compile container blocks (
ui:tabs,ui:steps) — recursively parse content - Validate container block data against Zod schemas
- Infer metadata from content (title from h1, description from first paragraph)
- Resolve block ID collisions
- Validate
glyph-iduniqueness - Extract inline references from
[text](#glyph:block-id)links - Resolve all references
- Build the final IR document
- Return
CompilationResultwith IR, diagnostics, andhasErrorsflag
The compiler uses a collect-all-errors strategy: IR is always produced, even when errors exist. When a block has interactive: true, the compiler propagates this into block.metadata.interactive.
3. Render Phase (@glyphjs/runtime)
The runtime receives the IR and renders it as React components:
createGlyphRuntime(config)creates a runtime instance with registered components<GlyphDocument ir={ir} />renders the full documentBlockRendererdispatches each block to the correct renderer:- Override renderer (user-provided)
- Registered
ui:*component definition - Built-in Markdown renderer (heading, paragraph, list, etc.)
- Fallback renderer (unknown types)
Each block is wrapped in an ErrorBoundary for fault isolation.
Interaction events: When onInteraction is provided in the runtime config, the BlockRenderer checks each block’s metadata.interactive flag. Blocks with interactive: true receive an onInteraction callback prop that components use to emit structured events (clicks, selections, submissions). The runtime automatically injects the documentId into each event before forwarding it to the consumer callback.
IR Format
The Glyph IR is a JSON document with this structure:
interface GlyphIR { version: string; // Always "1.0.0" id: string; // Content-addressed document ID metadata: { // Title, description, authors, tags title?: string; description?: string; authors?: string[]; tags?: string[]; }; blocks: Block[]; // Ordered array of content blocks references: Reference[]; // Cross-block relationships layout: LayoutHints; // Document layout mode and spacing}Each Block has an id, type, data payload, and source position.
Schemas
All 8 UI components have Zod schemas in @glyphjs/schemas. These are used:
- At compile time to validate YAML payloads
- At runtime (dev mode) to validate block data before rendering
- For JSON Schema generation (used by MCP tools and editor integrations)
Package Dependency Graph
@glyphjs/types (shared type definitions) ↓@glyphjs/schemas (Zod schemas, depends on types)@glyphjs/parser (Markdown → AST, depends on types)@glyphjs/ir (IR utilities, depends on types) ↓@glyphjs/compiler (AST → IR, depends on parser + ir + schemas) ↓@glyphjs/runtime (IR → React, depends on types + schemas)@glyphjs/components (8 UI components, depends on types)