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

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 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 testsbackend/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 testsbackend/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.