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

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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →