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>
292 lines
8.4 KiB
Markdown
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
|