# 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 frontend - `pages.py` — CRUD for `.mu` files across SOURCES_DIR and PAGES_DIR - `converter.py` — POST `/api/convert` wrapping md2txt (markdown -> micron) - `graph.py` — GET `/api/graph` parsing Micron `[label` + `slug]` links - `docker_utils.py` — POST `/api/restart` for NomadNet container **Frontend** (`frontend/src/`): - `EditorView.tsx` — main route, split pane: `EditorPane` (CodeMirror) + `PreviewPane` (Micron HTML) - `editorStore.ts` — state: `micronSource`, `isDirty`, `currentPage`, `previewMode` - `pagesStore.ts` — page list, fetch/delete/unpublish - `micronHighlight.ts` — CodeMirror StreamLanguage for Micron syntax - `micronRenderer.ts` — Micron markup to HTML for preview - `slashCommands.ts` — `/H1`, `/Bold`, etc. autocomplete for Micron codes - `wikiLinkCompletion.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, has `width` property, optional `title` - `Box` — border weight (none/light/heavy/double/rounded), padding - `Row` — horizontal container, children laid out left-to-right - `Col` — vertical container, children laid out top-to-bottom - `Spacer` — flexible space filler - `Pad` — explicit padding wrapper - `Heading` — level 1-3, renders with Micron `>`, `>>`, `>>>` - `Text` — body text with inline @modifiers (@bold, @italic, @color) - `Label` — fixed-width key:value pair - `Divider` — horizontal rule with character choice - `Link` — generates `[label` + `target]` in Micron output - `List` — 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/compile` accepts `{source: str, width: int}`, returns `{ascii: str, micron: str, warnings: list[str]}` - Old POST `/api/convert` can remain as deprecated during transition **`backend/pages.py`** — Modify storage model. - SOURCES_DIR stores `.uf` files instead of `.mu` files - `_list_all_page_names()`: scan for `.uf` in SOURCES_DIR, `.mu` in PAGES_DIR - `get_page()`: returns `.uf` source from SOURCES_DIR - `save_page()`: accepts `.uf` source, 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 `source` field (the .uf content) - SaveRequest model: `source` field replaces `micron` **`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.py` wrapping 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 1. **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 2. **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 3. **API test** — verify POST /api/compile returns valid response 4. **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 dataclasses - `backend/uframe/parser.py` — add viz keyword handlers - `backend/uframe/measure.py` — add viz size computation - `backend/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: - `ufSource` replaces `micronSource` - 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 tab - `oneDarkTheme.ts` — theme remains - `DashboardView.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: 1. `errors.py` — Exception types (no dependencies) 2. `ir.py` — Node dataclasses (depends on nothing) 3. `parser.py` — Parse .uf -> IR tree (depends on ir, errors) 4. `grid.py` — CharGrid buffer (depends on nothing) 5. `measure.py` — Size computation (depends on ir) 6. `layout.py` — Position assignment (depends on ir) 7. `paint.py` — Render to grid (depends on ir, grid) 8. `borders.py` — Junction resolution (depends on grid) 9. `emit_ascii.py` — ASCII output (depends on grid) 10. `emit_micron.py` — Micron output (depends on grid) 11. `__init__.py` — Public API tying it together 12. `converter.py` update — Wire /api/compile endpoint 13. `pages.py` update — .uf storage model 14. `graph.py` update — .uf link extraction --- ## Key Design Decisions 1. **Separate measure/layout/paint passes** — enables caching (re-layout without re-parse) and independent testability. 2. **CharGrid as intermediate** — decouples layout from emission. Same grid produces ASCII and Micron. Adding new emitters (HTML, ANSI) later is trivial. 3. **Border flag on cells** — merge pass identifies borders without confusing them with box-drawing text content. 4. **Style state in cells** — each cell carries its own style so Micron emitter can diff adjacent cells and emit minimal formatting codes. 5. **Line-based parser** — matches indentation-sensitive DSL. Simpler than full tokenizer. Upgradeable to PEG grammar later. 6. **No new Python dependencies** — pure stdlib. Box-drawing and braille characters are Unicode literals. 7. **SOURCES_DIR uses .uf extension** — clean break from .mu sources. Published files remain .mu (NomadNet requirement). 8. **Compile on backend** — single source of truth. Frontend is thin editor + preview client. Enables future CLI compilation and batch processing.