354 lines
15 KiB
Markdown
354 lines
15 KiB
Markdown
# 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.
|