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>
8.4 KiB
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:
/* 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
# 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
# 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:
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
# 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
# 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
# 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 RitzMainexamples/textinput/TextInput.py- Add RitzMainexamples/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)
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)
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
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)
@event('keydown')
def handle_keydown(event):
if event['key'] == '`' and event['ctrlKey']:
...
Option 2: External functions
@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
@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