diff --git a/media/style.css b/media/style.css index c01c088..5102a8b 100644 --- a/media/style.css +++ b/media/style.css @@ -2323,18 +2323,19 @@ 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; } -/* 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: 320px; + max-height: min(60vh, 260px); overflow: hidden; } diff --git a/src/test/thinking-height.test.ts b/src/test/thinking-height.test.ts new file mode 100644 index 0000000..07fb0de --- /dev/null +++ b/src/test/thinking-height.test.ts @@ -0,0 +1,34 @@ +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("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 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/); + assert.match(live ?? "", /overflow: hidden/); + }); +});