# How Claude HUD Manages Terminal Width Wrapping and Text Truncation in the Render Loop

> Discover how Claude HUD manages terminal width, wrapping, and text truncation. Learn its smart approach to visual length and color code preservation.

- Repository: [Jarrod Watts/claude-hud](https://github.com/jarrodwatts/claude-hud)
- Tags: internals
- Published: 2026-03-18

---

**Claude HUD handles terminal width constraints by first detecting the available columns via `process.stdout.columns`, then using ANSI-aware Unicode grapheme segmentation to measure visual length before applying intelligent wrapping or safe truncation with preserved color codes.**

The Claude HUD project provides a terminal heads-up display for AI-assisted development, requiring robust text rendering that adapts to varying terminal sizes without breaking visual formatting. Understanding how the render loop manages width constraints reveals sophisticated handling of ANSI codes, Unicode edge cases, and logical content separation.

## Detecting Terminal Width in the Claude HUD Render Loop

The render loop begins each frame by determining the available display width. In [`src/render/index.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/render/index.ts), the code queries `process.stdout.columns` to get the current terminal column count from Node.js.

If the stdout columns property is unavailable, the system falls back to the `COLUMNS` environment variable. When neither source provides a value, the render loop treats the width as unlimited, allowing content to flow without artificial constraints【`src/render/index.ts#L27-L38`】.

### Fallback Strategy for Unknown Widths

When operating without a known terminal width, Claude HUD disables wrapping and truncation logic entirely. This ensures that piping output to files or other processes does not inject unwanted line breaks or ellipsis characters that would corrupt the data stream.

## Measuring Visual Length with ANSI and Unicode Support

Standard JavaScript string `.length` properties return code unit counts, not display cells. Claude HUD implements a `visualLength` function that accurately calculates how many terminal columns a string occupies.

The function first strips ANSI escape sequences to measure only visible characters. It then segments the string into Unicode grapheme clusters using `Intl.Segmenter` when available, falling back to iterator-based segmentation for older environments. Each grapheme is classified by cell width: wide CJK characters and emoji occupy two columns, while zero-width joiners and combining marks occupy zero【`src/render/index.ts#L23-L34`】.

### Handling Complex Unicode Characters

The visual length calculation specifically accounts for:
- **Wide characters**: CJK ideographs and full-width forms consume two terminal cells
- **Emoji with ZWJ sequences**: Family and profession emoji combining multiple code points render as single wide graphemes
- **Zero-width marks**: Combining diacritics and variation selectors do not advance the cursor

### Preserving ANSI Escape Codes

When truncating or wrapping text, Claude HUD must not slice through ANSI escape sequences, which would leak color codes into subsequent content or corrupt the terminal state. The `sliceVisible` function walks the original string token-by-token, preserving any surrounding ANSI codes while stopping exactly when the accumulated visual width would exceed the target【`src/render/index.ts#L36-L78`】.

## Wrapping and Truncation Strategies

Once the render loop knows the terminal width and can measure visual length accurately, it applies two primary strategies to fit content: intelligent wrapping for multi-line content and safe truncation for single-line overflow.

### Intelligent Line Wrapping with Logical Separators

The `wrapLineToWidth` function attempts to keep logical UI elements together. Rather than wrapping at arbitrary character boundaries, it first tries to keep the entire line intact. If the content exceeds the width, it splits the line on logical separators like ` │ ` and ` | ` using the `splitWrapParts` helper.

The first segment—typically containing the model badge like `[Opus | Max]`—is kept together to avoid splitting inside the provider name. Subsequent segments are added to the current line until the visual length would exceed the terminal width. At that point, the current line is truncated to ensure it never overflows, and a new line begins with the remaining segments【`src/render/index.ts#L65-L94`】.

### Safe Text Truncation with Suffix

For content that must fit on a single line, `truncateToWidth` calculates how many visible cells can fit within the target width, accounting for the space needed for an ellipsis suffix. For very narrow terminals (fewer than 5 columns), it uses a shorter filler to ensure something remains visible.

After slicing the visible content, the function appends the ellipsis and injects an ANSI **RESET** code to prevent style leakage. This ensures that even when aggressively truncating coloured text, the terminal state remains clean【`src/render/index.ts#L80-L88】.

## The Complete Render Loop Implementation

In the top-level `render` function, after assembling UI elements, the system processes every line through the wrapping pipeline. Each line is split on newline characters and passed through `wrapLineToWidth` when a terminal width is known【`src/render/index.ts#L51-L55`】.

The final wrapped lines are printed to `stdout` with a leading `RESET` code to guarantee a clean style state, ensuring that any residual ANSI codes from previous terminal content do not interfere with the HUD display【`src/render/index.ts#L56-L59`】.

## Summary

- **Terminal width detection** queries `process.stdout.columns` with fallback to the `COLUMNS` environment variable, treating width as unlimited when unavailable.
- **Visual length calculation** uses `Intl.Segmenter` for Unicode grapheme segmentation, correctly handling wide CJK characters, emoji, and zero-width marks while stripping ANSI codes for measurement.
- **Safe truncation** via `truncateToWidth` preserves ANSI codes around sliced content and appends RESET codes to prevent style leakage.
- **Intelligent wrapping** via `wrapLineToWidth` respects logical separators like ` │ ` to keep model badges intact, truncating individual lines to prevent overflow while creating new lines for remaining content.
- **Clean output** ensures every rendered frame starts with a RESET code and never exceeds the detected terminal width.

## Frequently Asked Questions

### How does Claude HUD handle emoji and CJK characters in terminal width calculations?

Claude HUD uses the `visualLength` function with `Intl.Segmenter` to split strings into Unicode grapheme clusters. It classifies each grapheme by cell width: emoji and CJK ideographs consume two terminal columns, while zero-width joiners and combining marks consume zero. This ensures accurate width calculations even with complex Unicode sequences like family emoji or accented characters.

### What happens when the terminal width is unknown or changes dynamically?

When `process.stdout.columns` and the `COLUMNS` environment variable are both unavailable, the render loop treats the terminal width as unlimited. This disables wrapping and truncation, allowing content to flow without artificial line breaks. If the terminal resizes between frames, the next render cycle detects the new width via `process.stdout.columns` and re-wraps content accordingly.

### How does the render loop prevent ANSI color codes from breaking during truncation?

The `sliceVisible` function walks the string token-by-token, preserving surrounding ANSI escape sequences while calculating visual width. When truncating, `truncateToWidth` ensures that colour codes are never sliced in half. After truncation, it appends an ANSI **RESET** code to clear any active styles, preventing colour leakage into subsequent terminal output or the next HUD frame.

### Why does Claude HUD use logical separators instead of simple character wrapping?

The `wrapLineToWidth` function prioritizes UI readability by splitting on logical separators like ` │ ` and ` | ` rather than arbitrary character boundaries. This keeps related information together—such as the `[Model | Provider]` badge—preventing awkward breaks inside identifiers. When segments still exceed the width, the system truncates individual lines to ensure the display never overflows while maintaining logical grouping of HUD elements.