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>
This commit is contained in:
1 parent
ddd2c03a9d
commit
ea7dadc5ae
20 files changed
+1324
-70
No files matched your search
@@ -0,0 +1,291 @@
|
||||
# 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
|
||||
Reference in new issue
Block a user