Monorepo for Aesthetic.Computer
aesthetic.computer
KidLisp Live Parameter Editing#
🎯 Goal#
Enable live editing of KidLisp variables (like framerate, line coordinates, colors) while the program is running, without restarting from frame 0.
💡 Concept#
When you change a def value in the editor while the code is playing, update only that variable in the running instance instead of reloading the entire program.
🎬 User Experience#
Before (Current)#
(def speed 5)
(def color "red")
(line 0 0 100 100)
- User edits
speed 5→speed 10 - Presses Enter/Play
- Program restarts from frame 0 (frameCount resets, all state lost)
After (Proposed)#
(def speed 5)
(def color "red")
(line 0 0 100 100)
- User edits
speed 5→speed 10 - Variable updates live, program keeps running
- Frame count continues, timing state preserved
- Only the changed variable updates
🔧 Implementation Strategy#
Phase 1: Variable Hot-Swapping (Simplest)#
Detect when only def statements have changed:
// In kidlisp.mjs
updateVariable(name, value) {
// Update the variable without restarting
this.globalDef[name] = value;
// Keep frameCount, timing state, etc.
}
// In kidlisp.com or boot.mjs
function handleCodeUpdate(newCode, oldCode) {
const changedDefs = detectChangedDefs(newCode, oldCode);
if (changedDefs.length > 0 && onlyDefsChanged(newCode, oldCode)) {
// Hot-swap the variables
changedDefs.forEach(({ name, value }) => {
kidlispInstance.updateVariable(name, value);
});
// Don't reload, keep running!
} else {
// Structure changed, do full reload
kidlispInstance.reload(newCode);
}
}
Phase 2: Smart Diff Detection#
Compare AST to detect what changed:
function detectChanges(oldAST, newAST) {
return {
defsChanged: [...], // Variable definitions that changed
codeChanged: false, // Whether non-def code changed
timingChanged: false, // Whether timing expressions changed
};
}
Phase 3: Partial Recompilation (Advanced)#
For more complex changes:
- Changed
def→ hot-swap variable - Changed function body → recompile function, keep state
- Changed timing expression → update timing map, keep counters
- Changed drawing code → reparse that section
📊 What to Preserve#
Must Keep Running#
frameCount- Current frame numberlastSecondExecutions- Timing state for1s...,2s...etcsequenceCounters- Sequence state for timingonceExecuted- Track ofonceblocks- Embedded layer buffers
bakes- Baked background layers
Can Update#
globalDef- Variables defined with(def ...)- Parsed AST if code structure changed
- Function definitions (
laterfunctions)
🎨 UI Considerations#
Visual Feedback#
🔴 PLAYING (Frame 234) ← Show it's running
✏️ Variable 'speed' updated (5 → 10) ← Confirm change
Keyboard Shortcuts#
Cmd+Enter- Smart reload (hot-swap if possible, full reload if needed)Cmd+Shift+Enter- Force full reload (restart from frame 0)
🏗️ Architecture#
kidlisp.mjs Changes#
class KidLisp {
// New method
hotUpdateVariable(name, value) {
this.globalDef[name] = value;
// Invalidate any cached evaluations that use this variable
this.invalidateVariableCache(name);
}
// New method
canHotUpdate(newSource) {
const newAST = this.parse(newSource);
const diff = this.diffAST(this.ast, newAST);
return diff.onlyDefsChanged;
}
// Enhanced reload
reload(newSource, options = {}) {
if (options.preserveState && this.canHotUpdate(newSource)) {
// Hot update path
this.hotUpdate(newSource);
} else {
// Full reload path (current behavior)
this.fullReload(newSource);
}
}
}
boot.mjs Changes#
// Enhance existing kidlisp-reload handler
if (event.data?.type === "kidlisp-reload") {
const code = event.data.code;
const preserveState = event.data.preserveState !== false;
window.acSEND({
type: "piece-reload",
content: {
source: code,
preserveState: preserveState // NEW
}
});
}
kidlisp.com Changes#
// Add option to preserve state
function sendCode(preserveState = true) {
previewIframe.contentWindow.postMessage({
type: 'kidlisp-reload',
code: editor.getValue(),
preserveState: preserveState // NEW
}, aestheticUrl);
}
// Keyboard shortcuts
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
sendCode(true); // Smart reload
});
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => {
sendCode(false); // Force full reload
});
🧪 Test Cases#
Should Hot-Update#
; Change value
(def speed 5) → (def speed 10)
; Change color
(def color "red") → (def color "blue")
; Change multiple defs
(def x 10)
(def y 20)
Should Full-Reload#
; Add new code
(line 0 0 100 100) → (line 0 0 100 100) (circle 50 50 25)
; Change timing
(1s (ink red)) → (2s (ink red))
; Change structure
(repeat 5 i (box i)) → (repeat 10 i (box i))
🚀 Benefits#
- Faster iteration - Tweak values without losing animation progress
- Better debugging - See parameter effects immediately
- Live performance - Adjust values during live coding performances
- Teaching tool - Show immediate cause/effect of parameter changes
📝 Notes#
- Start simple: only hot-swap
defchanges - Can expand to timing expressions, function bodies later
- Keep full reload as fallback for complex changes
- Consider auto-detection vs manual mode selection
🎯 Success Criteria#
- ✅ Can change
(def speed 5)to(def speed 10)while running - ✅ Frame count continues from current value
- ✅ Timing state (1s..., 2s...) preserved
- ✅ Visual feedback shows what was updated
- ✅ Fallback to full reload for structural changes