449 lines
18 KiB
Markdown
449 lines
18 KiB
Markdown
# µ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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
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 `.uf` to 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)
|
|
- **`⚡ dynamic` badge**: auto-detected when source contains `source`, `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+S` save draft, `Ctrl+P` publish
|
|
- **Unsaved changes guard**: warns before navigating away
|
|
- **Backlink indicator**: shows which pages link to the current page
|
|
|
|
## Running Tests
|
|
|
|
```bash
|
|
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 `.uf` sources stored in `SOURCES_DIR`, compiled `.mu` in `PAGES_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
|