diff --git a/wasm/app/app.js b/wasm/app/app.js index 67a172e..c666f30 100644 --- a/wasm/app/app.js +++ b/wasm/app/app.js @@ -223,8 +223,14 @@ function runOnTierInWorker(tier, src, onLoading) { // First chunk spawns the in-progress block; subsequent // chunks append to it. User sees displays land // immediately, not just at the end of the run. + // Worker splits chunks on \n at the source and sends + // each line with an eol flag — re-attach the newline + // here so liveBlock.append still walks bytes looking + // for charCodeAt 10 and stacks per line. Avoids any + // unknown-quantity newline loss between Module.print + // and the main thread. if (!liveBlock) liveBlock = startLiveBlock(tier); - liveBlock.append(e.data.chunk); + liveBlock.append(e.data.chunk + (e.data.eol ? "\n" : "")); } else if (e.data.kind === "done") { w.removeEventListener("message", handler); workerState.pending[tier] = null; diff --git a/wasm/app/worker.mjs b/wasm/app/worker.mjs index 795f0eb..c729a5c 100644 --- a/wasm/app/worker.mjs +++ b/wasm/app/worker.mjs @@ -46,8 +46,40 @@ self.onmessage = async (e) => { // blocks the worker, but displays BEFORE/AFTER the bend // round-trip surface immediately instead of waiting for // the whole eval to finish. Long demos feel alive. + // + // Defensive newline normalization: the C-tier loader + // adds the trailing \n that Emscripten's Module.print + // strips, but we kept seeing horizontal output in fox's + // Firefox tab as if the \n was lost somewhere on the + // wire. To rule out anything between here and the main + // thread, split each chunk on \n at the source and post + // one message per line — newline preserved as a flag + // rather than a byte. The main-thread receiver knows to + // re-add the line break. (chunk) => { - self.postMessage({ kind: "chunk", runId, tier, chunk }); + if (!chunk) return; + let start = 0; + for (let i = 0; i < chunk.length; i++) { + if (chunk.charCodeAt(i) === 10) { + self.postMessage({ + kind: "chunk", + runId, + tier, + chunk: chunk.slice(start, i), + eol: true, + }); + start = i + 1; + } + } + if (start < chunk.length) { + self.postMessage({ + kind: "chunk", + runId, + tier, + chunk: chunk.slice(start), + eol: false, + }); + } }, ); self.postMessage({ kind: "done", runId, output }); diff --git a/www/playground/app.js b/www/playground/app.js index 67a172e..c666f30 100644 --- a/www/playground/app.js +++ b/www/playground/app.js @@ -223,8 +223,14 @@ function runOnTierInWorker(tier, src, onLoading) { // First chunk spawns the in-progress block; subsequent // chunks append to it. User sees displays land // immediately, not just at the end of the run. + // Worker splits chunks on \n at the source and sends + // each line with an eol flag — re-attach the newline + // here so liveBlock.append still walks bytes looking + // for charCodeAt 10 and stacks per line. Avoids any + // unknown-quantity newline loss between Module.print + // and the main thread. if (!liveBlock) liveBlock = startLiveBlock(tier); - liveBlock.append(e.data.chunk); + liveBlock.append(e.data.chunk + (e.data.eol ? "\n" : "")); } else if (e.data.kind === "done") { w.removeEventListener("message", handler); workerState.pending[tier] = null; diff --git a/www/playground/worker.mjs b/www/playground/worker.mjs index 795f0eb..c729a5c 100644 --- a/www/playground/worker.mjs +++ b/www/playground/worker.mjs @@ -46,8 +46,40 @@ self.onmessage = async (e) => { // blocks the worker, but displays BEFORE/AFTER the bend // round-trip surface immediately instead of waiting for // the whole eval to finish. Long demos feel alive. + // + // Defensive newline normalization: the C-tier loader + // adds the trailing \n that Emscripten's Module.print + // strips, but we kept seeing horizontal output in fox's + // Firefox tab as if the \n was lost somewhere on the + // wire. To rule out anything between here and the main + // thread, split each chunk on \n at the source and post + // one message per line — newline preserved as a flag + // rather than a byte. The main-thread receiver knows to + // re-add the line break. (chunk) => { - self.postMessage({ kind: "chunk", runId, tier, chunk }); + if (!chunk) return; + let start = 0; + for (let i = 0; i < chunk.length; i++) { + if (chunk.charCodeAt(i) === 10) { + self.postMessage({ + kind: "chunk", + runId, + tier, + chunk: chunk.slice(start, i), + eol: true, + }); + start = i + 1; + } + } + if (start < chunk.length) { + self.postMessage({ + kind: "chunk", + runId, + tier, + chunk: chunk.slice(start), + eol: false, + }); + } }, ); self.postMessage({ kind: "done", runId, output });