Files
ritz/PHASE4_CLEANUP.md
T
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

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 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)

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