From 02a21dd9369ac0e155cea8c83146fa51c18d15f1 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Tue, 11 Nov 2025 23:52:18 +0100 Subject: [PATCH] Fix viewport width issues in thinking selector and text rendering Thinking selector fix: - Replace hardcoded 50-character borders with DynamicBorder component that adjusts to viewport width - Prevents crash when terminal is resized narrow Text/Markdown rendering fixes: - Fix Markdown wrapSingleLine to check if adding next character would exceed width BEFORE adding it (was checking AFTER, causing lines to be 1 character too long) - Add word truncation in Text component for words longer than contentWidth (prevents long unbreakable words from exceeding width) These fixes prevent "Rendered line exceeds terminal width" errors. --- .../coding-agent/src/tui/thinking-selector.ts | 15 ++++++++++--- packages/tui/src/components/markdown.ts | 11 ++++++---- packages/tui/src/components/text.ts | 22 +++++++++++++++---- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/packages/coding-agent/src/tui/thinking-selector.ts b/packages/coding-agent/src/tui/thinking-selector.ts index d8919197..effe203e 100644 --- a/packages/coding-agent/src/tui/thinking-selector.ts +++ b/packages/coding-agent/src/tui/thinking-selector.ts @@ -1,7 +1,16 @@ import type { ThinkingLevel } from "@mariozechner/pi-agent"; -import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui"; +import { type Component, Container, type SelectItem, SelectList } from "@mariozechner/pi-tui"; import chalk from "chalk"; +/** + * Dynamic border component that adjusts to viewport width + */ +class DynamicBorder implements Component { + render(width: number): string[] { + return [chalk.blue("─".repeat(Math.max(1, width)))]; + } +} + /** * Component that renders a thinking level selector with borders */ @@ -20,7 +29,7 @@ export class ThinkingSelectorComponent extends Container { ]; // Add top border - this.addChild(new Text(chalk.blue("─".repeat(50)), 0, 0)); + this.addChild(new DynamicBorder()); // Create selector this.selectList = new SelectList(thinkingLevels, 5); @@ -42,7 +51,7 @@ export class ThinkingSelectorComponent extends Container { this.addChild(this.selectList); // Add bottom border - this.addChild(new Text(chalk.blue("─".repeat(50)), 0, 0)); + this.addChild(new DynamicBorder()); } getSelectList(): SelectList { diff --git a/packages/tui/src/components/markdown.ts b/packages/tui/src/components/markdown.ts index 803ce409..33f26fd9 100644 --- a/packages/tui/src/components/markdown.ts +++ b/packages/tui/src/components/markdown.ts @@ -404,7 +404,11 @@ export class Markdown implements Component { } } else { // Regular character - if (currentLength >= width) { + const char = line[i]; + const charWidth = visibleWidth(char); + + // Check if adding this character would exceed width + if (currentLength + charWidth > width) { // Need to wrap - close current line with reset if needed if (activeAnsiCodes.length > 0) { wrapped.push(currentLine + "\x1b[0m"); @@ -416,10 +420,9 @@ export class Markdown implements Component { } currentLength = 0; } - const char = line[i]; + currentLine += char; - // Count actual terminal column width, not string length - currentLength += visibleWidth(char); + currentLength += charWidth; i++; } } diff --git a/packages/tui/src/components/text.ts b/packages/tui/src/components/text.ts index 6e6598da..436a3a1c 100644 --- a/packages/tui/src/components/text.ts +++ b/packages/tui/src/components/text.ts @@ -84,13 +84,27 @@ export class Text implements Component { const currentVisible = visibleWidth(currentLine); const wordVisible = visibleWidth(word); + // If word is too long, truncate it + let finalWord = word; + if (wordVisible > contentWidth) { + // Truncate word to fit + let truncated = ""; + for (const char of word) { + if (visibleWidth(truncated + char) > contentWidth) { + break; + } + truncated += char; + } + finalWord = truncated; + } + if (currentVisible === 0) { - currentLine = word; - } else if (currentVisible + 1 + wordVisible <= contentWidth) { - currentLine += " " + word; + currentLine = finalWord; + } else if (currentVisible + 1 + visibleWidth(finalWord) <= contentWidth) { + currentLine += " " + finalWord; } else { lines.push(currentLine); - currentLine = word; + currentLine = finalWord; } }