15 KiB
uFrame Integration Plan for Micronomicon
Executive Summary
Replace the existing raw-Micron editor with a uFrame IDE. uFrame is a declarative DSL that compiles .uf files into ASCII art and Micron .mu files. The user edits .uf source; the backend compiles it; the frontend shows dual ASCII + Micron previews. On publish, the backend compiles .uf to .mu and writes the result to PAGES_DIR.
Current Architecture (as-is)
Backend (backend/): 5 Python files, no package structure.
main.py— FastAPI app, mounts routers at/api, serves static frontendpages.py— CRUD for.mufiles across SOURCES_DIR and PAGES_DIRconverter.py— POST/api/convertwrapping md2txt (markdown -> micron)graph.py— GET/api/graphparsing Micron[label+slug]linksdocker_utils.py— POST/api/restartfor NomadNet container
Frontend (frontend/src/):
EditorView.tsx— main route, split pane:EditorPane(CodeMirror) +PreviewPane(Micron HTML)editorStore.ts— state:micronSource,isDirty,currentPage,previewModepagesStore.ts— page list, fetch/delete/unpublishmicronHighlight.ts— CodeMirror StreamLanguage for Micron syntaxmicronRenderer.ts— Micron markup to HTML for previewslashCommands.ts—/H1,/Bold, etc. autocomplete for Micron codeswikiLinkCompletion.ts—[[autocomplete inserting Micron links
Storage model: Sources stored as .mu in SOURCES_DIR, published copies in PAGES_DIR. Both are Micron markup. No intermediate format.
Key invariant: NomadNet reads .mu files from PAGES_DIR. This must be preserved — uFrame compiles to .mu for publishing.
Phase 1 — uFrame Core Engine (Backend Python Package)
1.1 Package Structure
Create backend/uframe/ as a Python package:
backend/uframe/
__init__.py # Public API: compile(source, width) -> CompileResult
parser.py # Line-by-line indentation parser -> IR tree
ir.py # IR node dataclasses (Page, Box, Row, Col, Text, etc.)
grid.py # CharGrid class — 2D character + style buffer
measure.py # Bottom-up size computation pass
layout.py # Top-down position assignment pass
paint.py # Write chars into CharGrid
borders.py # Border merge pass — fix junction characters
emit_ascii.py # CharGrid -> plain text string
emit_micron.py # CharGrid -> Micron markup with style tags
errors.py # ParseError, LayoutError exception types
1.2 New Files to Create
backend/uframe/__init__.py
Public API surface. Exports compile(source: str, width: int = 72) -> CompileResult.
CompileResult is a dataclass with fields: ascii: str, micron: str, warnings: list[str].
The function orchestrates: parse -> measure -> layout -> paint -> merge_borders -> emit.
backend/uframe/ir.py
IR node types as Python dataclasses. Every node has:
children: list[Node]width,height(computed by measure)x,y(computed by layout)style: Style(fg, bg, bold, italic, underline)
Node types for Phase 1:
Page— root, haswidthproperty, optionaltitleBox— border weight (none/light/heavy/double/rounded), paddingRow— horizontal container, children laid out left-to-rightCol— vertical container, children laid out top-to-bottomSpacer— flexible space fillerPad— explicit padding wrapperHeading— level 1-3, renders with Micron>,>>,>>>Text— body text with inline @modifiers (@bold, @italic, @color)Label— fixed-width key:value pairDivider— horizontal rule with character choiceLink— generates[label+target]in Micron outputList— bulleted or numbered list items
backend/uframe/parser.py
Line-by-line, indentation-based parser. Design:
- Each line is
(indent_level, keyword, props_string) - Indent = 2 spaces per level (configurable)
- Keywords: page, box, row, col, spacer, pad, heading, text, label, divider, link, list
- Properties parsed from the rest of the line after the keyword
- Multiline text: subsequent indented lines without a keyword are text content
- Returns ParseTree with root Page node and warnings list
- Raises ParseError with line number + message for unrecoverable errors
backend/uframe/grid.py
CharGrid — the core rendering buffer:
- 2D array of Cell objects: (char, fg, bg, bold, italic, underline, border_flag)
- Methods: put(x, y, char, style), put_str(x, y, text, style), hline(x, y, length, char), vline(x, y, length, char), rect(x, y, w, h, weight)
- Border characters stored with border=True for the merge pass
- width and height properties
backend/uframe/measure.py
Bottom-up pass computing sizes:
- Leaf nodes (Text, Heading, Label, Divider) compute natural size
- Text wraps to available width, computes resulting height
- Row: width = sum(child widths), height = max(child heights)
- Col: width = max(child widths), height = sum(child heights)
- Box: adds 2 for borders (if weight != none), plus padding
- Percentage-based widths resolved relative to parent
backend/uframe/layout.py
Top-down pass assigning positions:
- Page starts at (0, 0)
- Row distributes x positions left-to-right
- Col distributes y positions top-to-bottom
- Spacer expands to fill remaining space
backend/uframe/paint.py
Renders each node into the CharGrid:
- Box: draws border rectangle, then paints children inside
- Text: writes word-wrapped text with style
- Heading: writes text, marks heading level for Micron emitter
- Divider: draws horizontal line
- Label: writes "key: value" with key highlighted
- Link: writes label text, stores target in metadata for Micron emitter
backend/uframe/borders.py
Post-paint pass over CharGrid:
- Scans for adjacent border cells
- Replaces with correct junction characters (T, cross, corner)
- Unicode box-drawing character lookup tables
- Handles mixed weights (light meets heavy, etc.)
backend/uframe/emit_ascii.py
Reads CharGrid row by row, outputs plain characters only. Strips trailing whitespace per line.
backend/uframe/emit_micron.py
Reads CharGrid row by row, inserts Micron formatting codes:
- Track current style state, emit format codes on change
- Headings get
>,>>,>>>prefixes - Links emit Micron link syntax
- Reset formatting at end of styled runs
backend/uframe/errors.py
Exception types: UFrameError (base), ParseError, LayoutError. Each carries line/col/message.
1.3 Existing Files to Modify
backend/converter.py — REPLACE contents.
Remove md2txt wrapper. Add uFrame compile endpoint:
- POST
/api/compileaccepts{source: str, width: int}, returns{ascii: str, micron: str, warnings: list[str]} - Old POST
/api/convertcan remain as deprecated during transition
backend/pages.py — Modify storage model.
- SOURCES_DIR stores
.uffiles instead of.mufiles _list_all_page_names(): scan for.ufin SOURCES_DIR,.muin PAGES_DIRget_page(): returns.ufsource from SOURCES_DIRsave_page(): accepts.ufsource, saves as.uf- On publish: compile .uf -> .mu via uframe.compile(), write .mu to PAGES_DIR
_extract_title(): parse .uf source for heading or page title= property- PageDetail model: add
sourcefield (the .uf content) - SaveRequest model:
sourcefield replacesmicron
backend/graph.py — Modify link extraction.
- Parse .uf source files for link nodes instead of Micron link pattern
- Use uframe.parser.parse() to get IR tree, walk for Link nodes
- Or: simpler regex for link keyword in .uf syntax
_extract_title(): parse .uf for page title= or first heading
backend/requirements.txt — Remove md2txt dependency (eventually).
1.4 API Changes
| Method | Path | Before | After |
|---|---|---|---|
| POST | /api/convert |
markdown -> micron | DEPRECATED |
| POST | /api/compile |
NEW | .uf source -> {ascii, micron, warnings} |
| GET | /api/pages/{name} |
returns {name, micron} | returns {name, source, micron} |
| POST | /api/pages/{name} |
body: {micron, publish} | body: {source, publish} |
1.5 Migration Strategy for Existing Pages
Existing .mu source files in SOURCES_DIR need a migration path:
- Option A (recommended): One-time migration script
backend/migrate_mu_to_uf.pywrapping existing Micron in minimal .uf template - Option B: Keep .mu fallback — if .uf not found but .mu exists in SOURCES_DIR, return raw Micron as read-only legacy
- pages.py get_page should check for .uf first, fall back to .mu
1.6 Verification Steps
-
Unit tests —
backend/tests/test_uframe/:- test_parser.py, test_grid.py, test_measure.py, test_layout.py
- test_borders.py, test_emit_ascii.py, test_emit_micron.py, test_compile.py
-
Golden file tests —
backend/tests/test_uframe/golden/:- .uf input files paired with expected .ascii and .mu outputs
- Cases: single box, nested rows/cols, heading+text page, mixed content
-
API test — verify POST /api/compile returns valid response
-
Edge cases: empty page, deeply nested boxes, text overflow, zero-width
Phase 2 — Data Visualization Nodes
2.1 New Files
backend/uframe/viz.py — Viz node implementations:
- Gauge, Meter, BarH, BarV, Sparkline (braille), Heatmap, Status, Table
2.2 Modified Files
backend/uframe/ir.py— add viz node dataclassesbackend/uframe/parser.py— add viz keyword handlersbackend/uframe/measure.py— add viz size computationbackend/uframe/paint.py— add viz rendering
2.3 Verification
- Golden file tests for each viz type
- Edge cases: empty data, single value, overflow
Phase 3 — Web IDE (Frontend Replacement)
3.1 New Files
frontend/src/components/editor/uframeHighlight.ts
CodeMirror language support for .uf syntax:
- Keywords: page, box, row, col, spacer, pad, heading, text, label, divider, link, list, gauge, sparkline, etc.
- Properties: width=, height=, weight=, title=, char=, @modifiers
- String literals, comments (#), indentation awareness
frontend/src/components/editor/uframeCommands.ts
Slash commands for .uf syntax:
- /box, /row, /col, /heading, /text, /divider, etc.
- Each inserts a snippet with correct indentation
- Property suggestions after =
frontend/src/components/editor/AsciiPreviewPane.tsx
ASCII output preview:
- Monospace rendering of plain-text ASCII art
- Terminal aesthetic (green-on-black optional)
frontend/src/hooks/useCompile.ts
Hook calling POST /api/compile:
- Debounced (300ms) on source change
- Returns { ascii, micron, warnings, isCompiling, error }
3.2 Modified Files
frontend/src/stores/editorStore.ts — Major rewrite:
ufSourcereplacesmicronSource- Add
compiledAscii,compiledMicron,warnings,isCompiling previewMode: "ascii" | "micron" | "raw" (was "preview" | "raw")
frontend/src/routes/EditorView.tsx — Significant changes:
- Use ufSource instead of micronSource
- Add compile-on-change hook (debounced /api/compile)
- Save sends source field not micron
- Replace micronHighlight with uframeHighlight
- Replace slashCommands with uframeCommands
- Replace single PreviewPane with tabbed ASCII + Micron preview
frontend/src/components/editor/PreviewPane.tsx — Major rewrite:
- Tab bar: "ASCII" | "Micron" | "Raw Micron"
- ASCII tab: compiledAscii in monospace pre block
- Micron tab: compiledMicron through existing renderMicron()
- Raw tab: raw Micron source
frontend/src/components/editor/ToolBar.tsx — Minor:
- Compile warnings count in status area
frontend/src/stores/pagesStore.ts — Minor:
- unpublishPage sends source not micron
frontend/src/components/editor/wikiLinkCompletion.ts — Adapt to .uf link syntax
3.3 Removed Files
frontend/src/components/editor/slashCommands.ts(replaced by uframeCommands.ts)frontend/src/components/editor/micronHighlight.ts(replaced by uframeHighlight.ts)
3.4 Files Kept As-Is
micronRenderer.ts— still needed for Micron preview taboneDarkTheme.ts— theme remainsDashboardView.tsx,GraphView.tsx— no changes- All shadcn/ui components — unchanged
3.5 Verification
- Create page with boxes, headings, text — verify ASCII and Micron previews
- Publish writes correct .mu to PAGES_DIR
- Page load round-trip: save .uf, reload, content preserved
- Dashboard still shows pages correctly
- Graph still shows links
- Keyboard shortcuts (Ctrl+S, Ctrl+P) still work
- Unsaved changes guard still fires
Phase 4+ — Forms, Dynamic, Components (Future)
Forms (Phase 4)
- IR nodes: Field, Password, Radio, Checkbox, Button
- Parser + measure + paint for each
- Micron emit generates form codes
Dynamic (Phase 5)
- source blocks, on_submit, if/else, for loops, state
- Requires runtime component beyond static compilation
Components (Phase 6)
- Reusable definitions, import, parameterized props
Implementation Order (Phase 1 detail)
Build sequence within Phase 1, each step testable in isolation:
errors.py— Exception types (no dependencies)ir.py— Node dataclasses (depends on nothing)parser.py— Parse .uf -> IR tree (depends on ir, errors)grid.py— CharGrid buffer (depends on nothing)measure.py— Size computation (depends on ir)layout.py— Position assignment (depends on ir)paint.py— Render to grid (depends on ir, grid)borders.py— Junction resolution (depends on grid)emit_ascii.py— ASCII output (depends on grid)emit_micron.py— Micron output (depends on grid)__init__.py— Public API tying it togetherconverter.pyupdate — Wire /api/compile endpointpages.pyupdate — .uf storage modelgraph.pyupdate — .uf link extraction
Key Design Decisions
-
Separate measure/layout/paint passes — enables caching (re-layout without re-parse) and independent testability.
-
CharGrid as intermediate — decouples layout from emission. Same grid produces ASCII and Micron. Adding new emitters (HTML, ANSI) later is trivial.
-
Border flag on cells — merge pass identifies borders without confusing them with box-drawing text content.
-
Style state in cells — each cell carries its own style so Micron emitter can diff adjacent cells and emit minimal formatting codes.
-
Line-based parser — matches indentation-sensitive DSL. Simpler than full tokenizer. Upgradeable to PEG grammar later.
-
No new Python dependencies — pure stdlib. Box-drawing and braille characters are Unicode literals.
-
SOURCES_DIR uses .uf extension — clean break from .mu sources. Published files remain .mu (NomadNet requirement).
-
Compile on backend — single source of truth. Frontend is thin editor + preview client. Enables future CLI compilation and batch processing.