diff --git a/PHASE4_CLEANUP.md b/PHASE4_CLEANUP.md new file mode 100644 index 0000000..85cb9be --- /dev/null +++ b/PHASE4_CLEANUP.md @@ -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
- - + - diff --git a/examples/runtime/DebugConsole.ll b/examples/runtime/DebugConsole.ll new file mode 100644 index 0000000..74534a9 --- /dev/null +++ b/examples/runtime/DebugConsole.ll @@ -0,0 +1,227 @@ +; 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 +} diff --git a/examples/runtime/DebugConsole.py b/examples/runtime/DebugConsole.py new file mode 100644 index 0000000..77aedc5 --- /dev/null +++ b/examples/runtime/DebugConsole.py @@ -0,0 +1,92 @@ +""" +Ritz Debug Console - Self-hosted debug interface written in Ritz itself! + +This demonstrates Ritz's capability to build complex interactive UIs with: +- Signal-based state management +- Event handling +- Conditional rendering +- Dynamic content updates + +The entire debug console - state, UI, and interactions - is written in Ritz! +""" + +from ritz.syntax import * + + +@external +def js_toggle_debug_console(): + """External JavaScript function to toggle debug console visibility.""" + pass # Implementation provided by JavaScript runtime + + +@component +def DebugConsole(): + """ + Self-hosted debug console for Ritz applications. + + Shows runtime information, memory stats, and console commands. + """ + def toggle(): + """Toggle debug console visibility by calling JavaScript bridge.""" + js_toggle_debug_console() + + # Start hidden - JavaScript will handle toggling via class manipulation + with Div(class_='ritz-debug hidden', id='ritz-debug'): + # Header with toggle button + with Div(class_='debug-header'): + with H3(): + yield '🎭 Ritz Debug' + with Button(onclick=toggle): + yield '×' + + # Status indicator + with Div(class_='status success', id='status'): + yield '✅ Main loaded!' + + # Debug content sections + with Div(class_='debug-content'): + # Component info + with Div(class_='debug-section'): + with H4(): + yield 'COMPONENT' + with Pre(): + yield 'Main.wasm (Multi-Part Switches)' + + # Features section + with Div(class_='debug-section'): + with H4(): + yield 'FEATURES' + with Pre(): + yield '✓ Python source to WASM\n' + yield '✓ Reactive signals (no vDOM)\n' + yield '✓ Conditional CSR (match/case)\n' + yield '✓ Event handlers\n' + yield '✓ Self-hosted debug console!\n' + + # Runtime stats section + with Div(class_='debug-section'): + with H4(): + yield 'RUNTIME STATS' + with Pre(): + yield 'Memory: JavaScript will update\n' + yield 'Signals: JavaScript will update\n' + yield 'DOM nodes: JavaScript will update\n' + + # Console commands section + with Div(class_='debug-section'): + with H4(): + yield 'CONSOLE COMMANDS' + with Pre(): + yield 'ritzRuntime.getLog()\n' + yield 'ritzRuntime.nodes.size\n' + yield 'ritzInstance.exports\n' + + # Toggle hint (appears on page load) + with Div(class_='debug-toggle-hint'): + yield 'Press Ctrl+` to toggle debug' + + +@component +def RitzMain(): + """Entry point for debug console component.""" + yield DebugConsole() diff --git a/examples/runtime/DebugConsole.wasm b/examples/runtime/DebugConsole.wasm new file mode 100755 index 0000000..fd9fdd8 Binary files /dev/null and b/examples/runtime/DebugConsole.wasm differ diff --git a/examples/runtime/Main.ll b/examples/runtime/Main.ll new file mode 100644 index 0000000..9fd3030 --- /dev/null +++ b/examples/runtime/Main.ll @@ -0,0 +1,302 @@ +; 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 [14 x i8] c"debug-section\00" +@.str.3 = private unnamed_addr constant [3 x i8] c"h4\00" +@.str.4 = private unnamed_addr constant [9 x i8] c"FEATURES\00" +@.str.5 = private unnamed_addr constant [4 x i8] c"pre\00" +@.str.6 = private unnamed_addr constant [17 x i8] c"CONSOLE COMMANDS\00" +@.str.7 = private unnamed_addr constant [3 x i8] c"id\00" +@.str.8 = private unnamed_addr constant [11 x i8] c"ritz-debug\00" +@.str.9 = private unnamed_addr constant [13 x i8] c"debug-header\00" +@.str.10 = private unnamed_addr constant [7 x i8] c"status\00" +@.str.11 = private unnamed_addr constant [6 x i8] c"Ready\00" +@.str.12 = private unnamed_addr constant [14 x i8] c"debug-content\00" +@.str.13 = private unnamed_addr constant [10 x i8] c"COMPONENT\00" +@.str.14 = private unnamed_addr constant [1 x i8] c"\00" +@.str.15 = private unnamed_addr constant [18 x i8] c"debug-toggle-hint\00" +@.str.16 = 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) + +; Signal storage for DebugConsole (reactive state in WASM memory) +@signal_DebugConsole_is_open = global i32 0 + +; Conditional rendering state +@cond_0_marker = global i32 0 +@cond_0_current = global i32 0 +@cond_0_then_root = global i32 0 +@cond_0_else_root = global i32 0 +@cond_1_marker = global i32 0 +@cond_1_current = global i32 0 +@cond_1_then_root = global i32 0 +@cond_1_else_root = global i32 0 + +; Signal helper functions +define i32 @get_is_open() { +entry: + %val = load i32, i32* @signal_DebugConsole_is_open + ret i32 %val +} + +define void @set_is_open(i32 %newval) { +entry: + store i32 %newval, i32* @signal_DebugConsole_is_open + ; TODO: Trigger DOM update callback + ret void +} + +; Event handler: toggle +define void @toggle() { +entry: + ret void +} + +; Initialize conditional 0 +define void @_init_conditional_0() { +entry: + ; Render both branches upfront + %var0 = call i32 @_render_cond_0_then() + %var1 = call i32 @_render_cond_0_else() + store i32 %var0, i32* @cond_0_then_root + store i32 %var1, i32* @cond_0_else_root + %var2 = icmp ne i32 0, 0 + %var3 = load i32, i32* @cond_0_marker + br i1 %var2, label %insert_then, label %insert_else +insert_then: + call void @js_replace_at_marker(i32 %var3, i32 0, i32 %var0) + store i32 1, i32* @cond_0_current + ret void +insert_else: + call void @js_replace_at_marker(i32 %var3, i32 0, i32 %var1) + store i32 0, i32* @cond_0_current + ret void +} + +; Re-evaluate conditional 0 +define void @reevaluate_conditional_0() { +entry: + %var4 = icmp ne i32 0, 0 + %var5 = zext i1 %var4 to i32 + %var6 = load i32, i32* @cond_0_current + %var7 = icmp ne i32 %var6, %var5 + br i1 %var7, label %swap, label %done +swap: + br i1 %var4, label %swap_to_then, label %swap_to_else +swap_to_then: + %var8 = load i32, i32* @cond_0_else_root + %var9 = load i32, i32* @cond_0_then_root + %var10 = load i32, i32* @cond_0_marker + call void @js_replace_at_marker(i32 %var10, i32 %var8, i32 %var9) + store i32 1, i32* @cond_0_current + br label %done +swap_to_else: + %var11 = load i32, i32* @cond_0_then_root + %var12 = load i32, i32* @cond_0_else_root + %var13 = load i32, i32* @cond_0_marker + call void @js_replace_at_marker(i32 %var13, i32 %var11, i32 %var12) + store i32 0, i32* @cond_0_current + br label %done +done: + ret void +} + +define i32 @_render_cond_0_then() { +entry: + %var15 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var14 = call i32 @js_create_element(i8* %var15) + %var17 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var16 = call i32 @js_create_element(i8* %var17) + %var18 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0 + %var19 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.2, i32 0, i32 0 + call void @js_set_attribute(i32 %var16, i8* %var18, i8* %var19) + %var21 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.3, i32 0, i32 0 + %var20 = call i32 @js_create_element(i8* %var21) + %var22 = call i32 @js_create_text(i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str.4, i32 0, i32 0)) + call void @js_append_child(i32 %var20, i32 %var22) + call void @js_append_child(i32 %var16, i32 %var20) + %var24 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.5, i32 0, i32 0 + %var23 = call i32 @js_create_element(i8* %var24) + call void @js_append_child(i32 %var16, i32 %var23) + call void @js_append_child(i32 %var14, i32 %var16) + ret i32 %var14 +} + +define i32 @_render_cond_0_else() { +entry: + %var26 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var25 = call i32 @js_create_element(i8* %var26) + ret i32 %var25 +} + +; Initialize conditional 1 +define void @_init_conditional_1() { +entry: + ; Render both branches upfront + %var27 = call i32 @_render_cond_1_then() + %var28 = call i32 @_render_cond_1_else() + store i32 %var27, i32* @cond_1_then_root + store i32 %var28, i32* @cond_1_else_root + %var29 = icmp ne i32 0, 0 + %var30 = load i32, i32* @cond_1_marker + br i1 %var29, label %insert_then, label %insert_else +insert_then: + call void @js_replace_at_marker(i32 %var30, i32 0, i32 %var27) + store i32 1, i32* @cond_1_current + ret void +insert_else: + call void @js_replace_at_marker(i32 %var30, i32 0, i32 %var28) + store i32 0, i32* @cond_1_current + ret void +} + +; Re-evaluate conditional 1 +define void @reevaluate_conditional_1() { +entry: + %var31 = icmp ne i32 0, 0 + %var32 = zext i1 %var31 to i32 + %var33 = load i32, i32* @cond_1_current + %var34 = icmp ne i32 %var33, %var32 + br i1 %var34, label %swap, label %done +swap: + br i1 %var31, label %swap_to_then, label %swap_to_else +swap_to_then: + %var35 = load i32, i32* @cond_1_else_root + %var36 = load i32, i32* @cond_1_then_root + %var37 = load i32, i32* @cond_1_marker + call void @js_replace_at_marker(i32 %var37, i32 %var35, i32 %var36) + store i32 1, i32* @cond_1_current + br label %done +swap_to_else: + %var38 = load i32, i32* @cond_1_then_root + %var39 = load i32, i32* @cond_1_else_root + %var40 = load i32, i32* @cond_1_marker + call void @js_replace_at_marker(i32 %var40, i32 %var38, i32 %var39) + store i32 0, i32* @cond_1_current + br label %done +done: + ret void +} + +define i32 @_render_cond_1_then() { +entry: + %var42 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var41 = call i32 @js_create_element(i8* %var42) + %var44 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var43 = call i32 @js_create_element(i8* %var44) + %var45 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0 + %var46 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.2, i32 0, i32 0 + call void @js_set_attribute(i32 %var43, i8* %var45, i8* %var46) + %var48 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.3, i32 0, i32 0 + %var47 = call i32 @js_create_element(i8* %var48) + %var49 = call i32 @js_create_text(i8* getelementptr inbounds ([17 x i8], [17 x i8]* @.str.6, i32 0, i32 0)) + call void @js_append_child(i32 %var47, i32 %var49) + call void @js_append_child(i32 %var43, i32 %var47) + %var51 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.5, i32 0, i32 0 + %var50 = call i32 @js_create_element(i8* %var51) + call void @js_append_child(i32 %var43, i32 %var50) + call void @js_append_child(i32 %var41, i32 %var43) + ret i32 %var41 +} + +define i32 @_render_cond_1_else() { +entry: + %var53 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var52 = call i32 @js_create_element(i8* %var53) + ret i32 %var52 +} + +; Component render function +define i32 @render_DebugConsole(i32 %component_name, i32 %features, i32 %console_commands) { +entry: + %var55 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var54 = call i32 @js_create_element(i8* %var55) + %var56 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.7, i32 0, i32 0 + %var57 = getelementptr inbounds [11 x i8], [11 x i8]* @.str.8, i32 0, i32 0 + call void @js_set_attribute(i32 %var54, i8* %var56, i8* %var57) + %var59 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var58 = call i32 @js_create_element(i8* %var59) + %var60 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0 + %var61 = getelementptr inbounds [13 x i8], [13 x i8]* @.str.9, i32 0, i32 0 + call void @js_set_attribute(i32 %var58, i8* %var60, i8* %var61) + call void @js_append_child(i32 %var54, i32 %var58) + %var63 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var62 = call i32 @js_create_element(i8* %var63) + %var64 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0 + %var65 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.10, i32 0, i32 0 + call void @js_set_attribute(i32 %var62, i8* %var64, i8* %var65) + %var66 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.7, i32 0, i32 0 + %var67 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.10, i32 0, i32 0 + call void @js_set_attribute(i32 %var62, i8* %var66, i8* %var67) + %var68 = call i32 @js_create_text(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.11, i32 0, i32 0)) + call void @js_append_child(i32 %var62, i32 %var68) + call void @js_append_child(i32 %var54, i32 %var62) + %var70 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var69 = call i32 @js_create_element(i8* %var70) + %var71 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0 + %var72 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.12, i32 0, i32 0 + call void @js_set_attribute(i32 %var69, i8* %var71, i8* %var72) + %var74 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var73 = call i32 @js_create_element(i8* %var74) + %var75 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0 + %var76 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.2, i32 0, i32 0 + call void @js_set_attribute(i32 %var73, i8* %var75, i8* %var76) + %var78 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.3, i32 0, i32 0 + %var77 = call i32 @js_create_element(i8* %var78) + %var79 = call i32 @js_create_text(i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.str.13, i32 0, i32 0)) + call void @js_append_child(i32 %var77, i32 %var79) + call void @js_append_child(i32 %var73, i32 %var77) + %var81 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.5, i32 0, i32 0 + %var80 = call i32 @js_create_element(i8* %var81) + %var83 = getelementptr inbounds [1 x i8], [1 x i8]* @.str.14, i32 0, i32 0 + %var82 = call i32 @js_create_text(i8* %var83) + call void @js_append_child(i32 %var80, i32 %var82) + call void @js_append_child(i32 %var73, i32 %var80) + call void @js_append_child(i32 %var69, i32 %var73) + %var84 = call i32 @js_create_marker() + call void @js_append_child(i32 %var69, i32 %var84) + store i32 %var84, i32* @cond_0_marker + call void @_init_conditional_0() + %var85 = call i32 @js_create_marker() + call void @js_append_child(i32 %var69, i32 %var85) + store i32 %var85, i32* @cond_1_marker + call void @_init_conditional_1() + call void @js_append_child(i32 %var54, i32 %var69) + %var87 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0 + %var86 = call i32 @js_create_element(i8* %var87) + %var88 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0 + %var89 = getelementptr inbounds [18 x i8], [18 x i8]* @.str.15, i32 0, i32 0 + call void @js_set_attribute(i32 %var86, i8* %var88, i8* %var89) + %var90 = call i32 @js_create_text(i8* getelementptr inbounds ([29 x i8], [29 x i8]* @.str.16, i32 0, i32 0)) + call void @js_append_child(i32 %var86, i32 %var90) + ret i32 %var54 +} + +; Component render function +define i32 @render_RitzMain() { +entry: + %var91 = call i32 @render_DebugConsole() + ret i32 %var91 +} + +; Main entry point +define void @main() { +entry: + %root = call i32 @render_RitzMain() + call void @js_set_root(i32 %root) + ret void +} diff --git a/examples/runtime/Main.wasm b/examples/runtime/Main.wasm new file mode 100755 index 0000000..964d2a4 Binary files /dev/null and b/examples/runtime/Main.wasm differ diff --git a/examples/runtime/ritz-runtime.js b/examples/runtime/ritz-runtime.js index f0d97d2..8ee9c96 100644 --- a/examples/runtime/ritz-runtime.js +++ b/examples/runtime/ritz-runtime.js @@ -77,6 +77,14 @@ class RitzRuntime { } } + js_toggle_debug_console() { + // Bridge function to toggle debug console from WASM + if (window.ritzLoader) { + window.ritzLoader.toggleDebugConsole(); + } + this._log('[Debug] Toggle debug console from WASM'); + } + js_set_root(elemId) { const element = this.nodes.get(elemId); if (element) { @@ -151,11 +159,16 @@ class RitzRuntime { this._log(`[Reactive] ${name}()`); } } + + // Update debug console stats if debug mode is enabled + if (window.ritzLoader && this.debugEnabled) { + window.ritzLoader.updateDebugStats(); + } } updateSignalDisplay(signalName, value) { // Find DOM elements that should display this signal - // For now, update any element with class matching signal name or data-signal attribute + // IMPORTANT: Only search within this runtime's root element to avoid cross-contamination const selectors = [ `[data-signal="${signalName}"]`, `.${signalName}`, @@ -163,7 +176,8 @@ class RitzRuntime { ]; selectors.forEach(selector => { - const elements = document.querySelectorAll(selector); + // Only query within this runtime's root element, not the entire document + const elements = this.rootElement.querySelectorAll(selector); elements.forEach(el => { // Check if text contains the signal name or looks like a counter const text = el.textContent; @@ -284,8 +298,10 @@ class RitzLoader { constructor(options = {}) { this.rootElement = options.rootElement || document.getElementById('app'); this.statusElement = options.statusElement || document.getElementById('status'); + this.debug = options.debug !== undefined ? options.debug : false; this.onLoad = options.onLoad || (() => {}); this.onError = options.onError || ((err) => console.error(err)); + this.debugConsoleRuntime = null; } async load(wasmPath, componentName = null, props = {}) { @@ -353,6 +369,11 @@ class RitzLoader { this.onLoad(runtime, instance); + // Load debug console if debug mode is enabled + if (this.debug) { + await this.loadDebugConsole(componentName); + } + return { runtime, instance }; } catch (error) { @@ -362,6 +383,149 @@ class RitzLoader { } } + async loadDebugConsole(appComponentName) { + try { + console.log('[Debug] Loading debug console...'); + + // Create a container for the debug console + const debugContainer = document.createElement('div'); + debugContainer.id = 'debug-console-root'; + document.body.appendChild(debugContainer); + + console.log('[Debug] Created container:', debugContainer); + + // Create debug console runtime + const debugRuntime = new RitzRuntime(debugContainer); + debugRuntime.debugEnabled = false; // Don't log debug console's own operations + + // Load DebugConsole.wasm from runtime directory + const debugWasmPath = '../runtime/DebugConsole.wasm'; + console.log('[Debug] Fetching:', debugWasmPath); + + const response = await fetch(debugWasmPath); + if (!response.ok) { + console.warn('Could not load debug console:', response.status); + return; + } + + console.log('[Debug] DebugConsole.wasm loaded successfully'); + + const wasmBytes = await response.arrayBuffer(); + + // Create imports for debug console WASM + const imports = { + env: { + js_create_element: (ptr) => debugRuntime.js_create_element(ptr), + js_create_text: (ptr) => debugRuntime.js_create_text(ptr), + js_create_text_with_int: (p, i) => debugRuntime.js_create_text_with_int(p, i), + js_append_child: (p, c) => debugRuntime.js_append_child(p, c), + js_set_attribute: (e, n, v) => debugRuntime.js_set_attribute(e, n, v), + js_set_root: (e) => debugRuntime.js_set_root(e), + js_bind_event: (e, n, h) => debugRuntime.js_bind_event(e, n, h), + js_update_text_content: (n, t) => debugRuntime.js_update_text_content(n, t), + js_update_text_with_int: (n, p, i) => debugRuntime.js_update_text_with_int(n, p, i), + js_create_marker: () => debugRuntime.js_create_marker(), + js_replace_at_marker: (m, o, n) => debugRuntime.js_replace_at_marker(m, o, n), + js_toggle_debug_console: () => debugRuntime.js_toggle_debug_console(), + } + }; + + const { instance } = await WebAssembly.instantiate(wasmBytes, imports); + + // Setup debug runtime + debugRuntime.setMemory(instance.exports.memory); + debugRuntime.setWasmInstance(instance); + + // Render debug console + console.log('[Debug] Rendering debug console...'); + instance.exports.main(); + debugRuntime.updateSignals(); + + this.debugConsoleRuntime = debugRuntime; + + console.log('[Debug] Debug console rendered. Checking DOM...'); + const debugElement = document.querySelector('.ritz-debug'); + console.log('[Debug] Debug element:', debugElement); + console.log('[Debug] Debug element classes:', debugElement?.className); + + // Hide the loader status element (we have our own in debug console) + if (this.statusElement) { + this.statusElement.style.display = 'none'; + } + + // Populate runtime stats + this.updateDebugStats(appComponentName); + + // Setup keyboard shortcut (Ctrl+`) + document.addEventListener('keydown', (e) => { + if (e.ctrlKey && e.key === '`') { + e.preventDefault(); + console.log('[Debug] Toggle debug console'); + this.toggleDebugConsole(); + } + }); + + // Make debug console and loader globally available + window.ritzDebugConsole = debugRuntime; + window.ritzLoader = this; + + console.log('🎭 Ritz Debug Console loaded! Press Ctrl+` to toggle.'); + + } catch (error) { + console.warn('Failed to load debug console:', error); + } + } + + toggleDebugConsole() { + // Toggle the debug console by toggling body class + // The Ritz debug console component manages its own visibility via signal + // But we need to trigger the body class for layout + document.body.classList.toggle('debug-open'); + + // Find the debug console element and toggle its hidden class + const debugElement = document.querySelector('.ritz-debug'); + if (debugElement) { + debugElement.classList.toggle('hidden'); + } + } + + updateDebugStats(componentName = 'Main') { + // Update status with component name (inside debug console) + const statusEl = document.querySelector('#ritz-debug .status'); + if (statusEl) { + statusEl.textContent = `✅ ${componentName} loaded!`; + statusEl.className = 'status success'; + } + + // Update component section + const componentPre = document.querySelector('#ritz-debug .debug-section:nth-child(1) pre'); + if (componentPre && window.ritzInstance) { + const wasmSize = window.ritzInstance.exports.memory.buffer.byteLength; + componentPre.textContent = `${componentName}.wasm (${Math.round(wasmSize / 1024)}KB)`; + } + + // Update runtime stats + const statsPre = document.querySelector('#ritz-debug .debug-section:nth-child(3) pre'); + if (statsPre && window.ritzRuntime && window.ritzInstance) { + const memory = window.ritzInstance.exports.memory.buffer.byteLength; + const memoryKB = Math.round(memory / 1024); + const memoryMB = (memory / 1024 / 1024).toFixed(2); + const nodeCount = window.ritzRuntime.nodes.size; + + // Count signals (globals that start with @signal_) + const exports = Object.keys(window.ritzInstance.exports); + const signalCount = exports.filter(name => + name.startsWith('get_') || name.startsWith('set_') + ).length / 2; + + statsPre.textContent = + `Memory: ${memoryKB}KB (${memoryMB}MB)\n` + + `Signals: ${signalCount} reactive\n` + + `DOM nodes: ${nodeCount} cached\n` + + `Log entries: ${window.ritzRuntime.log.length}`; + } + } + setStatus(message, type = '') { if (this.statusElement) { this.statusElement.textContent = message; diff --git a/examples/runtime/styles.css b/examples/runtime/styles.css index 0e7528f..ff7ac13 100644 --- a/examples/runtime/styles.css +++ b/examples/runtime/styles.css @@ -98,3 +98,142 @@ ul { li { margin: 8px 0; } + +/* ======================================== + Ritz Debug Console Styles + ======================================== */ + +.ritz-debug { + position: fixed; + top: 0; + right: 0; + width: 400px; + height: 100vh; + background: #1e1e1e; + color: #d4d4d4; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 13px; + box-shadow: -2px 0 10px rgba(0,0,0,0.3); + transition: transform 0.3s ease; + z-index: 9999; + overflow-y: auto; +} + +.ritz-debug.hidden { + transform: translateX(100%); +} + +.ritz-debug .debug-header { + background: #252525; + padding: 15px; + border-bottom: 1px solid #3e3e3e; + display: flex; + justify-content: space-between; + align-items: center; +} + +.ritz-debug .debug-header h3 { + margin: 0; + font-size: 16px; + font-weight: 600; + color: #d4d4d4; +} + +.ritz-debug .debug-header button { + background: none; + border: none; + color: #d4d4d4; + font-size: 24px; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + line-height: 1; + transform: none; + box-shadow: none; +} + +.ritz-debug .debug-header button:hover { + color: #fff; + background: none; + transform: none; + box-shadow: none; +} + +#ritz-debug .status { + position: static !important; + margin: 15px; + padding: 10px; + background: #2d2d30; + border: none !important; + border-left: 3px solid #007acc !important; + border-radius: 3px; + font-size: 12px; + box-shadow: none !important; +} + +#ritz-debug .status.success { + background: #2d2d30 !important; + border-left-color: #4ec9b0 !important; + color: #4ec9b0 !important; +} + +#ritz-debug .status.error { + background: #2d2d30 !important; + border-left-color: #f48771 !important; + color: #f48771 !important; +} + +.debug-content { + padding: 15px; +} + +.debug-section { + margin-bottom: 20px; +} + +.debug-section h4 { + margin: 0 0 10px 0; + font-size: 12px; + text-transform: uppercase; + color: #858585; + font-weight: 600; +} + +.debug-section pre { + background: #252525; + padding: 10px; + border-radius: 3px; + overflow-x: auto; + margin: 0; + font-size: 11px; + line-height: 1.6; + color: #d4d4d4; +} + +.debug-toggle-hint { + position: fixed; + bottom: 20px; + right: 20px; + background: #252525; + color: #d4d4d4; + padding: 8px 12px; + border-radius: 4px; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 11px; + box-shadow: 0 2px 8px rgba(0,0,0,0.3); + opacity: 0; + animation: fadeInOut 3s ease-in-out; + pointer-events: none; + z-index: 10000; +} + +@keyframes fadeInOut { + 0%, 100% { opacity: 0; } + 10%, 90% { opacity: 1; } +} + +/* Adjust app container when debug is open */ +body.debug-open #app { + margin-right: 420px; +} diff --git a/examples/template.html b/examples/template.html index 7a67bb5..46517fe 100644 --- a/examples/template.html +++ b/examples/template.html @@ -12,13 +12,14 @@