Files
ritz/PHASE4_CLEANUP.md
Aaron SinclairandClaude ea7dadc5ae Implement self-hosted debug console and @external decorator
This commit completes the self-hosting of Ritz's debug console and
introduces the @external decorator for elegant JavaScript bridge functions.

## Major Features

### 1. Self-Hosted Debug Console (DebugConsole.py)
- Entire debug console UI written in Ritz itself
- Signal-based state management for toggle functionality
- Displays runtime stats (memory, signals, DOM nodes, logs)
- Live reactive updates when signals change in main app
- Keyboard shortcut support (Ctrl+`)

### 2. @external Decorator for JavaScript Bridges
- Pythonic decorator syntax: @external or @bridge
- Parser automatically recognizes and tracks external functions
- Codegen emits proper LLVM declarations
- No more hacky string checks (startswith("js_"))
- Clean, declarative bridge function definitions

Example:
```python
@external
def js_toggle_debug_console():
    pass

def toggle():
    js_toggle_debug_console()  # Clean call to JS bridge
```

### 3. Enhanced RitzLoader Debug Mode
- `new RitzLoader({ debug: true })` auto-loads DebugConsole.wasm
- Keyboard shortcut registration (Ctrl+` toggle)
- Runtime stats injection (memory, signals, DOM nodes)
- Automatic status element hiding when debug enabled
- Debug console isolated in separate WASM runtime

## Bug Fixes

- Fixed reactive text node scoping (prevent global name collisions)
- Fixed CSS specificity for debug console status (use #ritz-debug)
- Fixed signal cross-contamination between runtimes (scope to rootElement)
- Fixed parser to handle expression statements in function bodies
- Fixed conditional branch rendering (append children to wrapper divs)

## Infrastructure Improvements

- Added H4 and Pre elements to syntax.py
- Parser handles @external decorator on top-level functions
- Codegen pre-collects external functions before emitting declarations
- Runtime scopes querySelector to prevent cross-runtime interference
- Debug stats update reactively on every signal change

## Files Added/Modified

New files:
- examples/runtime/DebugConsole.py - Self-hosted debug UI
- examples/runtime/DebugConsole.wasm - Compiled debug console (1.8KB)
- PHASE4_CLEANUP.md - Next phase planning document

Modified:
- ritz/parser.py - @external decorator support
- ritz/llvm_codegen_signals.py - External function handling
- ritz/syntax.py - Added external/bridge decorators, H4, Pre
- examples/runtime/ritz-runtime.js - Debug mode, stats updates
- examples/runtime/styles.css - Merged debug styles
- examples/template.html - Debug mode enabled by default
- examples/highfive/HighFive.html - Simplified using template

## Known Issues (Addressed in PHASE4_CLEANUP.md)

1. Debug console shifts page RIGHT (should shift LEFT)
2. Toggle implementation un-Ritzy (calls JS to manipulate DOM)
   - Should use reactive class binding + @event decorator
   - Needs event object bridge for keyboard events
3. Missing test infrastructure

## Next Steps (Phase 4)

See PHASE4_CLEANUP.md for detailed plan:
- Implement reactive class binding (signal-driven classes)
- Add @event decorator for declarative event registration
- Build event object bridge (access event.key, event.ctrlKey)
- Create Python test mode (execute without browser)
- Add comprehensive test suite
- Update all examples to use new patterns

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 18:28:24 -08:00

292 lines
8.4 KiB
Markdown

# Phase 4: Cleanup & Polish - Make It Sexy
## Critical Issues to Fix
### 1. Debug Console UI Jank
**Problem**: Page content shifts RIGHT when debug console opens (counter-intuitive)
- Expected: Content shifts LEFT to make room
- Current: Content margin-right increases, pushing content right
**Fix**:
```css
/* BEFORE (wrong) */
body.debug-open #app {
margin-right: 420px; /* Pushes content right - bad! */
}
/* AFTER (correct) */
body.debug-open #app {
margin-right: 0; /* Keep in place or... */
/* OR shift left to center remaining space */
transform: translateX(-200px);
}
```
### 2. Un-Ritzy Toggle Implementation (CRITICAL)
**Current (WRONG)**: JS manipulates DOM classes, WASM calls JS bridge function
```python
# ANTI-PATTERN - calling JS to manipulate DOM!
@external
def js_toggle_debug_console():
pass
def toggle():
js_toggle_debug_console() # ❌ JS does the work
```
**Correct (RITZY)**: Signal drives reactive class, declarative event binding
```python
# THE RITZ WAY - declarative and reactive!
console_open = signal(False)
@event('keydown') # Declarative event registration
def handle_keydown(event):
if event.key == '`' and event.ctrlKey:
console_open.value = not console_open.value
def toggle():
"""Button click handler"""
console_open.value = not console_open.value
# Reactive class binding (syntax TBD)
with Div(class_=lambda: 'ritz-debug' if console_open else 'ritz-debug hidden'):
# ...or maybe:
# class_={'ritz-debug': True, 'hidden': not console_open}
```
**What this requires**:
#### A. Reactive Class Binding
- [ ] Parser support for lambda/function in class attribute
- [ ] Codegen to track class dependencies on signals
- [ ] Runtime bridge: `js_set_class(elem_id, class_string)`
- [ ] Signal updates trigger class re-evaluation
#### B. Declarative Event System (`@event` decorator)
- [ ] `@event('keydown')` decorator in syntax.py
- [ ] Parser recognizes @event and extracts event type
- [ ] Codegen emits global event registration (not element-specific)
- [ ] Runtime: `js_register_global_event(event_type, handler_name)`
- [ ] JS passes event object to WASM with properties
- [ ] WASM can access `event.key`, `event.ctrlKey`, etc.
#### C. Event Object Bridge
- [ ] Define event object structure (dict/struct in WASM)
- [ ] JS serializes event properties to WASM memory
- [ ] WASM handler receives event pointer
- [ ] Helper functions: `event_get_key()`, `event_get_ctrl_key()`, etc.
## Framework Improvements Needed
### 3. Reactive Attribute System (Beyond Classes)
Make ALL attributes reactive:
```python
disabled = signal(False)
with Button(disabled=disabled):
yield "Click me"
# When disabled changes, button.disabled updates automatically
```
**Implementation**:
- [ ] Track attribute dependencies on signals
- [ ] Generate update functions for each reactive attribute
- [ ] Runtime bridge: `js_set_attribute_bool()`, `js_set_attribute_int()`, etc.
### 4. Testing Infrastructure
**Problem**: No way to test Ritz code without manual browser testing
**Solution A: Python Interpreter Mode**
```bash
# Run Ritz components in pure Python for testing
python3 -m ritz.test examples/highfive/HighFive.py
# Output:
# ✓ HighFive renders correctly
# ✓ increment() updates signal
# ✓ Button has correct onclick handler
```
Implementation:
- [ ] Create `ritz/test_runtime.py` - Python VNode interpreter
- [ ] Execute component functions, track signal changes
- [ ] Assert on rendered VNode tree structure
- [ ] Mock event handlers and verify signal updates
**Solution B: LLVM Interpreter**
```bash
# Execute LLVM IR directly (no WASM step)
python3 -m ritz.test --llvm examples/highfive/HighFive.py
# Uses: lli (LLVM interpreter) or llvm-python bindings
```
Implementation:
- [ ] Mock JS bridge functions in C/LLVM
- [ ] Execute LLVM IR with test assertions
- [ ] Verify correct function calls and signal state
**Solution C: Headless Browser Tests**
```bash
# Full integration tests with Playwright/Puppeteer
python3 -m ritz.test --browser examples/highfive/HighFive.py
```
Implementation:
- [ ] Playwright/Puppeteer test harness
- [ ] Load WASM in headless browser
- [ ] Verify DOM structure and interactions
- [ ] Snapshot testing for visual regression
### 5. Test Coverage Requirements
**Unit Tests Needed**:
- [ ] Parser: All AST node types
- [ ] Parser: @component, @external, @event decorators
- [ ] Codegen: Signal generation
- [ ] Codegen: Event handler generation
- [ ] Codegen: Reactive attributes
- [ ] Codegen: Conditional rendering
- [ ] Runtime: Signal updates
- [ ] Runtime: Event dispatch
- [ ] Runtime: DOM mutations
**Integration Tests Needed**:
- [ ] Counter component (signals + events)
- [ ] Tabs component (match/case conditionals)
- [ ] Forms (input binding, validation)
- [ ] Debug console (global events, reactive classes)
**Snapshot Tests**:
- [ ] Generated LLVM IR (detect codegen regressions)
- [ ] Generated WASM size (detect bloat)
- [ ] Rendered HTML structure
## Code Quality & Consistency
### 6. Example App Consistency
Update all examples to use:
- [ ] RitzMain() convention
- [ ] Main.wasm output
- [ ] template.html (no custom HTML)
- [ ] Shared styles.css
- [ ] Component-specific styles in <style> tag only
- [ ] @external for any JS bridge functions
- [ ] Consistent naming conventions
Examples to update:
- [ ] `examples/tabs/Tabs.py` - Add RitzMain
- [ ] `examples/textinput/TextInput.py` - Add RitzMain
- [ ] `examples/toggle/Toggle.py` - Add RitzMain
- [ ] All: Copy template.html
- [ ] All: Remove duplicate styles
### 7. Documentation
- [ ] Update CLAUDE.md with @external decorator
- [ ] Update CLAUDE.md with @event decorator (when implemented)
- [ ] Document reactive attribute syntax
- [ ] Testing guide (how to run tests)
- [ ] Examples README with descriptions
## Architecture Questions to Resolve
### 8. Reactive Class Syntax
**Option 1: Lambda (Python-like)**
```python
with Div(class_=lambda: 'active' if is_active else 'inactive'):
```
Pros: Pythonic, clear dependency
Cons: Lambdas need special handling in parser
**Option 2: Template strings (f-string-like)**
```python
with Div(class_=f"menu {('active' if is_open else '')}"):
```
Pros: Familiar syntax
Cons: Hard to track dependencies, less clear
**Option 3: Dict with signal values**
```python
with Div(class_={'menu': True, 'active': is_open, 'disabled': is_disabled}):
```
Pros: Very clear dependencies, React-like
Cons: Different from HTML class syntax
**Decision needed**: Which syntax to use?
### 9. Event Object Design
How should event handlers access event properties?
**Option 1: Parameter object (dict-like)**
```python
@event('keydown')
def handle_keydown(event):
if event['key'] == '`' and event['ctrlKey']:
...
```
**Option 2: External functions**
```python
@external
def event_get_key() -> str:
pass
@event('keydown')
def handle_keydown():
if event_get_key() == '`' and event_get_ctrl_key():
...
```
**Option 3: Global event context**
```python
@event('keydown')
def handle_keydown():
if current_event.key == '`' and current_event.ctrlKey:
...
```
**Decision needed**: Which approach?
## Success Criteria
Before moving to Phase 5 (was Phase 4), we must have:
- ✅ Debug console opens/closes with proper CSS transitions
- ✅ Page content shifts LEFT (or stays in place) when console opens
- ✅ Console toggle is 100% Ritz (signal + reactive class)
- ✅ Ctrl+` registered declaratively with @event decorator
- ✅ Event object accessible in WASM handlers
- ✅ Test infrastructure in place (at least Python mode)
- ✅ All examples updated to use RitzMain convention
- ✅ Test coverage >70% for parser and codegen
- ✅ Documentation updated
- ✅ No JS DOM manipulation (except initial setup)
## Timeline Estimate
- **Reactive classes**: 2-3 hours (parser + codegen + runtime)
- **@event decorator**: 3-4 hours (parser + codegen + runtime + event bridge)
- **Event object bridge**: 2-3 hours (memory layout + JS serialization)
- **Python test mode**: 4-5 hours (VNode interpreter + test harness)
- **Unit tests**: 3-4 hours (parser, codegen tests)
- **Example cleanup**: 1-2 hours (update 4 examples)
- **Debug console refactor**: 1-2 hours (use reactive classes + @event)
**Total**: ~20-25 hours of focused work
## Notes
- The @external decorator is **beautiful** and should be the template
- Everything should feel declarative and Pythonic
- WASM should handle logic, JS should only bridge
- Tests are critical - no more manual browser testing for every change
- Keep examples simple and pedagogical