# µ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