Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Voce IR

AI-native intermediate representation for user interfaces.

The code is gone. The experience remains.

Voce IR is a binary IR format, like SPIR-V for graphics but for UI. AI generates typed IR from natural language, a validator enforces quality rules, and a compiler emits optimized output across 7 targets:

TargetOutput
DOMSingle-file HTML with inline CSS, zero-dependency JS, ARIA attributes
WebGPUWGSL shaders, PBR materials, particle systems
WASMState machines and compute as WebAssembly functions
HybridPer-component target analysis (DOM + WebGPU + WASM)
iOSSwiftUI views with VoiceOver accessibility
AndroidJetpack Compose with Material Design and TalkBack
EmailTable-based HTML with inline CSS for cross-client support

Pipeline

Natural Language
    → [AI Bridge] → JSON IR
    → [Validator] → 9 quality passes (46 rules)
    → [Compiler] → Optimized output
    → [Deploy]   → Vercel / Cloudflare / Netlify / Static

Quick Start

# Install
cargo install voce-validator

# Validate an IR file
voce validate my-page.voce.json

# Compile to HTML
voce compile my-page.voce.json

# Deploy
voce deploy my-page.voce.json --adapter static

Three Pillars

Every design decision in Voce IR is anchored to three non-negotiable pillars:

  1. Stability — Security is a compile error, not a configuration option. CSRF required on mutations, HTTPS enforced, auth routes need redirects.

  2. Experience — Zero-runtime output. No framework JS shipped. Spring physics solved at compile time. Every byte serves the user.

  3. Accessibility — Missing SemanticNode is a validation error, not a warning. Keyboard equivalents required on every gesture. Heading hierarchy enforced.

Learn More

Installation

This guide walks you through installing the Voce IR toolchain.

Prerequisites

  • Rust 1.85 or later – Voce IR uses Rust edition 2024. Install Rust via rustup if you don’t have it:

    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    
  • A terminal – All Voce commands run from the command line.

Install the CLI

The voce binary ships as part of the voce-validator crate. Install it with Cargo:

cargo install voce-validator

This compiles and installs the voce binary to your Cargo bin directory (typically ~/.cargo/bin/).

Verify the installation

voce --version

You should see output like:

voce 1.0.0

If the command is not found, ensure ~/.cargo/bin is in your PATH:

export PATH="$HOME/.cargo/bin:$PATH"

Add that line to your shell profile (~/.bashrc, ~/.zshrc, etc.) to make it permanent.

Available commands

Run voce --help to see all available subcommands:

Usage: voce <COMMAND>

Commands:
  validate   Validate a .voce.json IR file
  compile    Compile IR to a target output (HTML, WebGPU, etc.)
  inspect    Print a summary of an IR file
  preview    Compile and open in a browser
  json2bin   Convert JSON IR to binary FlatBuffers format
  bin2json   Convert binary FlatBuffers IR back to JSON
  help       Print this message or the help of the given subcommand(s)

Updating

To update to the latest version:

cargo install voce-validator --force

Building from source

If you want to work with the development version:

git clone https://github.com/nicholasgriffintn/voce-ir.git
cd voce-ir
cargo build --workspace

The compiled binary will be at target/debug/voce.

Next steps

Now that you have the CLI installed, continue to Your First IR File to create a minimal Voce IR document by hand.

Your First IR File

Voce IR documents are JSON files with a .voce.json extension. In this guide you will create a minimal “Hello World” IR file by hand and validate it.

The minimal document

Create a file called hello.voce.json with the following content:

{
  "schema_version_major": 1,
  "schema_version_minor": 0,
  "root": {
    "node_id": "root",
    "viewport_width": { "value": 1024, "unit": "Px" },
    "children": [
      {
        "value_type": "TextNode",
        "value": {
          "node_id": "greeting",
          "content": "Hello, world!",
          "heading_level": 1,
          "font_size": { "value": 36, "unit": "Px" },
          "font_weight": "Bold",
          "color": { "r": 0, "g": 0, "b": 0, "a": 255 }
        }
      }
    ],
    "metadata": {
      "title": "Hello World",
      "description": "A minimal Voce IR document."
    }
  },
  "metadata": {
    "title": "Hello World",
    "description": "A minimal Voce IR document.",
    "language": "en"
  }
}

Structure breakdown

Top-level fields

FieldPurpose
schema_version_majorMajor version of the Voce IR schema. Breaking changes increment this.
schema_version_minorMinor version. Additive changes increment this.
rootThe ViewRoot node – every document has exactly one.
metadataDocument-level metadata (title, description, language).

The ViewRoot (root)

The root node represents the top-level viewport:

  • node_id – A unique string identifier. The root is conventionally called "root".
  • viewport_width – The design viewport width. Uses a value/unit pair (e.g., 1024 pixels).
  • children – An array of child nodes. Each child is a tagged union with value_type and value.
  • metadata – Page metadata used for SEO and document identification.

Child nodes (the tagged union)

Every child in the children array has two fields:

  • value_type – The node kind. Common types include TextNode, Container, Surface, MediaNode, FormNode, and many others.
  • value – The node’s data, whose shape depends on the value_type.

The TextNode

The simplest visible node. In our example:

  • node_id – Unique identifier for this node ("greeting").
  • content – The text string to display.
  • heading_level – Semantic heading level (1-6). Setting this makes the compiler emit an <h1>-<h6> tag. Omit it for body text.
  • font_size – Size with unit. Supports Px, Rem, Em, Vw, Vh, Percent.
  • font_weight – One of Thin, Light, Regular, Medium, SemiBold, Bold, ExtraBold, Black.
  • color – RGBA color with values 0-255.

Validate your file

Run the validator to confirm your IR is structurally correct:

voce validate hello.voce.json

If everything is valid, you will see:

hello.voce.json: VALID (1 node, 0 warnings)

If there are problems – say you forgot the node_id – the validator reports the exact issue:

hello.voce.json: INVALID
  [STR001] root.children[0]: TextNode missing required field "node_id"

Adding more nodes

Here is the same document with a subtitle added below the heading:

{
  "schema_version_major": 1,
  "schema_version_minor": 0,
  "root": {
    "node_id": "root",
    "viewport_width": { "value": 1024, "unit": "Px" },
    "children": [
      {
        "value_type": "TextNode",
        "value": {
          "node_id": "heading",
          "content": "Hello, world!",
          "heading_level": 1,
          "font_size": { "value": 36, "unit": "Px" },
          "font_weight": "Bold",
          "color": { "r": 0, "g": 0, "b": 0, "a": 255 }
        }
      },
      {
        "value_type": "TextNode",
        "value": {
          "node_id": "subtitle",
          "content": "This page was built with Voce IR.",
          "font_size": { "value": 18, "unit": "Px" },
          "color": { "r": 100, "g": 100, "b": 100, "a": 255 }
        }
      }
    ],
    "metadata": {
      "title": "Hello World",
      "description": "A minimal Voce IR document."
    }
  },
  "metadata": {
    "title": "Hello World",
    "description": "A minimal Voce IR document.",
    "language": "en"
  }
}

Note that the subtitle omits heading_level – it will render as a paragraph, not a heading.

Next steps

Your IR file is ready. Continue to Compiling to HTML to turn it into a working web page.

Compiling to HTML

Once you have a valid .voce.json file, the Voce compiler transforms it into production-ready output. This guide covers the DOM compile target, which produces a single-file HTML page.

Validate first

Always validate before compiling. The compiler assumes valid input:

voce validate hello.voce.json
hello.voce.json: VALID (2 nodes, 0 warnings)

Compile

Run the compile command:

voce compile hello.voce.json

By default this writes output to the dist/ directory:

Compiling hello.voce.json → dist/hello.html
  2 nodes compiled
  Output: dist/hello.html (4.2 KB)

What the compiler produces

The output is a single self-contained HTML file. Open dist/hello.html in any browser and you will see your rendered page.

Key properties of the compiled output:

  • Zero runtime dependencies. No JavaScript frameworks, no CSS libraries, no CDN links. The HTML file contains everything it needs inline.
  • Semantic markup. A TextNode with heading_level: 1 becomes an <h1>. Containers become appropriate structural elements. Accessibility attributes are wired automatically from SemanticNode references.
  • Surgical DOM updates. When the IR includes state machines, the compiler emits minimal JavaScript that performs targeted DOM mutations – similar to what Svelte or SolidJS produce, but generated from binary IR rather than a component DSL.
  • No supply chain risk. Because there are zero external dependencies in the output, there is no attack surface from third-party packages.

Output options

Custom output directory

voce compile hello.voce.json --output ./build

JSON output mode

For debugging, you can emit a JSON representation of the compile plan:

voce compile hello.voce.json --format json

Preview in the browser

The preview command compiles and immediately opens the result in your default browser:

voce preview hello.voce.json

This is the fastest way to iterate. It compiles to a temporary directory and launches the browser in one step.

Compile targets

The DOM/HTML target is the default. Voce supports multiple compile targets:

TargetFlagOutput
DOM (HTML)--target domSingle-file .html
WebGPU--target webgpuHTML + WebGPU rendering
WASM--target wasmWebAssembly module
Hybrid--target hybridDOM for content, WebGPU for effects
iOS (SwiftUI)--target iosSwift source files
Android (Compose)--target androidKotlin source files
Email--target emailEmail-safe HTML

For example, to compile for email:

voce compile hello.voce.json --target email

Inspecting the IR

Before compiling, you can get a quick summary of what is in your IR file:

voce inspect hello.voce.json
Document: Hello World
Schema:   v1.0
Nodes:    2 (1 TextNode, 1 ViewRoot)
Language: en
Warnings: 0

A complete workflow

Putting it all together, a typical workflow looks like this:

# 1. Create or generate the IR file
#    (by hand, via AI, or from an existing tool)

# 2. Validate
voce validate my-page.voce.json

# 3. Compile
voce compile my-page.voce.json

# 4. Preview
voce preview my-page.voce.json

# 5. Deploy the output
#    dist/my-page.html is a static file -- serve it from anywhere

Next steps

Writing IR by hand works for learning, but Voce is designed for AI authorship. Continue to AI Generation to learn how to generate IR from natural language.

AI Generation

Voce IR is designed from the ground up for AI authorship. Rather than having an AI write framework code, the AI generates typed IR directly and a compiler handles the output. There are two ways to generate IR with AI: the TypeScript SDK and the MCP server.

Approaches

TypeScript SDK

The @voce-ir/ai-bridge package provides a high-level API for generating IR from natural language prompts. It handles schema context, validation, and iterative refinement automatically.

MCP Server

The Voce MCP (Model Context Protocol) server exposes IR generation as a tool that any MCP-compatible client can call – including Claude Desktop, Claude Code, and other AI assistants. This lets you generate and compile IR through conversation without writing any integration code.

SDK quickstart

Install

npm install @voce-ir/ai-bridge

Set your API key

The SDK uses the Anthropic API to generate IR. Export your key:

export ANTHROPIC_API_KEY=sk-ant-...

Generate IR from a prompt

import { VoceGenerator } from "@voce-ir/ai-bridge";

const generator = new VoceGenerator({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

// Describe what you want in natural language
const result = await generator.generate(
  "A landing page with a bold headline that says 'Ship faster with Voce', " +
  "a subtitle explaining the product, and a blue call-to-action button."
);

// result.ir contains the validated .voce.json document
console.log(JSON.stringify(result.ir, null, 2));

// result.warnings contains any validation warnings
if (result.warnings.length > 0) {
  console.warn("Warnings:", result.warnings);
}

Generate and compile in one step

import { VoceGenerator } from "@voce-ir/ai-bridge";
import { writeFileSync } from "fs";

const generator = new VoceGenerator({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

const result = await generator.generateAndCompile(
  "A contact form with name, email, and message fields. " +
  "Include proper validation and a submit button.",
  { target: "dom" }
);

// result.html contains the compiled single-file HTML
writeFileSync("dist/contact.html", result.html);

Iterative refinement

The SDK supports multi-turn refinement. The AI asks clarifying questions when the prompt is ambiguous, and you can provide follow-up instructions:

const session = generator.createSession();

// Initial generation
let result = await session.generate(
  "A pricing page with three tiers"
);

// Refine based on the result
result = await session.refine(
  "Make the middle tier visually prominent and add a 'Most Popular' badge"
);

// Further refinement
result = await session.refine(
  "Add a toggle to switch between monthly and annual pricing"
);

// The final IR incorporates all refinements
writeFileSync("pricing.voce.json", JSON.stringify(result.ir, null, 2));

MCP server

Setup with Claude Desktop

Add the Voce MCP server to your Claude Desktop configuration (claude_desktop_config.json):

{
  "mcpServers": {
    "voce-ir": {
      "command": "npx",
      "args": ["@voce-ir/mcp-server"],
      "env": {
        "ANTHROPIC_API_KEY": "sk-ant-..."
      }
    }
  }
}

Once configured, you can ask Claude to generate Voce IR through normal conversation. The MCP server exposes tools for generating, validating, compiling, and previewing IR.

Available MCP tools

ToolDescription
voce_generateGenerate IR from a natural language description
voce_validateValidate an IR document
voce_compileCompile IR to a target format
voce_previewCompile and return a preview URL
voce_inspectReturn a summary of an IR document

The playground

Try Voce IR without installing anything at voce-ir.xyz. The playground provides:

  • A prompt input for natural language descriptions
  • Live IR preview with syntax highlighting
  • One-click compilation to HTML
  • A visual preview of the compiled output

Note that the playground requires an API key for generation features. Compilation and validation work without one.

How generation works

Under the hood, the AI generation pipeline works as follows:

  1. Schema context. The SDK provides the AI model with the full Voce IR schema – all node types, their fields, valid values, and constraints.
  2. Generation. The model generates a .voce.json document from your natural language prompt.
  3. Validation. The generated IR is run through the full validator (structural checks, accessibility, security, SEO, forms, i18n).
  4. Auto-repair. If validation fails, the SDK sends the errors back to the model for automatic correction. This loop runs up to 3 times.
  5. Output. The validated IR is returned, ready for compilation.

This pipeline ensures that AI-generated IR is always valid and meets all quality gates – accessibility, security, and SEO checks are compile errors, not optional linting.

Next steps

voce validate

Run the full Voce IR validation suite against a .voce.json file. The validator executes 9 independent passes covering structural integrity, accessibility, security, SEO, and more. Any failing pass causes a non-zero exit.

Usage

voce validate <FILE> [OPTIONS]

Arguments

ArgumentDescription
<FILE>Path to a .voce.json IR file (required)

Options

FlagDefaultDescription
--format <FORMAT>terminalOutput format: terminal (colored, human-readable) or json (machine-readable)
--warn-as-erroroffTreat warnings as errors, causing a non-zero exit code

Validation Passes

The validator runs the following passes in order:

PassCode PrefixWhat it checks
StructuralSTR001–STR005Required fields, node completeness, document structure
ReferencesREF001–REF009All node refs resolve, no dangling IDs, no cycles
State MachineSTA001–STA004Valid states, transitions, initial state exists
AccessibilityA11Y001–A11Y005Keyboard equivalents, heading hierarchy, alt text, form labels
SecuritySEC001–SEC004CSRF on mutations, auth redirects, HTTPS enforcement, password autocomplete
SEOSEO001–SEO007Title present, description length, single h1, OpenGraph completeness
FormsFRM001–FRM009Fields required, labels present, unique names, email pattern validation
InternationalizationI18N001–I18N003Localized key non-empty, default value present, consistency across locales
MotionMOT001–MOT005ReducedMotion fallback required, damping > 0, duration warnings

Exit Codes

CodeMeaning
0All passes succeeded (no errors, no warnings or --warn-as-error not set)
1One or more validation errors (or warnings when --warn-as-error is set)

Examples

Validate a file with colored terminal output:

voce validate examples/landing-page.voce.json

Validate and get JSON output (useful for CI or piping to jq):

voce validate examples/landing-page.voce.json --format json

Fail the build on any warning:

voce validate my-app.voce.json --warn-as-error

Combine with other tools:

# Validate, then compile only if valid
voce validate app.voce.json && voce compile app.voce.json

JSON Output Schema

When --format json is used, the output is a JSON object:

{
  "valid": false,
  "errors": [
    { "pass": "A11Y", "code": "A11Y003", "message": "Image node missing alt text", "node_id": "img-hero" }
  ],
  "warnings": [
    { "pass": "MOT", "code": "MOT005", "message": "Animation duration exceeds 5s", "node_id": "fade-in" }
  ]
}

voce compile

Compile a validated Voce IR file into a single-file HTML document. The compiler runs the full validation suite first – if validation fails, compilation is aborted and errors are printed.

Usage

voce compile <FILE> [OPTIONS]

Arguments

ArgumentDescription
<FILE>Path to a .voce.json IR file (required)

Options

FlagDefaultDescription
-o, --output <PATH>dist/<stem>.htmlOutput file path. If omitted, derives from the input filename.
--debugoffAdd data-voce-id attributes to every emitted DOM element, mapping each back to its IR node ID.

How It Works

  1. Validate – all 9 validation passes run. Any error aborts compilation.
  2. Resolve layout – Taffy computes flexbox/grid geometry at compile time.
  3. Emit HTML – a single self-contained .html file is written. All styles are inlined. There are zero runtime dependencies.

The compiled output follows the SolidJS/Svelte pattern of surgical DOM construction – no virtual DOM, no framework runtime.

Exit Codes

CodeMeaning
0Compilation succeeded, output file written
1Validation failed (errors printed to stderr)
2Compilation error (e.g., unsupported node type, I/O failure)

Examples

Compile with default output path:

voce compile examples/landing-page.voce.json
# writes dist/landing-page.html

Compile to a specific output file:

voce compile app.voce.json -o build/index.html

Compile with debug attributes for development:

voce compile app.voce.json --debug
# Each element gets: <div data-voce-id="container-main">...</div>

Validate and compile in sequence:

voce validate app.voce.json --format json && voce compile app.voce.json

Debug Mode

When --debug is passed, every emitted HTML element receives a data-voce-id attribute containing the IR node ID it was generated from. This is useful for:

  • Tracing compiled output back to the source IR
  • Browser DevTools inspection
  • Integration with voce inspect for cross-referencing

Do not ship debug builds to production – the extra attributes increase file size and expose internal structure.

Output Format

The compiled HTML file is fully self-contained:

  • Inline <style> block with all computed styles
  • Inline <script> block for state machines and event handlers (if present)
  • No external dependencies, CDN links, or network requests
  • Valid HTML5 with lang attribute and semantic structure

voce deploy

Validate, compile, and prepare a deployment bundle for a target hosting platform. Wraps the full pipeline (validate, compile) and adds platform-specific configuration files.

Usage

voce deploy <FILE> [OPTIONS]

Arguments

ArgumentDescription
<FILE>Path to a .voce.json IR file (required)

Options

FlagDefaultDescription
--adapter <ADAPTER>staticDeployment target: static, vercel, cloudflare, netlify
--dry-runoffShow what would be generated without writing any files

Adapters

Each adapter produces a deployment-ready bundle in dist/:

AdapterOutput
staticCompiled HTML only. Suitable for any static file host (S3, GitHub Pages, rsync).
vercelHTML plus vercel.json with routing and cache headers.
cloudflareHTML plus _headers and _redirects files for Cloudflare Pages.
netlifyHTML plus netlify.toml with headers and redirect rules.

Configuration

The deploy command reads defaults from .voce/config.toml if present:

[deploy]
adapter = "vercel"
output_dir = "dist"

Command-line flags override config file values.

Exit Codes

CodeMeaning
0Deployment bundle created successfully
1Validation failed
2Compilation failed
3Deployment preparation failed (e.g., missing config, I/O error)

Examples

Deploy as static files (default):

voce deploy app.voce.json
# writes dist/app.html

Deploy to Vercel:

voce deploy app.voce.json --adapter vercel
# writes dist/app.html and dist/vercel.json

Preview what a Cloudflare deploy would produce:

voce deploy app.voce.json --adapter cloudflare --dry-run

Deploy with config file defaults:

# With .voce/config.toml setting adapter = "netlify"
voce deploy app.voce.json
# uses netlify adapter from config

Dry Run

The --dry-run flag prints the list of files that would be written and their approximate sizes, without creating or modifying anything on disk. Use this to verify adapter output before committing to a deploy.

$ voce deploy app.voce.json --adapter vercel --dry-run
[dry-run] dist/app.html (12.4 KB)
[dry-run] dist/vercel.json (0.3 KB)

Pipeline

The deploy command runs the full pipeline internally:

  1. voce validate <FILE> – abort on errors
  2. voce compile <FILE> – produce HTML
  3. Generate adapter-specific files
  4. Write all files to the output directory

voce inspect

Display a human-readable summary of a Voce IR file. Shows node counts, type distribution, state machines, and feature usage without compiling or validating.

Usage

voce inspect <FILE>

Arguments

ArgumentDescription
<FILE>Path to a .voce.json IR file (required)

Output

The inspect command prints a structured summary to stdout. There are no output format options – the output is always a human-readable table.

Summary Sections

Document overview – total node count, document-level metadata (title, locale, auth configuration).

Node type distribution – count of each node type present in the IR (e.g., Container: 5, TextNode: 12, MediaNode: 3).

State machines – names, state counts, and transition counts for each StateMachine node.

Features detected – which optional IR features are in use: animations, forms, navigation/routing, i18n, SEO metadata, theming, accessibility annotations, data/backend bindings.

Exit Codes

CodeMeaning
0IR file parsed and summary printed
1File could not be read or parsed as valid JSON

Examples

Inspect a landing page IR:

voce inspect examples/landing-page.voce.json

Example output:

Voce IR Summary
===============

Document: landing-page
Nodes:    37
Types:    11 distinct

Node Distribution:
  Container      8
  Surface        4
  TextNode      12
  MediaNode      3
  FormNode       1
  FormField      4
  StateMachine   1
  SemanticNode   2
  PageMetadata   1
  AnimationTransition 1

State Machines:
  form-states    3 states, 4 transitions

Features:
  [x] Accessibility
  [x] Forms
  [x] SEO metadata
  [x] Animation
  [ ] Navigation
  [ ] Internationalization
  [ ] Theming
  [ ] Data bindings

Use Cases

  • Quick audit – understand what an IR file contains before validating or compiling it.
  • CI reporting – log IR complexity metrics alongside build output.
  • Debugging – verify that AI-generated IR includes the expected node types and features.
  • Diffing – compare inspect output across versions to spot structural changes.

Notes

The inspect command does not validate the IR. A file with structural errors can still be inspected as long as it is parseable JSON. To check correctness, use voce validate.

voce preview

Compile a Voce IR file and open the result in the default web browser. A convenience command that combines voce compile with a platform-aware browser launch.

Usage

voce preview <FILE>

Arguments

ArgumentDescription
<FILE>Path to a .voce.json IR file (required)

How It Works

  1. Validate – runs the full 9-pass validation suite.
  2. Compile – produces a self-contained HTML file in a temporary location (or dist/ if it exists).
  3. Open – launches the compiled HTML in the default browser using the platform-appropriate command.

Platform Commands

PlatformCommand used
macOSopen <file>
Linuxxdg-open <file>
Windowsstart <file>

Exit Codes

CodeMeaning
0File compiled and browser launch initiated
1Validation failed
2Compilation failed
3Browser could not be opened

Examples

Preview a landing page:

voce preview examples/landing-page.voce.json

Preview after making changes to the IR:

# Edit the IR (or regenerate via AI), then preview
voce preview app.voce.json

Chain with validation for verbose feedback:

voce validate app.voce.json && voce preview app.voce.json

Comparison with compile

voce compilevoce preview
ValidatesYesYes
Writes HTMLTo -o path or dist/To temporary or dist/
Opens browserNoYes
Debug attributes--debug flagNot available
Custom output path-o flagNot available

For production builds, use voce compile. For quick iteration during development, use voce preview.

Notes

  • The preview command always compiles a fresh copy. It does not cache previous builds.
  • Debug attributes (data-voce-id) are not included in preview builds. Use voce compile --debug if you need them, then open the output file manually.
  • If no default browser is configured on Linux, xdg-open may fail silently. Set the BROWSER environment variable as a fallback.
  • The compiled HTML is fully self-contained with no external dependencies, so it renders correctly when opened as a local file.

Schema Overview

Voce IR uses FlatBuffers as its binary serialization format. Every UI is represented as a VoceDocument containing a tree of typed nodes spanning 11 domains: layout, state, motion, navigation, accessibility, theming, data, forms, SEO, and i18n.

Binary Format

FlatBuffers provides zero-copy deserialization, schema evolution with forward/backward compatibility, and a compact binary representation. Voce IR files use the VOCE file identifier and the .voce extension.

The binary format is immutable by design. Runtime mutable state lives in a separate reactive layer managed by the compiler output, not in the buffer itself.

JSON Canonical Representation

Every Voce IR binary round-trips losslessly to and from JSON. The JSON representation serves as:

  • AI generation target – LLMs emit JSON, which is then compiled to binary
  • Debugging format – human-inspectable when needed
  • Version control diffing – text diffs for review workflows
  • Escape hatch – interop with tools that cannot read FlatBuffers

The JSON form is not intended for hand-authoring. It is a machine-readable text serialization of the IR.

ChildUnion Pattern

FlatBuffers does not support vectors of unions directly in Rust codegen. Voce IR works around this with a wrapper table:

{
  "children": [
    { "value_type": "Container", "value": { "node_id": "c1", "layout": "Flex" } },
    { "value_type": "TextNode", "value": { "node_id": "t1", "content": "Hello" } }
  ]
}

The ChildUnion is a union of all 27 node types across all domains. Each child entry is wrapped in a ChildNode table containing a single value field of type ChildUnion.

ChildUnion Members

DomainNode Types
LayoutContainer, Surface, TextNode, MediaNode
StateStateMachine, DataNode, ComputeNode, EffectNode, ContextNode
MotionAnimationTransition, Sequence, GestureHandler, ScrollBinding, PhysicsBody
NavigationRouteMap
AccessibilitySemanticNode, LiveRegion, FocusTrap
ThemingThemeNode, PersonalizationSlot, ResponsiveRule
DataActionNode, SubscriptionNode, AuthContextNode, ContentSlot, RichTextNode
FormsFormNode

VoceDocument

The root table of every Voce IR file.

FieldTypeRequiredDescription
schema_version_majorint32noMajor schema version (default 0)
schema_version_minorint32noMinor schema version (default 1)
rootViewRootyesTop-level view root for the document
routesRouteMapnoApplication-level route map for multi-route apps
themeThemeNodenoPrimary theme
alternate_themes[ThemeNode]noAdditional themes (dark, high-contrast, etc.)
authAuthContextNodenoApplication-level auth configuration
i18nI18nConfignoInternationalization configuration

Minimal Example

{
  "schema_version_major": 0,
  "schema_version_minor": 1,
  "root": {
    "node_id": "root",
    "document_language": "en",
    "children": [
      {
        "value_type": "TextNode",
        "value": {
          "node_id": "greeting",
          "content": "Hello, world",
          "heading_level": 1
        }
      }
    ]
  }
}

Foundation Types

These shared types appear throughout the schema.

TypeFieldsDescription
Colorr, g, b, a (ubyte)RGBA color value
Lengthvalue (float32), unit (LengthUnit)Dimensional value with unit
Durationms (float32)Time duration in milliseconds
Easingeasing_type, control points/spring paramsAnimation timing function
EdgeInsetstop, right, bottom, left (Length)Four-sided spacing
CornerRadiitop_left, top_right, bottom_right, bottom_left (Length)Per-corner radius
Shadowoffset_x, offset_y, blur, spread, color, insetBox shadow definition
DataBindingsource_node_id, field_pathRuntime data reference

LengthUnit Values

Px, Rem, Em, Percent, Vw, Vh, Dvh, Svh, Auto, FitContent, MinContent, MaxContent, Fr

EasingType Values

Linear, CubicBezier, Spring, Steps, CustomLinear

Layout Nodes

Layout nodes define the spatial composition of a Voce IR document. There are five layout node types: ViewRoot (the document root), Container (grouping and layout), Surface (visual rectangles), TextNode (styled text), and MediaNode (images, video, audio).

ViewRoot

Top-level container for a document or route. One ViewRoot per page. Defines viewport bounds, document language, and holds the flat semantic node list.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
children[ChildNode]noChild nodes
widthLengthnoViewport width
heightLengthnoViewport height
backgroundColornoDocument background color
document_languagestringnoBCP 47 language tag (e.g., “en”)
text_directionTextDirectionnoLtr (default), Rtl, or Auto
semantic_nodes[SemanticNode]noFlat list of semantic nodes referenced by visual nodes
metadataPageMetadatanoPer-page SEO metadata
{
  "node_id": "root",
  "document_language": "en",
  "text_direction": "Ltr",
  "background": { "r": 255, "g": 255, "b": 255, "a": 255 },
  "children": []
}

Container

Groups children with a layout strategy. The primary structural node for composition.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
children[ChildNode]noChild nodes
layoutContainerLayoutnoStack (default), Flex, Grid, or Absolute
directionLayoutDirectionnoRow, Column (default), RowReverse, ColumnReverse
main_alignAlignmentnoMain axis alignment (default Start)
cross_alignAlignmentnoCross axis alignment (default Start)
gapLengthnoGap between children
paddingEdgeInsetsnoInner padding
wrapboolnoEnable flex wrapping (default false)
grid_columns[Length]noColumn track sizes for Grid layout
grid_rows[Length]noRow track sizes for Grid layout
widthLengthnoExplicit width
heightLengthnoExplicit height
min_widthLengthnoMinimum width constraint
max_widthLengthnoMaximum width constraint
min_heightLengthnoMinimum height constraint
max_heightLengthnoMaximum height constraint
overflow_xOverflownoHorizontal overflow (default Visible)
overflow_yOverflownoVertical overflow (default Visible)
clipboolnoClip overflowing content (default false)
positionPositionnoRelative (default), Absolute, Fixed, Sticky
topLengthnoTop offset (for positioned elements)
rightLengthnoRight offset
bottomLengthnoBottom offset
leftLengthnoLeft offset
z_indexint32noStacking order (default 0)
opacityfloat32noOpacity 0.0-1.0 (default 1.0)
backgroundColornoBackground color
borderBorderSidesnoPer-side border configuration
corner_radiusCornerRadiinoPer-corner border radius
shadow[Shadow]noBox shadows
semantic_node_idstringnoReference to a SemanticNode
{
  "node_id": "hero-row",
  "layout": "Flex",
  "direction": "Row",
  "gap": { "value": 16, "unit": "Px" },
  "padding": {
    "top": { "value": 24, "unit": "Px" },
    "right": { "value": 24, "unit": "Px" },
    "bottom": { "value": 24, "unit": "Px" },
    "left": { "value": 24, "unit": "Px" }
  },
  "main_align": "Center",
  "cross_align": "Center",
  "children": []
}

Surface

A visible rectangular region used for cards, backgrounds, dividers, and decorative elements.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
children[ChildNode]noChild nodes
fillColornoFill color
strokeColornoStroke/border color
stroke_widthLengthnoStroke thickness
corner_radiusCornerRadiinoPer-corner border radius
shadow[Shadow]noBox shadows
opacityfloat32noOpacity 0.0-1.0 (default 1.0)
borderBorderSidesnoPer-side border configuration
widthLengthnoExplicit width
heightLengthnoExplicit height
min_widthLengthnoMinimum width constraint
max_widthLengthnoMaximum width constraint
min_heightLengthnoMinimum height constraint
max_heightLengthnoMaximum height constraint
paddingEdgeInsetsnoInner padding
decorativeboolnoIf true, no SemanticNode required (default false)
semantic_node_idstringnoReference to a SemanticNode
{
  "node_id": "card",
  "fill": { "r": 248, "g": 248, "b": 248, "a": 255 },
  "corner_radius": {
    "top_left": { "value": 8, "unit": "Px" },
    "top_right": { "value": 8, "unit": "Px" },
    "bottom_right": { "value": 8, "unit": "Px" },
    "bottom_left": { "value": 8, "unit": "Px" }
  },
  "shadow": [{
    "offset_x": { "value": 0, "unit": "Px" },
    "offset_y": { "value": 2, "unit": "Px" },
    "blur": { "value": 8, "unit": "Px" },
    "spread": { "value": 0, "unit": "Px" },
    "color": { "r": 0, "g": 0, "b": 0, "a": 25 }
  }],
  "decorative": false,
  "children": []
}

TextNode

Styled text content. All typography properties are explicit with no cascade.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
contentstringnoStatic text content
content_bindingDataBindingnoDynamic content from a DataNode
localized_contentLocalizedStringnoi18n content (alternative to static content)
font_familystringnoFont family name
font_sizeLengthnoFont size
font_weightFontWeightno100-900 weight (default Regular/400)
line_heightfloat32noLine height multiplier (default 1.5)
letter_spacingLengthnoLetter spacing
text_alignTextAlignnoStart (default), Center, End, Justify
text_overflowTextOverflownoClip (default), Ellipsis, Fade
text_decorationTextDecorationnoNone (default), Underline, LineThrough
max_linesint32noMaximum number of visible lines
colorColornoText color
opacityfloat32noOpacity 0.0-1.0 (default 1.0)
heading_levelint8no0 = not a heading, 1-6 = h1-h6 (default 0)
semantic_node_idstringnoReference to a SemanticNode
{
  "node_id": "page-title",
  "content": "Welcome to Voce",
  "heading_level": 1,
  "font_size": { "value": 3, "unit": "Rem" },
  "font_weight": "Bold",
  "color": { "r": 17, "g": 24, "b": 39, "a": 255 }
}

MediaNode

Image, video, audio, or SVG with explicit dimensions, loading strategy, and format negotiation.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
media_typeMediaTypenoImage (default), Video, Audio, Svg
srcstringyesSource URL
altstringnoAlt text (required for non-decorative images)
widthLengthnoExplicit width (recommended for CLS prevention)
heightLengthnoExplicit height
aspect_ratiofloat32noAspect ratio (width/height)
object_fitObjectFitnoCover (default), Contain, Fill, ScaleDown, None
loadingLoadingStrategynoEager or Lazy (default)
corner_radiusCornerRadiinoPer-corner border radius
opacityfloat32noOpacity 0.0-1.0 (default 1.0)
srcset_widths[int32]noWidths for responsive srcset generation
sizesstringnoSizes attribute for responsive images
decorativeboolnoIf true, no alt text required (default false)
above_foldboolnoIf true, compiler uses eager loading (default false)
semantic_node_idstringnoReference to a SemanticNode
{
  "node_id": "hero-image",
  "media_type": "Image",
  "src": "/images/hero.webp",
  "alt": "Product dashboard showing analytics overview",
  "width": { "value": 100, "unit": "Percent" },
  "aspect_ratio": 1.778,
  "above_fold": true,
  "loading": "Eager"
}

State & Logic Nodes

All state in Voce IR is modeled as explicit, typed finite state machines. There are no implicit closures, dependency arrays, or hook ordering. The compiler can statically analyze every possible state transition.

StateMachine

A named finite state machine with typed states, transitions, guards, and effects. Every component’s behavior is a state machine. The validator checks reachability (no dead states) and deadlock freedom.

State

FieldTypeRequiredDescription
namestringyesState name
initialboolnoWhether this is the initial state (default false)
terminalboolnoWhether this is a final state (default false)

Transition

FieldTypeRequiredDescription
eventstringyesEvent name that triggers this transition
fromstringyesSource state name
tostringyesTarget state name
guardstringnoReference to a ComputeNode that returns bool
effectstringnoReference to an EffectNode to execute on transition

StateMachine

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringnoHuman-readable name (e.g., “auth-flow”)
states[State]yesList of states
transitions[Transition]yesList of transitions
{
  "node_id": "btn-state",
  "name": "button-state",
  "states": [
    { "name": "idle", "initial": true },
    { "name": "hovered" },
    { "name": "pressed" },
    { "name": "disabled", "terminal": true }
  ],
  "transitions": [
    { "event": "hover", "from": "idle", "to": "hovered" },
    { "event": "unhover", "from": "hovered", "to": "idle" },
    { "event": "press", "from": "hovered", "to": "pressed" },
    { "event": "release", "from": "pressed", "to": "idle", "effect": "submit-effect" }
  ]
}

DataNode

Declares an external data dependency. The compiler emits fetch code with caching, error handling, and loading states.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringnoHuman-readable name
sourceDataSourceyesEndpoint configuration
cache_strategyCacheStrategynoNone, StaleWhileRevalidate (default), CacheUntilInvalidated, Static
stale_timeuint32noFreshness duration in ms (default 30000)
cache_timeuint32noCache retention in ms (default 300000)
cache_tags[string]noTags for cache invalidation
auth_requiredboolnoWhether authentication is needed (default false)
loading_state_machinestringnoStateMachine tracking loading/error/success
query_params[KeyValue]noQuery parameters for filtering, sorting, pagination

DataSource

FieldTypeRequiredDescription
providerDataSourceProvidernoRest (default), GraphQL, Supabase, Firebase, Convex, Custom
endpointstringnoAPI endpoint URL
resourcestringnoResource path or query
methodHttpMethodnoGET (default), POST, PUT, PATCH, DELETE
headers[KeyValue]noCustom request headers
{
  "node_id": "user-data",
  "name": "current-user",
  "source": {
    "provider": "Rest",
    "endpoint": "/api/users/me",
    "method": "GET"
  },
  "cache_strategy": "StaleWhileRevalidate",
  "stale_time": 60000,
  "auth_required": true
}

ComputeNode

A pure function from inputs to output. Referentially transparent, so the compiler can memoize or pre-compute at build time.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringnoHuman-readable name
inputs[ComputeInput]yesInput bindings from other nodes
expressionstringyesExpression to evaluate
output_typestringnoOutput type hint for the compiler

ComputeInput

FieldTypeRequiredDescription
namestringyesParameter name in the expression
source_node_idstringyesSource DataNode, ContextNode, or ComputeNode
field_pathstringnoDot-path into the source data
{
  "node_id": "total-compute",
  "name": "order-total",
  "inputs": [
    { "name": "price", "source_node_id": "product-data", "field_path": "price" },
    { "name": "qty", "source_node_id": "cart-ctx", "field_path": "quantity" }
  ],
  "expression": "price * qty",
  "output_type": "number"
}

EffectNode

A side effect triggered by a state transition. Effects attach to transitions, never to states, eliminating mount/unmount ambiguity.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringnoHuman-readable name
effect_typeEffectTypenoAnalytics, ApiCall, Storage, Haptic, Log, Navigate, Custom
config[KeyValue]noConfiguration payload
api_sourceDataSourcenoEndpoint configuration for ApiCall effects
idempotentboolnoWhether safe to retry (default false)
{
  "node_id": "track-click",
  "name": "analytics-click",
  "effect_type": "Analytics",
  "config": [
    { "key": "event", "value": "button_click" },
    { "key": "category", "value": "cta" }
  ]
}

ContextNode

Shared state scoped to a subtree. Replaces React Context, Redux, and prop drilling. Typed with explicit read/write boundaries.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringyesContext name
initial_valuestringnoInitial value as JSON string
writers[string]noNode IDs allowed to write (empty = any descendant)
globalboolnoIf true, not scoped to subtree (default false)
{
  "node_id": "cart-ctx",
  "name": "shopping-cart",
  "initial_value": "{ \"items\": [], \"quantity\": 0 }",
  "global": true
}

Motion & Interaction Nodes

Animation is a first-class IR concern in Voce IR. Every motion declaration includes a reduced-motion fallback, which the validator enforces as required. The compiler uses a tiered output strategy: CSS transitions, Web Animations API, then minimal requestAnimationFrame JavaScript.

AnimationTransition

Animates property changes between state machine states. The compiler chooses the optimal output technique based on the trigger type and interruptibility requirements.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringnoHuman-readable name
target_node_idstringyesNode to animate
trigger_state_machinestringnoStateMachine that triggers this animation
trigger_eventstringnoEvent on the state machine that starts animation
properties[AnimatedProperty]yesProperties to animate
durationDurationnoAnimation duration in ms
delayDurationnoDelay before animation starts
easingEasingnoTiming function (supports Spring)
reduced_motionReducedMotionnoRequired alternative for prefers-reduced-motion

AnimatedProperty

FieldTypeRequiredDescription
propertystringyesCSS-like property path (e.g., “opacity”, “transform.translateY”)
fromstringyesValue in the starting state
tostringyesValue in the ending state

Easing

The Easing table supports multiple timing function types.

FieldTypeRequiredDescription
easing_typeEasingTypenoLinear (default), CubicBezier, Spring, Steps, CustomLinear
x1, y1, x2, y2float32noControl points for CubicBezier
stiffnessfloat32noSpring stiffness (default 200)
dampingfloat32noSpring damping (default 20)
massfloat32noSpring mass (default 1)
stepsint32noStep count for Steps easing
points[float32]noPre-computed points for CustomLinear (CSS linear())
{
  "node_id": "fade-in",
  "target_node_id": "hero-section",
  "trigger_state_machine": "page-state",
  "trigger_event": "enter",
  "properties": [
    { "property": "opacity", "from": "0", "to": "1" },
    { "property": "transform.translateY", "from": "20px", "to": "0px" }
  ],
  "duration": { "ms": 300 },
  "easing": { "easing_type": "Spring", "stiffness": 300, "damping": 25, "mass": 1 },
  "reduced_motion": { "strategy": "Remove" }
}

Sequence

Choreographed animation timeline. Multiple AnimationTransitions played in sequence or parallel with stagger offsets.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringnoHuman-readable name
steps[SequenceStep]yesOrdered list of animation steps
staggerDurationnoDelay between sequential elements
iterationsint32noRepeat count, 0 = infinite (default 1)
alternateboolnoReverse on alternate iterations (default false)
reduced_motionReducedMotionnoAlternative for the entire sequence

SequenceStep

FieldTypeRequiredDescription
transition_idstringyesReference to an AnimationTransition node
delayDurationnoDelay before this step starts
parallelboolnoIf true, runs concurrently with previous step (default false)
{
  "node_id": "entrance-seq",
  "steps": [
    { "transition_id": "fade-in-title" },
    { "transition_id": "fade-in-subtitle", "delay": { "ms": 100 } },
    { "transition_id": "fade-in-cta", "delay": { "ms": 100 } }
  ],
  "stagger": { "ms": 50 },
  "reduced_motion": { "strategy": "Remove" }
}

GestureHandler

Maps touch, mouse, and keyboard input to state transitions or continuous property updates. The validator requires a keyboard_key for accessibility.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
target_node_idstringyesNode that receives the gesture
gesture_typeGestureTypenoTap, DoubleTap, LongPress, Drag, Swipe, Pinch, Hover, Focus
trigger_eventstringnoState machine event to fire on gesture
trigger_state_machinestringnoTarget state machine
continuous_propertystringnoProperty to update for drag/continuous gestures
continuous_axisstringnoAxis for continuous gestures
keyboard_keystringnoKeyboard equivalent (required by validator)
keyboard_modifierstringnoModifier key (Shift, Ctrl, Alt, Meta)
threshold_pxfloat32noGesture distance threshold in pixels
velocity_thresholdfloat32noGesture velocity threshold
{
  "node_id": "card-tap",
  "target_node_id": "card-surface",
  "gesture_type": "Tap",
  "trigger_event": "select",
  "trigger_state_machine": "card-state",
  "keyboard_key": "Enter"
}

ScrollBinding

Binds node properties to scroll position. Compiled to CSS scroll-driven animations where supported, with IntersectionObserver fallback.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
target_node_idstringyesNode whose properties are scroll-linked
scroll_triggerScrollTriggernoViewProgress (default) or ScrollProgress
scroll_axisScrollAxisnoVertical (default) or Horizontal
range_startfloat32noStart of scroll range, 0.0-1.0 (default 0.0)
range_endfloat32noEnd of scroll range, 0.0-1.0 (default 1.0)
properties[AnimatedProperty]yesProperties to animate over the scroll range
scroll_container_idstringnoScroll container (default: nearest ancestor)
reduced_motionReducedMotionnoAlternative for prefers-reduced-motion
{
  "node_id": "parallax-bg",
  "target_node_id": "bg-image",
  "scroll_trigger": "ViewProgress",
  "properties": [
    { "property": "transform.translateY", "from": "0px", "to": "-50px" }
  ],
  "reduced_motion": { "strategy": "Remove" }
}

PhysicsBody

Attaches physics simulation to a node for spring animations, momentum scrolling, and procedural motion. Non-interruptible springs are compiled to CSS linear() at build time.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
target_node_idstringyesNode to apply physics to
stiffnessfloat32noSpring stiffness (default 300)
dampingfloat32noSpring damping (default 25, must be > 0)
massfloat32noSpring mass (default 1)
frictionfloat32noMomentum friction, 0-1 (default 0.05)
restitutionfloat32noBounciness, 0-1 (default 0)
interruptibleboolnoIf true, uses rAF instead of CSS (default false)
{
  "node_id": "spring-body",
  "target_node_id": "draggable-card",
  "stiffness": 400,
  "damping": 30,
  "mass": 1,
  "interruptible": true
}

ReducedMotion

Every animation must reference a ReducedMotion alternative. The validator rejects IR where any AnimationTransition, Sequence, or ScrollBinding lacks one.

FieldTypeRequiredDescription
strategyReducedMotionStrategynoRemove (default), Simplify, ReduceDuration, Functional
simplified_properties[AnimatedProperty]noReplacement properties for Simplify strategy
reduced_durationDurationnoShortened duration for ReduceDuration strategy

Strategy values:

  • Remove – snap to final state, no animation
  • Simplify – replace with a simpler animation (e.g., fade instead of slide)
  • ReduceDuration – keep the animation but make it near-instant
  • Functional – animation serves a functional purpose (spinner, progress); simplify but do not remove

Navigation & Routing Nodes

Routing in Voce IR is modeled as a state machine where states are views and transitions are navigation events. The schema supports nested routes, guards for auth checks, data preloading, and sitemap generation.

RouteMap

Top-level routing configuration for a multi-route application. Referenced from VoceDocument.routes.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
routes[RouteEntry]yesList of route definitions
not_found_routestringnoRoute name or path for 404 pages
default_transitionRouteTransitionConfignoDefault transition animation between routes
{
  "node_id": "app-routes",
  "routes": [
    { "path": "/", "name": "home", "view_root_id": "home-root" },
    { "path": "/about", "name": "about", "view_root_id": "about-root" }
  ],
  "not_found_route": "/404"
}

RouteEntry

Defines a single route mapping a URL path to a ViewRoot.

FieldTypeRequiredDescription
pathstringyesURL path pattern (e.g., “/products/:id”)
namestringnoRoute name for programmatic navigation
view_root_idstringyesViewRoot to render for this route
guardRouteGuardnoAccess control configuration
preload_data[string]noDataNode IDs to prefetch on navigation
transitionRouteTransitionConfignoTransition animation for this route
sitemap_priorityfloat32noSitemap priority 0.0-1.0 (default 0.5)
sitemap_change_freqChangeFrequencynoAlways, Hourly, Daily, Weekly, Monthly, Yearly, Never
sitemap_last_modifiedstringnoLast modification date (ISO 8601)
exclude_from_sitemapboolnoExclude from generated sitemap (default false)
children[RouteEntry]noNested child routes
{
  "path": "/dashboard",
  "name": "dashboard",
  "view_root_id": "dashboard-root",
  "guard": {
    "requires_auth": true,
    "required_roles": ["user"],
    "redirect_on_fail": "/login"
  },
  "preload_data": ["user-data", "dashboard-stats"],
  "sitemap_priority": 0.3,
  "exclude_from_sitemap": true
}

RouteGuard

Access control for a route. The compiler emits authentication and authorization checks before rendering.

FieldTypeRequiredDescription
requires_authboolnoWhether authentication is required (default false)
required_roles[string]noRoles the user must have to access the route
redirect_on_failstringnoPath to redirect to if guard fails
custom_guardstringnoReference to a ComputeNode for custom logic
{
  "requires_auth": true,
  "required_roles": ["admin"],
  "redirect_on_fail": "/login"
}

RouteTransitionConfig

Configures the animation played when navigating between routes.

FieldTypeRequiredDescription
transition_typeRouteTransitionTypenoNone, Crossfade, Slide, SharedElement, Custom
durationDurationnoTransition duration in ms
easingEasingnoTiming function
slide_directionSlideDirectionnoLeft, Right, Up, Down (for Slide type)
shared_elements[SharedElementPair]noPaired elements for SharedElement transitions
custom_sequence_idstringnoReference to a Sequence node (for Custom type)
reduced_motionReducedMotionnoAlternative for prefers-reduced-motion

SharedElementPair

FieldTypeRequiredDescription
transition_namestringyesIdentifier for the shared transition
source_node_idstringnoNode in the source route
target_node_idstringnoNode in the target route
{
  "transition_type": "Crossfade",
  "duration": { "ms": 200 },
  "easing": { "easing_type": "CubicBezier", "x1": 0.4, "y1": 0, "x2": 0.2, "y2": 1 },
  "reduced_motion": { "strategy": "Remove" }
}

Accessibility & Semantics Nodes

Accessibility in Voce IR is structurally required, not opt-in. Every interactive visual node must reference a SemanticNode. The validator rejects IR where interactive elements lack semantic annotations. Explicit opt-outs (decorative: true, presentation: true) are supported for valid exceptions.

SemanticNode

Parallel semantic tree entry for a visual node. Carries ARIA role, label, relationships, and keyboard focus configuration. Visual nodes reference SemanticNodes via the semantic_node_id field. All SemanticNodes live in a flat list on ViewRoot.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
rolestringyesARIA role (e.g., “button”, “heading”, “navigation”, “main”)
labelstringnoAccessible label announced by screen readers
labelled_bystringnoNode ID whose content labels this node (aria-labelledby)
described_bystringnoNode ID providing extended description (aria-describedby)
controlsstringnoNode ID this element controls (aria-controls)
owned_bystringnoNode ID that owns this element (aria-owns)
heading_levelint8noHeading level 1-6, only valid when role=“heading” (default 0)
tab_indexint32noKeyboard focus order (-2=unset, -1=programmatic, 0=natural, >0=explicit)
hiddenboolnoHidden from the accessibility tree (default false)
aria_expandedint8no-1=unset, 0=false, 1=true
aria_selectedint8no-1=unset, 0=false, 1=true
aria_checkedint8no-1=unset, 0=false, 1=true, 2=mixed
aria_disabledboolnoWhether element is disabled (default false)
aria_requiredboolnoWhether element is required (default false)
aria_invalidboolnoWhether element has invalid input (default false)
aria_value_minfloat32noMinimum value for range widgets
aria_value_maxfloat32noMaximum value for range widgets
aria_value_nowfloat32noCurrent value for range widgets
aria_value_textstringnoHuman-readable value for range widgets
custom_aria[KeyValue]noCustom ARIA attributes (escape hatch)

The validator enforces that interactive roles (“button”, “link”, “textbox”, etc.) have either label or labelled_by set.

{
  "node_id": "sem-cta-btn",
  "role": "button",
  "label": "Get started with Voce IR",
  "tab_index": 0
}

LiveRegion

Declares a region whose content changes are announced by screen readers. Used for toast notifications, form errors, cart updates, and status messages.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
target_node_idstringyesVisual node this live region is attached to
politenessLiveRegionPolitenessnoPolite (default) or Assertive or Off
atomicboolnoAnnounce entire region on change (default false)
relevantLiveRegionRelevantnoAdditions (default), Removals, Text, All
role_descriptionstringnoDescriptive label (e.g., “Shopping cart updates”)

Politeness values:

  • Polite – wait for the user to be idle before announcing
  • Assertive – interrupt current speech to announce immediately
  • Off – region is silent
{
  "node_id": "toast-live",
  "target_node_id": "toast-container",
  "politeness": "Assertive",
  "atomic": true,
  "role_description": "Notification"
}

FocusTrap

Constrains keyboard focus to a subtree. Used for modals, drawers, and dialogs. The compiler emits focus management JavaScript.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
container_node_idstringyesContainer node whose subtree traps focus
initial_focus_node_idstringnoNode to focus on activation (default: first focusable)
escape_behaviorFocusTrapEscapenoCloseOnEscape (default), NoEscape, FireEvent
escape_state_machinestringnoStateMachine for FireEvent escape behavior
escape_eventstringnoEvent to fire on Escape for FireEvent
restore_focusboolnoRestore previous focus on deactivation (default true)
{
  "node_id": "modal-trap",
  "container_node_id": "modal-container",
  "initial_focus_node_id": "modal-close-btn",
  "escape_behavior": "CloseOnEscape",
  "restore_focus": true
}

ReducedMotion

Every animation in the IR must reference a ReducedMotion alternative. The validator rejects any AnimationTransition, Sequence, or ScrollBinding that lacks one. See the Motion chapter for the full ReducedMotion reference.

Strategy values:

StrategyDescription
RemoveRemove animation entirely, snap to final state
SimplifyReplace with a simpler animation (e.g., opacity fade)
ReduceDurationKeep animation but reduce to near-instant duration
FunctionalAnimation is functional (spinner, progress bar); simplify only

Theming & Personalization Nodes

Design tokens, multi-theme support, responsive breakpoints, and personalization slots. Theme switching is modeled as a state machine transition (e.g., light to dark).

ThemeNode

A named set of design tokens. Multiple themes can coexist (light, dark, high-contrast). Referenced from VoceDocument.theme and VoceDocument.alternate_themes.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringyesTheme name (e.g., “light”, “dark”)
colorsColorPalettenoColor token definitions
typographyTypographyScalenoTypography token definitions
spacingSpacingScalenoSpacing token definitions
shadowsShadowScalenoShadow scale definitions
radiiRadiusScalenoBorder radius scale definitions
transition_durationDurationnoAnimation duration when switching to theme
transition_easingEasingnoEasing function for theme transition

ColorPalette

Semantic color tokens. Each token is a Color struct (r, g, b, a).

FieldTypeDescription
primaryColorPrimary brand color
primary_foregroundColorText on primary
secondaryColorSecondary brand color
secondary_foregroundColorText on secondary
accentColorAccent color
accent_foregroundColorText on accent
backgroundColorPage background
foregroundColorDefault text color
surfaceColorCard/surface background
surface_foregroundColorText on surface
mutedColorMuted/subtle background
muted_foregroundColorText on muted
border_colorColorDefault border color
errorColorError state color
error_foregroundColorText on error
successColorSuccess state color
success_foregroundColorText on success
warningColorWarning state color
warning_foregroundColorText on warning
infoColorInfo state color
info_foregroundColorText on info

TypographyScale

FieldTypeDescription
font_bodyFontDefinitionBody text font
font_displayFontDefinitionDisplay/heading font
font_monoFontDefinitionMonospace font
size_scale[Length]Size steps (xs through 4xl)
line_height_scale[float32]Line heights matching size_scale indexes
letter_spacingLengthDefault letter spacing

SpacingScale

FieldTypeDescription
baseLengthBase spacing unit (e.g., 4px)
multipliers[float32]Multiplier scale (actual spacing = base * multiplier)
{
  "node_id": "theme-light",
  "name": "light",
  "colors": {
    "primary": { "r": 59, "g": 130, "b": 246, "a": 255 },
    "primary_foreground": { "r": 255, "g": 255, "b": 255, "a": 255 },
    "background": { "r": 255, "g": 255, "b": 255, "a": 255 },
    "foreground": { "r": 17, "g": 24, "b": 39, "a": 255 }
  },
  "spacing": {
    "base": { "value": 4, "unit": "Px" },
    "multipliers": [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32]
  }
}

PersonalizationSlot

A point in the IR that adapts based on user context: locale, device type, color scheme preference, A/B test cohort, or custom conditions.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringnoHuman-readable name
variants[PersonalizationVariant]yesList of conditional variants
default_variant_indexint32noFallback variant index (default 0)

PersonalizationVariant

FieldTypeRequiredDescription
conditions[PersonalizationCondition]yesAll conditions must be true to activate
show_nodes[string]noNode IDs to show when active
hide_nodes[string]noNode IDs to hide when active
overrides[PropertyOverride]noProperty overrides to apply

PersonalizationCondition

FieldTypeDescription
condition_typePersonalizationConditionTypeLocale, DeviceType, ColorScheme, ReducedMotion, HighContrast, Viewport, Custom
operatorstringComparison: “eq”, “neq”, “gt”, “lt”, “gte”, “lte”, “contains”
valuestringValue to compare against
{
  "node_id": "mobile-variant",
  "name": "mobile-layout",
  "variants": [
    {
      "conditions": [
        { "condition_type": "DeviceType", "operator": "eq", "value": "mobile" }
      ],
      "hide_nodes": ["desktop-sidebar"],
      "show_nodes": ["mobile-nav"]
    }
  ],
  "default_variant_index": 0
}

ResponsiveRule

Adapts layout based on viewport dimensions using explicit breakpoints with property overrides. Unlike CSS media queries, there is no cascading.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
breakpoints[Breakpoint]yesBreakpoint definitions
responsive_overrides[ResponsiveOverride]yesPer-breakpoint property overrides

Breakpoint

FieldTypeRequiredDescription
namestringyesBreakpoint name (e.g., “sm”, “md”, “lg”)
min_widthLengthyesMinimum viewport width for this breakpoint

ResponsiveOverride

FieldTypeRequiredDescription
breakpoint_namestringyesWhich breakpoint this applies at
overrides[PropertyOverride]yesProperty overrides at this breakpoint

PropertyOverride

FieldTypeRequiredDescription
target_node_idstringyesNode to override
propertystringyesProperty name to override
valuestringyesNew value at this breakpoint
{
  "node_id": "responsive-grid",
  "breakpoints": [
    { "name": "sm", "min_width": { "value": 640, "unit": "Px" } },
    { "name": "lg", "min_width": { "value": 1024, "unit": "Px" } }
  ],
  "responsive_overrides": [
    {
      "breakpoint_name": "sm",
      "overrides": [
        { "target_node_id": "content-grid", "property": "grid_columns", "value": "1" }
      ]
    },
    {
      "breakpoint_name": "lg",
      "overrides": [
        { "target_node_id": "content-grid", "property": "grid_columns", "value": "3" }
      ]
    }
  ]
}

Data & Backend Nodes

The data layer covers mutations (ActionNode), real-time subscriptions (SubscriptionNode), authentication (AuthContextNode), CMS content (ContentSlot), and structured rich text (RichTextNode). Data reads are handled by DataNode in the State chapter.

ActionNode

Declares a server mutation with optimistic update strategy, cache invalidation, and error handling. The compiler emits mutation calls with rollback support.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringnoHuman-readable name
sourceDataSourceyesEndpoint configuration
methodHttpMethodnoPOST (default), GET, PUT, PATCH, DELETE
input_typestringnoInput type hint for the compiler
output_typestringnoOutput type hint for the compiler
optimisticOptimisticConfignoOptimistic update behavior
invalidates[string]noDataNode IDs to refetch after success
invalidate_tags[string]noCache tags to invalidate
error_handlingErrorHandlingnoError and retry configuration
auth_requiredboolnoWhether authentication is needed (default false)
required_roles[string]noRoles required to execute this action
csrf_protectedboolnoCSRF protection enabled (default true)
idempotentboolnoWhether safe to retry (default false)

OptimisticConfig

FieldTypeRequiredDescription
strategyOptimisticStrategynoNone (default), MirrorInput, CustomTransform
target_data_node_idstringnoDataNode to optimistically update
rollbackRollbackStrategynoRevert (default) or ShowErrorKeepData

ErrorHandling

FieldTypeRequiredDescription
retryRetryConfignoRetry configuration
fallbackErrorFallbacknoShowToast (default), ShowInlineError, Redirect
redirect_pathstringnoPath for Redirect fallback
{
  "node_id": "add-to-cart",
  "name": "add-item",
  "source": {
    "provider": "Rest",
    "endpoint": "/api/cart/items"
  },
  "method": "POST",
  "optimistic": {
    "strategy": "MirrorInput",
    "target_data_node_id": "cart-data",
    "rollback": "Revert"
  },
  "invalidates": ["cart-data"],
  "csrf_protected": true
}

SubscriptionNode

Real-time data via WebSocket, Server-Sent Events, or polling. Keeps a DataNode updated with live changes.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringnoHuman-readable name
sourceDataSourceyesEndpoint configuration
transportSubscriptionTransportnoWebSocket (default), ServerSentEvents, Polling
channelstringnoChannel, topic, or table to subscribe to
filterstringnoFilter expression for the subscription
update_strategyUpdateStrategynoReplace (default), Merge, Append
target_data_node_idstringyesDataNode to keep updated
connectionConnectionConfignoReconnection and heartbeat settings

ConnectionConfig

FieldTypeRequiredDescription
reconnectboolnoAuto-reconnect on disconnect (default true)
reconnect_interval_msuint32noReconnect interval in ms (default 3000)
max_retriesint32noMaximum reconnect attempts (default 10)
heartbeat_interval_msuint32noHeartbeat interval in ms (default 30000)
{
  "node_id": "chat-sub",
  "name": "chat-messages",
  "source": {
    "provider": "Supabase",
    "endpoint": "wss://project.supabase.co/realtime/v1"
  },
  "transport": "WebSocket",
  "channel": "messages",
  "update_strategy": "Append",
  "target_data_node_id": "messages-data"
}

AuthContextNode

Application-level auth configuration. Provider-agnostic; the compiler adapts output to the chosen provider.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringnoHuman-readable name
providerAuthProvidernoAuth0, Clerk, Supabase, Firebase, NextAuth, Custom
session_strategySessionStrategynoJwtCookie (default), JwtHeader, SessionCookie
user_typestringnoType hint for the user object shape
role_fieldstringnoField path for roles in the user object
login_action_idstringnoActionNode reference for login
logout_action_idstringnoActionNode reference for logout
refresh_action_idstringnoActionNode reference for token refresh
{
  "node_id": "app-auth",
  "provider": "Supabase",
  "session_strategy": "JwtCookie",
  "role_field": "user_metadata.role",
  "login_action_id": "login-action",
  "logout_action_id": "logout-action"
}

ContentSlot

Declares a CMS content dependency. The cache strategy determines compiler behavior: static content is baked in at build time, ISR adds revalidation, and dynamic content is fetched at runtime.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
content_keystringyesContent entry ID or path in the CMS
sourceContentSourcenoCMS provider and endpoint configuration
fallbackstringnoFallback content if CMS fetch fails
cache_strategyContentCacheStrategynoStatic (default), Isr, Dynamic
content_typeContentTypenoText (default), RichText, Media, Structured
localestringnoLocale for this content slot
{
  "node_id": "hero-content",
  "content_key": "homepage-hero",
  "source": {
    "provider": "sanity",
    "endpoint": "https://project.api.sanity.io/v2023-01-01"
  },
  "cache_strategy": "Isr",
  "content_type": "RichText"
}

RichTextNode

Structured rich text with paragraphs, headings, lists, images, code blocks, and more. Maps directly from Sanity Portable Text, Contentful Rich Text JSON, and Payload Lexical JSON.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
blocks[RichTextBlock]yesOrdered list of content blocks
content_slotContentSlotnoCMS source for the content

RichTextBlock

FieldTypeRequiredDescription
block_typeRichTextBlockTypenoParagraph, Heading, UnorderedList, OrderedList, ListItem, Image, CodeBlock, Blockquote, Divider, Table, TableRow, TableCell
levelint8noHeading level (1-6) or list depth
children[RichTextSpan]noInline text content
media_srcstringnoImage source URL (for Image blocks)
media_altstringnoImage alt text (for Image blocks)
code_languagestringnoLanguage hint (for CodeBlock blocks)
rows[RichTextBlock]noNested rows (for Table blocks)

RichTextSpan

FieldTypeRequiredDescription
textstringyesText content
marks[RichTextMark]noBold, Italic, Underline, Strikethrough, Code, Subscript, Superscript
link_urlstringnoURL if this span is a link
{
  "node_id": "article-body",
  "blocks": [
    {
      "block_type": "Heading",
      "level": 2,
      "children": [{ "text": "Getting Started" }]
    },
    {
      "block_type": "Paragraph",
      "children": [
        { "text": "Voce IR uses " },
        { "text": "FlatBuffers", "marks": ["Bold"] },
        { "text": " for zero-copy deserialization." }
      ]
    }
  ]
}

Forms Nodes

Declarative forms with compiler-generated validation, progressive enhancement, and accessible markup. The compiler emits native <form> elements that work without JavaScript, then layers on client-side validation and enhanced submission handling.

FormNode

Top-level form declaration containing fields, validation, and submission configuration.

FieldTypeRequiredDescription
node_idstringyesUnique identifier
namestringnoHuman-readable name
fields[FormField]yesList of form fields
field_groups[FormFieldGroup]noLogical groupings of fields (rendered as fieldset)
cross_validations[CrossFieldValidation]noValidations spanning multiple fields
validation_modeValidationModenoOnSubmit, OnBlur, OnChange, OnBlurThenChange (default)
submissionFormSubmissionyesSubmission handling configuration
initial_values_node_idstringnoDataNode providing initial values (edit forms)
autosaveAutosaveConfignoDraft persistence configuration
semantic_node_idstringnoReference to a SemanticNode

ValidationMode Values

ValueDescription
OnSubmitValidate all fields on submit only
OnBlurValidate each field on blur
OnChangeValidate on every keystroke
OnBlurThenChangeValidate on blur, then on change after first error (default)
{
  "node_id": "contact-form",
  "name": "contact",
  "fields": [
    {
      "name": "email",
      "field_type": "Email",
      "label": "Email address",
      "autocomplete": "Email",
      "validations": [
        { "rule_type": "Required", "message": "Email is required" },
        { "rule_type": "Email", "message": "Enter a valid email" }
      ]
    },
    {
      "name": "message",
      "field_type": "Textarea",
      "label": "Message",
      "validations": [
        { "rule_type": "Required", "message": "Message is required" },
        { "rule_type": "MinLength", "value": "10", "message": "At least 10 characters" }
      ]
    }
  ],
  "validation_mode": "OnBlurThenChange",
  "submission": {
    "action_node_id": "submit-contact",
    "encoding": "Json",
    "progressive": true,
    "success_redirect": "/thank-you"
  }
}

FormField

Individual form field with type, label, validation, and accessibility attributes.

FieldTypeRequiredDescription
namestringyesField identifier (form submission key)
field_typeFormFieldTypenoText (default), Email, Password, Number, Tel, Url, Search, Select, MultiSelect, Checkbox, Radio, Textarea, File, Date, Time, DateTimeLocal, Hidden, Color, Range
labelstringyesVisible label (required for accessibility)
placeholderstringnoPlaceholder text
descriptionstringnoHelp text below the field (aria-describedby)
initial_valuestringnoDefault value
validations[ValidationRule]noClient-side validation rules
async_validations[AsyncValidation]noServer-side async validations
options[SelectOption]noOptions for Select and Radio fields
visible_whenstringnoConditional visibility expression
disabled_whenstringnoConditional disable expression
autocompleteAutocompleteHintnoBrowser autocomplete hint (default Off)
acceptstringnoAccepted MIME types for File fields
max_file_sizeuint32noMax file size in bytes for File fields
multipleboolnoAllow multiple files (default false)
stepfloat32noStep value for Number and Range fields
semantic_node_idstringnoReference to a SemanticNode

AutocompleteHint Values

Off, On, Name, GivenName, FamilyName, Email, Username, NewPassword, CurrentPassword, Tel, StreetAddress, City, State, PostalCode, Country, CreditCardNumber, CreditCardExp, CreditCardCsc

{
  "name": "password",
  "field_type": "Password",
  "label": "Password",
  "autocomplete": "NewPassword",
  "validations": [
    { "rule_type": "Required", "message": "Password is required" },
    { "rule_type": "MinLength", "value": "8", "message": "At least 8 characters" },
    { "rule_type": "Pattern", "value": "[A-Z]", "message": "Must contain an uppercase letter" }
  ]
}

ValidationRule

A single client-side validation constraint applied to a FormField.

FieldTypeRequiredDescription
rule_typeValidationTypenoRequired, MinLength, MaxLength, Pattern, Min, Max, Email, Url, Custom
valuestringnoParameter for the rule (e.g., “8” for MinLength)
messagestringyesError message displayed on validation failure
{ "rule_type": "MaxLength", "value": "500", "message": "Maximum 500 characters" }

FormSubmission

Configures how the form is submitted and what happens on success or failure.

FieldTypeRequiredDescription
action_node_idstringyesActionNode that handles submission
encodingFormEncodingnoUrlEncoded, Multipart, Json (default)
progressiveboolnoWorks without JS via native form action (default true)
success_eventstringnoStateMachine event to fire on success
success_state_machinestringnoTarget StateMachine for success event
success_redirectstringnoRoute to navigate to on success
error_displaystringno“field” (map to fields) or “summary” (error list)

FormFieldGroup

Logical grouping of fields, rendered as <fieldset> with <legend>.

FieldTypeRequiredDescription
labelstringyesGroup label (rendered as legend)
field_names[string]yesField names belonging to this group
descriptionstringnoGroup description

CrossFieldValidation

Validation spanning multiple fields (e.g., password confirmation).

FieldTypeRequiredDescription
field_names[string]yesFields involved in this validation
expressionstringyesExpression that must evaluate to true
messagestringyesError message on failure
target_fieldstringnoWhich field displays the error
{
  "field_names": ["password", "confirm_password"],
  "expression": "password == confirm_password",
  "message": "Passwords must match",
  "target_field": "confirm_password"
}

SEO Nodes

Search engine optimization metadata for Voce IR documents. The compiler emits <head> meta tags, JSON-LD structured data, and generates sitemap.xml and robots.txt. SEO metadata is attached per-page via the metadata field on ViewRoot.

PageMetadata

Per-page SEO configuration. One per ViewRoot. The validator warns if title exceeds 60 characters or description exceeds 160 characters.

FieldTypeRequiredDescription
titlestringyesPage title (validator warns if > 60 chars)
title_templatestringnoTitle template (e.g., “%s
descriptionstringnoMeta description (validator warns if > 160 chars)
canonical_urlstringnoCanonical URL for this page
robotsRobotsDirectivenoRobots meta directives
open_graphOpenGraphDatanoOpen Graph metadata
twitter_cardTwitterCardDatanoTwitter Card metadata
alternates[AlternateLink]noHreflang alternate links for i18n
structured_data[StructuredData]noJSON-LD structured data blocks
custom_meta[MetaTag]noCustom meta tags (escape hatch)
{
  "title": "Voce IR Documentation",
  "title_template": "%s | Voce IR",
  "description": "Schema reference and guide for the Voce IR intermediate representation.",
  "canonical_url": "https://voce-ir.xyz/docs",
  "robots": { "index": true, "follow": true },
  "open_graph": {
    "title": "Voce IR Documentation",
    "description": "Schema reference and guide for the Voce IR intermediate representation.",
    "og_type": "Website",
    "site_name": "Voce IR"
  }
}

OpenGraphData

Open Graph protocol metadata for social sharing previews.

FieldTypeRequiredDescription
titlestringnoOG title (falls back to PageMetadata.title)
descriptionstringnoOG description
imagestringnoImage URL or MediaNode reference
image_altstringnoAlt text for the OG image
image_widthint32noImage width in pixels
image_heightint32noImage height in pixels
og_typeOGTypenoWebsite (default), Article, Product, Profile
urlstringnoCanonical page URL
site_namestringnoSite name
localestringnoContent locale (e.g., “en_US”)
{
  "title": "Introducing Voce IR",
  "description": "An AI-native UI intermediate representation.",
  "image": "https://voce-ir.xyz/og-image.png",
  "image_alt": "Voce IR logo and tagline",
  "image_width": 1200,
  "image_height": 630,
  "og_type": "Website",
  "site_name": "Voce IR"
}

StructuredData

JSON-LD structured data block for search engine rich results. The validator checks basic conformance to the declared Schema.org type.

FieldTypeRequiredDescription
schema_typestringyesSchema.org type (e.g., “Article”, “Product”, “FAQ”, “BreadcrumbList”)
properties_jsonstringyesJSON-LD properties as a JSON string
{
  "schema_type": "Article",
  "properties_json": "{\"headline\":\"Getting Started with Voce IR\",\"author\":{\"@type\":\"Person\",\"name\":\"Voce Team\"},\"datePublished\":\"2025-01-15\"}"
}

TwitterCardData

Twitter (X) Card metadata for link previews on the platform.

FieldTypeRequiredDescription
card_typeTwitterCardTypenoSummary, SummaryLargeImage (default), App, Player
titlestringnoCard title
descriptionstringnoCard description
imagestringnoImage URL
image_altstringnoAlt text for the image
sitestringno@username of the site
creatorstringno@username of the content creator

RobotsDirective

Controls search engine crawling and indexing behavior for the page.

FieldTypeRequiredDescription
indexboolnoAllow indexing (default true)
followboolnoFollow links on page (default true)
max_snippetint32noMax snippet length, -1 = unlimited (default)
max_image_previewImagePreviewSizenoNone, Standard, Large (default)
max_video_previewint32noMax video preview seconds, -1 = unlimited
no_archiveboolnoPrevent cached page (default false)
no_translateboolnoPrevent translation (default false)

Hreflang link for international SEO, connecting pages across locales.

FieldTypeRequiredDescription
hreflangstringyesBCP 47 language tag (e.g., “en-US”, “x-default”)
hrefstringyesURL of the alternate page
[
  { "hreflang": "en-US", "href": "https://voce-ir.xyz/en/docs" },
  { "hreflang": "fr-FR", "href": "https://voce-ir.xyz/fr/docs" },
  { "hreflang": "x-default", "href": "https://voce-ir.xyz/docs" }
]

MetaTag

Custom meta tag for cases not covered by the structured fields above.

FieldTypeRequiredDescription
namestringyesMeta tag name or property attribute
contentstringyesMeta tag content value
is_propertyboolnoUse property= instead of name= (default false)

Internationalization Nodes

Voce IR supports two i18n compilation modes. In static mode (default), the compiler resolves all LocalizedStrings against the target locale’s MessageCatalog and emits fully resolved HTML per locale with zero runtime i18n code. In runtime mode, a single output is emitted with locale switching and on-demand translation loading (~1KB runtime).

LocalizedString

A reference to a translated message, used in TextNode content, FormField labels, PageMetadata, and anywhere user-visible text appears. The validator checks that every message_key exists in all declared locale catalogs.

FieldTypeRequiredDescription
message_keystringyesMessage key (e.g., “hero.title”, “form.email.label”)
default_valuestringnoFallback value in the primary language
parameters[MessageParameter]noTyped parameters for interpolation
descriptionstringnoContext for translators

MessageParameter

FieldTypeRequiredDescription
namestringyesParameter name (e.g., “count”, “name”)
param_typeMessageParamTypenoStringParam (default), NumberParam, DateParam, CurrencyParam, PluralParam, SelectParam
format_optionsFormatOptionsnoFormatting configuration
{
  "message_key": "cart.item_count",
  "default_value": "{count, plural, one {# item} other {# items}} in your cart",
  "parameters": [
    { "name": "count", "param_type": "PluralParam" }
  ],
  "description": "Shopping cart item count displayed in the header"
}

MessageCatalog

A collection of translated messages for a single locale. Stored as a companion file alongside the IR, not embedded in the main binary.

FieldTypeRequiredDescription
localestringyesBCP 47 locale tag (e.g., “en-US”, “fr-FR”)
messages[Message]yesAll translated messages for this locale
fallback_localestringnoFallback locale (e.g., “en” for “en-US”)

Message

FieldTypeRequiredDescription
keystringyesMessage key matching LocalizedString.message_key
valuestringyesICU MessageFormat translated string

ICU MessageFormat supports plurals, select, and nested expressions:

  • Simple: "Hello {name}"
  • Plural: "{count, plural, one {# item} other {# items}}"
  • Select: "{gender, select, female {She} male {He} other {They}} commented"
{
  "locale": "fr-FR",
  "messages": [
    { "key": "hero.title", "value": "Bienvenue sur Voce" },
    { "key": "hero.subtitle", "value": "Representation intermediaire pour l'IA" },
    { "key": "cart.item_count", "value": "{count, plural, one {# article} other {# articles}} dans votre panier" }
  ],
  "fallback_locale": "en"
}

FormatOptions

Locale-aware formatting configuration for number, date, and currency parameters.

FieldTypeRequiredDescription
number_styleNumberStylenoDecimal (default), Currency, Percent, Unit
currency_codestringnoISO 4217 code (e.g., “USD”, “EUR”) for Currency style
date_styleDateStylenoShort, Medium (default), Long, Full, Custom
custom_date_patternstringnoICU date pattern for Custom style (e.g., “yyyy-MM-dd”)
min_fraction_digitsint32noMinimum fractional digits for numbers
max_fraction_digitsint32noMaximum fractional digits for numbers
{
  "number_style": "Currency",
  "currency_code": "EUR",
  "min_fraction_digits": 2,
  "max_fraction_digits": 2
}

I18nConfig

Application-level internationalization configuration. Added to VoceDocument.i18n.

FieldTypeRequiredDescription
default_localestringyesDefault/primary locale (e.g., “en-US”)
supported_locales[string]yesAll supported locale tags
modestringno“static” (default) or “runtime”

Compilation modes:

  • static – one output per locale. All LocalizedStrings resolved at compile time. Zero runtime cost. Best for SEO and performance.
  • runtime – single output with locale switching. Translations loaded on demand. Adds ~1KB of i18n runtime. Best for SPAs with in-app language switching.
{
  "default_locale": "en-US",
  "supported_locales": ["en-US", "fr-FR", "de-DE", "ja-JP"],
  "mode": "static"
}

Pipeline Overview

Voce IR follows a SPIR-V-inspired pipeline: a binary intermediate representation flows through validation and compilation stages before reaching the end user. No human-readable source code exists in the pipeline. The AI generates IR directly, the validator enforces correctness, and the compiler emits optimized output for each target platform.

Pipeline Stages

Natural Language
       |
       v
 +-----------+
 | AI Bridge |   LLM generates JSON IR from conversation
 +-----------+
       |
       v
 +-----------+
 | JSON IR   |   Machine-readable text (.voce.json)
 +-----------+
       |
       v
 +-----------+
 | Validator |   9 passes, 46 rules — errors block compilation
 +-----------+
       |
       v
 +-----------+
 | Compiler  |   7 targets — DOM, WebGPU, WASM, Hybrid, iOS, Android, Email
 +-----------+
       |
       v
 +-----------+
 | Deployer  |   4 adapters — Static, Cloudflare, Netlify, Vercel
 +-----------+
       |
       v
   End User

Stage 1: AI Bridge

The AI bridge (packages/ai-bridge/) is a TypeScript layer that sits between the LLM and the rest of the pipeline. It manages the conversation, applies style packs, and produces valid JSON IR. The bridge uses structured output to ensure the LLM emits well-formed IR conforming to the FlatBuffers schema.

Key responsibilities:

  • Conversation management (anti-vibe-coding: the AI asks questions, pushes back)
  • Style pack selection and token injection
  • Schema-aware JSON generation
  • Intent-IR pair matching via RAG

Stage 2: JSON IR

The JSON representation is the canonical text form of the binary IR. It is not source code – it is a machine-readable serialization used for AI generation, debugging, and version control diffing. Files use the .voce.json extension. The voce json2bin command converts JSON to the binary FlatBuffers format (.voce), and voce bin2json reverses the process.

Stage 3: Validator

The validator (packages/validator/) runs 9 ordered passes over the IR, checking 46 rules across structural integrity, reference resolution, state machines, accessibility, security, SEO, forms, internationalization, and motion safety. Validation errors block compilation entirely – there is no “build with warnings” mode for critical rules.

Passes execute in dependency order:

  1. Structural (STR) – document shape, required fields, node nesting
  2. References (REF) – all ID references resolve to existing nodes
  3. State Machine (STA) – valid transitions, initial states, no orphans
  4. Accessibility (A11Y) – keyboard equivalents, heading hierarchy, alt text
  5. Security (SEC) – CSRF on mutations, auth redirects, HTTPS enforcement
  6. SEO – title, description, h1 count, Open Graph completeness
  7. Forms (FRM) – field labels, unique names, validation rules
  8. Internationalization (I18N) – localized key presence, default values
  9. Motion (MOT) – ReducedMotion required, physics constraints, duration limits

Stage 4: Compiler

Seven compile targets live in separate crates under packages/. Each compiler reads validated IR and emits platform-specific output with zero runtime dependencies:

TargetCrateOutput
DOMcompiler-domSingle-file HTML
WebGPUcompiler-webgpuWGSL shaders + JS harness
WASMcompiler-wasmWAT/WASM modules
Hybridcompiler-hybridPer-component target analysis
iOScompiler-iosSwiftUI views
Androidcompiler-androidJetpack Compose functions
Emailcompiler-emailTable-based HTML

Stage 5: Deploy

Four deploy adapters handle the last mile, packaging compiler output for specific hosting environments:

AdapterCrateDescription
Staticadapter-staticPlain files, any static host
Cloudflareadapter-cloudflareCloudflare Workers / Pages
Netlifyadapter-netlifyNetlify Functions + deploy config
Verceladapter-vercelVercel serverless + edge config

Design Principles

  • No human-readable code in the pipeline. The IR is the source of truth, not a stepping stone to hand-editable files.
  • Accessibility is a compile error. Missing semantic information blocks the build, not just produces a warning.
  • Zero runtime dependencies. Compiled output has no npm packages, no CDN links, no framework bundles. This eliminates the supply chain attack surface.
  • Binary IR is not human-readable by design. JSON exists for AI generation and debugging, not for human authorship.

IR Format

Voce IR uses FlatBuffers as its binary wire format. FlatBuffers provide zero-copy deserialization, schema evolution, and compact binary encoding – properties borrowed from GPU shader pipelines (SPIR-V) rather than traditional web frameworks.

File Extensions

ExtensionFormatPurpose
.voceBinaryFlatBuffers binary, used at compile time
.voce.jsonJSONCanonical text representation for AI, debug

The two formats are interchangeable. The CLI provides round-trip conversion:

voce json2bin input.voce.json -o output.voce
voce bin2json input.voce -o output.voce.json

Internally, both commands delegate to flatc (the FlatBuffers compiler) with the Voce schema. The JSON form is what AI models emit; the binary form is what the validator and compilers consume.

Schema Organization

FlatBuffers schemas live in packages/schema/schemas/. Each .fbs file covers one domain:

FileDomain
types.fbsPrimitive types (RGB, Edge, Dimension, etc.)
layout.fbsViewRoot, Container, Surface, TextNode, MediaNode
state.fbsStateMachine, DataNode, ComputeNode, EffectNode, ContextNode
motion.fbsAnimationTransition, Sequence, GestureHandler, ScrollBinding, PhysicsBody
navigation.fbsRouteMap, RouteEntry, RouteTransition, RouteGuard
a11y.fbsSemanticNode, LiveRegion, FocusTrap
theming.fbsThemeNode, ColorPalette, TypographyScale, SpacingScale, PersonalizationSlot, ResponsiveRule
data.fbsActionNode, SubscriptionNode, AuthContextNode, ContentSlot, RichTextNode
forms.fbsFormNode, FormField, ValidationRule, FormSubmission
seo.fbsPageMetadata, OpenGraphData, StructuredData
i18n.fbsLocalizedString, MessageCatalog, FormatOptions, I18nConfig
voce.fbsMaster file – ChildUnion, ChildNode, VoceDocument

The master file voce.fbs includes all domain files and defines the document root. This is the single compilation target for flatc.

The ChildUnion Wrapper Pattern

FlatBuffers unions allow heterogeneous node types in a single tree. However, the Rust codegen does not support vectors of unions directly. Voce solves this with a wrapper table:

union ChildUnion {
  Container,
  Surface,
  TextNode,
  MediaNode,
  StateMachine,
  // ... 27 total node types
  FormNode
}

table ChildNode {
  value: ChildUnion;
}

Parent nodes store children as [ChildNode] – a vector of wrapper tables, each containing one union variant. This pattern adds one level of indirection but preserves type safety and allows the full 27-type union to appear anywhere in the tree.

VoceDocument Root

Every IR file has a VoceDocument at its root:

table VoceDocument {
  schema_version_major: int32 = 0;
  schema_version_minor: int32 = 1;
  root: ViewRoot (required);
  routes: RouteMap;
  theme: ThemeNode;
  alternate_themes: [ThemeNode];
  auth: AuthContextNode;
  i18n: I18nConfig;
}

The root field is the visual tree entry point (always a ViewRoot). Top-level configuration – routing, theming, authentication, and internationalization – lives alongside the root rather than nested inside it.

The binary file uses the FlatBuffers file identifier "VOCE" (4 bytes at offset 4), enabling quick format detection without parsing.

Schema Versioning

The schema_version_major and schema_version_minor fields follow semver conventions:

  • Minor bump: New optional fields, new union members. Old validators and compilers can still read the IR (FlatBuffers forward-compatibility).
  • Major bump: Removed fields, changed semantics, breaking structural changes. Requires matching validator/compiler versions.

FlatBuffers’ wire format naturally supports forward compatibility – unknown fields are silently ignored by older readers. This means minor version bumps require no coordination between the AI bridge and the compiler.

JSON Canonical Form

The JSON representation mirrors the FlatBuffers schema exactly. Field names match the schema, enums use string names, and nested tables become nested objects. This is not a separate format – it is the standard FlatBuffers JSON encoding produced by flatc --json.

A minimal valid document in JSON:

{
  "schema_version_major": 0,
  "schema_version_minor": 1,
  "root": {
    "id": "root",
    "children": []
  }
}

The JSON form exists for three reasons: AI generation (LLMs produce text, not binary), debugging (humans can inspect the IR during development), and version control (text diffs are meaningful, binary diffs are not). It is never the primary runtime format.

Compiler Architecture

Voce IR supports seven compile targets. Each compiler lives in its own Rust crate under packages/, reads validated IR, and emits platform-specific output with zero runtime dependencies. The compiler selection happens at build time – the same IR can be compiled to any supported target without modification.

Compiler Crates

CrateTargetOutput Format
compiler-domDOMSingle-file HTML + CSS + JS
compiler-webgpuWebGPUWGSL shaders + JS harness
compiler-wasmWASMWAT text format / WASM binary
compiler-hybridHybridMixed targets per component
compiler-iosiOSSwiftUI view files
compiler-androidAndroidJetpack Compose Kotlin
compiler-emailEmailTable-based HTML

DOM Compiler

The DOM compiler (packages/compiler-dom/) is the primary compile target and the most mature. It emits a single self-contained HTML file with inlined CSS and JavaScript. No framework, no bundler, no CDN dependencies.

Internal pipeline stages:

  1. Lower – Transform IR nodes into a compiler-internal representation (compiler_ir.rs) optimized for code generation
  2. Animation – Process motion nodes into CSS keyframes and JS animation code
  3. Assets – Resolve and inline media references
  4. Emit – Generate the final HTML string with embedded styles and scripts

The output follows patterns from SolidJS and Svelte compiled output: surgical DOM mutations rather than virtual DOM diffing. State changes produce direct element.textContent = value assignments, not tree reconciliation.

WebGPU Compiler

The WebGPU compiler targets GPU-accelerated rendering using the WebGPU API. It produces WGSL (WebGPU Shading Language) shader programs alongside a JavaScript harness that manages the render pipeline.

Key capabilities:

  • PBR (Physically Based Rendering) material support
  • Scene3D, MeshNode, and ShaderNode compilation
  • Particle system emission as compute shaders
  • Automatic fallback annotations for non-WebGPU browsers

WASM Compiler

The WASM compiler translates StateMachine and ComputeNode logic into WebAssembly Text Format (WAT), which can then be assembled into .wasm binaries. This target is used when state logic needs to run at near-native speed in the browser.

The compiler maps Voce state machines to WASM function tables: each state becomes a function, transitions become conditional branches, and data bindings become memory load/store operations.

Hybrid Compiler

The hybrid compiler performs per-component target analysis. Rather than compiling the entire document to one target, it examines each subtree and selects the optimal compiler:

  • Static content with no interactivity routes to DOM (minimal output)
  • Heavy animation or 3D content routes to WebGPU
  • Complex state logic routes to WASM
  • The final output stitches the pieces together with a thin coordination layer

This allows a single page to mix GPU-rendered hero sections with lightweight DOM content sections, optimizing both performance and payload size.

iOS Compiler

The iOS compiler emits SwiftUI view code. IR layout nodes map to SwiftUI’s VStack, HStack, ZStack, and LazyVGrid. Theming tokens become SwiftUI Color and Font definitions. Navigation maps to NavigationStack and NavigationLink.

Accessibility semantics translate directly – Voce’s SemanticNode maps to SwiftUI’s .accessibilityLabel, .accessibilityHint, and role modifiers.

Android Compiler

The Android compiler targets Jetpack Compose, emitting Kotlin composable functions. IR containers become Column, Row, and Box composables. Theming maps to Material 3 MaterialTheme with custom color schemes generated from the IR’s ThemeNode.

State machines compile to Compose State holders with LaunchedEffect for side effects, matching the IR’s reactive model.

Email Compiler

The email compiler produces HTML that renders correctly across email clients – a notoriously constrained environment. It uses table-based layouts (not flexbox or grid), inline styles (not CSS classes), and conservative markup that passes Litmus and Email on Acid testing.

Key constraints the compiler handles:

  • All layout via nested <table> elements
  • All styles inlined on each element
  • No JavaScript (email clients strip it)
  • Image references as absolute URLs (no inlining)
  • MSO conditional comments for Outlook compatibility

Shared Architecture

All seven compilers share common patterns:

  • Input: Validated VoceIr (the serde model, not raw FlatBuffers)
  • Output: A string or file bundle representing the compiled artifact
  • No runtime dependencies: Every compiler produces self-contained output
  • Snapshot testing: Compiler output is tested with insta snapshots to catch regressions
  • Accessibility preservation: Semantic information from the IR must appear in the compiled output – compilers cannot silently drop it

The compiler selection is exposed through the CLI:

voce compile input.voce.json --target dom -o output.html
voce compile input.voce.json --target ios -o OutputView.swift
voce compile input.voce.json --target email -o newsletter.html

Validation Passes

The Voce validator runs 9 ordered passes over the IR, enforcing 46 rules that span structural correctness, accessibility, security, and more. Validation errors block compilation – there is no way to skip or suppress critical failures. This is by design: accessibility and security are compile errors, not warnings.

The ValidationPass Trait

Every pass implements the ValidationPass trait defined in packages/validator/src/passes/mod.rs:

#![allow(unused)]
fn main() {
pub trait ValidationPass {
    fn name(&self) -> &'static str;
    fn run(&self, ir: &VoceIr, index: &NodeIndex, result: &mut ValidationResult);
}
}

The VoceIr is a serde-based IR model, separate from the FlatBuffers generated types. This decoupling is intentional – the validator works with JSON- deserialized data, not raw FlatBuffers buffers. The NodeIndex provides pre-built lookup tables (ID-to-node maps, parent chains) so passes can resolve references without redundant traversals.

Passes execute in dependency order. Structural checks run first because later passes assume the document shape is valid. Reference resolution runs second because domain passes may follow reference chains.

Error Code Taxonomy

Each rule has a unique error code with a prefix identifying its pass:

STR – Structural (5 rules)

CodeRule
STR001Document must have a root ViewRoot
STR002All nodes must have a non-empty id field
STR003Node IDs must be unique within the document
STR004Children must be valid for their parent node type
STR005Required fields must be present (per schema)

REF – References (9 rules)

CodeRule
REF001target_id references must resolve to existing nodes
REF002State machine initial_state must name a valid state
REF003Transition targets must name valid states
REF004Animation target_id must resolve
REF005Route guard redirect must name a valid route
REF006Context provider_id must resolve
REF007Subscription source_id must resolve
REF008Form field form_id must resolve to a FormNode
REF009Scroll binding source_id must resolve

STA – State Machine (4 rules)

CodeRule
STA001State machine must have at least one state
STA002Initial state must exist in the state list
STA003All transition targets must be reachable states
STA004No orphan states (every state reachable from initial)

A11Y – Accessibility (5 rules)

CodeRule
A11Y001Interactive elements must have keyboard equivalents
A11Y002Heading levels must not skip (h1 -> h3 without h2)
A11Y003Images must have alt text (or decorative: true)
A11Y004Form fields must have associated labels
A11Y005Focus traps must have an escape mechanism

SEC – Security (4 rules)

CodeRule
SEC001Mutation actions must include CSRF protection
SEC002Auth-guarded routes must specify a redirect
SEC003External URLs must use HTTPS
SEC004Password fields must have autocomplete attribute

SEO (7 rules)

CodeRule
SEO001Page must have a <title> (via PageMetadata)
SEO002Title length must be 10-60 characters
SEO003Meta description must be present
SEO004Description length must be 50-160 characters
SEO005Exactly one h1 heading per page
SEO006Open Graph data must include title, description, image
SEO007Structured data must have @type field

FRM – Forms (4 rules)

CodeRule
FRM001Form must have at least one field
FRM002Every field must have a label
FRM003Field names must be unique within their form
FRM004Email fields must have email validation

I18N – Internationalization (3 rules)

CodeRule
I18N001Localized string keys must be non-empty
I18N002Every localized string must have a default value
I18N003All locales must have consistent key sets

MOT – Motion (5 rules)

CodeRule
MOT001Animations must specify a ReducedMotion alternative
MOT002Physics bodies must have damping > 0
MOT003Animation duration should not exceed 10 seconds
MOT004Sequences must have at least one step
MOT005Gesture handlers must specify a recognized gesture

Output Formats

The validator reports diagnostics in two formats, controlled by CLI flags:

  • Colored terminal output (default) – human-readable with error codes, node paths, and descriptions
  • JSON output (--format json) – machine-readable array of diagnostics for integration with CI pipelines and editor tooling
voce validate my-page.voce.json              # colored terminal output
voce validate my-page.voce.json --format json # JSON diagnostics

Serde IR Model

The validator does not read FlatBuffers binary directly. Instead, it deserializes JSON into a parallel serde-based IR model defined in packages/validator/src/ir.rs. This model mirrors the FlatBuffers schema but uses standard Rust types (String, Vec, Option) rather than FlatBuffers accessors. The separation keeps validation logic clean and testable without requiring binary serialization in test fixtures.

Style Packs

Style packs are design presets for AI-generated output – think of them as LoRA adapters for UI. Instead of describing colors, fonts, and spacing from scratch in every conversation, you select a style pack and the AI bridge applies its design tokens to the generated IR.

What a Style Pack Contains

Each pack defines a complete visual identity:

  • Color palette – background, foreground, primary, surface, muted, and optional accent colors (as RGB values)
  • Typography – heading and body font families, sizes, weights, and line height
  • Spacing – base unit and a scale array for consistent rhythm
  • Border radii – small, medium, and large values for component rounding
  • Component patterns – example IR files showing how the pack’s tokens apply to common UI patterns (hero sections, pricing cards, forms)

The type definitions live in packages/ai-bridge/src/packs/types.ts:

export interface StylePack {
  id: string;
  name: string;
  description: string;
  tags: string[];
  tokens: DesignTokens;
  examples: PackExample[];
}

Built-in Packs

Voce ships with three built-in style packs:

minimal-saas

A clean, utilitarian design for SaaS dashboards and landing pages. High contrast, generous whitespace, and a neutral palette with a single accent color. Typography uses a system font stack for fast loading.

Tags: saas, landing, clean, dashboard

editorial

A content-first design for blogs, documentation, and long-form reading. Serif headings, generous line height, narrow content column, and muted colors that keep attention on the text.

Tags: blog, editorial, content, documentation

ecommerce

A conversion-oriented design for product pages and storefronts. Bold primary colors, tight spacing for product grids, prominent call-to-action buttons, and image-heavy layouts.

Tags: ecommerce, product, store, conversion

How the AI Bridge Uses Packs

When a user starts a conversation, the AI bridge can select a style pack based on the user’s description (or the user can request one explicitly). The bridge then:

  1. Loads the pack’s design tokens
  2. Injects token values into the IR’s ThemeNode during generation
  3. Uses the pack’s example IR files for RAG (retrieval-augmented generation) matching – if the user asks for a “pricing section,” the bridge retrieves the pack’s pricing example as context for the LLM

The pack’s tags field enables automatic matching. When a user says “build me a SaaS landing page,” the bridge scores packs by tag overlap and selects the best fit.

Pack Examples and RAG

Each pack includes PackExample entries:

export interface PackExample {
  filename: string;
  description: string;
  tags: string[];
  irJson?: string;
}

The description and tags fields are indexed for similarity search. When the AI bridge receives a user intent, it retrieves the most relevant examples from the active pack and includes them as few-shot context for the LLM. This grounds generation in concrete, validated IR rather than relying solely on schema knowledge.

Creating a Custom Pack

To add a new style pack:

  1. Create a new directory under packages/ai-bridge/src/packs/ with your pack ID as the name
  2. Define a StylePack object with your design tokens
  3. Add example IR files that demonstrate your pack’s visual language
  4. Register the pack in the loader (packages/ai-bridge/src/packs/loader.ts)

The pack’s tokens must use RGB values (0-255 per channel). The spacing scale is an array of multipliers applied to the base unit – for example, a base of 8 with scale [0.5, 1, 2, 3, 4, 6, 8] produces 4, 8, 16, 24, 32, 48, 64 pixel values.

Packs vs. Themes

Style packs and IR themes (ThemeNode) serve different roles:

  • Style packs are an AI bridge concept. They guide generation by providing design tokens and examples. They exist at authoring time.
  • Themes are an IR concept. They are embedded in the generated document and survive compilation. They exist at runtime.

The AI bridge translates pack tokens into theme nodes during generation. A single pack can produce multiple theme variants (light/dark) stored as alternate_themes in the VoceDocument.

Contributing

This guide covers development setup, code conventions, the working pattern for new features, and the process for adding a new compile target.

Development Setup

Prerequisites

  • Rust 1.85+ (edition 2024 support required)
  • FlatBuffers compiler (flatc) for schema regeneration
  • Node.js 18+ and npm (for the TypeScript AI bridge)

Clone and Build

git clone https://github.com/fireburnsup/voce-ir.git
cd voce-ir

# Build the entire workspace
cargo build --workspace

# Run all tests
cargo test --workspace

# Lint (must pass with zero warnings)
cargo clippy --workspace -- -D warnings

# Format check
cargo fmt --check

If all four commands pass, your environment is ready.

Regenerating FlatBuffers Bindings

After editing any .fbs file in packages/schema/schemas/:

# Rust bindings
flatc --rust -o packages/schema/src/generated/ packages/schema/schemas/voce.fbs

# TypeScript bindings (for AI bridge)
flatc --ts -o packages/ai-bridge/src/generated/ packages/schema/schemas/voce.fbs

Code Conventions

Rust

  • Edition 2024 (latest stable). Fall back to edition 2021 per-crate only if a dependency requires it (e.g., FlatBuffers codegen).
  • Error handling: thiserror for library error types, anyhow for CLI entry points. No unwrap() in library code – propagate errors with ?.
  • CLI arguments: clap derive API.
  • Naming: snake_case for files and functions, PascalCase for types and enums.
  • Documentation: Every public function and type has a /// doc comment.
  • Formatting: cargo fmt with default settings. Run before every commit.
  • Linting: cargo clippy -- -D warnings enforces a zero-warnings policy. CI will reject PRs with clippy warnings.

Testing

  • Unit tests live in-file under #[cfg(test)] modules.
  • Integration tests live in tests/.
  • Every new node type needs valid and invalid test IR in tests/schema/.
  • Compiler output uses insta snapshot testing to catch regressions.

Working Pattern

When implementing a new feature, follow this sequence:

  1. Schema – If new IR types are needed, add or modify .fbs files in packages/schema/schemas/. Update the ChildUnion in voce.fbs if adding a new node type.

  2. Bindings – Regenerate Rust and TypeScript bindings with flatc.

  3. Validation – Add a validation pass (or extend an existing one) in packages/validator/src/passes/. Implement the ValidationPass trait and register the pass in all_passes().

  4. Compiler – Add codegen support in the relevant compiler crate(s). At minimum, the DOM compiler (packages/compiler-dom/) should handle the new node type.

  5. Tests – Write tests for each layer: schema validity, validator acceptance/rejection, and compiler snapshot output.

  6. Verify – Run the full suite before submitting:

    cargo test --workspace && cargo clippy --workspace -- -D warnings
    
  7. Commit – Use conventional commit messages:

    feat(validator): add FRM005 rule for phone field validation
    fix(compiler-dom): correct heading level output for nested sections
    

Adding a New Compile Target

To add a new compiler (e.g., Flutter, React Native):

  1. Create a new crate: cargo new --lib packages/compiler-flutter

  2. Add it to the workspace Cargo.toml members list.

  3. Depend on the schema crate for IR types and the validator’s serde IR model for input deserialization.

  4. Implement a compile function that accepts validated VoceIr and returns the target output (string, file bundle, or byte stream).

  5. Follow the shared compiler patterns:

    • Zero runtime dependencies in output
    • Accessibility semantics must be preserved (never silently drop them)
    • Use insta for snapshot testing
  6. Register the new target in the CLI’s --target enum (packages/cli/ or the relevant binary crate).

  7. Add integration tests that compile the reference landing page IR and verify the output.

Pull Request Process

  1. Fork the repository and create a feature branch.
  2. Follow the working pattern above.
  3. Ensure cargo test --workspace and cargo clippy --workspace -- -D warnings both pass.
  4. Write a clear PR description explaining what changed and why.
  5. Link any related issues.

PRs that introduce new validation rules should include both positive tests (valid IR that passes) and negative tests (invalid IR that triggers the expected error code).

Project Structure Reference

Key directories: packages/schema/ (FlatBuffers schema + bindings), packages/validator/ (9-pass validator), packages/compiler-*/ (7 compile targets), packages/ai-bridge/ (TypeScript AI layer), packages/adapter-*/ (4 deploy adapters), tests/ (integration tests), examples/ (reference IR).