Fix reactive conditional rendering with proper branch swapping
Implements efficient reactive conditionals that pre-render branches once and swap DOM nodes without recreation, enabling fast switching with thousands of elements. Key Fixes: - Runtime now calls reevaluate_conditional_*() on signal changes - Match/case swap logic uses phi nodes to load correct old case - Previously hardcoded case_0 as old value, causing accumulation - Branch render functions now wrap all content in container div Implementation: - Pre-render both if/else branches at init - Store roots in globals (@cond_N_then_root, @cond_N_else_root) - On signal change, swap DOM nodes via js_replace_at_marker() - Match statements use switch/phi pattern to select old case Tests: - Added test_reactive_conditionals.py (5 new tests) - Updated test_llvm_codegen_conditional.py for reactive infrastructure - All 35 tests passing Performance: No re-rendering on switches, perfect for dynamic content. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
ac7726df46
commit
e686903170
4 files changed
+272
-41
No files matched your search
@@ -115,6 +115,22 @@ class RitzRuntime {
|
||||
this.updateSignalDisplay(signalName, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-evaluate all reactive conditionals
|
||||
for (const [name, fn] of Object.entries(exports)) {
|
||||
if (name.startsWith('reevaluate_conditional_') && typeof fn === 'function') {
|
||||
fn();
|
||||
this._log(`[Reactive] ${name}()`);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-evaluate all reactive match statements
|
||||
for (const [name, fn] of Object.entries(exports)) {
|
||||
if (name.startsWith('reevaluate_match_') && typeof fn === 'function') {
|
||||
fn();
|
||||
this._log(`[Reactive] ${name}()`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateSignalDisplay(signalName, value) {
|
||||
@@ -154,22 +170,33 @@ class RitzRuntime {
|
||||
// Replace DOM node at marker position
|
||||
const marker = this.nodes.get(markerId);
|
||||
if (!marker) {
|
||||
console.error(`Marker ${markerId} not found`);
|
||||
console.error(`[replace_at_marker] Marker ${markerId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const parent = marker.parentNode;
|
||||
if (!parent) {
|
||||
console.error(`Marker has no parent`);
|
||||
console.error(`[replace_at_marker] Marker ${markerId} has no parent`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove old node if it exists (0 means no old node)
|
||||
this._log(`[replace_at_marker] marker=${markerId}, old=${oldNodeId}, new=${newNodeId}`);
|
||||
|
||||
// Remove old node if it exists (0 means no old node to remove)
|
||||
if (oldNodeId !== 0) {
|
||||
const oldNode = this.nodes.get(oldNodeId);
|
||||
if (oldNode && oldNode.parentNode) {
|
||||
if (!oldNode) {
|
||||
console.warn(`[replace_at_marker] Old node ${oldNodeId} not found in registry`);
|
||||
} else if (!oldNode.parentNode) {
|
||||
console.warn(`[replace_at_marker] Old node ${oldNodeId} not in DOM`);
|
||||
} else if (oldNode.parentNode !== parent) {
|
||||
console.warn(`[replace_at_marker] Old node ${oldNodeId} in wrong parent`);
|
||||
oldNode.parentNode.removeChild(oldNode);
|
||||
this._log(`Removed old node ${oldNodeId}`);
|
||||
this._log(`[replace_at_marker] Removed ${oldNodeId} from wrong parent`);
|
||||
} else {
|
||||
// Remove from correct parent
|
||||
parent.removeChild(oldNode);
|
||||
this._log(`[replace_at_marker] Removed ${oldNodeId} from correct parent`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,11 +204,20 @@ class RitzRuntime {
|
||||
if (newNodeId !== 0) {
|
||||
const newNode = this.nodes.get(newNodeId);
|
||||
if (!newNode) {
|
||||
console.error(`New node ${newNodeId} not found`);
|
||||
console.error(`[replace_at_marker] New node ${newNodeId} not found`);
|
||||
return;
|
||||
}
|
||||
parent.insertBefore(newNode, marker.nextSibling);
|
||||
this._log(`Inserted node ${newNodeId} at marker ${markerId}`);
|
||||
|
||||
// Insert right after the marker
|
||||
// If newNode is already in DOM elsewhere, this will move it
|
||||
const nextSibling = marker.nextSibling;
|
||||
if (nextSibling) {
|
||||
parent.insertBefore(newNode, nextSibling);
|
||||
this._log(`[replace_at_marker] Inserted ${newNodeId} before sibling`);
|
||||
} else {
|
||||
parent.appendChild(newNode);
|
||||
this._log(`[replace_at_marker] Appended ${newNodeId} (no sibling)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user