18 KiB
µFrame (Micronomicon)
A self-hosted web IDE and CLI for building rich terminal UIs using a declarative DSL. Compiles .uf source files into both plain ASCII art and styled Micron .mu pages for NomadNet — a decentralized communication platform running on the Reticulum mesh network.
What It Does
Write this:
page "Node Status" 60
box double "Relay Alpha-7"
align center
text "Reticulum Network Node"
gauge "CPU" 62 100 28 warn=75 crit=90
status "East Relay" online
table "Routes"
columns "Dest" 20 | "Hops" 6 | "Status" 10
row "relay-east" | "2" | "@color{0f0}{alive}"
Get this (ASCII):
╔═ Relay Alpha-7 ════════════════════════════════════════════╗
║ Reticulum Network Node ║
╚════════════════════════════════════════════════════════════╝
CPU ████████████████████░░░░░░░░ 62%
● East Relay
┌────────────────────┬──────┬──────────┐
│Dest │Hops │Status │
├────────────────────┼──────┼──────────┤
│relay-east │2 │● alive │
└────────────────────┴──────┴──────────┘
And the same content as Micron .mu with color tags, bold, links, and interactive form fields — ready to serve on NomadNet.
Quick Start
Web IDE
# Backend
cd backend
source .venv/bin/activate
PAGES_DIR=~/.nomadnetwork/storage/pages \
SOURCES_DIR=~/.micron-editor/sources \
uvicorn main:app --reload --port 8080
# Frontend (separate terminal)
cd frontend
npm run dev
Open http://localhost:5173 → click New Page → click Examples → pick a template.
CLI
cd backend
source .venv/bin/activate
# Render to terminal
python -m uframe render page.uf
# Render Micron only
python -m uframe render page.uf --micron
# Compile to .mu file
python -m uframe compile page.uf --out page.mu
# Validate without output
python -m uframe check page.uf
# Compile and deploy to NomadNet
python -m uframe deploy page.uf
Tech Stack
| Layer | Technology |
|---|---|
| Backend | Python 3.13 + FastAPI + uvicorn |
| µFrame | Pure Python (zero deps): parser → IR → CharGrid → emitters |
| Frontend | React 19 + Vite + TypeScript |
| UI | shadcn/ui + Tailwind CSS v4 |
| Editor | CodeMirror 6 (custom µFrame syntax mode) |
| Graph | React Flow (@xyflow/react) + dagre |
| State | Zustand |
| Container | Docker + Compose |
Project Structure
micronomicon/
├── backend/
│ ├── main.py # FastAPI app + static file serving
│ ├── converter.py # POST /api/compile endpoint
│ ├── pages.py # CRUD /api/pages (.uf sources + .mu publish)
│ ├── graph.py # GET /api/graph (link parser)
│ ├── docker_utils.py # POST /api/restart (NomadNet container)
│ └── uframe/ # µFrame engine (16 modules, ~3800 LOC)
│ ├── __init__.py # compile(source, width) → CompileResult
│ ├── parser.py # .uf DSL → IR tree (indentation-based)
│ ├── ir.py # 30+ IR node dataclasses
│ ├── grid.py # CharGrid — 2D char + style buffer
│ ├── chars.py # Unicode tables (box-drawing, braille)
│ ├── measure.py # Bottom-up size computation
│ ├── layout.py # Top-down position assignment
│ ├── paint.py # IR nodes → CharGrid rendering
│ ├── borders.py # Junction merging post-pass
│ ├── emit_ascii.py # CharGrid → plain text
│ ├── emit_micron.py # CharGrid → Micron with style tags
│ ├── codegen.py # Dynamic page → executable Python script
│ ├── cli.py # CLI: render / compile / check / deploy
│ ├── errors.py # ParseError, LayoutError, CompileWarning
│ └── tests/ # 42 tests (compile, dynamic, components)
├── frontend/src/
│ ├── routes/ # DashboardView, EditorView, GraphView
│ ├── components/editor/
│ │ ├── EditorPane.tsx # CodeMirror 6 host
│ │ ├── PreviewPane.tsx # ASCII / Micron / Raw / Script tabs
│ │ ├── ToolBar.tsx # Save, Publish, Examples, Backlinks
│ │ ├── uframeHighlight.ts # µFrame syntax highlighting
│ │ ├── uframeCommands.ts # "/" slash command palette
│ │ ├── micronRenderer.ts # Micron → HTML preview renderer
│ │ └── examples.ts # 9 built-in example templates
│ ├── hooks/
│ │ ├── useCompile.ts # Debounced POST /api/compile
│ │ └── useUnsavedGuard.ts # Prevent accidental navigation
│ └── stores/
│ ├── editorStore.ts # Zustand: source, compiled output, preview mode
│ └── pagesStore.ts # Zustand: page list, delete, unpublish
└── docs/
├── framework-design-v3.md # Full DSL spec + rendering model
└── dynamic-templates.md # Dynamic page addendum
API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/health | Health check |
| POST | /api/compile | Compile .uf → {ascii, micron, script, is_dynamic} |
| GET | /api/pages | List all pages with metadata |
| GET | /api/pages/{name} | Read page source |
| POST | /api/pages/{name} | Save page — { source, publish } |
| DELETE | /api/pages/{name} | Delete source and/or .mu file |
| GET | /api/graph | Page link graph (nodes + edges) |
| POST | /api/restart | Restart NomadNet Docker container |
Storage
~/.micron-editor/sources/ ← .uf source files (drafts + published)
~/.nomadnetwork/storage/pages/ ← Compiled .mu files served by NomadNet
- Save Draft: writes
.ufto sources dir only - Publish (static): compiles
.uf→.mu, writes to pages dir (chmod 644) - Publish (dynamic): compiles
.uf→ executable Python script, writes to pages dir (chmod 755)
NomadNet auto-detects the execute bit: static pages are served as-is, dynamic pages are executed and their stdout is served.
µFrame DSL Reference
Layout
page "Title" [width] # root (default width 64)
box [light|heavy|double|rounded] "Title" # bordered panel
row [gap] # horizontal layout
col [width] # column in a row
spacer [lines] # vertical whitespace
pad [t] [r] [b] [l] # inner margin
Content
heading [1|2|3] "Text" # styled heading
text "Content with @bold{inline} @color{hex}{modifiers}"
label "Key" "Value" # aligned key-value pair
list [bullet|dash]
item "Entry"
link "Display text" "/dest.mu" # clickable in Micron
divider [light|heavy|double|dash|dot] # horizontal rule
# comment # ignored in output
Data Visualization
gauge "Label" value max width [warn=N crit=N] # ████░░░░ bar with thresholds
sparkline "Label" "1,3,5,8,7,5" width # ⣀⣤⣶⣿⣷⣤ braille chart
status "Label" [online|offline|degraded] # ●○◐ colored indicators
table "Title"
columns "Name" 20 | "Hops" 6 | "Status" 10
row "relay" | "2" | "@color{0f0}{● alive}"
Forms
form "name"
field "name" [width] "placeholder" # text input
password "name" [width] "placeholder" # masked input
radio "group" "Opt A" | "Opt B" | "Opt C" # radio buttons
checkbox "name" "Label" # checkbox
button "Label" "/action/path" # submit link
Dynamic Features
Any page using source, if, for, on_submit, or state becomes dynamic: it compiles to an executable Python script instead of static Micron. NomadNet runs the script on each request and serves its stdout.
Variables
let name = "Relay Alpha" # string assignment
let threshold = 75 # numeric
let tags = "alpha","beta","gamma" # comma-separated → list
Variables are substituted with $name in text, labels, and other content. They work in both static and dynamic pages.
Data Sources
source var_name : type "command" [timeout=N]
Sources fetch data at render time and bind results to variables:
| Type | Description | Example |
|---|---|---|
shell |
Run shell command, capture stdout | source cpu : shell "cat /proc/loadavg" |
file |
Read file contents as string | source motd : file "/etc/motd" |
json |
Read + parse JSON file → dict/list | source config : json "/etc/config.json" |
python |
Evaluate Python expression | source ts : python "datetime.now().strftime('%H:%M')" |
http |
HTTP request, auto-parses JSON | source data : http "https://api.example.com/data" |
sqlite |
SQLite query → list of dicts | source users : sqlite "/path/db" "SELECT * FROM users" |
env |
Read environment variable | source key : env "API_KEY" |
param |
Read URL parameter from link | source hash : param "hash" |
rns |
Query Reticulum via rnstatus |
source peers : rns "peers" |
Shell commands have a default 5-second timeout (override with timeout=N).
Python expressions have access to: datetime (the class, so datetime.now() works), timedelta, secrets, os, json. Expressions are evaluated via eval() — single expressions only, not statements.
# Python source examples
source timestamp : python "datetime.now().strftime('%H:%M:%S')"
source rand_id : python "secrets.token_hex(4)"
source cpu_sim : python "secrets.randbelow(60) + 20"
source uptime : python "str(timedelta(seconds=12345))"
source hostname : python "os.uname().nodename"
HTTP requests return parsed JSON (dict/list) or raw string. Default timeout 10s.
# GET request — JSON auto-parsed into dict
source todo : http "https://api.example.com/todos/1"
text "Title: $todo.title"
# POST with JSON body
source result : http "https://api.example.com/search" method=POST body='{"q":"relay"}'
# Custom headers (semicolon-separated)
source data : http "https://api.example.com/data" headers='Authorization: Bearer tok123'
# Use $var references in URL, headers, and body — resolved at runtime
source token : env "API_TOKEN"
source data : http "https://api.example.com/data" headers='Authorization: Bearer $token'
Env reads server-side environment variables. Use this for secrets — tokens never appear in .uf source or compiled scripts.
source api_key : env "API_KEY"
source db_pass : env "DB_PASSWORD"
SQLite queries return a list of dicts (or a single dict for one row). Uses Python stdlib sqlite3.
# Query returns list of dicts with column names as keys
source nodes : sqlite "/data/network.db" "SELECT name, status, hops FROM nodes"
# Iterate results
for node in $nodes
label "$node.name" "$node.status ($node.hops hops)"
# Single row queries return a dict directly
source config : sqlite "/data/app.db" "SELECT value FROM config WHERE key='theme'"
text "Theme: $config.value"
Conditionals
if $cpu > 90
text "ALERT: CPU critical"
elif $cpu > 75
text "Warning: elevated"
else
text "All clear"
Conditions are Python expressions. $var references resolve to the variable's value. Supports >, <, >=, <=, ==, !=, && (and), || (or).
Loops
for peer in $peers
status "$peer.name" $peer.state
Iterates over lists (from JSON sources), dicts (wrapped as single-item list), or newline-delimited strings (from shell output). Access nested fields with $item.field.
Cache Control
cache 0 # never cache (re-execute every request)
cache 300 # cache for 5 minutes
Emits the #!c=N header that NomadNet uses to control page caching.
Form Submission Handling
on_submit "form_name"
# Runs when the named form is submitted
# Form field values are available as $field_name
source results : shell "search.py '$query'"
text "Found: $results"
Field values are read from FIELD_* environment variables set by NomadNet.
Persistent State
state "counter" "/tmp/counter.json" # load JSON into $counter
Loads a JSON file into a variable. Use _save_state(path, data) in the generated script to persist changes.
Using Variables in Content
text "Hello, $name" # inline substitution
label "CPU" "$cpu_pct%" # in labels
gauge "CPU" $cpu_pct 100 28 warn=75 crit=90 # as gauge values
status "$peer" $state # in status indicators
link "View $name" "/page/detail.mu" # in links
Generated Script Runtime
The compiled script includes these helpers, available in on_submit and source blocks:
| Helper | Description |
|---|---|
_shell(cmd, timeout=5) |
Execute shell command, return stdout |
_read_file(path) |
Read file contents |
_read_json(path) |
Read + parse JSON file |
_http(url, method, body, headers) |
HTTP request, auto-parse JSON response |
_sqlite(db_path, query) |
SQLite query → list of dicts (or single dict) |
_get_field(name, default) |
Read submitted form field |
_get_param(name, default) |
Read URL parameter |
_load_state(path) |
Load state from JSON file |
_save_state(path, data) |
Save state to JSON file |
_iter(val) |
Make a value iterable (list/dict/string) |
Components
# Define a reusable component
component stat(label, value, max)
gauge "$label" $value $max 20
# Use it
stat "CPU" 62 100
stat "MEM" 84 100
# Import standard library
use std/dashboard
banner "My Node" "Mesh Network"
resources 62 84
Standard Libraries
| Library | Components |
|---|---|
std/dashboard |
banner(title, subtitle), resources(cpu, mem), peer_status(name, state) |
std/status-bar |
status_bar(label, value, max), status_item(name, state) |
std/nav |
nav_link(label, dest), nav_divider() |
Rendering Pipeline
.uf source
│
▼
Parse ──→ IR Tree (30+ node types)
│
▼
Measure (bottom-up: compute sizes)
│
▼
Layout (top-down: assign positions)
│
▼
Paint (depth-first: write chars into CharGrid)
│
▼
Merge Borders (fix junction characters)
│
├──→ ASCII emitter → plain text
├──→ Micron emitter → styled .mu (with colors, links, form tags)
└──→ Codegen (if dynamic) → executable Python script
Web IDE Features
- Split-pane editor: µFrame DSL source (left) / live preview (right)
- Syntax highlighting: keywords, strings, variables, comments in distinct colors
/command palette: type/to insert layout, content, data viz, form, and style primitives- 4 preview tabs: ASCII | Micron (rendered) | Raw (Micron source) | Script (dynamic pages only)
⚡ dynamicbadge: auto-detected when source containssource,if,for, etc.- Examples dropdown: 9 built-in templates (Hello World → Full Node Page → Dynamic Dashboard)
- Pages dashboard: table view with Published/Draft/Orphan status badges
- Page graph: React Flow visualization of inter-page links
- Keyboard shortcuts:
Ctrl+Ssave draft,Ctrl+Ppublish - Unsaved changes guard: warns before navigating away
- Backlink indicator: shows which pages link to the current page
Running Tests
cd backend
source .venv/bin/activate
python -m pytest uframe/tests/ -v
42 tests covering:
- Static compilation (boxes, headings, text, gauges, tables, links, lists, spacers, dividers)
- Form elements (field, radio, checkbox, button)
- Dynamic pages (source, if/for, let, state, on_submit, codegen)
- Components (inline definitions, standard library, parameter substitution)
Conventions
- µFrame engine is pure Python stdlib — zero external dependencies
- Backend is FastAPI; no ORM, flat file storage
- Frontend uses shadcn/ui components in
frontend/src/components/ui/ - Feature components grouped by domain:
dashboard/,editor/,shared/ - State management via Zustand stores
- All
.ufsources stored inSOURCES_DIR, compiled.muinPAGES_DIR
References
- µFrame design spec:
docs/framework-design-v3.md - Dynamic templates spec:
docs/dynamic-templates.md - NomadNet: https://github.com/markqvist/NomadNet
- Micron syntax: https://github.com/fr33n0w/micron-composer
- Reticulum: https://github.com/markqvist/Reticulum