feat: added a twist
This commit is contained in:
17
.claude/launch.json
Normal file
17
.claude/launch.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Backend (uvicorn)",
|
||||
"runtimeExecutable": "/bin/sh",
|
||||
"runtimeArgs": ["-c", "PAGES_DIR=$HOME/.nomadnetwork/storage/pages SOURCES_DIR=$HOME/.micron-editor/sources /Users/dtoro/Projects/micronomicon/backend/.venv/bin/uvicorn main:app --reload --port 8080 --app-dir /Users/dtoro/Projects/micronomicon/backend"],
|
||||
"port": 8080
|
||||
},
|
||||
{
|
||||
"name": "Frontend (vite)",
|
||||
"runtimeExecutable": "/Users/dtoro/Projects/micronomicon/frontend/node_modules/.bin/vite",
|
||||
"runtimeArgs": ["/Users/dtoro/Projects/micronomicon/frontend"],
|
||||
"port": 5173
|
||||
}
|
||||
]
|
||||
}
|
||||
353
.claude/plans/nifty-beaming-hanrahan-agent-a35bf8f885e907d39.md
Normal file
353
.claude/plans/nifty-beaming-hanrahan-agent-a35bf8f885e907d39.md
Normal file
@@ -0,0 +1,353 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user