Skip to content
Back to skills

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
developmenttypescriptgonodetestingdebuggingapiperformancedocumentation

Works with

  • terminal
  • api

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 2569-tui_quick_reference_f67a283f --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2569 Tui Quick Reference F67a283f?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2569 Tui Quick Reference F67a283f
[![Security: A β€” Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2569-tui-quick-reference-f67a283f/badge)](https://www.skillsdirectory.com/skills/tools-only-2569-tui-quick-reference-f67a283f)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# 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

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.