Files
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

228 lines
13 KiB
LLVM
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
; Generated by Ritz Framework with Signals
; Components: DebugConsole, RitzMain
; String constants
@.str.0 = private unnamed_addr constant [4 x i8] c"div\00"
@.str.1 = private unnamed_addr constant [6 x i8] c"class\00"
@.str.2 = private unnamed_addr constant [18 x i8] c"ritz-debug hidden\00"
@.str.3 = private unnamed_addr constant [3 x i8] c"id\00"
@.str.4 = private unnamed_addr constant [11 x i8] c"ritz-debug\00"
@.str.5 = private unnamed_addr constant [13 x i8] c"debug-header\00"
@.str.6 = private unnamed_addr constant [3 x i8] c"h3\00"
@.str.7 = private unnamed_addr constant [16 x i8] c"🎭 Ritz Debug\00"
@.str.8 = private unnamed_addr constant [7 x i8] c"button\00"
@.str.9 = private unnamed_addr constant [6 x i8] c"click\00"
@.str.10 = private unnamed_addr constant [7 x i8] c"toggle\00"
@.str.11 = private unnamed_addr constant [3 x i8] c"×\00"
@.str.12 = private unnamed_addr constant [15 x i8] c"status success\00"
@.str.13 = private unnamed_addr constant [7 x i8] c"status\00"
@.str.14 = private unnamed_addr constant [17 x i8] c"✅ Main loaded!\00"
@.str.15 = private unnamed_addr constant [14 x i8] c"debug-content\00"
@.str.16 = private unnamed_addr constant [14 x i8] c"debug-section\00"
@.str.17 = private unnamed_addr constant [3 x i8] c"h4\00"
@.str.18 = private unnamed_addr constant [10 x i8] c"COMPONENT\00"
@.str.19 = private unnamed_addr constant [4 x i8] c"pre\00"
@.str.20 = private unnamed_addr constant [32 x i8] c"Main.wasm (Multi-Part Switches)\00"
@.str.21 = private unnamed_addr constant [9 x i8] c"FEATURES\00"
@.str.22 = private unnamed_addr constant [27 x i8] c"✓ Python source to WASM
\00"
@.str.23 = private unnamed_addr constant [32 x i8] c"✓ Reactive signals (no vDOM)
\00"
@.str.24 = private unnamed_addr constant [34 x i8] c"✓ Conditional CSR (match/case)
\00"
@.str.25 = private unnamed_addr constant [20 x i8] c"✓ Event handlers
\00"
@.str.26 = private unnamed_addr constant [32 x i8] c"✓ Self-hosted debug console!
\00"
@.str.27 = private unnamed_addr constant [14 x i8] c"RUNTIME STATS\00"
@.str.28 = private unnamed_addr constant [32 x i8] c"Memory: JavaScript will update
\00"
@.str.29 = private unnamed_addr constant [33 x i8] c"Signals: JavaScript will update
\00"
@.str.30 = private unnamed_addr constant [35 x i8] c"DOM nodes: JavaScript will update
\00"
@.str.31 = private unnamed_addr constant [17 x i8] c"CONSOLE COMMANDS\00"
@.str.32 = private unnamed_addr constant [22 x i8] c"ritzRuntime.getLog()
\00"
@.str.33 = private unnamed_addr constant [24 x i8] c"ritzRuntime.nodes.size
\00"
@.str.34 = private unnamed_addr constant [22 x i8] c"ritzInstance.exports
\00"
@.str.35 = private unnamed_addr constant [18 x i8] c"debug-toggle-hint\00"
@.str.36 = private unnamed_addr constant [29 x i8] c"Press Ctrl+` to toggle debug\00"
; JavaScript bridge function declarations
declare i32 @js_create_element(i8*)
declare i32 @js_create_text(i8*)
declare i32 @js_create_text_with_int(i8*, i32)
declare void @js_append_child(i32, i32)
declare void @js_set_attribute(i32, i8*, i8*)
declare void @js_set_root(i32)
declare void @js_bind_event(i32, i8*, i8*)
declare void @js_update_text_content(i32, i8*)
declare void @js_update_text_with_int(i32, i8*, i32)
declare i32 @js_create_marker()
declare void @js_replace_at_marker(i32, i32, i32)
; External functions (@external decorator)
declare void @js_toggle_debug_console()
; Event handler: toggle
define void @toggle() {
entry:
call void @js_toggle_debug_console()
ret void
}
; Component render function
define i32 @render_DebugConsole() {
entry:
%var1 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
%var0 = call i32 @js_create_element(i8* %var1)
%var2 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
%var3 = getelementptr inbounds [18 x i8], [18 x i8]* @.str.2, i32 0, i32 0
call void @js_set_attribute(i32 %var0, i8* %var2, i8* %var3)
%var4 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.3, i32 0, i32 0
%var5 = getelementptr inbounds [11 x i8], [11 x i8]* @.str.4, i32 0, i32 0
call void @js_set_attribute(i32 %var0, i8* %var4, i8* %var5)
%var7 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
%var6 = call i32 @js_create_element(i8* %var7)
%var8 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
%var9 = getelementptr inbounds [13 x i8], [13 x i8]* @.str.5, i32 0, i32 0
call void @js_set_attribute(i32 %var6, i8* %var8, i8* %var9)
%var11 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.6, i32 0, i32 0
%var10 = call i32 @js_create_element(i8* %var11)
%var12 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.7, i32 0, i32 0))
call void @js_append_child(i32 %var10, i32 %var12)
call void @js_append_child(i32 %var6, i32 %var10)
%var14 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.8, i32 0, i32 0
%var13 = call i32 @js_create_element(i8* %var14)
%var15 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.9, i32 0, i32 0
%var16 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.10, i32 0, i32 0
call void @js_bind_event(i32 %var13, i8* %var15, i8* %var16)
%var17 = call i32 @js_create_text(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.11, i32 0, i32 0))
call void @js_append_child(i32 %var13, i32 %var17)
call void @js_append_child(i32 %var6, i32 %var13)
call void @js_append_child(i32 %var0, i32 %var6)
%var19 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
%var18 = call i32 @js_create_element(i8* %var19)
%var20 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
%var21 = getelementptr inbounds [15 x i8], [15 x i8]* @.str.12, i32 0, i32 0
call void @js_set_attribute(i32 %var18, i8* %var20, i8* %var21)
%var22 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.3, i32 0, i32 0
%var23 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.13, i32 0, i32 0
call void @js_set_attribute(i32 %var18, i8* %var22, i8* %var23)
%var24 = call i32 @js_create_text(i8* getelementptr inbounds ([17 x i8], [17 x i8]* @.str.14, i32 0, i32 0))
call void @js_append_child(i32 %var18, i32 %var24)
call void @js_append_child(i32 %var0, i32 %var18)
%var26 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
%var25 = call i32 @js_create_element(i8* %var26)
%var27 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
%var28 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.15, i32 0, i32 0
call void @js_set_attribute(i32 %var25, i8* %var27, i8* %var28)
%var30 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
%var29 = call i32 @js_create_element(i8* %var30)
%var31 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
%var32 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.16, i32 0, i32 0
call void @js_set_attribute(i32 %var29, i8* %var31, i8* %var32)
%var34 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.17, i32 0, i32 0
%var33 = call i32 @js_create_element(i8* %var34)
%var35 = call i32 @js_create_text(i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.str.18, i32 0, i32 0))
call void @js_append_child(i32 %var33, i32 %var35)
call void @js_append_child(i32 %var29, i32 %var33)
%var37 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.19, i32 0, i32 0
%var36 = call i32 @js_create_element(i8* %var37)
%var38 = call i32 @js_create_text(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @.str.20, i32 0, i32 0))
call void @js_append_child(i32 %var36, i32 %var38)
call void @js_append_child(i32 %var29, i32 %var36)
call void @js_append_child(i32 %var25, i32 %var29)
%var40 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
%var39 = call i32 @js_create_element(i8* %var40)
%var41 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
%var42 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.16, i32 0, i32 0
call void @js_set_attribute(i32 %var39, i8* %var41, i8* %var42)
%var44 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.17, i32 0, i32 0
%var43 = call i32 @js_create_element(i8* %var44)
%var45 = call i32 @js_create_text(i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str.21, i32 0, i32 0))
call void @js_append_child(i32 %var43, i32 %var45)
call void @js_append_child(i32 %var39, i32 %var43)
%var47 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.19, i32 0, i32 0
%var46 = call i32 @js_create_element(i8* %var47)
%var48 = call i32 @js_create_text(i8* getelementptr inbounds ([27 x i8], [27 x i8]* @.str.22, i32 0, i32 0))
call void @js_append_child(i32 %var46, i32 %var48)
%var49 = call i32 @js_create_text(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @.str.23, i32 0, i32 0))
call void @js_append_child(i32 %var46, i32 %var49)
%var50 = call i32 @js_create_text(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @.str.24, i32 0, i32 0))
call void @js_append_child(i32 %var46, i32 %var50)
%var51 = call i32 @js_create_text(i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.25, i32 0, i32 0))
call void @js_append_child(i32 %var46, i32 %var51)
%var52 = call i32 @js_create_text(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @.str.26, i32 0, i32 0))
call void @js_append_child(i32 %var46, i32 %var52)
call void @js_append_child(i32 %var39, i32 %var46)
call void @js_append_child(i32 %var25, i32 %var39)
%var54 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
%var53 = call i32 @js_create_element(i8* %var54)
%var55 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
%var56 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.16, i32 0, i32 0
call void @js_set_attribute(i32 %var53, i8* %var55, i8* %var56)
%var58 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.17, i32 0, i32 0
%var57 = call i32 @js_create_element(i8* %var58)
%var59 = call i32 @js_create_text(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str.27, i32 0, i32 0))
call void @js_append_child(i32 %var57, i32 %var59)
call void @js_append_child(i32 %var53, i32 %var57)
%var61 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.19, i32 0, i32 0
%var60 = call i32 @js_create_element(i8* %var61)
%var62 = call i32 @js_create_text(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @.str.28, i32 0, i32 0))
call void @js_append_child(i32 %var60, i32 %var62)
%var63 = call i32 @js_create_text(i8* getelementptr inbounds ([33 x i8], [33 x i8]* @.str.29, i32 0, i32 0))
call void @js_append_child(i32 %var60, i32 %var63)
%var64 = call i32 @js_create_text(i8* getelementptr inbounds ([35 x i8], [35 x i8]* @.str.30, i32 0, i32 0))
call void @js_append_child(i32 %var60, i32 %var64)
call void @js_append_child(i32 %var53, i32 %var60)
call void @js_append_child(i32 %var25, i32 %var53)
%var66 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
%var65 = call i32 @js_create_element(i8* %var66)
%var67 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
%var68 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.16, i32 0, i32 0
call void @js_set_attribute(i32 %var65, i8* %var67, i8* %var68)
%var70 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.17, i32 0, i32 0
%var69 = call i32 @js_create_element(i8* %var70)
%var71 = call i32 @js_create_text(i8* getelementptr inbounds ([17 x i8], [17 x i8]* @.str.31, i32 0, i32 0))
call void @js_append_child(i32 %var69, i32 %var71)
call void @js_append_child(i32 %var65, i32 %var69)
%var73 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.19, i32 0, i32 0
%var72 = call i32 @js_create_element(i8* %var73)
%var74 = call i32 @js_create_text(i8* getelementptr inbounds ([22 x i8], [22 x i8]* @.str.32, i32 0, i32 0))
call void @js_append_child(i32 %var72, i32 %var74)
%var75 = call i32 @js_create_text(i8* getelementptr inbounds ([24 x i8], [24 x i8]* @.str.33, i32 0, i32 0))
call void @js_append_child(i32 %var72, i32 %var75)
%var76 = call i32 @js_create_text(i8* getelementptr inbounds ([22 x i8], [22 x i8]* @.str.34, i32 0, i32 0))
call void @js_append_child(i32 %var72, i32 %var76)
call void @js_append_child(i32 %var65, i32 %var72)
call void @js_append_child(i32 %var25, i32 %var65)
call void @js_append_child(i32 %var0, i32 %var25)
%var78 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
%var77 = call i32 @js_create_element(i8* %var78)
%var79 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
%var80 = getelementptr inbounds [18 x i8], [18 x i8]* @.str.35, i32 0, i32 0
call void @js_set_attribute(i32 %var77, i8* %var79, i8* %var80)
%var81 = call i32 @js_create_text(i8* getelementptr inbounds ([29 x i8], [29 x i8]* @.str.36, i32 0, i32 0))
call void @js_append_child(i32 %var77, i32 %var81)
ret i32 %var0
}
; Component render function
define i32 @render_RitzMain() {
entry:
%var82 = call i32 @render_DebugConsole()
ret i32 %var82
}
; Main entry point
define void @main() {
entry:
%root = call i32 @render_RitzMain()
call void @js_set_root(i32 %root)
ret void
}