Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: notebook kidlisp uses postMessage instead of URL encoding

Sends full source to AC iframe via postMessage (kidlisp-reload),
matching the kidlisp.com editor protocol. Fixes commas and newlines
breaking in URL-encoded paths. Also adds xkq-variations workbook.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+120 -40
+65 -37
notebooks/aesthetic.py
··· 221 221 def kidlisp(code, width="100%", height=500, auto_scale=False, tv_mode=False, density=None, show_source=True): 222 222 """ 223 223 Run kidlisp code in Aesthetic Computer. 224 - 224 + 225 + Loads a blank AC iframe and sends the full source via postMessage 226 + (same protocol as the kidlisp.com editor), avoiding URL-encoding issues 227 + with commas, newlines, and special characters. 228 + 225 229 Args: 226 230 code (str): The kidlisp code to execute 227 231 width: Virtual width in AC pixels (default: "100%") ··· 232 236 show_source (bool): If True, display syntax-highlighted source below iframe (default: True) 233 237 """ 234 238 importlib.reload(importlib.import_module('aesthetic')) 235 - 236 - # Use Node.js to encode kidlisp code and get the full URL 239 + 237 240 clean_code = code.strip() 238 - url = encode_kidlisp_with_node(clean_code) 239 - 240 - # Add TV mode parameter if requested 241 + base_url = _get_base_url() 242 + 243 + # Load prompt piece as a blank canvas, then send code via postMessage 244 + url = f"{base_url}/prompt?nolabel=true&nogap=true&notebook=true" 245 + 241 246 if tv_mode: 242 - separator = "&" if "?" in url else "?" 243 - url += f"{separator}tv=true" 247 + url += "&tv=true" 244 248 245 249 density_value = _normalize_density(density, _get_ipython_context()) 246 250 if density_value is not None: 247 - separator = "&" if "?" in url else "?" 248 - url += f"{separator}density={density_value}" 249 - 250 - # Create a stable ID based on content hash to reduce flicker on re-runs 251 + url += f"&density={density_value}" 252 + 251 253 content_hash = hashlib.md5(f"{clean_code}{width}{height}{tv_mode}{density_value}".encode()).hexdigest()[:8] 252 254 iframe_id = f"ac-iframe-{content_hash}" 253 255 254 256 iframe_width, iframe_height = _compute_iframe_dimensions(width, height, density_value) 255 - 256 - # Always use HTML approach with no margins, positioned at top-left 257 + 258 + # Escape the code for embedding in JavaScript 259 + import json 260 + escaped_code = json.dumps(clean_code) 261 + 262 + style = "background: transparent; margin: 0; padding: 0; border: none; display: block;" 257 263 if auto_scale: 258 - iframe_html = f''' 259 - <div style="margin: -8px -8px 0 -8px; padding: 0; overflow: hidden;"> 260 - <iframe id="{iframe_id}" src="{url}" 261 - width="{iframe_width}" 262 - height="{iframe_height}" 263 - frameborder="0" 264 - style="background: transparent; margin: 0; padding: 0; border: none; display: block; transform-origin: top left; max-width: 100%; height: auto; aspect-ratio: 1/1;"> 265 - </iframe> 266 - </div> 267 - ''' 268 - else: 269 - iframe_html = f''' 270 - <div style="margin: -8px -8px 0 -8px; padding: 0; overflow: hidden;"> 271 - <iframe id="{iframe_id}" src="{url}" 272 - width="{iframe_width}" 273 - height="{iframe_height}" 274 - frameborder="0" 275 - style="background: transparent; margin: 0; padding: 0; border: none; display: block;"> 276 - </iframe> 277 - </div> 278 - ''' 279 - 264 + style += " transform-origin: top left; max-width: 100%; height: auto; aspect-ratio: 1/1;" 265 + 266 + # Embed iframe + JS that sends code via postMessage once the iframe is ready 267 + iframe_html = f''' 268 + <div style="margin: -8px -8px 0 -8px; padding: 0; overflow: hidden;"> 269 + <iframe id="{iframe_id}" src="{url}" 270 + width="{iframe_width}" 271 + height="{iframe_height}" 272 + frameborder="0" 273 + style="{style}"> 274 + </iframe> 275 + </div> 276 + <script> 277 + (function() {{ 278 + var iframe = document.getElementById("{iframe_id}"); 279 + var code = {escaped_code}; 280 + var origin = "{base_url}"; 281 + var sent = false; 282 + function trySend() {{ 283 + if (sent) return; 284 + if (iframe && iframe.contentWindow) {{ 285 + iframe.contentWindow.postMessage({{ 286 + type: "kidlisp-reload", 287 + code: code 288 + }}, origin); 289 + sent = true; 290 + }} 291 + }} 292 + // Listen for ready signal from the iframe 293 + window.addEventListener("message", function handler(e) {{ 294 + if (e.source === iframe.contentWindow && e.data && e.data.type === "ready") {{ 295 + trySend(); 296 + window.removeEventListener("message", handler); 297 + }} 298 + }}); 299 + // Also try after a delay as fallback 300 + iframe.addEventListener("load", function() {{ 301 + setTimeout(trySend, 500); 302 + setTimeout(trySend, 1500); 303 + }}); 304 + }})(); 305 + </script> 306 + ''' 307 + 280 308 display(HTML(iframe_html)) 281 - 309 + 282 310 # Display source code if requested 283 311 if show_source: 284 312 _display_kidlisp_source(clean_code)
+55 -3
notebooks/xkq-variations.ipynb
··· 160 160 }, 161 161 { 162 162 "cell_type": "code", 163 - "execution_count": null, 163 + "execution_count": 1, 164 164 "id": "7avz6prnjgk", 165 165 "metadata": {}, 166 - "outputs": [], 166 + "outputs": [ 167 + { 168 + "data": { 169 + "text/html": [ 170 + "\n", 171 + " <div style=\"margin: -8px -8px 0 -8px; padding: 0; overflow: hidden;\">\n", 172 + " <iframe id=\"ac-iframe-bf9876e8\" src=\"https://localhost:8888/black~mask_0_0_w_h/2,_ink_zebra_8~box_?_?_?~unmask~scroll_(?_0_-1_1)_10~ink_rainbow_48,_line,_spin_3,_burn~zoom_0.25,_scroll_frame*3?nolabel=true&nogap=true&density=2\" \n", 173 + " width=\"100%\" \n", 174 + " height=\"480\" \n", 175 + " frameborder=\"0\"\n", 176 + " style=\"background: transparent; margin: 0; padding: 0; border: none; display: block;\">\n", 177 + " </iframe>\n", 178 + " </div>\n", 179 + " " 180 + ], 181 + "text/plain": [ 182 + "<IPython.core.display.HTML object>" 183 + ] 184 + }, 185 + "metadata": {}, 186 + "output_type": "display_data" 187 + }, 188 + { 189 + "data": { 190 + "text/html": [ 191 + "\n", 192 + " <pre style=\"margin-top: 12px; padding: 12px; background: #f9f9f9; border: 1px solid #e0e0e0; border-radius: 4px; font-family: 'Courier New', monospace; font-size: 12px; line-height: 1.5; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;\">\n", 193 + " black\n", 194 + "mask <span style=<span style=\"color: #FF9800<span style=\"color: #999;\">;\">\"color: #2196F3;\"</span>>0</span> <span style=<span style=\"color: #FF9800;\">\"color: #2196F3;\"</span>>0</span> w h/<span style=<span style=\"color: #FF9800;\">\"color: #2196F3;\"</span>>2</span>, <span style=<span style=\"color: #FF9800;\">\"color: #4CAF50; font-weight: bold;\"</span>>ink</span> zebra <span style=<span style=\"color: #FF9800;\">\"color: #2196F3;\"</span>>8</span></span>\n", 195 + "<span style=<span style=\"color: #FF9800<span style=\"color: #999;\">;\">\"color: #4CAF50; font-weight: bold;\"</span>>box</span> ? ? ?</span>\n", 196 + "unmask\n", 197 + "scroll <span style=\"color: #9C27B0;\">(</span>? <span style=<span style=\"color: #FF9800<span style=\"color: #999;\">;\">\"color: #2196F3;\"</span>>0</span> -<span style=<span style=\"color: #FF9800;\">\"color: #2196F3;\"</span>>1</span> <span style=<span style=\"color: #FF9800;\">\"color: #2196F3;\"</span>>1</span><span style=\"color: #9C27B0;\">)</span> <span style=<span style=\"color: #FF9800;\">\"color: #2196F3;\"</span>>10</span></span>\n", 198 + "<span style=<span style=\"color: #FF9800<span style=\"color: #999;\">;\">\"color: #4CAF50; font-weight: bold;\"</span>>ink</span> rainbow <span style=<span style=\"color: #FF9800;\">\"color: #2196F3;\"</span>>48</span>, <span style=<span style=\"color: #FF9800;\">\"color: #4CAF50; font-weight: bold;\"</span>>line</span>, spin <span style=<span style=\"color: #FF9800;\">\"color: #2196F3;\"</span>>3</span>, burn</span>\n", 199 + "zoom <span style=<span style=\"color: #FF9800<span style=\"color: #999;\">;\">\"color: #2196F3;\"</span>>0.25</span>, scroll frame*<span style=<span style=\"color: #FF9800;\">\"color: #2196F3;\"</span>>3</span></span>\n", 200 + " </pre>\n", 201 + " " 202 + ], 203 + "text/plain": [ 204 + "<IPython.core.display.HTML object>" 205 + ] 206 + }, 207 + "metadata": {}, 208 + "output_type": "display_data" 209 + } 210 + ], 167 211 "source": [ 168 212 "%%ac 240\n", 169 213 "black\n", ··· 456 500 "name": "python3" 457 501 }, 458 502 "language_info": { 503 + "codemirror_mode": { 504 + "name": "ipython", 505 + "version": 3 506 + }, 507 + "file_extension": ".py", 508 + "mimetype": "text/x-python", 459 509 "name": "python", 460 - "version": "3.11.0" 510 + "nbconvert_exporter": "python", 511 + "pygments_lexer": "ipython3", 512 + "version": "3.11.15" 461 513 } 462 514 }, 463 515 "nbformat": 4,