diff --git a/docs/research/CODEMODE_TUI_PRESENTATION_2026-10-04.md b/docs/research/CODEMODE_TUI_PRESENTATION_2026-10-04.md index 8e4de4c5..8685d076 100644 --- a/docs/research/CODEMODE_TUI_PRESENTATION_2026-10-04.md +++ b/docs/research/CODEMODE_TUI_PRESENTATION_2026-10-04.md @@ -34,3 +34,15 @@ Focused tests passed 13/13 in 1.72 seconds, including actual locked SDK 0.99.1 r The local repository check passed. One complete local test run passed 2,283 Node tests (9 skipped; 128.75 seconds) and 1,158 UI tests (32.13 seconds). The candidate was under a narrow cancellation-display correction during that run; final focused tests and exact-head CI must separately cover the frozen correction. No timeout increase, disabled assertion, executor replacement or installed-host edit was used. Real terminal pixels, keyboard expansion in the user's actual Session and package reload remain unverified. Installation was not changed. + +## 2026-10-05: background continuity after truncation + +- Status: validated at the ANSI renderer and isolated native SDK boundaries; live terminal pixels after reload remain unverified. +- Source boundary: main `214fac3786f80107e22d5bd2b53b472461ef16b2` plus the repair linked from [#683](https://github.com/openpi-dev/openpi/issues/683). +- Created / verified: 2026-10-05. This supplements the earlier observations without replacing their evidence. + +A new user screenshot showed the card background ending immediately after a truncated Bash preview. Pi's public `truncateToWidth` inserts SGR `ESC[0m`, resetting background as well as foreground. Wrapping the complete padded row once with `theme.bg` does not restore the background after that embedded reset. The regression reproduced an unpainted ellipsis and trailing space before the fix. + +The OpenPI frame now applies its owning background independently to each segment separated by the native full-style reset. The original reset is retained between segments, preserving foreground/style termination. Displayed text, width, truncation budgets, execution and result evidence are unchanged. This repair applies to main and child presentations through their shared Code Mode renderer. + +The regression checks ANSI background state for every visible character, including padding, at 40/120/240 columns in collapsed and expanded views with a long Chinese-containing Bash preview. It deliberately retains escape sequences; a stripped-text comparison could not detect this defect. Focused renderer/native tests pass 21/21 on locked Pi 0.99.1 and installed Pi 1.0.2, without model calls. Full repository checks and independent review are reported on the repair PR. The installed dirty source checkout was preserved; no assertion here proves that a running Session reloaded the repair. diff --git a/extensions/codemode-display/render.ts b/extensions/codemode-display/render.ts index e5cd60a5..26d640ae 100644 --- a/extensions/codemode-display/render.ts +++ b/extensions/codemode-display/render.ts @@ -115,9 +115,15 @@ function framed( if (edges.top) rows.unshift(""); if (edges.bottom()) rows.push(""); const bg = tone(); - return rows.map((row) => - theme.bg(bg, row + " ".repeat(Math.max(0, width - visibleWidth(row)))), - ); + return rows.map((row) => { + const padded = row + " ".repeat(Math.max(0, width - visibleWidth(row))); + // Pi's truncation/wrapping resets all SGR styles. Reapply the owning + // card background after each reset, including ellipsis and padding. + return padded + .split("\x1b[0m") + .map((segment) => theme.bg(bg, segment)) + .join("\x1b[0m"); + }); }, invalidate() {}, }; diff --git a/tests/extensions/codemode-display/render.test.ts b/tests/extensions/codemode-display/render.test.ts index 9636d710..26b2714c 100644 --- a/tests/extensions/codemode-display/render.test.ts +++ b/tests/extensions/codemode-display/render.test.ts @@ -552,3 +552,51 @@ test("a shared row puts the status in the call header and hides output", () => { assert.doesNotMatch(body.join("\n"), /completed/u); assert.doesNotMatch(body.join("\n"), /first|second|│/u); }); + +test("truncated previews keep the card background through ellipsis and right padding", () => { + const colorTheme = { + fg: (_color: string, text: string) => `\x1b[38;5;145m${text}\x1b[39m`, + bg: (_color: string, text: string) => `\x1b[48;5;237m${text}\x1b[49m`, + bold: (text: string) => `\x1b[1m${text}\x1b[22m`, + } as Theme; + const value = result("done", [ + { + id: "1", + name: "bash", + status: "running", + args: JSON.stringify({ + command: `bunx biome format --write ${"extensions/中文.ts ".repeat(20)}`, + }), + }, + ]); + for (const width of [40, 120, 240]) { + for (const expanded of [false, true]) { + const rows = codemodeRenderers.renderResult!( + value, + { expanded, isPartial: true }, + colorTheme, + context(expanded), + ).render(width); + if (!expanded) + assert.ok( + rows.some((row) => row.includes("\x1b[0m")), + "exercise native truncation/wrapping resets", + ); + for (const row of rows) { + assert.equal(visibleWidth(row), width); + let background = false; + for (const token of row.matchAll(/\x1b\[([\d;]*)m|([^\x1b])/gu)) { + if (token[1] !== undefined) { + if (token[1] === "0" || token[1] === "49") background = false; + if (token[1] === "48;5;237") background = true; + } else { + assert.ok( + background, + `unpainted cell at width=${width}, expanded=${expanded}: ${JSON.stringify(row)}`, + ); + } + } + } + } + } +});