How CloddsBot Displays Boot Progress in TTY Mode: Terminal UI Deep Dive

CloddsBot renders a live, multi-step boot progress indicator in TTY mode by cycling Unicode spinner frames every 80 milliseconds while updating an in-memory state machine that tracks startup phases from configuration validation to HTTP gateway initialization.

When starting the CloddsBot from an interactive terminal, the application transforms standard console output into a dynamic dashboard. This terminal UI provides real-time visual feedback during the bot's initialization sequence, replacing static log lines with animated progress indicators that update as each subsystem comes online.

TTY Detection and Conditional Rendering

The boot display logic lives in src/index.ts and begins with an environment check. Before rendering any visual elements, the code verifies that stdout is attached to a TTY:

if (!process.stdout.isTTY) return;

As implemented in src/index.ts#L52, this guard clause ensures that non-interactive environments—such as Docker containers or CI pipelines—bypass the spinner entirely. When process.stdout.isTTY evaluates to false, CloddsBot falls back to the standard logger defined in src/utils/logger.ts, emitting plain text INFO messages instead of animated UI elements.

The Boot Progress State Machine

The visual progression relies on a lightweight state management system that tracks discrete startup phases through structured data objects.

Step Tracking with startupSteps

At the core of the system lies the startupSteps array, which holds objects conforming to the StartupStep interface:

interface StartupStep {
  name: string;
  status: 'pending' | 'active' | 'success' | 'error';
  detail?: string;
}

Located at src/index.ts#L27-L44, this structure supports two primary mutation functions: addStep() to register new phases and updateStep() to modify their status. The lifecycle begins by pre-populating six critical boot phases: Validating configuration, Loading config, Connecting to database, Starting market feeds, Connecting channels, and Starting HTTP gateway.

Spinner Animation Frames

The visual motion comes from a static array of Unicode Braille patterns cycled by the render loop:

const spinnerFrames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];

Defined at src/index.ts#L35, these characters create the illusion of rotation when printed sequentially. The animation advances every 80 milliseconds while the boot process remains incomplete.

Rendering the Live Progress Interface

The terminal UI updates through a coordinated rendering system that clears and redraws the console buffer to create seamless animation.

The renderProgress() Method

The renderProgress() function, spanning lines 51-88 in src/index.ts, handles the actual drawing logic. It performs three distinct operations:

  1. Buffer Clearing: Erases previous output to prevent scrollback accumulation
  2. Header Printing: Displays the static banner "🚀 Starting Clodds..."
  3. Step Iteration: Loops through startupSteps and prints context-aware icons:
    • ✓ (green checkmark) for completed steps
    • ✗ (red X) for failed steps
    • ○ for pending steps
    • Current spinner frame for active steps

Each rendered line includes optional detail text populated via the detail property of the step object, allowing subsystem-specific status messages to appear alongside the visual indicator.

The Spinner Timer Loop

Animation timing is controlled by startSpinner(), which initializes a setInterval timer at src/index.ts#L90-L96. This background process:

  • Advances the current frame index through spinnerFrames
  • Triggers renderProgress() to repaint the UI
  • Continues execution until stopSpinner() is called upon boot completion or error

The 80-millisecond interval provides smooth animation without excessive CPU overhead during the intensive initialization sequence.

Lifecycle Integration in main()

The main() function orchestrates the connection between business logic and visual feedback. During the startup sequence at src/index.ts#L92-L104, the code:

  1. Pre-registers all six startup steps with addStep()
  2. Calls startSpinner() to begin the animation loop
  3. Updates individual step statuses via updateStep() as src/gateway/index.ts initializes the database connections and market feeds
  4. Invokes stopSpinner() to halt the timer once the HTTP gateway binds to its port

This tight integration ensures that the visual progress accurately reflects the internal state of the gateway subsystem, providing users with immediate feedback when specific components stall during initialization.

Fallback Behavior for Non-TTY Environments

When executed without an attached terminal, the UI layer silently deactivates. In this mode, CloddsBot relies entirely on the Winston-based logger configured in src/utils/logger.ts, producing output suitable for log aggregation systems:

$ docker run alsk1992/clodds start
INFO  Starting Clodds...
INFO  Config loaded {port:18789}
INFO  Database connection established
INFO  Clodds is running!

This dual-mode architecture ensures that operational visibility remains intact regardless of execution context, whether running interactively in a local terminal or as a daemonized container in production.

Summary

  • TTY Detection: process.stdout.isTTY check at line 52 gates all visual rendering
  • State Management: The startupSteps array tracks six predefined boot phases using addStep() and updateStep()
  • Animation: Unicode Braille spinner frames cycle every 80ms via setInterval in startSpinner()
  • Rendering: renderProgress() clears the terminal and redraws status icons (✓, ✗, ○, or spinner) for each step
  • Cleanup: stopSpinner() terminates the interval timer upon successful boot or error
  • Fallback: Non-TTY execution routes to standard logging in src/utils/logger.ts

Frequently Asked Questions

How does CloddsBot detect whether to show the TTY progress UI?

CloddsBot checks process.stdout.isTTY at the entry point in src/index.ts. This Node.js property returns true only when stdout is connected to a terminal, allowing the code to bypass spinner initialization in Docker containers, systemd services, or redirected output scenarios.

What happens if a boot step fails during the TTY animation?

When updateStep() receives an error status, renderProgress() displays a red ✗ icon next to that step's name. The spinner continues for remaining active steps, but the failed step remains visually marked, and the final static frame persists after stopSpinner() clears the interval timer.

Can the spinner speed be configured or disabled?

The 80-millisecond interval is hardcoded in the setInterval call within startSpinner() at line 94. To modify the animation speed or disable motion entirely, you would need to edit this value directly in src/index.ts, as no external configuration hook currently exposes this timing parameter.

Why does the TTY UI use Unicode Braille characters for the spinner?

The Braille pattern characters (⠋ through ⠏) provide optimal visual density and smooth rotation in monospace terminal fonts. These specific glyphs occupy a single column width while offering eight distinct animation frames, creating a smoother illusion of motion than traditional ASCII spinners (|, /, -, \) in modern terminal emulators.

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 →