From 29e3d941e683e5c8492f9a9833370a21e0eaa427 Mon Sep 17 00:00:00 2001 From: Pi on Code Agent <313040514+pi-on-code-agent[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 04:19:22 +0000 Subject: [PATCH 1/2] fix: halve thinking block height --- media/style.css | 4 ++-- src/test/thinking-height.test.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 src/test/thinking-height.test.ts diff --git a/media/style.css b/media/style.css index c01c088..f35b54a 100644 --- a/media/style.css +++ b/media/style.css @@ -2323,7 +2323,7 @@ html.pi-viewport-recovering #pi-web-app { expected while reading; scrolling past the end chains to the conversation). */ .thinking-block:not(.thinking-collapsed) .thinking-content { display: block; - max-height: min(60vh, 520px); + max-height: min(60vh, 260px); overflow-y: auto; } @@ -2334,7 +2334,7 @@ html.pi-viewport-recovering #pi-web-app { display: flex; flex-direction: column; justify-content: flex-end; - max-height: 320px; + max-height: 160px; overflow: hidden; } diff --git a/src/test/thinking-height.test.ts b/src/test/thinking-height.test.ts new file mode 100644 index 0000000..cbaf9f1 --- /dev/null +++ b/src/test/thinking-height.test.ts @@ -0,0 +1,28 @@ +import * as assert from "node:assert"; +import { readFileSync } from "node:fs"; + +const styles = readFileSync( + new URL("../../media/style.css", import.meta.url), + "utf8", +); + +suite("Thinking block height", () => { + test("halves the finished reader height to 260px", () => { + const rule = styles.match(/\.thinking-block:not\(\.thinking-collapsed\) \.thinking-content \{[\s\S]*?\n\}/)?.[0]; + assert.ok(rule, "finished thinking reader rule not found"); + assert.match(rule, /max-height: min\(60vh, 260px\)/); + }); + + test("halves the live streaming tail height to 160px", () => { + const rule = styles.match(/\.thinking-block:has\(\.thinking-spinner\) \.thinking-content \{[\s\S]*?\n\}/)?.[0]; + assert.ok(rule, "live thinking rule not found"); + assert.match(rule, /max-height: 160px/); + }); + + test("keeps scroll behaviour intact for the smaller blocks", () => { + const reader = styles.match(/\.thinking-block:not\(\.thinking-collapsed\) \.thinking-content \{[\s\S]*?\n\}/)?.[0]; + const live = styles.match(/\.thinking-block:has\(\.thinking-spinner\) \.thinking-content \{[\s\S]*?\n\}/)?.[0]; + assert.match(reader ?? "", /overflow-y: auto/); + assert.match(live ?? "", /overflow: hidden/); + }); +}); From 683316dcc738173a4fd2b30f28c665740a20b9a2 Mon Sep 17 00:00:00 2001 From: Pi on Code Agent <313040514+pi-on-code-agent[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:26:57 +0000 Subject: [PATCH 2/2] fix: use one thinking max height in both modes --- media/style.css | 9 +++++---- src/test/thinking-height.test.ts | 16 +++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/media/style.css b/media/style.css index f35b54a..5102a8b 100644 --- a/media/style.css +++ b/media/style.css @@ -2327,14 +2327,15 @@ html.pi-viewport-recovering #pi-web-app { overflow-y: auto; } -/* Live (spinner present): bottom-aligned latest lines, capped and clipped - without a nested scrollbar so the wheel always scrolls the conversation. - Kept last so it wins over the reader rule while the model is thinking. */ +/* Live (spinner present): bottom-aligned latest lines, capped with the same + max height as the finished reader and clipped without a nested scrollbar so + the wheel always scrolls the conversation. Kept last so it wins over the + reader rule while the model is thinking. */ .thinking-block:has(.thinking-spinner) .thinking-content { display: flex; flex-direction: column; justify-content: flex-end; - max-height: 160px; + max-height: min(60vh, 260px); overflow: hidden; } diff --git a/src/test/thinking-height.test.ts b/src/test/thinking-height.test.ts index cbaf9f1..07fb0de 100644 --- a/src/test/thinking-height.test.ts +++ b/src/test/thinking-height.test.ts @@ -13,13 +13,19 @@ suite("Thinking block height", () => { assert.match(rule, /max-height: min\(60vh, 260px\)/); }); - test("halves the live streaming tail height to 160px", () => { - const rule = styles.match(/\.thinking-block:has\(\.thinking-spinner\) \.thinking-content \{[\s\S]*?\n\}/)?.[0]; - assert.ok(rule, "live thinking rule not found"); - assert.match(rule, /max-height: 160px/); + test("uses the same max height for the live streaming tail", () => { + const reader = styles.match(/\.thinking-block:not\(\.thinking-collapsed\) \.thinking-content \{[\s\S]*?\n\}/)?.[0]; + const live = styles.match(/\.thinking-block:has\(\.thinking-spinner\) \.thinking-content \{[\s\S]*?\n\}/)?.[0]; + assert.ok(live, "live thinking rule not found"); + assert.match(live, /max-height: min\(60vh, 260px\)/); + assert.strictEqual( + live?.match(/max-height: [^;]+;/)?.[0], + reader?.match(/max-height: [^;]+;/)?.[0], + "both thinking modes must cap at the same height", + ); }); - test("keeps scroll behaviour intact for the smaller blocks", () => { + test("keeps per-mode overflow behaviour intact", () => { const reader = styles.match(/\.thinking-block:not\(\.thinking-collapsed\) \.thinking-content \{[\s\S]*?\n\}/)?.[0]; const live = styles.match(/\.thinking-block:has\(\.thinking-spinner\) \.thinking-content \{[\s\S]*?\n\}/)?.[0]; assert.match(reader ?? "", /overflow-y: auto/);