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.
This commit is contained in:
Mario Zechner 2025-11-11 23:52:18 +01:00
parent a3b3849188
commit 02a21dd936
3 changed files with 37 additions and 11 deletions

View file

@ -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 {

View file

@ -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++;
}
}

View file

@ -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;
}
}