Back to skills
SKILL.md
2569 Tui Quick Reference F67a283f
ASecurity**Building a new TUI component? Start here.** ---
- 9 stars
- 0 votes
- 0 copies
- 0 views
- Added October 11, 2026
Works with
Security analysis
100/100npx -y skills add tools-only/X-Skills --skill 2569-tui_quick_reference_f67a283f --agent claude-codeAre you the author of 2569 Tui Quick Reference F67a283f?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tools-only-2569-tui-quick-reference-f67a283f)# TUI Component Quick Reference
**Building a new TUI component? Start here.**
---
## π Quick Start Template
```typescript
import { Container, Text, truncateToWidth } from "@mariozechner/pi-tui";
import type { Theme } from "@mariozechner/pi-coding-agent";
import {
getVisibleWidth,
calculateBorderFill,
validateLineWidth,
renderTopBorder,
renderBottomBorder
} from "./tui-validation.js";
export class MyComponent {
private theme: Theme;
private cachedWidth?: number;
private cachedLines?: string[];
constructor(theme: Theme) {
this.theme = theme;
}
render(width: number): string[] {
// Cache check
if (this.cachedLines && this.cachedWidth === width) {
return this.cachedLines;
}
const lines: string[] = [];
// Top border
const title = this.theme.fg("accent", this.theme.bold("My Component"));
const topBorder = renderTopBorder(title, width, (s) => this.theme.fg("border", s));
validateLineWidth(topBorder, width, "MyComponent", "top border");
lines.push(topBorder);
// Empty line
const emptyLine = this.theme.fg("border", `β${" ".repeat(width - 2)}β`);
validateLineWidth(emptyLine, width, "MyComponent", "empty line");
lines.push(emptyLine);
// Content
const content = "Your content here";
const contentLine = this.renderContentLine(content, width);
lines.push(contentLine);
// Empty line
lines.push(emptyLine);
// Bottom border
const bottomBorder = renderBottomBorder(width, (s) => this.theme.fg("border", s));
validateLineWidth(bottomBorder, width, "MyComponent", "bottom border");
lines.push(bottomBorder);
// Cache results
this.cachedWidth = width;
this.cachedLines = lines;
return lines;
}
private renderContentLine(content: string, width: number): string {
const innerWidth = width - 4; // 2 border + 2 spaces
const truncated = truncateToWidth(content, innerWidth);
const contentWidth = getVisibleWidth(truncated);
const padding = Math.max(0, innerWidth - contentWidth);
const line = this.theme.fg("border", "β") +
" " +
truncated +
" ".repeat(padding) +
this.theme.fg("border", "β");
validateLineWidth(line, width, "MyComponent", "content");
return line;
}
invalidate(): void {
this.cachedWidth = undefined;
this.cachedLines = undefined;
}
}
```
---
## π The Golden Rules
### 1. ALWAYS use `truncateToWidth()` on final lines
```typescript
// β BAD
lines.push(content);
// β
GOOD
lines.push(truncateToWidth(content, width));
```
### 2. NEVER hardcode border calculations
```typescript
// β BAD
"ββ Title " + "β".repeat(width - 15) + "β"
// β
GOOD
const titleWidth = getVisibleWidth(title);
const fill = calculateBorderFill(width, titleWidth);
"ββ " + title + " " + "β".repeat(fill) + "β"
```
### 3. ALWAYS validate critical lines
```typescript
// β BAD
lines.push(border);
// β
GOOD
validateLineWidth(border, width, "MyComponent", "top border");
lines.push(border);
```
### 4. Use `getVisibleWidth()` not `.length`
```typescript
// β BAD
const width = title.length; // Includes ANSI codes!
// β
GOOD
const width = getVisibleWidth(title); // Strips ANSI
```
---
## π¨ Common Patterns
### Bordered Panel
```typescript
// Top border
const title = this.theme.fg("accent", "Panel Title");
const topBorder = renderTopBorder(title, width, (s) => this.theme.fg("border", s));
validateLineWidth(topBorder, width, "Component", "top border");
lines.push(topBorder);
// Content lines
for (const item of items) {
const line = renderContentLine(item, width, (s) => this.theme.fg("border", s));
validateLineWidth(line, width, "Component", "content");
lines.push(line);
}
// Bottom border
const bottomBorder = renderBottomBorder(width, (s) => this.theme.fg("border", s));
validateLineWidth(bottomBorder, width, "Component", "bottom border");
lines.push(bottomBorder);
```
### Simple List (No Borders)
```typescript
for (const item of items) {
const styled = this.theme.fg("dim", `β’ ${item}`);
const line = truncateToWidth(styled, width);
validateLineWidth(line, width, "Component", "list item");
lines.push(line);
}
```
### Progress Bar
```typescript
import { renderProgressBar } from "./progress-bar.js";
const barWidth = Math.min(20, width - 10); // Leave room for label
const bar = renderProgressBar(progress, barWidth, theme);
const label = `Progress: ${progress}%`;
const line = truncateToWidth(`${label} [${bar}]`, width);
validateLineWidth(line, width, "Component", "progress");
lines.push(line);
```
---
## β οΈ Common Mistakes
### Mistake #1: Hardcoded Border Math
```typescript
// β WRONG
"ββ " + title + " " + "β".repeat(width - 22) + "β"
```
**Why it fails:** Assumes title is always exactly 17 visible characters.
**Fix:**
```typescript
// β
CORRECT
const titleWidth = getVisibleWidth(title);
const fill = calculateBorderFill(width, titleWidth);
"ββ " + title + " " + "β".repeat(fill) + "β"
```
---
### Mistake #2: Using `.length` on Styled Strings
```typescript
// β WRONG
const title = this.theme.fg("accent", "Title");
const width = title.length; // Includes \x1b[31m...\x1b[0m codes!
```
**Why it fails:** ANSI escape codes add invisible characters.
**Fix:**
```typescript
// β
CORRECT
const title = this.theme.fg("accent", "Title");
const width = getVisibleWidth(title); // Strips ANSI codes
```
---
### Mistake #3: No Validation
```typescript
// β WRONG
lines.push(border);
```
**Why it fails:** If border is too wide, Pi crashes with no debug info.
**Fix:**
```typescript
// β
CORRECT
validateLineWidth(border, width, "MyComponent", "top border");
lines.push(border);
```
Then check `~/.pi/agent/tui-width-errors.log` if crashes occur.
---
### Mistake #4: Forgetting to Cache
```typescript
// β BAD
render(width: number): string[] {
// Recalculates everything on every render
const lines = [];
// ... expensive rendering ...
return lines;
}
```
**Why it fails:** Performance - renders hundreds of times per second.
**Fix:**
```typescript
// β
GOOD
render(width: number): string[] {
if (this.cachedLines && this.cachedWidth === width) {
return this.cachedLines;
}
const lines = [];
// ... rendering ...
this.cachedWidth = width;
this.cachedLines = lines;
return lines;
}
invalidate(): void {
this.cachedWidth = undefined;
this.cachedLines = undefined;
}
```
---
## π§ͺ Testing Checklist
Before committing a new TUI component:
- [ ] Import `tui-validation.ts` utilities
- [ ] Use `calculateBorderFill()` for all borders
- [ ] Use `getVisibleWidth()` not `.length`
- [ ] Call `validateLineWidth()` on all critical lines
- [ ] Use `truncateToWidth()` on final output
- [ ] Implement caching (`cachedLines`, `cachedWidth`)
- [ ] Test in narrow terminal (80 cols)
- [ ] Test in wide terminal (200+ cols)
- [ ] Test with themed/colored content
- [ ] Check `~/.pi/agent/tui-width-errors.log` for violations
---
## π API Reference
### `tui-validation.ts`
```typescript
// Width measurement
getVisibleWidth(str: string): number
// Strips ANSI codes, returns actual visible character count
// Border calculations
calculateBorderFill(width: number, titleWidth: number): number
// Returns fill needed: width - titleWidth - 5
// Rendering helpers
renderTopBorder(title: string, width: number, theme: (s: string) => string): string
// Renders: ββ title βββββββββββ
renderBottomBorder(width: number, theme: (s: string) => string): string
// Renders: βββββββββββββββββββ
renderEmptyLine(width: number, theme: (s: string) => string): string
// Renders: β β
renderContentLine(content: string, width: number, theme: (s: string) => string): string
// Renders: β content β
// Validation
validateLineWidth(line: string, maxWidth: number, component: string, context: string): void
// Logs violations to ~/.pi/agent/tui-width-errors.log
// Does NOT throw - just logs for debugging
```
---
## π Related Documentation
- **Pi TUI API:** `/opt/homebrew/lib/node_modules/@mariozechner/pi-coding-agent/docs/tui.md`
- **Dex TUI Reference:** `06-Resources/Dex_System/Pi_TUI_Reference.md`
- **Bug fix details:** `System/PRDs/tui-width-bug-fix.md`
- **Examples:** Look at `progress-indicator.ts`, `task-board.ts`, `daily-plan-wizard.ts`
---
## π‘ Pro Tips
1. **Start with a working example** - Copy `progress-indicator.ts` structure
2. **Validate early** - Add validation from day 1, not after bugs appear
3. **Test narrow first** - 80-column terminals expose issues fastest
4. **Use theme callbacks** - `(s) => theme.fg("border", s)` makes colors consistent
5. **Cache aggressively** - Rendering is expensive, cache whenever width unchanged
---
**Questions?** Check `System/PRDs/tui-width-fix-summary.md` for debugging tips.
Attribution
Comments
Loading commentsβ¦
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.