Files
micronomicon/.claude/plans/nifty-beaming-hanrahan-agent-a35bf8f885e907d39.md
2026-04-01 00:53:55 +02:00

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.