From 32d38754f863e71b7d19ee396295d3f463cd9ee4 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 15 Jun 2026 05:42:02 -0400 Subject: [PATCH] playground streaming: appendChild(TextNode) instead of textContent += MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit liveBlock.append was doing `pre.textContent += chunk` on every streamed chunk. textContent's setter wipes all existing child nodes and creates a single new text node from the concatenated read+new value — perfectly correct semantically but under a fast C-tier emit loop (one chunk per (newline) printf) the per-update restringify was visibly dropping line breaks, rendering tick 0tick 10000tick 20000tick 30000… horizontally instead of stacking vertically. `pre.appendChild(document.createTextNode(chunk))` appends a new text node alongside existing ones — no read-back, no restringify. Browser renders the sequence of text nodes as concatenated text inside the
 which already has `white-space: pre`, so every
embedded \n produces a real line break.

Pairs with the c-tier loader's `currentOnChunk(line + "\n")` fix:
loader adds the \n that Emscripten's Module.print convention
stripped, append preserves the \n end-to-end into the DOM.
---
 wasm/app/app.js       | 9 ++++++++-
 www/playground/app.js | 9 ++++++++-
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/wasm/app/app.js b/wasm/app/app.js
index 2c61f8a..1f9d71e 100644
--- a/wasm/app/app.js
+++ b/wasm/app/app.js
@@ -263,7 +263,14 @@ function startLiveBlock(tierName) {
     outputEl.appendChild(block);
     return {
         append(chunk) {
-            pre.textContent += chunk;
+            // Append a TextNode rather than `pre.textContent += chunk`
+            // — the latter re-reads + restringifies + re-sets all
+            // existing children on every chunk, which under a fast
+            // C-tier loop (one chunk per (newline) printf) was
+            // dropping line breaks and rendering "tick 0tick 10000…"
+            // horizontally instead of stacking vertically. TextNode
+            // append preserves every byte verbatim.
+            pre.appendChild(document.createTextNode(chunk));
             outputEl.scrollTop = outputEl.scrollHeight;
         },
         finalize(elapsedMs, kind, fullOutput) {
diff --git a/www/playground/app.js b/www/playground/app.js
index 2c61f8a..1f9d71e 100644
--- a/www/playground/app.js
+++ b/www/playground/app.js
@@ -263,7 +263,14 @@ function startLiveBlock(tierName) {
     outputEl.appendChild(block);
     return {
         append(chunk) {
-            pre.textContent += chunk;
+            // Append a TextNode rather than `pre.textContent += chunk`
+            // — the latter re-reads + restringifies + re-sets all
+            // existing children on every chunk, which under a fast
+            // C-tier loop (one chunk per (newline) printf) was
+            // dropping line breaks and rendering "tick 0tick 10000…"
+            // horizontally instead of stacking vertically. TextNode
+            // append preserves every byte verbatim.
+            pre.appendChild(document.createTextNode(chunk));
             outputEl.scrollTop = outputEl.scrollHeight;
         },
         finalize(elapsedMs, kind, fullOutput) {