How the Astryx CLI `layout expand` Command Works and What Grammar It Uses

The astryx layout expand command validates a layout expression written in Astryx's compact (XLE) or outline (XLO) syntax, expands it to a fully-typed TSX component, and optionally writes the result to a file.

In the facebook/astryx repository, the layout expand CLI command bridges high-level layout declarations with production-ready React components. Understanding this pipeline—including its dual-surface grammar and block-module system—helps developers generate maintainable UI scaffolding from concise expressions.

CLI Architecture and Execution Flow

The command is implemented as a seven-stage pipeline. Each stage is traceable to specific source files in packages/cli/.

Stage 1: Command Registration

registerLayout() in packages/cli/clients/cli/commands/layout.mjs defines the layout expand subcommand. It accepts flags including --file, --form, --name, and --loose. This file also handles the global --json flag for machine-readable output.

Stage 2: Expression Input

readExpression() reads the layout string from three possible sources, with a 5 MiB size limit:

  • Positional argument
  • --file path
  • Stdin (via -)

Source: packages/cli/clients/cli/commands/layout.mjs (lines 48-95)

Stage 3: Analysis and Validation

layoutExpand() calls analyze() from packages/cli/api/layout/_adapter.mjs. This parses the expression, binds component references via validate.mjs, and collects errors and warnings before expansion proceeds.

Source: packages/cli/api/layout/expand/expand.mjs (lines 98-102)

Stage 4: Block Module Resolution

All {block} hints in the AST are collected via collectHintNames(). Blocks operate in two modes:

  • Import-mode: References application components
  • Splice-mode: Reads template blocks, strips asset references, and inlines the result

Source: packages/cli/api/layout/expand/expand.mjs (lines 31-84)

Stage 5: AST to TSX Expansion

expand(doc, registry, options) in packages/cli/foundation/xle/expand.mjs performs the core transformation:

  • Walks the validated AST
  • Generates JSX tags
  • Creates useState scaffolding
  • Resolves slot props
  • Emits complete TSX plus metadata (components used, state count, TODOs)

Stage 6: Optional File Write

When a target path is provided, assertWithin() guarantees the write stays within the current working directory. The file is written via fs.writeFileSync.

Source: packages/cli/api/layout/expand/expand.mjs (lines 21-38)

Stage 7: Result Return

The command returns a typed envelope {type:'layout.expand', data:{…}}. Human mode prints formatted results; --json mode emits raw JSON.

Source: packages/cli/clients/cli/commands/layout.mjs (lines 122-150)

The XLE/XLO Grammar System

Astryx supports two surface syntaxes that compile to the same internal AST. The grammar implementation lives in packages/cli/foundation/xle/parse.mjs.

Compact Syntax (XLE)

XLE is one-line, whitespace-insensitive, and Emmet-inspired. Tokens separate at depth-0 whitespace; groups use (); repeats use *N.


V[g6] > C{card-callout}*4

This expands to: a VStack with gap=6 containing four Card components, each with the card-callout block hint.

Outline Syntax (XLO)

XLO is multi-line and indentation-based. It supports explicit constructs for chains (>), slots (@slot:), repeats (repeat N:), and an overlays: section for open-state wiring.


Layout > LayoutHeader pad=0
  @title "Dashboard"
LayoutContent
  Card

Core Grammar Tokens

The parser recognizes these token types in order of precedence:

Token Syntax Purpose
ID #identifier Element ID
Modifier .modifier Enum/class modifier
Payload "text" or 'text' Content payload
Attributes [key=value ...] Attribute block
Hint {block-name} Block/module reference
Repeat *N Repeat count
Selected ! Selected state flag

Attribute Token Grammar

Attribute tokens support:

  • key=value pairs
  • Flags: +flag
  • Slots: @slot=value
  • Shorthands: p6 (padding), g{min:2} (gap with constraints)
  • Escaped counters: $

Lexical Rules

  • Identifiers start with a letter; subsequent characters may be letters, digits, -, or _
  • Whitespace is ignored outside quoted strings
  • Brackets ()[]{}<> increase parsing depth; parsing terminates at depth-0 whitespace

Parser Dispatch

detectForm() determines whether input is compact or outline. The appropriate parser—parseCompact() or parseOutline()—builds the XLE AST using these node types:

  • XLENode: Single element
  • XLEGroup: Parenthesized group
  • XLEItem: Repeatable unit

Both surfaces share the same attribute-token grammar, so validate.mjs and expand.mjs operate independently of the original dialect.

Practical Usage Examples

Basic Compact Expression

astryx layout expand "V[g6] > C{card-callout}*4"

Human-mode output shows warnings, the written path, and component list.

File-Based Outline with Custom Name

astryx layout expand --file my-layout.txt --name DashboardLayout ./src/DashboardLayout.tsx

Contents of my-layout.txt:


Layout > LayoutHeader pad=0
  @title "Dashboard"
LayoutContent
  Card

JSON Output for CI Integration

astryx --json layout expand "V[g6] > C{card-callout}*4"
{
  "type": "layout.expand",
  "data": {
    "form": "compact",
    "code": "// Generated by `astryx layout expand` …",
    "componentsUsed": ["VStack", "Card"],
    "states": 2,
    "todos": [],
    "warnings": [],
    "written": null
  }
}

Grammar Verification with check

astryx layout check "V[g6] > C{card-callout}*4"

Output:


✓ Valid (parsed as compact)

compact:
  V[g6] > C{card-callout}*4

outline:
  VStack gap=6
    Card {card-callout}

Source File Reference

File Purpose
packages/cli/clients/cli/commands/layout.mjs CLI registration and I/O handling
packages/cli/api/layout/expand/expand.mjs Core expansion orchestration
packages/cli/foundation/xle/parse.mjs Dual-syntax grammar parser
packages/cli/foundation/xle/expand.mjs AST-to-TSX code generator
packages/cli/foundation/xle/validate.mjs Component binding and attribute validation
packages/cli/api/layout/_adapter.mjs Shared analysis layer

Summary

  • astryx layout expand is a seven-stage pipeline from raw expression to TSX output
  • XLE (compact) and XLO (outline) are surface syntaxes with identical semantics
  • The grammar supports IDs, modifiers, payloads, attributes, block hints, repeats, and selection flags
  • Block modules resolve as either imports or inline splices depending on hint context
  • The expand() function in foundation/xle/expand.mjs generates useState scaffolding and JSX

Frequently Asked Questions

What is the difference between XLE and XLO syntax?

XLE is compact and line-oriented, ideal for quick CLI usage and embedding in scripts. XLO is verbose and indentation-based, better for version control and complex layouts with overlays. Both parse to the same AST, so validation and expansion behavior is identical.

How does the layout expand command handle block references?

Blocks in {braces} are collected via collectHintNames() and resolved as either import-mode (application components) or splice-mode (template blocks read and inlined without assets). This happens in packages/cli/api/layout/expand/expand.mjs before TSX generation.

Can I use layout expand in CI/CD pipelines?

Yes. Pass --json for machine-parseable output. The command exits with non-zero status on validation errors, and the JSON envelope includes warnings, todos, and componentsUsed for automated checks.

Where is the grammar formally defined?

The grammar implementation is in packages/cli/foundation/xle/parse.mjs. It uses recursive descent with depth tracking for bracket matching. No separate grammar file exists; the parser is hand-written with explicit token precedence for component names, #id, .mod, payloads, attributes, hints, repeats, and flags.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →