From d05cebfb96763af105a02a5a187642fbc9698cf6 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 12:22:34 +0100 Subject: [PATCH 1/2] fix(web): notebook error tracebacks no longer show ANSI color codes IPython colors every saved traceback frame with ANSI SGR sequences, and the notebook preview printed them raw into a plain
. Strip SGR from error traceback frames only; stream text, sources and literal escape spellings stay as saved, and the frame newline boundary is unchanged. Co-authored-by: Claude Opus 5.5Co-authored-by: GPT 6.1 Sol --- .../src/components/files/FilePreviewPanel.test.ts | 15 ++++++++++----- .../components/files/extendedDocumentPreview.ts | 8 +++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/files/FilePreviewPanel.test.ts b/apps/web/src/components/files/FilePreviewPanel.test.ts index d9361ba21244a..7b8bd0592ae30 100644 --- a/apps/web/src/components/files/FilePreviewPanel.test.ts +++ b/apps/web/src/components/files/FilePreviewPanel.test.ts @@ -296,16 +296,18 @@ Third}`; }, ], }); - // IPython traceback frames can omit trailing newlines or place ANSI resets after them. + // IPython traceback frames carry ANSI SGR colors and can omit trailing newlines. const failed = openNotebook( bytes( JSON.stringify({ nbformat: 4, cells: [ + { cell_type: "markdown", source: "\u001b[1m# Notes\u001b[0m" }, { cell_type: "code", - source: "fail()", + source: 'print("\u001b[32m")\nfail()', outputs: [ + { output_type: "stream", text: "\u001b[33mwarning\u001b[0m\n" }, { output_type: "error", traceback: [ @@ -330,7 +332,7 @@ Third}`; "\u001b[31mTraceback (most recent call last):\n\u001b[0m", "\u001b[32mCell In[3], line 1\n----> 1 fail()\n\u001b[0m\u001b[39m", "\u001b[31mValueError: preview proof\u001b[0m", - "final entry", + "final entry \\x1b[31m kept", ], }, ], @@ -340,7 +342,10 @@ Third}`; ), ); if (failed.type !== "notebook") throw new Error("Expected notebook"); - expect(failed.cells[0]!.outputs).toEqual([ + expect(failed.cells[0]!.source).toBe("\u001b[1m# Notes\u001b[0m"); + expect(failed.cells[1]!.source).toBe('print("\u001b[32m")\nfail()'); + expect(failed.cells[1]!.outputs).toEqual([ + { kind: "text", text: "\u001b[33mwarning\u001b[0m\n" }, { kind: "text", text: "-----\nValueError Traceback (most recent call last)\nCell In[1], line 4\n----> 4 fail()\nCell In[1], line 2, in fail()\n----> 2 raise ValueError()\nValueError: preview proof", @@ -351,7 +356,7 @@ Third}`; }, { kind: "text", - text: "\u001b[31mTraceback (most recent call last):\n\u001b[0m\u001b[32mCell In[3], line 1\n----> 1 fail()\n\u001b[0m\u001b[39m\u001b[31mValueError: preview proof\u001b[0m\nfinal entry", + text: "Traceback (most recent call last):\nCell In[3], line 1\n----> 1 fail()\nValueError: preview proof\nfinal entry \\x1b[31m kept", }, ]); expect(() => openNotebook(bytes('{"nbformat":3,"cells":[]}'))).toThrow("version 4"); diff --git a/apps/web/src/components/files/extendedDocumentPreview.ts b/apps/web/src/components/files/extendedDocumentPreview.ts index 6b840a00543a6..78bf09541bc07 100644 --- a/apps/web/src/components/files/extendedDocumentPreview.ts +++ b/apps/web/src/components/files/extendedDocumentPreview.ts @@ -431,12 +431,14 @@ function notebookText(value: unknown, limit = 256_000): string { if (result.length > limit) throw new Error("A notebook cell is too large to preview."); return result; } -// IPython traceback frames may omit a final newline or follow it with ANSI SGR sequences. +// IPython traceback frames carry ANSI SGR colors, which a plain cannot render, and may omit a final newline. function tracebackLines(value: unknown): unknown { if (!Array.isArray(value)) return value; - const frames = value.filter((frame): frame is string => typeof frame === "string"); + const frames = value + .filter((frame): frame is string => typeof frame === "string") + .map((frame) => frame.replaceAll(/\u001b\[[\d;:]*m/g, "")); return frames.map((frame, index) => - index && !/\n(?:\u001b\[[\d;:]*m)*$/.test(frames[index - 1]!) ? `\n${frame}` : frame, + index && !frames[index - 1]!.endsWith("\n") ? `\n${frame}` : frame, ); } export function openNotebook(bytes: ArrayBuffer): OfficeWorkerResult { From 3d1120fa4d5dc502dc21c776e337d430d874dfa7 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sat, 14 Nov 2026 12:28:59 +0100 Subject: [PATCH 2/2] test(web): trim notebook ANSI case to traceback behavior Co-authored-by: GPT 6.1 SolCo-Authored-By: Claude Opus 5.5 --- apps/web/src/components/files/FilePreviewPanel.test.ts | 8 ++------ apps/web/src/components/files/extendedDocumentPreview.ts | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/files/FilePreviewPanel.test.ts b/apps/web/src/components/files/FilePreviewPanel.test.ts index 7b8bd0592ae30..d689a393d8db2 100644 --- a/apps/web/src/components/files/FilePreviewPanel.test.ts +++ b/apps/web/src/components/files/FilePreviewPanel.test.ts @@ -302,12 +302,10 @@ Third}`; JSON.stringify({ nbformat: 4, cells: [ - { cell_type: "markdown", source: "\u001b[1m# Notes\u001b[0m" }, { cell_type: "code", source: 'print("\u001b[32m")\nfail()', outputs: [ - { output_type: "stream", text: "\u001b[33mwarning\u001b[0m\n" }, { output_type: "error", traceback: [ @@ -342,10 +340,8 @@ Third}`; ), ); if (failed.type !== "notebook") throw new Error("Expected notebook"); - expect(failed.cells[0]!.source).toBe("\u001b[1m# Notes\u001b[0m"); - expect(failed.cells[1]!.source).toBe('print("\u001b[32m")\nfail()'); - expect(failed.cells[1]!.outputs).toEqual([ - { kind: "text", text: "\u001b[33mwarning\u001b[0m\n" }, + expect(failed.cells[0]!.source).toBe('print("\u001b[32m")\nfail()'); + expect(failed.cells[0]!.outputs).toEqual([ { kind: "text", text: "-----\nValueError Traceback (most recent call last)\nCell In[1], line 4\n----> 4 fail()\nCell In[1], line 2, in fail()\n----> 2 raise ValueError()\nValueError: preview proof", diff --git a/apps/web/src/components/files/extendedDocumentPreview.ts b/apps/web/src/components/files/extendedDocumentPreview.ts index 78bf09541bc07..384e3a3e78dac 100644 --- a/apps/web/src/components/files/extendedDocumentPreview.ts +++ b/apps/web/src/components/files/extendedDocumentPreview.ts @@ -431,7 +431,7 @@ function notebookText(value: unknown, limit = 256_000): string { if (result.length > limit) throw new Error("A notebook cell is too large to preview."); return result; } -// IPython traceback frames carry ANSI SGR colors, which a plain cannot render, and may omit a final newline. +// IPython traceback frames carry ANSI SGR colors acannot render and may omit a final newline. function tracebackLines(value: unknown): unknown { if (!Array.isArray(value)) return value; const frames = value