A library that converts ASCII wireframes into working HTML/UI with scene management
+---------------------------+
| "WYREFRAME" | Draw in ASCII
| [__email____________] | β
| [__password_________] | Parse to AST!
| [ Login ] |
| < Forgot password > |
+---------------------------+
- Syntax v2.3: 9 core UI elements, string literals, emoji shortcodes, and component props
- Grid-aware parsing: 2D coordinate lexer understands ASCII-art alignment, Unicode wide characters, and tabs
- Implicit layout inference: Row/Column arrangement detected from element positions β no layout keywords
- Error recovery: Collects all errors/warnings with precise source locations instead of halting at the first problem
- Tunable heuristics: Every parser tolerance is named, documented, and overridable per parse
- TypeScript/ReScript: Full type safety with both language support
npm install wyreframeimport { parse } from 'wyreframe/parser/v2';
const source = `
@scene: login
@title: Login Page
@device: mobile
+---------------------------+
| "Login" |
| [__email____________] |
| [__password_________] |
| [x] Remember me |
| [ Sign In ] |
| < Create Account > |
+---------------------------+
`;
const result = parse(source);
if (result.success) {
console.log(result.blocks); // all @scene/@component blocks
console.log(result.ast); // first block (SceneBlock)
} else {
console.error(result.errors); // [{ code, message, location, recoverable }]
}
console.warn(result.warnings); // non-fatal issues with ruleId traceabilitySee docs/syntax-v2.md for the full specification.
@scene: login # Scene declaration (slug required)
@title: Login Page # Optional page title
@device: mobile # mobile | tablet | desktop
@transition: fade # Default transition effect@component: user-card # Reusable component declaration
@props: name, avatar? # Props (`?` suffix = optional)| Syntax | Element | Description |
|---|---|---|
+---+ ... +---+ |
Container | Box drawn with +, -, | |
Plain text |
Text | Fallback; alignment inferred from position |
[ Text ] |
Button | Clickable button, id auto-slugged from text |
< Text > |
Link | Navigation link, id auto-slugged from text |
[__email__] |
Input | Text input, placeholder from inner text |
[v: Choose...] |
Select | Dropdown with placeholder |
[x] / [ ] Label |
Checkbox | Checked / unchecked (also [X], [v], [V]) |
(*) / ( ) Label |
Radio | Selected / unselected, auto-grouped |
--- / === |
Divider | Normal / bold; supports --- label --- and -#id- |
| Syntax | Element | Description |
|---|---|---|
"text" |
String literal | Inner syntax disabled; supports \", \\, \$, multiline |
:check: |
Emoji shortcode | 14 built-ins (:check:, :star:, :user:, ...), extensible |
${prop} |
Prop placeholder | Inside @component only; ${prop?} optional, ${prop:default} default value |
+--#login-form--+ | Format 1: ID in the top border
| [ Submit ] |
+---------------+
+---------------+
| #login-form | | Format 2: standalone `| #id |` line
| [ Submit ] |
+---------------+
Elements starting on the same visual row become a Row; different rows become a Column. No layout keywords needed:
+--------------------------------+
| [ Cancel ] [ Confirm ] | <- Row (same start row)
| "Terms apply" | <- Column relative to the row above
+--------------------------------+
import {
parse, // (source, options?) => ParseResult
parseWireframe, // (source) => ParseResult (default options)
version, // "2.3.0"
implementation, // "rescript-v2"
defaultOptions,
} from 'wyreframe/parser/v2';const result = parse(source, {
strict: false, // true: halt on first error, all errors fatal
tabSize: 4, // tab expansion width for visual columns
maxDepth: 10, // max container nesting depth
heuristics: { // partial override; unset fields keep defaults
containerColumnTolerance: 1, // Β± cols for wall alignment
containerWidthTolerance: 2, // Β± cols for border width match
radioHorizontalGap: 6, // max gap for same-row radio grouping
centerSymmetryThreshold: 0.15, // center-align detection
rightAlignThreshold: 0.10, // right-align detection
dividerMinRun: 3, // min dashes for a divider
},
emojiRegistry: { // per-parse shortcode overrides
rocket: 'π',
},
});interface ParseResult {
ast?: BlockNode; // first block (legacy single-block accessor)
blocks: BlockNode[]; // all @scene/@component blocks in order
errors: ParseError[]; // { code, message, location, recoverable }
warnings: ParseWarning[]; // { code, message, location, ruleId? }
success: boolean; // errors.length === 0
}All source locations use 0-based row / col (visual columns, Unicode-aware) / offset. See docs/types.md for the complete AST node types.
Note: HTML rendering, scene transitions, and the Interaction DSL currently run on the V1 parser and V1 syntax (
#idinputs,"text"links,'text'emphasis). V2 renderer integration is planned β see the migration path. V1 syntax differs from v2.3; see docs/api.md before mixing them.
import { createUI } from 'wyreframe';
const ui = `
@scene: login
+---------------------------+
| 'WYREFRAME' |
| +---------------------+ |
| | #email | |
| +---------------------+ |
| [ Login ] |
+---------------------------+
#email:
placeholder: "Enter your email"
[Login]:
@click -> goto(dashboard, slide-left)
`;
const result = createUI(ui);
if (result.success) {
document.getElementById('app').appendChild(result.root);
result.sceneManager.goto('login');
}The V1 surface (parse, render, createUI, fix, fixOnly, SceneManager, render options) is documented in docs/api.md.
Both parsers ship side by side:
| Stage | Status |
|---|---|
1. V2 parser developed in isolation (src/parser/v2/) |
β Done |
| 2. Side-by-side testing | In progress |
| 3. Runtime feature flag for parser selection | Planned |
| 4. V2 default, V1 fallback | Planned |
| 5. V1 removal | Planned |
import { parse as parseV1 } from 'wyreframe/parser'; // V1
import { parse as parseV2 } from 'wyreframe/parser/v2'; // V2 (typed)- Syntax v2.3 Reference
- API Reference
- Type Definitions
- Examples
- Developer Guide
- Testing Guide
- Live Demo
npm install
npm run res:build # ReScript build
npm run ts:build # TypeScript build
npm run build # Full build (res + ts + typecheck)
npm run dev # Dev server (http://localhost:3000/examples)
npm test # Run tests
npm run test:watch # Test watch mode
npm run test:coverage # Generate coverage reportThe V2 parser pipeline:
- Grid-aware Lexer: Tokenizes source with both 1D offsets and 2D
(row, col)visual coordinates (Unicode wide chars = 2 cols, tab-expanded); aGridIndexenables random column lookups - Block Parser: Detects
@scene:/@component:headers and parses header attributes - Element Registry: Walks 12 element parsers in priority order (String 115 β ... β Container 10 β Text 1); disambiguation lives in each parser's
canParse - Layout Inferrer: Derives Row/Column groups from element start rows, addressing children by index range
- Validator: Cross-cutting checks (duplicate IDs/props, unknown prop refs, radio group consistency)
The V1 renderer generates pure DOM elements with CSS-based scene visibility and transitions. See docs/developer-guide.md.
GPL-3.0 License - see LICENSE for details.