Version: 0.4.3 Language: TypeScript/ReScript Last Updated: 2025-12-27
Note: This document uses the TypeScript-friendly API with
result.successpattern. For ReScript, use the pattern matching syntax shown in API Documentation.
- Basic Examples
- Element Types
- Scene Management
- Interactions
- Error Handling
- Advanced Patterns
- Real-World Examples
The most basic wireframe with a single box.
import { parse } from 'wyreframe';
const wireframe = `
+--------+
| Box |
+--------+
`;
const result = parse(wireframe);
if (result.success) {
console.log('Scenes:', result.ast.scenes.length); // 1
console.log('Elements:', result.ast.scenes[0].elements); // Box with text
}Output AST:
{
"scenes": [
{
"id": "default",
"title": "",
"transition": "none",
"elements": [
{
"TAG": "Box",
"name": null,
"bounds": { "top": 0, "left": 0, "bottom": 2, "right": 9 },
"children": [
{
"TAG": "Text",
"content": "Box",
"emphasis": false,
"position": { "row": 1, "col": 2 },
"align": "Center"
}
]
}
]
}
]
}Box with a name in the top border.
const wireframe = `
+--Login--+
| |
+----------+
`;
const result = parse(wireframe);
if (result.success) {
const box = result.ast.scenes[0].elements[0];
if (box.TAG === 'Box') {
console.log('Box name:', box.name); // "Login"
}
}Boxes containing other boxes.
const wireframe = `
+--Outer-----------+
| |
| +--Inner--+ |
| | | |
| +---------+ |
| |
+------------------+
`;
const result = parse(wireframe);
if (result.success) {
const outer = result.ast.scenes[0].elements[0];
if (outer.TAG === 'Box') {
console.log('Outer box name:', outer.name); // "Outer"
console.log('Children:', outer.children.length); // 1
const inner = outer.children[0];
if (inner.TAG === 'Box') {
console.log('Inner box name:', inner.name); // "Inner"
}
}
}Buttons are defined with square brackets.
const wireframe = `
+---------------------------+
| [ Submit ] |
| [ Cancel ] |
+---------------------------+
`;
const result = parse(wireframe);
if (result.success) {
const box = result.ast.scenes[0].elements[0];
if (box.TAG === 'Box') {
box.children.forEach(element => {
if (element.TAG === 'Button') {
console.log(`Button: ${element.text} (${element.align})`);
// Button: Submit (Center)
// Button: Cancel (Center)
}
});
}
}Alignment Examples:
const wireframe = `
+---------------------------+
| [ Left ] | <- Left aligned
| [ Center ] | <- Center aligned
| [ Right ] | <- Right aligned
+---------------------------+
`;Input fields are defined with hash prefix.
const wireframe = `
+---------------------------+
| #username |
| #password |
| #email |
+---------------------------+
`;
const result = parse(wireframe);
if (result.success) {
const box = result.ast.scenes[0].elements[0];
if (box.TAG === 'Box') {
box.children.forEach(element => {
if (element.TAG === 'Input') {
console.log(`Input: ${element.id}`);
// Input: username
// Input: password
// Input: email
}
});
}
}Links are defined with double quotes.
const wireframe = `
+---------------------------+
| "Forgot Password" |
| "Create Account" |
| "Help Center" |
+---------------------------+
`;
const result = parse(wireframe);
if (result.success) {
const box = result.ast.scenes[0].elements[0];
if (box.TAG === 'Box') {
box.children.forEach(element => {
if (element.TAG === 'Link') {
console.log(`Link: ${element.text} -> ${element.id}`);
// Link: Forgot Password -> forgot-password
// Link: Create Account -> create-account
// Link: Help Center -> help-center
}
});
}
}Checkboxes with checked/unchecked state.
const wireframe = `
+---------------------------+
| [x] Remember me |
| [ ] Subscribe to updates |
| [x] Agree to terms |
+---------------------------+
`;
const result = parse(wireframe);
if (result.success) {
const box = result.ast.scenes[0].elements[0];
if (box.TAG === 'Box') {
box.children.forEach(element => {
if (element.TAG === 'Checkbox') {
const status = element.checked ? 'checked' : 'unchecked';
console.log(`Checkbox: ${element.label} (${status})`);
// Checkbox: Remember me (checked)
// Checkbox: Subscribe to updates (unchecked)
// Checkbox: Agree to terms (checked)
}
});
}
}Plain and emphasized text.
const wireframe = `
+---------------------------+
| * Welcome to Wyreframe |
| Please sign in below |
| * Important Note |
+---------------------------+
`;
const result = parse(wireframe);
if (result.success) {
const box = result.ast.scenes[0].elements[0];
if (box.TAG === 'Box') {
box.children.forEach(element => {
if (element.TAG === 'Text') {
const style = element.emphasis ? 'emphasized' : 'normal';
console.log(`Text: "${element.content}" (${style})`);
// Text: "Welcome to Wyreframe" (emphasized)
// Text: "Please sign in below" (normal)
// Text: "Important Note" (emphasized)
}
});
}
}Horizontal divider lines separate sections.
const wireframe = `
+---------------------------+
| Header Section |
|===========================|
| Body Section |
|===========================|
| Footer Section |
+---------------------------+
`;
const result = parse(wireframe);
if (result.success) {
const box = result.ast.scenes[0].elements[0];
if (box.TAG === 'Box') {
// Dividers create Section elements
box.children.forEach(element => {
if (element.TAG === 'Section') {
console.log(`Section: ${element.name}`);
console.log(` Children: ${element.children.length}`);
}
});
}
}Define a scene with metadata.
const wireframe = `
@scene: login
@title: Login Screen
@transition: fade
+---------------------------+
| * Login |
| |
| #email |
| #password |
| |
| [ Sign In ] |
+---------------------------+
`;
const result = parse(wireframe);
if (result.success) {
const scene = result.ast.scenes[0];
console.log('Scene ID:', scene.id); // "login"
console.log('Scene Title:', scene.title); // "Login Screen"
console.log('Transition:', scene.transition); // "fade"
}Define target device for responsive rendering.
const wireframe = `
@scene: mobile-login
@title: Mobile Login
@device: mobile
+---------------------------+
| * Login |
| |
| #email |
| #password |
| |
| [ Sign In ] |
+---------------------------+
`;
const result = parse(wireframe);
if (result.success) {
const scene = result.ast.scenes[0];
console.log('Device:', scene.device); // "Mobile"
}Supported Device Types:
// Preset devices
@device: desktop // 1440x900
@device: laptop // 1280x800
@device: tablet // 768x1024
@device: tablet-landscape // 1024x768
@device: mobile // 375x812
@device: mobile-landscape // 812x375
// Custom dimensions
@device: 1920x1080 // Custom sizeSeparate scenes with ---.
const wireframe = `
@scene: login
@title: Login
+---------------------------+
| [ Login ] |
+---------------------------+
---
@scene: dashboard
@title: Dashboard
@transition: slide-left
+---------------------------+
| * Dashboard |
| |
| Welcome back! |
+---------------------------+
`;
const result = parse(wireframe);
if (result.success) {
const ast = result.ast;
console.log('Number of scenes:', ast.scenes.length); // 2
ast.scenes.forEach(scene => {
console.log(`Scene: ${scene.id} - ${scene.title}`);
// Scene: login - Login
// Scene: dashboard - Dashboard
});
}const wireframe = `
@scene: login
+---------------------------+
| [ Login ] |
+---------------------------+
`;
const interactions = `
@scene: login
[ Login ]:
variant: primary
@click -> goto(dashboard, slide-left)
`;
const result = parse(wireframe, interactions);
if (result.success) {
const ast = result.ast;
const scene = ast.scenes[0];
const button = scene.elements[0];
if (button.TAG === 'Button') {
console.log('Button ID:', button.id);
// Properties and actions are merged into the element
}
}const wireframe = `
@scene: registration
+---------------------------+
| #email |
| #password |
| [ Register ] |
+---------------------------+
`;
const interactions = `
@scene: registration
#email:
placeholder: "Enter your email"
@change -> validate(email)
#password:
placeholder: "Enter password"
type: password
@change -> validate(password)
[ Register ]:
variant: primary
@click -> submitForm(email, password)
`;
const result = parse(wireframe, interactions);
if (result.success) {
console.log('Wireframe with interactions parsed successfully');
// Elements now have merged properties and actions
}const interactions = `
@scene: checkout
[ Proceed ]:
variant: primary
@click -> goto(payment) if validated
@click -> showError() if !validated
[ Cancel ]:
variant: secondary
@click -> back()
`;import { parse, formatError } from 'wyreframe';
const wireframe = `
+--Unclosed--+
| |
+----------
`; // Missing closing corner
const result = parse(wireframe);
if (!result.success) {
const errors = result.errors;
console.log(`Found ${errors.length} error(s):`);
errors.forEach((error, index) => {
console.log(`\nError ${index + 1}:`);
console.log(formatError(error));
});
}Output:
Found 1 error(s):
Error 1:
❌ Box is not closed
Box opened at row 1, column 0 but never closed on the bottom side.
1 │ +--Unclosed--+
→ 2 │ | |
│ ^
3 │ +----------
💡 Solution: Add the closing corner '+' at the end of the bottom border to match the width of the top border.
const result = parse(wireframe);
if (!result.success) {
const errors = result.errors;
const criticalErrors = errors.filter(e => e.severity === 'Error');
const warnings = errors.filter(e => e.severity === 'Warning');
if (criticalErrors.length > 0) {
console.error('Critical errors that prevent parsing:');
criticalErrors.forEach(error => console.error(formatError(error)));
}
if (warnings.length > 0) {
console.warn('Warnings (parsing can continue):');
warnings.forEach(warning => console.warn(formatError(warning)));
}
// Decide whether to proceed based on error severity
if (criticalErrors.length === 0) {
console.log('Proceeding with warnings...');
// Can use partial AST
}
}function isValidWireframe(wireframe: string): boolean {
const result = parse(wireframe);
if (!result.success) {
const criticalErrors = result.ast.filter(e => e.severity === 'Error');
return criticalErrors.length === 0;
}
return true;
}
// Usage
if (isValidWireframe(userInput)) {
console.log('Wireframe is valid');
} else {
console.log('Wireframe has errors');
}function traverseElements(
elements: Element[],
callback: (element: Element, depth: number) => void,
depth: number = 0
): void {
elements.forEach(element => {
callback(element, depth);
if (element.TAG === 'Box') {
traverseElements(element.children, callback, depth + 1);
} else if (element.TAG === 'Section') {
traverseElements(element.children, callback, depth + 1);
} else if (element.TAG === 'Row') {
traverseElements(element.children, callback, depth + 1);
}
});
}
// Usage
const result = parse(wireframe);
if (result.success) {
const ast = result.ast;
ast.scenes.forEach(scene => {
console.log(`\nScene: ${scene.id}`);
traverseElements(scene.elements, (element, depth) => {
const indent = ' '.repeat(depth);
console.log(`${indent}- ${element.TAG}`);
});
});
}function findElementsByType<T extends Element['TAG']>(
ast: AST,
type: T
): Extract<Element, { TAG: T }>[] {
const results: Extract<Element, { TAG: T }>[] = [];
function search(elements: Element[]): void {
elements.forEach(element => {
if (element.TAG === type) {
results.push(element as Extract<Element, { TAG: T }>);
}
if (element.TAG === 'Box') {
search(element.children);
} else if (element.TAG === 'Section') {
search(element.children);
} else if (element.TAG === 'Row') {
search(element.children);
}
});
}
ast.scenes.forEach(scene => search(scene.elements));
return results;
}
// Usage
const result = parse(wireframe);
if (result.success) {
const allButtons = findElementsByType(result.ast, 'Button');
const allInputs = findElementsByType(result.ast, 'Input');
console.log(`Found ${allButtons.length} buttons`);
console.log(`Found ${allInputs.length} inputs`);
}function elementToHTML(element: Element): string {
switch (element.TAG) {
case 'Button':
return `<button id="${element.id}" class="align-${element.align.toLowerCase()}">${element.text}</button>`;
case 'Input':
return `<input id="${element.id}" placeholder="${element.placeholder || ''}" />`;
case 'Link':
return `<a id="${element.id}" class="align-${element.align.toLowerCase()}">${element.text}</a>`;
case 'Checkbox':
const checked = element.checked ? 'checked' : '';
return `<label><input type="checkbox" ${checked} /> ${element.label}</label>`;
case 'Text':
const tag = element.emphasis ? 'strong' : 'span';
return `<${tag} class="align-${element.align.toLowerCase()}">${element.content}</${tag}>`;
case 'Divider':
return '<hr />';
case 'Box':
const childHTML = element.children.map(elementToHTML).join('\n');
const className = element.name ? `box-${element.name.toLowerCase()}` : 'box';
return `<div class="${className}">\n${childHTML}\n</div>`;
case 'Section':
const sectionHTML = element.children.map(elementToHTML).join('\n');
return `<section class="${element.name}">\n${sectionHTML}\n</section>`;
case 'Row':
const rowHTML = element.children.map(elementToHTML).join('\n');
return `<div class="row align-${element.align.toLowerCase()}">\n${rowHTML}\n</div>`;
default:
return '';
}
}
function astToHTML(ast: AST): string {
return ast.scenes.map(scene => {
const elementsHTML = scene.elements.map(elementToHTML).join('\n');
return `
<div id="${scene.id}" class="scene" data-transition="${scene.transition}">
<h1>${scene.title}</h1>
${elementsHTML}
</div>
`;
}).join('\n');
}
// Usage
const result = parse(wireframe);
if (result.success) {
const html = astToHTML(result.ast);
console.log(html);
}Complete login form with validation and navigation.
const loginWireframe = `
@scene: login
@title: Login to Your Account
@transition: fade
+---------------------------+
| * Login to Your Account |
| |
| Email |
| #email |
| |
| Password |
| #password |
| |
| [x] Remember me |
| |
| [ Sign In ] |
| |
| "Forgot password?" |
| "Create an account" |
+---------------------------+
`;
const loginInteractions = `
@scene: login
#email:
placeholder: "Enter your email"
type: email
@change -> validate(email)
#password:
placeholder: "Enter password"
type: password
@change -> validate(password)
[ Sign In ]:
variant: primary
@click -> validate(email, password)
@click -> goto(dashboard, slide-left) if validated
"Forgot password?":
@click -> goto(forgot-password, fade)
"Create an account":
@click -> goto(register, slide-up)
`;
const result = parse(loginWireframe, loginInteractions);
if (result.success) {
console.log('Login form parsed successfully');
const html = astToHTML(result.ast);
// Render to DOM or save to file
}Multi-section dashboard layout.
const dashboardWireframe = `
@scene: dashboard
@title: Dashboard
@transition: slide-left
+---------------------------+
| * Dashboard |
|===========================|
| Statistics |
| |
| +--Stats--+ +--Stats--+|
| | 1,234 | | 5,678 ||
| | Users | | Sales ||
| +---------+ +---------+|
|===========================|
| Recent Activity |
| |
| User logged in |
| Order #123 shipped |
| New message received |
|===========================|
| [ View Reports ] |
| [ Settings ] |
+---------------------------+
`;
const result = parse(dashboardWireframe);
if (result.success) {
const scene = result.ast.scenes[0];
// Find all sections
traverseElements(scene.elements, (element, depth) => {
if (element.TAG === 'Section') {
console.log(`Section: ${element.name}`);
console.log(` Children: ${element.children.length}`);
}
});
}Wizard-style multi-step form.
const wizardWireframe = `
@scene: step1
@title: Step 1: Personal Info
+---------------------------+
| * Step 1 of 3 |
|===========================|
| Personal Information |
| |
| #firstName |
| #lastName |
| #birthdate |
| |
| [ Next ] |
+---------------------------+
---
@scene: step2
@title: Step 2: Contact Info
@transition: slide-left
+---------------------------+
| * Step 2 of 3 |
|===========================|
| Contact Information |
| |
| #email |
| #phone |
| |
| [ Back ] [ Next ] |
+---------------------------+
---
@scene: step3
@title: Step 3: Confirmation
@transition: slide-left
+---------------------------+
| * Step 3 of 3 |
|===========================|
| Review Your Information |
| |
| [ Back ] [ Submit ] |
+---------------------------+
`;
const wizardInteractions = `
@scene: step1
[ Next ]:
variant: primary
@click -> goto(step2, slide-left)
---
@scene: step2
[ Back ]:
variant: secondary
@click -> back()
[ Next ]:
variant: primary
@click -> goto(step3, slide-left)
---
@scene: step3
[ Back ]:
variant: secondary
@click -> back()
[ Submit ]:
variant: primary
@click -> submitForm()
@click -> goto(success, fade)
`;
const result = parse(wizardWireframe, wizardInteractions);
if (result.success) {
console.log(`Wizard has ${result.ast.scenes.length} steps`);
result.ast.scenes.forEach((scene, index) => {
console.log(`Step ${index + 1}: ${scene.title}`);
});
}Complex settings page with multiple sections and controls.
const settingsWireframe = `
@scene: settings
@title: Settings
+---------------------------+
| * Settings |
|===========================|
| Account |
| |
| Email |
| #account-email |
| |
| Password |
| #account-password |
| |
| [ Update Account ] |
|===========================|
| Preferences |
| |
| [x] Email notifications |
| [ ] SMS notifications |
| [x] Dark mode |
|===========================|
| Privacy |
| |
| [ ] Public profile |
| [x] Show online status |
|===========================|
| [ Cancel ] [ Save ] |
+---------------------------+
`;
const result = parse(settingsWireframe);
if (result.success) {
const scene = result.ast.scenes[0];
// Count different element types
const buttons = findElementsByType(result.ast, 'Button');
const inputs = findElementsByType(result.ast, 'Input');
const checkboxes = findElementsByType(result.ast, 'Checkbox');
console.log('Settings page elements:');
console.log(` ${buttons.length} buttons`);
console.log(` ${inputs.length} inputs`);
console.log(` ${checkboxes.length} checkboxes`);
}import { describe, it, expect } from '@jest/globals';
import { parse } from 'wyreframe';
describe('Wyreframe Parser', () => {
it('should parse a simple button', () => {
const wireframe = `
+------------------+
| [ Click Me ] |
+------------------+
`;
const result = parse(wireframe);
expect(result.success).toBe(true);
if (result.success) {
const box = result.ast.scenes[0].elements[0];
expect(box.TAG).toBe('Box');
if (box.TAG === 'Box') {
const button = box.children[0];
expect(button.TAG).toBe('Button');
if (button.TAG === 'Button') {
expect(button.text).toBe('Click Me');
expect(button.id).toBe('click-me');
expect(button.align).toBe('Center');
}
}
}
});
it('should detect unclosed boxes', () => {
const wireframe = `
+------------------+
| |
+------------------
`;
const result = parse(wireframe);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.errors.length).toBeGreaterThan(0);
// Error has message property with error details
}
});
it('should parse nested boxes', () => {
const wireframe = `
+--Outer-----------+
| +--Inner--+ |
| | | |
| +---------+ |
+------------------+
`;
const result = parse(wireframe);
expect(result.success).toBe(true);
if (result.success) {
const outer = result.ast.scenes[0].elements[0];
expect(outer.TAG).toBe('Box');
if (outer.TAG === 'Box') {
expect(outer.name).toBe('Outer');
expect(outer.children.length).toBe(1);
const inner = outer.children[0];
expect(inner.TAG).toBe('Box');
if (inner.TAG === 'Box') {
expect(inner.name).toBe('Inner');
}
}
}
});
});function benchmark(wireframe: string, iterations: number = 100): void {
const times: number[] = [];
for (let i = 0; i < iterations; i++) {
const start = performance.now();
parse(wireframe);
const end = performance.now();
times.push(end - start);
}
const avg = times.reduce((a, b) => a + b, 0) / times.length;
const min = Math.min(...times);
const max = Math.max(...times);
console.log(`Benchmark Results (${iterations} iterations):`);
console.log(` Average: ${avg.toFixed(2)}ms`);
console.log(` Min: ${min.toFixed(2)}ms`);
console.log(` Max: ${max.toFixed(2)}ms`);
}
// Usage
const largeWireframe = generateLargeWireframe(500); // 500 lines
benchmark(largeWireframe, 50);- API Documentation - Complete API reference
- Type Reference - All type definitions
- Developer Guide - Extending the parser
Version: 0.4.3 Last Updated: 2025-12-27 License: GPL-3.0