Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

AZUREALAsynchronous Zoned Unified Runtime Environment for Agentic LLMs – is a Rust TUI application that wraps Claude Code CLI and OpenAI Codex CLI to enable multi-agent development workflows with git worktree isolation.

Built with ratatui, AZUREAL provides a terminal-native interface for orchestrating concurrent AI coding agents, each operating in its own git worktree with independent branches, sessions, and working directories. Instead of running one agent at a time in a single checkout, you can run several agents simultaneously across isolated feature branches, all managed from a single terminal window.

Version 1.0.0 | MIT License | Author: xCORViSx


Philosophy

AZUREAL is built around two core ideas:

Mostly-Stateless Architecture

Runtime state is derived from git, not stored in custom databases or config files. Worktrees are discovered via git worktree list. Branches are discovered via git branch. The active backend (Claude or Codex) is derived from whichever model is currently selected. When you close the application and reopen it, the state reconstructs itself from your repository.

Persistent data is minimal by design:

  • Session store – a single SQLite file (.azureal/sessions.azs) holds all conversation history, portable across machines by copying one file.
  • Configuration – two small TOML files (azufig.toml), one global and one per-project, store preferences and registered projects.
  • Agent session files – temporary JSONL files that are parsed during live streaming, ingested into the SQLite store on exit, then deleted.

There is no daemon, no server process, and no external database to manage.

Multi-Agent Concurrent Development

Traditional AI-assisted development is sequential: you prompt an agent, wait for it to finish, review the result, then prompt again. AZUREAL breaks this pattern by letting you run multiple agents in parallel, each in its own git worktree. While one agent refactors a module on feat-a, another can be writing tests on feat-b, and a third can be fixing a bug on fix-c – all at the same time, with no interference between them.

Git worktrees provide the isolation guarantee. Each worktree has its own working directory, its own branch, and its own uncommitted changes. Agents cannot step on each other because they are literally working in different directories on different branches.


Key Capabilities

Multi-Worktree Sessions

Create, archive, rename, and delete git worktrees from within the TUI. Each worktree hosts independent agent sessions with PID-keyed slots, so you can even run multiple agents on the same branch concurrently.

3-Pane Layout

The default view divides the terminal into three panes: a file tree on the left, a file viewer in the center, and the session pane on the right. Pane proportions are tuned for readability, and Tab/Shift+Tab cycles focus between them.

Embedded Terminal

A full shell per worktree with color support, click-to-position cursor, word navigation, mouse drag selection with auto-scroll, and mouse wheel scrolling. Toggle it with a single keystroke.

Git Panel

A dedicated overlay for the full git workflow: staging, committing with AI-generated messages, squash merging, rebasing, conflict resolution with Claude-assisted RCR (Rebase Conflict Resolution), and pull/push operations.

Health Panel

Scan your codebase for oversized “god files” and missing documentation, then spawn an agent to fix them directly from the panel.

Speech-to-Text

Dictate prompts with a keybinding. Audio is transcribed locally via Whisper – nothing leaves your machine.

Projects Panel

Switch between multiple projects without leaving the application. Agent processes continue running in the background when you switch away, with activity status icons per project.

Dual-Backend Support (Claude + Codex)

Cycle between Claude models (Opus, Sonnet, Haiku) and Codex models (GPT-5.4, GPT-5.3-codex, GPT-5.2-codex, and others) with a single keybinding. The backend is derived automatically from the selected model – no manual configuration required. A single session can span prompts to both backends.

Session Store with Context Injection

Conversations persist in a SQLite database with automatic compaction. Context is injected into each new prompt from the store, eliminating dependency on external session files for conversation continuity. The store is the single source of truth.


Target Audience

AZUREAL is designed for developers who:

  • Already use AI coding assistants (Claude Code, OpenAI Codex) and want to scale beyond single-agent, single-branch workflows.
  • Work on projects large enough to benefit from concurrent feature development across multiple branches.
  • Prefer terminal-native tools and keyboard-driven interfaces over GUI applications.
  • Want git worktree isolation without the overhead of manually managing worktrees, sessions, and agent processes.

Familiarity with git (especially branching and worktrees) and at least one supported AI coding CLI is assumed throughout this manual.


How This Manual Is Organized

The manual is divided into three sections:

User Guide

The main body of the manual, covering everything you need to use AZUREAL day-to-day:

  • Getting Started – requirements, installation, and first launch.
  • The TUI Interface – layout, pane navigation, mouse support, and text selection.
  • Keybindings & Input Modes – vim-style modes, global keybindings, leader sequences, and the help overlay.
  • Git Worktrees – creating, managing, archiving, and browsing worktrees.
  • Agent Sessions – Claude and Codex backends, model switching, multi-agent concurrency, and session lifecycle.
  • Session Store & Persistence – the SQLite .azs format, context injection, compaction, and portability.
  • The Session Pane – markdown rendering, tool call display, session list, todo widget, context meter, and clickable elements.
  • The File Browser & Viewer – file tree, syntax highlighting, markdown preview, image viewing, diffs, tabs, and edit mode.
  • The Git Panel – staging, AI commit messages, squash merge, rebase, conflict resolution, and push/pull.
  • The Embedded Terminal – terminal modes, shell integration, and terminal-specific features.
  • Projects Panel – managing multiple projects, parallel operation, and background processes.

Features

Standalone feature pages for capabilities that span multiple parts of the interface:

  • Speech-to-text, run commands, preset prompts, health panel, completion notifications, and debug dump.

Reference

Technical and configuration details:

  • Configuration – global and project-level azufig.toml settings.
  • Architecture & Internals – event loop, render pipeline, performance characteristics, and file watcher.
  • Platform Support – macOS, Linux, and Windows specifics.
  • CLI Reference – command-line flags and subcommands.
  • Complete Keybinding Reference – every keybinding in one table.

This manual tracks AZUREAL v1.0.0 — the first stable release.

Getting Started

This section walks you through everything needed to go from zero to a running AZUREAL instance: system requirements, installation, and what to expect the first time you launch the application.

What is AZUREAL?

AZUREAL (Asynchronous Zoned Unified Runtime Environment for Agentic LLMs) is a terminal-based interface that wraps Claude Code CLI and OpenAI Codex CLI into a single multi-agent development environment. Each feature branch gets its own git worktree with isolated agent sessions, so you can run multiple AI assistants concurrently across different parts of your project without any cross-contamination.

The core workflow loop looks like this:

  1. Open your project in AZUREAL.
  2. Create a worktree for a feature branch.
  3. Prompt an agent (Claude or Codex) to work in that worktree.
  4. Switch to another worktree and prompt a different agent in parallel.
  5. Review changes, commit, squash-merge back to main.

All of this happens inside a single terminal window with keyboard-driven navigation, a built-in file browser, git staging panel, embedded terminal, and persistent session history.

In This Section

  • Requirements – What you need installed before AZUREAL will build and run.
  • Installation – How to get the binary onto your system.
  • First Launch – What happens when you run azureal for the first time.

Requirements

AZUREAL has a small set of hard requirements and a few optional dependencies that unlock additional features.

Platform Support

PlatformStatusNotes
macOSPrimaryMetal GPU acceleration for Whisper; .app bundle icon support
LinuxSupportedCPU-based Whisper; all features functional
WindowsSupportedConPTY terminal backend; cmd.exe and PowerShell shells; CUDA GPU-accelerated Whisper

Terminal Emulator

AZUREAL requires a modern terminal emulator with true-color and mouse support. For the best experience:

PlatformRecommendedAlso tested
macOSKittyGhostty, Alacritty, WezTerm, Terminal.app
LinuxKittyGhostty, Alacritty, WezTerm, Konsole
WindowsWindows Terminal

Kitty and Windows Terminal deliver the best overall experience for two reasons:

  1. Input protocol support. Kitty provides the Kitty keyboard protocol on macOS and Linux, which allows AZUREAL to distinguish ambiguous key combinations (e.g., Tab vs Ctrl+I, Enter vs Ctrl+M). Windows Terminal provides full ConPTY support with reliable mouse and key reporting.

  2. Rendering fidelity. Both terminals produce the cleanest interpretation of AZUREAL’s box-drawing characters (, , , , , etc.), Unicode glyphs, and styled borders. Pane separators, tab bars, dialog frames, and half-block splash art all render pixel-perfect with correct line joining and consistent glyph widths.

Other listed terminals work well – the main difference is that terminals without the Kitty keyboard protocol trigger Alt+ fallback bindings for certain shortcuts, and some terminals may show minor visual artifacts in complex border intersections or half-block character rendering. See Platform Differences for details.

The legacy conhost.exe on Windows is not recommended due to limited ANSI support.

Required

Git 2.15+

AZUREAL relies heavily on git worktree commands, which require Git 2.15 or later. Verify your version:

git --version

At Least One Agent CLI

You need at least one of the following agent backends installed. Both can be active simultaneously – AZUREAL derives the backend from whichever model you select.

Claude Code CLI (for Claude models):

# macOS / Linux
curl -fsSL https://claude.ai/install.sh | bash

# Windows (PowerShell)
irm https://claude.ai/install.ps1 | iex

Codex CLI (for OpenAI models – optional):

npm install -g @openai/codex

Rust (Latest Stable)

Required only if building from source. Install via rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Pre-built binaries do not require Rust.

LLVM/Clang + CMake

Build dependency for whisper-rs, which provides speech-to-text support. Even if you do not plan to use speech input, the crate is compiled as part of the build.

macOS:

Install Xcode Command Line Tools (includes Clang and required headers):

xcode-select --install

CMake, if not already present:

brew install cmake

Linux (Debian/Ubuntu):

sudo apt install libclang-dev cmake

Windows:

winget install LLVM.LLVM Kitware.CMake Ninja-build.Ninja

After installing LLVM on Windows, set the LIBCLANG_PATH environment variable to the LLVM bin directory (e.g., C:\Program Files\LLVM\bin).

The Ninja build system is required because CMake’s default Visual Studio generator uses MSBuild, which prevents CUDA from inheriting the Windows SDK include paths. Set CMAKE_GENERATOR=Ninja in your environment, or build from a VS Developer Command Prompt with Ninja in PATH.

NVIDIA CUDA Toolkit (Windows)

Required on Windows for GPU-accelerated Whisper inference. The Windows build compiles Whisper with CUDA support, so the CUDA Toolkit must be present at build time.

winget install Nvidia.CUDA

Restart your terminal after installation so the CUDA_PATH environment variable is available. The build system reads CUDA_PATH to locate the CUDA libraries automatically.

Note: This requirement applies only to Windows. macOS uses Metal for GPU acceleration (handled automatically), and Linux uses CPU inference.

Optional

Nerd Font

AZUREAL uses Nerd Font glyphs for file tree icons (file type icons, folder icons, git status indicators). When a Nerd Font is not detected, the application falls back to emoji-based icons automatically.

Recommended fonts:

Configure your terminal emulator to use the installed Nerd Font.

Whisper Model (Speech-to-Text)

To use the built-in speech-to-text feature, download a Whisper model to the expected path:

~/.azureal/speech/ggml-small.en.bin

Models are available from HuggingFace. The ggml-small.en.bin model offers a good balance of accuracy and speed. Larger models (medium, large) improve accuracy at the cost of higher latency and memory usage.

On macOS, Whisper runs on the Metal GPU for faster inference. On Windows, Whisper uses CUDA GPU acceleration (requires NVIDIA GPU + CUDA Toolkit). Linux uses CPU inference.

Quick Checklist

Before proceeding to installation, confirm:

  • Git 2.15+ is installed
  • At least one agent CLI is installed (Claude Code or Codex)
  • LLVM/Clang and CMake are available (if building from source)
  • NVIDIA CUDA Toolkit is installed (Windows only, if building from source)
  • A Nerd Font is configured in your terminal (recommended)

Installation

There are two ways to install AZUREAL: downloading a pre-built binary from GitHub Releases, or building from source with Cargo.

Download the latest release for your platform from GitHub Releases.

The AZUREAL binary is self-installing. On first run, it detects that it has not been installed to a standard location and copies itself to the appropriate path:

PlatformInstall Path
macOS/usr/local/bin/azureal (falls back to ~/.local/bin/azureal if /usr/local/bin is not writable)
Linux/usr/local/bin/azureal (falls back to ~/.local/bin/azureal if /usr/local/bin is not writable)
Windows%USERPROFILE%\.azureal\bin\azureal.exe

If the binary installs to a user-local path, make sure that path is in your shell’s PATH environment variable.

After the self-install completes, you can run azureal from any terminal.

From Source

Clone the repository and build with Cargo:

git clone https://github.com/xCORViSx/azureal.git
cd azureal
cargo install --path .

This compiles a release-optimized binary and places it in ~/.cargo/bin/, which is typically already on your PATH if you installed Rust via rustup.

Build Requirements

Building from source requires all items listed in Requirements, including Rust (latest stable), LLVM/Clang, and CMake.

Verifying the Installation

Run the following to confirm AZUREAL is installed and accessible:

azureal --version

You should see output showing the current version number.

Updating

Pre-built binary: Download the new release and run it. The self-install mechanism overwrites the previous binary.

From source: Pull the latest changes and rebuild:

git pull
cargo install --path .

First Launch

This page describes what happens the first time you run azureal, and what each screen means.

The Splash Screen

When AZUREAL starts, it displays a splash screen: a 2x-scale block-character rendering of the word “AZUREAL” with a dim butterfly mascot in the background. This screen is shown for a minimum of 3 seconds while the application performs git discovery – scanning the current directory for a git repository, resolving the main worktree root, and enumerating existing worktrees.

No input is accepted during the splash. It transitions automatically once both the timer and git discovery complete.

Project Detection

What happens next depends on where you launched azureal from.

Inside a Git Repository

If you run azureal from within a git repository (or any subdirectory of one), the project is auto-registered and loads immediately. AZUREAL resolves the repository root via git rev-parse --git-common-dir, registers it in the global config, and transitions to the main interface.

Outside a Git Repository

If you run azureal from a directory that is not part of any git repository, AZUREAL opens the Projects panel full-screen. From here you can:

  • Select an existing registered project to open.
  • Register a new project by navigating to its path.
  • Initialize a new git repository and register it as a project.

Welcome Modal

When a project loads but has no worktrees yet (only the main branch exists), AZUREAL displays a welcome modal with the following options:

KeyAction
MBrowse the main branch – opens the file browser and session pane on main
WnCreate a new worktree – W leader sequence followed by n to open the branch dialog
POpen the Projects panel – switch to a different project or register a new one
Ctrl+QQuit AZUREAL

This modal appears only when there are no feature worktrees. Once you create your first worktree, subsequent launches skip the modal and load the last active worktree directly.

Configuration Files Created

On first launch, AZUREAL creates two configuration files:

Global Config

~/.azureal/azufig.toml

Stores application-wide settings: registered project paths and display names, permission mode preferences, global run commands, and global preset prompts. This file is shared across all projects.

Project-Local Config

<project-root>/.azureal/azufig.toml

Stores project-specific settings: file tree hidden entries, health scan scope directories, project-local run commands, preset prompts, and git settings like per-branch auto-rebase rules and auto-resolve file lists.

This file lives at the main worktree root (the original clone directory) and is shared by all worktrees in the project. The entire .azureal/ directory is gitignored by default to prevent the session store and runtime files from causing rebase conflicts.

Session Store

The session store file is not created at first launch. It is created lazily:

<project-root>/.azureal/sessions.azs

This SQLite database (with a custom .azs extension) is created only when you send your first prompt to an agent or open the session list. It stores all conversation history, compaction summaries, and session metadata. The .azs extension signals that it is a managed binary file and should not be edited manually.

What to Do Next

After the splash screen and project detection complete, you are ready to start working:

  1. Create a worktree (Wn from the welcome modal – W leader followed by n) to start an isolated feature branch.
  2. Send a prompt to an agent by typing in the session pane input area and pressing Enter.
  3. Browse the file tree to review changes the agent has made.

For a full tour of the interface layout and navigation, continue to The TUI Interface.

The TUI Interface

AZUREAL presents a ratatui-based terminal user interface organized around panes, a worktree tab row, and a status bar. The interface has two primary layouts – Normal Mode for day-to-day development and Git Mode (Shift+G) for repository operations – plus a collection of overlay panels that appear on demand.

Normal Mode

┌─ [★ main] │ [○ feat-a] │ [● feat-b] ┐
├──────────┬───────────────┬───────────┤
│FileTree  │    Viewer     │           │
│  (15%)   │    (50%)      │Session(35%)│
├──────────┴───────────────┤           │
│  Input / Terminal        │           │
├──────────────────────────┴───────────┤
│             Status Bar               │
└──────────────────────────────────────┘

The Worktree Tab Row sits at the top, giving a persistent overview of every active and archived worktree. Below it, three panes divide the workspace: FileTree on the left (15% width), the Viewer in the center (50%), and the Session pane on the right (35%, spanning the full height from tab row to status bar). The Input/Terminal area sits beneath FileTree and Viewer, sharing their combined width. The Status Bar occupies the final row.

Git Mode

╔════════════════════════════════════╗
║ [main] [feat-a] [feat-b] (tab bar) ║
╠══════════╦═══════════════╦═════════╣
║ Actions  ║   Viewer      ║Commits  ║
║──────────║               ║         ║
║ Files    ║               ║         ║
╠══════════╩═══════════════╩═════════╣
║ GIT: wt (Tab/⇧Tab:cycle | Enter)  ║
╚════════════════════════════════════╝

Pressing Shift+G replaces the normal layout with a dedicated git panel. The left column splits into an Actions section and a Changed Files section, the center remains the Viewer (showing diffs), and the right column displays a Commit history. A minimal status bar at the bottom replaces the normal one.

Visual Identity

All accent colors throughout the interface use the AZURE constant (#3399FF), tying the color palette to the “Azureal” name. Active tabs, borders, badges, and highlighted elements all share this single brand color.

OS Terminal Title

The host terminal’s title bar updates dynamically to reflect your context:

  • No project loaded: AZUREAL
  • Project active: AZUREAL @ <project> : <branch>

The title updates automatically on startup, session switch, and project switch, and resets to empty on exit.

Splash Screen

On launch, a 2x-scale block-character “AZUREAL” logo renders in AZURE while git discovery, session parsing, and file I/O run in the background. A minimum 3-second display ensures the branding registers even on fast machines. The splash is replaced by the full interface once the event loop starts.

What This Chapter Covers

  • Layout & Panes – detailed breakdown of every pane, its dimensions, and its purpose.
  • Focus & Navigation – how keyboard focus cycles between panes and how overlays interact with focus.
  • Mouse Support – click, scroll, and double-click behavior across every pane.
  • Text Selection & Copy – drag-to-select, clipboard copy, and how selection interacts with pane content.

Layout & Panes

AZUREAL’s interface is composed of six distinct regions. Each has a fixed purpose, and most have configurable behavior described in later chapters.

Pane Map (Normal Mode)

┌─ [★ main] │ [○ feat-a] │ [● feat-b] ┐   ← Worktree Tab Row (1 row)
├──────────┬───────────────┬───────────┤
│          │               │           │
│ FileTree │    Viewer     │  Session  │
│  (15%)   │    (50%)      │   (35%)   │
│          │               │           │
├──────────┴───────────────┤           │
│  Input / Terminal        │           │
├──────────────────────────┴───────────┤
│             Status Bar               │   ← Status Bar (1 row)
└──────────────────────────────────────┘

Width percentages are relative to the terminal width. The Session pane extends the full height from below the tab row down to the status bar; the Input/Terminal area shares its vertical space with the Viewer and FileTree.


Worktree Tab Row

Height: 1 row, pinned to the top of the screen.

Purpose: Displays every worktree as a horizontal tab, giving at-a-glance status for the entire project.

Behavior:

  • Not focusable – it sits outside the focus cycle.
  • [ and ] globally switch between tabs (wrapping at both ends).
  • Click any tab to select it.
  • The [★ main] tab always appears first. It highlights in yellow when main-browse mode is active (Shift+M).

Tab indicators:

SymbolMeaning
Main branch
(green)Agent process running
(AZURE)Unread session output
Normal / idle
(dim)Archived worktree

Priority order: Running overrides Unread, which overrides normal status. Unread clears per-session when that specific session is viewed, and the branch indicator disappears only when all unread sessions on that branch have been seen.

Styling:

  • Active tab: AZURE background, white foreground, bold.
  • Inactive tab: gray text with status symbol prefix.
  • Archived tab: dim gray text with prefix.

Pagination: Tabs use greedy packing. When they overflow the available width, an N/M page indicator appears.


FileTree (15%)

Position: Left column, below the tab row.

Purpose: An always-visible directory tree for the currently selected worktree.

Key features:

  • Nerd Font icons with automatic detection – approximately 60 file types are mapped to language-brand colors (Rust in orange, Python in blue, and so on). If Nerd Font glyphs are not detected, the tree falls back to emoji icons.
  • Expand and collapse directories.
  • Double-click to open a file in the Viewer or expand/collapse a directory.
  • Border title shows Filetree (worktree_name) with an optional [pos/total] scroll indicator when content overflows.
  • File actions: add (a), delete (d), rename (r), copy (c), move (m) via an inline action bar at the bottom of the pane.
  • Options overlay (O): toggle visibility of dotfiles and common hidden entries. Settings persist to the project’s azufig.toml.

Viewer (50%)

Position: Center column, below the tab row.

Purpose: A dual-purpose content display – it renders either file content or diff detail, depending on context.

File viewing:

  • Syntax-highlighted source with line numbers (25 languages via tree-sitter).
  • Markdown files render with prettified formatting: styled headers, bullets, numbered lists, blockquotes, syntax-highlighted code blocks, and box-drawn tables. Line numbers are hidden for markdown.
  • Image files render via terminal graphics protocol (Kitty, Sixel, or halfblock fallback). Images auto-fit the viewport.

Diff viewing:

  • When a diff is selected in the Session pane, the Viewer switches to show diff detail with syntax highlighting.

Viewer Tabs:

  • Up to 12 tabs across 2 rows (6 per row, fixed-width).
  • t saves the current file to a tab; Alt+T opens the tab dialog.
  • x closes the active tab.
  • The tab bar renders inside the Viewer border at rows 1-2, overlaying empty padding so content shifts down accordingly.

Session (35%, Full Height)

Position: Right column, spanning from below the tab row to above the status bar.

Purpose: Displays the agent conversation output – prompts, responses, and tool call results from Claude or Codex sessions.

Border titles (three positions):

PositionContent
Left[x/y] – current message position in the session
Center[session name] – custom name or truncated UUID
RightToken usage badge + PID or exit code
  • Token usage is color-coded: green below 60%, yellow at 60-80%, red above 80%.
  • PID shows in green while the agent process is running; switches to exit code on completion (green for 0, red for non-zero).

Session list overlay (s): Replaces the conversation view with a session file browser, showing status dot, session name, last modified time, and message count. j/k navigate, Enter loads a session, a starts a new one. / activates name filter; // switches to content search.


Input / Terminal

Position: Below the FileTree and Viewer, spanning their combined width.

Purpose: Accepts prompt input for the agent, or hosts an embedded terminal emulator. Only one mode is active at a time – the area toggles between prompt input and the terminal.

  • In prompt mode, text input supports word-wrapped editing with cursor positioning via mouse click.
  • In terminal mode, a full embedded terminal occupies the same space.

Status Bar

Height: 1 row, pinned to the bottom of the screen.

Layout (three sections):

SectionContent
LeftWorktree status dot + display name + branch (branch hidden when identical to name)
CenterStatus messages (clickable – copies message to system clipboard)
RightCPU% + PID badge, rendered in AZURE (#3399FF)

The status bar stores its rect for mouse hit-testing so clicks on the center section can copy the current message.


Git Mode Layout

Pressing Shift+G replaces the normal layout:

╔════════════════════════════════════╗
║ [main] [feat-a] [feat-b] (tab bar) ║
╠══════════╦═══════════════╦═════════╣
║ Actions  ║   Viewer      ║Commits  ║
║──────────║               ║         ║
║ Files    ║               ║         ║
╠══════════╩═══════════════╩═════════╣
║ GIT: wt (Tab/⇧Tab:cycle | Enter)  ║
╚════════════════════════════════════╝
  • Left column: Split into an Actions section (stage, commit, push, etc.) and a Changed Files list.
  • Center: The Viewer, showing diff content for the selected file.
  • Right column: Commit history for the current branch.
  • Bottom bar: A minimal git-mode status line with cycling hints.

The tab row persists at the top, identical in behavior to normal mode.

Focus & Navigation

AZUREAL uses a keyboard-driven focus model. One pane holds focus at any time, and the focused pane receives all keyboard input. Visual borders update to reflect which pane is active.

Focus Cycle

Tab and Shift+Tab cycle focus through the four focusable panes:

FileTree  →  Viewer  →  Session  →  Input
    ↑                                  │
    └──────────────────────────────────┘
  • Tab moves forward (FileTree to Viewer to Session to Input, then wraps).
  • Shift+Tab moves backward (the reverse order).
  • The Worktree Tab Row and Status Bar are not part of the focus cycle – they are controlled by their own dedicated keys.

Focus and Overlays

Several panes host overlay panels that replace their content temporarily (session list, filetree options, help). The focus system interacts with overlays as follows:

  • Tab closes overlays. Pressing Tab while an overlay is open dismisses the overlay and advances focus to the next pane.
  • Shift+Tab preserves some overlays. When the session list overlay is open and Shift+Tab would land on the FileTree, the overlay stays open. This allows quickly glancing at the file tree without losing your place in the session list.
  • Click outside dismisses overlays. Clicking on any pane that is not the overlay’s host pane closes the overlay and focuses the clicked pane.

Mouse Focus

Clicking any pane immediately sets focus to that pane, bypassing the Tab cycle. This is the fastest way to jump to a non-adjacent pane. See Mouse Support for the full click behavior reference.

Worktree Tab Row Navigation

The tab row is outside the focus cycle but has its own navigation keys:

  • [ – switch to the previous worktree tab (wraps around).
  • ] – switch to the next worktree tab (wraps around).
  • Shift+M – toggle main-branch browse mode (highlights the [★ main] tab in yellow).
  • Click any tab to select it directly.

Switching worktrees via the tab row does not change which pane has focus.

Git Mode Navigation

In Git Mode (Shift+G), the focus cycle changes to match the git panel layout:

  • Tab / Shift+Tab cycles through the three git-specific panes (Actions, Files, Commits). The Viewer always shows the diff for the selected file or commit but is not a separate focus target.
  • The bottom bar shows Tab/⇧Tab:cycle | Enter as a reminder.
  • Shift+G or Esc exits Git Mode and returns to the normal focus cycle.

Pane-Specific Navigation

Each pane accepts its own set of keys when focused. A brief summary:

PaneNavigation
FileTreej/k to move, Enter to open, h/l to collapse/expand
Viewerj/k to scroll, Alt+Up/Alt+Down for top/bottom
Sessionj/k to scroll, s for session list, Alt+Up/Alt+Down for top/bottom
InputStandard text editing, Enter to send prompt

Detailed keybindings for each pane are covered in the Keybindings & Input Modes chapter.

Mouse Support

AZUREAL supports mouse interaction across every pane. Click to focus, scroll to navigate content, double-click to open files, and drag to select text.

Click Behavior

All pane rectangles are cached during each render frame, enabling precise hit-testing for every mouse event.

Worktree Tab Row

  • Click a tab to switch to that worktree. The tab row stores per-tab screen x-ranges during rendering, so clicks resolve to the correct tab even with variable-width labels.

FileTree

  • Click an entry to highlight it.
  • Double-click a file to open it in the Viewer.
  • Double-click a directory to expand or collapse it.
  • Double-click detection uses a 500ms window at the same screen position.

Viewer

Session

  • Click to focus the Session pane.
  • Drag to select text within the conversation output.

Input / Terminal

  • Click to focus and enter prompt mode. The cursor is positioned at the clicked location using word-wrap-aware coordinate mapping – the click position on screen is translated through the wrapping break points to the correct character index in the input buffer.

Status Bar

  • Click the center section to copy the current status message to the system clipboard.

Overlays

  • Click outside an overlay (help, branch dialog, run command picker) to dismiss it. The click is consumed by the dismissal – it does not pass through to the pane underneath.

Scroll Behavior

Scrolling works in any pane that has scrollable content:

PaneScroll effect
FileTreeScrolls the directory listing
ViewerScrolls the file content or diff
SessionScrolls the conversation output
TerminalScrolls the terminal scrollback buffer
Todo WidgetScrolls the todo checklist

Scroll events are routed to whichever pane the mouse cursor is over, regardless of which pane currently has keyboard focus. This means you can scroll the Session pane while the Input pane has focus, for example.

Hit-Testing Architecture

During each render pass, AZUREAL caches the screen rectangles for all panes:

  • pane_worktree_tabs – the tab row area
  • pane_worktrees – the FileTree area
  • pane_viewer – the Viewer area
  • pane_session – the Session pane area
  • pane_todo – the Todo Widget area (when visible)
  • input_area – the Input/Terminal area
  • pane_status – the Status Bar area

Mouse events are tested against these rects using Rect::contains(Position::new(col, row)). The same rect cache is shared between click and scroll handlers, so hit-testing is consistent across all interaction types.

The Worktree Tab Row additionally stores a worktree_tab_hits: Vec<(u16, u16, Option<usize>)> mapping each tab’s screen x-range to its worktree index (None for the main-browse tab, Some(idx) for feature worktrees). This is rebuilt during draw_worktree_tabs() each frame.

Text Selection & Copy

AZUREAL supports mouse-driven text selection with clipboard integration. Drag to select text in the Viewer, Session, Input, or Edit Mode, then copy with a platform-appropriate shortcut.

Selecting Text

Click and drag within a pane to create a selection. The selection model works as follows:

  1. Mouse down records the anchor point, converting screen coordinates to cache coordinates immediately. The anchor is tagged with a pane identifier (Viewer, Session, Input, or Edit Mode) so that drag events route to the correct handler.

  2. Mouse drag extends the selection from the anchor to the current cursor position. Only the cursor position is re-mapped from screen to cache coordinates on each drag event – the anchor remains fixed in cache space, so auto-scrolling during drag does not shift the selection start.

  3. Mouse up finalizes the selection.

Selections are stored as a four-tuple:

(start_line, start_col, end_line, end_col)

Values are normalized so that start is always before or equal to end, regardless of drag direction.

Auto-Scroll

When dragging above or below a pane’s visible content area, the pane automatically scrolls in that direction. Because the anchor is stored in cache coordinates (not screen coordinates), the starting point of the selection remains stable as the viewport moves.

Pane-Specific Behavior

Viewer

  • Selection highlighting is applied per-line via apply_selection_to_line().
  • Line number gutter is excluded from selection. When copying, the gutter (line numbers) is stripped so only file content reaches the clipboard.
  • In markdown preview mode (no line numbers), selection covers the full rendered width.

Session

  • Selection respects content bounds – bubble chrome is excluded. Gutters, borders, headers, and decorative elements (the orange | gutter, AZURE | border, colored-background headers, bottom borders, code fences) are not selectable.
  • Each cache line’s selectable region is computed by compute_line_content_bounds(). Lines that are entirely decorative (bounds (0, 0)) are skipped during selection highlighting.
  • Copy extracts only the textual content within those bounds, trims leading and trailing blank lines, and skips decoration lines.

Input

  • Selection maps screen coordinates to character indices using screen_to_input_char(), accounting for word-wrap break points.
  • The fast-draw path for the input pane is bypassed when a selection is active, ensuring selection styling renders correctly.

Edit Mode

  • When the Viewer is in edit mode, clicks and drags use screen_to_edit_pos() to map screen coordinates to source-line and source-column positions, walking source lines and summing wrap counts.
  • Edit mode selections are stored separately as viewer_edit_selection: Option<(anchor_line, anchor_col, drag_line, drag_col)> and move the edit cursor to the drag endpoint.

Copying

Cmd+C (macOS) or Ctrl+C (Windows / Linux) copies selected text from whichever pane has an active selection.

PaneCopy behavior
ViewerCopies file content; line number gutter stripped
SessionCopies conversation text; bubble chrome excluded
InputCopies the selected portion of the prompt input
Edit ModeCopies the selected source text
Git Mode (Viewer)Copies diff content (gutter=0, no line numbers to strip)
Git Mode (Status Box)Copies the result message when the status box is selected

Clipboard access uses the arboard crate for cross-platform system clipboard integration.

Git Mode

In Git Mode, Cmd+C and Cmd+A are intercepted before the git panel’s own input handler. If no viewer selection exists, Cmd+C falls back to copying the result_message from the git status box. Cmd+A selects the status box when the viewer cache is empty.

Clearing Selections

Selections are cleared automatically when any of the following occurs:

  • Click anywhere (a new click starts fresh).
  • Scroll in any pane.
  • Tab or Shift+Tab (focus change).
  • Focus change via mouse click on a different pane.

This ensures stale selections do not linger after navigation.

Selection Rendering

Selected text is highlighted with an Rgb(60, 60, 100) background color. The highlighting is applied by splitting styled spans at the selection boundaries and patching the background color of the overlapping region. This runs at O(spans-in-line) per visible line – negligible rendering cost.

Keybindings & Input Modes

AZUREAL is a keyboard-driven application. Every action is reachable from the keyboard, and the keybinding system is designed around two principles: vim-style modal input and a single source of truth.


The interface operates in one of four modes at any given time, each indicated by a colored border on the focused pane:

ModeBorder ColorPurpose
CommandRedKeys trigger actions – nothing is typed
PromptYellowKeys are typed as text for an agent prompt
TerminalAzureKeys are forwarded to the embedded PTY shell
Speech RecordingMagentaAudio is being captured for transcription

You always start in command mode. Pressing p enters prompt mode. Pressing T toggles the terminal (entering terminal mode when it gains focus). Esc returns to command mode from any other mode.

Mode determines what a keystroke does. The same physical key can trigger a global action in command mode, insert a character in prompt mode, or send input to a shell in terminal mode. This is covered in detail in Vim-Style Modes.


Centralized Keybinding System

All keybindings are defined once in the keybindings module. The function lookup_action() is the single entry point that resolves a key event into an action. Individual input handlers never define their own bindings – they only handle keys that lookup_action() did not resolve.

This centralized design means:

  • No duplicate bindings. A key combination maps to exactly one action in a given context.
  • Guard logic is consistent. Globals never fire during text input, edit mode, or active filters.
  • Modal panels use per-modal lookup functions. The health panel, git panel, and projects panel each have their own lookup function for panel-specific keys, but these are still defined in the keybindings module alongside everything else.
  • The help overlay reads from the same source. What ? displays is always in sync with what the keys actually do.

Chapter Contents

  • Vim-Style Modes – The four input modes, how they interact, and how the border color tells you where you are.
  • Global Keybindings – The full table of keys available in command mode.
  • Leader Sequences – Multi-key sequences starting with W for worktree operations.
  • Platform Differences – macOS vs. Windows/Linux modifier key mappings, symbol rendering, and the Option key workaround.
  • Help Overlay – The ? overlay that shows all keybindings organized by section.

Vim-Style Modes

AZUREAL borrows the modal editing concept from Vim: a keystroke’s meaning depends on which mode is active. There are four modes, each visually indicated by the border color of the focused pane.


Command Mode (Red Border)

Command mode is the default. When the application launches, you are in command mode.

In this mode, every key press is interpreted as an action. Typing j scrolls down one line – it does not insert the letter “j” anywhere. Typing p enters prompt mode. Typing ? opens the help overlay.

No text input occurs in command mode. It is purely navigational.

Enter command mode by pressing Esc from any other mode.


Prompt Mode (Yellow Border)

Prompt mode is for composing agent prompts. Pressing p from command mode enters prompt mode. The input box appears at the bottom of the session pane, and keystrokes are typed as text.

Submitting a Prompt

Press Enter to submit the prompt to the active agent backend.

If the agent is already running when you press Enter, the behavior changes: the current agent run is cancelled and the prompt text is stored as a staged prompt. Once cancellation completes, the staged prompt is automatically sent to the agent. This lets you interrupt and redirect an agent without losing your next instruction.

Multi-Line Input

Press Shift+Enter to insert a newline instead of submitting. The input box grows dynamically to accommodate multi-line prompts, expanding up to three-quarters of the terminal height. Once the prompt is submitted or cleared, the input box shrinks back to a single line.

Returning to Command Mode

Press Esc to leave prompt mode and return to command mode. The input box content is preserved – re-entering prompt mode with p restores whatever was typed.


Terminal Mode (Azure Border)

Terminal mode is active when the embedded terminal pane has focus. In this mode, all keystrokes are forwarded directly to the PTY shell running inside the terminal.

Toggle the terminal with T (Shift+T) from command mode. When the terminal is shown and focused, you are in terminal mode. Press Esc to return to command mode without closing the terminal – or press T again to hide it entirely.

The terminal runs a full shell per worktree. See The Embedded Terminal for details on shell integration and terminal-specific features.


Speech Recording Mode (Magenta Border)

Speech recording mode is active while audio is being captured for speech-to-text transcription. The border turns magenta to indicate that the microphone is live.

This mode is entered and exited via the speech-to-text keybinding (see Speech-to-Text). While recording, most other keybindings are suppressed to avoid accidental actions.


How Modes Interact

Only one mode is active at a time. The active mode is always visible from the border color of the focused pane. The flow between modes follows a simple pattern:

              p
Command ───────────> Prompt
  ^  ^                 │
  │  │     Esc          │
  │  │<────────────────┘
  │  │
  │  │     Esc
  │  │<────────────────┐
  │  │                 │
  │  T ───────────> Terminal
  │
  │        Esc
  └────────────────── Speech

Esc is the universal escape hatch. From any mode, pressing Esc returns to command mode. This means you never need to remember a mode-specific exit key – Esc always works.


Guard Logic

The centralized keybinding system includes guard logic that prevents global keybindings from firing when they would conflict with text input:

  • Prompt mode: Letter keys type text. Global bindings like j (scroll) and p (enter prompt mode) are suppressed.
  • Terminal mode: All keys are forwarded to the PTY. No global bindings fire.
  • Edit mode (in the file viewer): Letter keys type text into the editor. Global bindings are suppressed.
  • Active filters: When a search/filter input is active (e.g., in the session list), letter keys type into the filter field.

Only Esc and a small set of modifier-key combinations (like Ctrl+Q to quit) remain active across all modes.

Global Keybindings

These keybindings are available in command mode (red border). They are suppressed during prompt mode, terminal mode, edit mode, and active filter inputs. See Vim-Style Modes for details on when globals are active.


Mode Switching

KeyAction
pEnter prompt mode
T (Shift+T)Toggle terminal
EscReturn to command mode

Scrolling (Per-Pane)

KeyAction
jScroll down one line
kScroll up one line
J (Shift+J)Page scroll down
K (Shift+K)Page scroll up

These are defined per-pane (FileTree, Viewer, Session, Terminal) rather than as global bindings, but they behave consistently across all scrollable panes. The scroll targets whichever pane currently has focus.


Pane Focus

KeyAction
TabCycle pane focus forward
Shift+TabCycle pane focus backward

Focus cycles through the four panes: FileTree, Viewer, Session, and Input.


Panels & Overlays

KeyAction
G (Shift+G)Open git actions panel
H (Shift+H)Open health panel
M (Shift+M)Browse main branch
P (Shift+P)Open projects panel
?Open help overlay

Panels are modal overlays that take focus when opened. Each panel defines its own keybindings for navigation within the panel (see panel-specific chapters). Press Esc to close a panel and return to command mode.


Worktree Tabs

KeyAction
[Switch to previous worktree tab
]Switch to next worktree tab

These keys cycle through the worktree tab row at the top of the interface. The active worktree determines which branch, session, and terminal are shown.


Run Commands

KeyAction
rExecute a run command
R (Shift+R)Add a new run command

Run commands are predefined shell commands (build, test, lint, etc.) that execute in the active worktree. See Run Commands for configuration details.


Clipboard & Agent Control

KeyAction
Cmd+C / Ctrl+CCopy selection (platform-dependent)
Ctrl+C / Alt+CCancel running agent (platform-dependent)
Ctrl+MCycle model (Claude Opus, Sonnet, Haiku, Codex models)
Ctrl+QQuit the application

The copy and cancel keys differ between macOS and other platforms. See Platform Differences for the full mapping.


Quick Reference

The complete table in one place:

KeyAction
pEnter prompt mode
TToggle terminal
EscReturn to command mode (per-pane)
Tab / Shift+TabCycle pane focus
GGitView panel
HHealth panel
MBrowse main branch
PProjects panel
rRun command
RAdd run command
[ / ]Switch worktree tab
?Help overlay
Cmd+C / Ctrl+CCopy selection
Ctrl+C / Alt+CCancel agent
Ctrl+MCycle model
Ctrl+QQuit

Leader Sequences

Leader sequences are multi-key commands that start with a leader key. AZUREAL uses W (Shift+W) as the leader key for worktree operations. This avoids overloading single-key bindings while keeping worktree management fast and discoverable.


How Leader Sequences Work

  1. Press W from command mode, regardless of which pane has focus.
  2. The status bar updates to show [W ...], indicating a leader sequence is in progress.
  3. Press the second key to resolve the action.
  4. The action executes and the leader state clears.

If you press W and then change your mind, press Esc to cancel the sequence and return to normal command mode. The [W ...] indicator disappears.


Worktree Leader Sequences

SequenceAction
W then nCreate a new worktree
W then rRename the active worktree
W then aArchive the active worktree
W then dDelete a worktree

Create (Wn)

Opens the worktree creation dialog. You provide a branch name, and AZUREAL creates a new git worktree with that branch, adds it to the tab row, and switches to it.

Rename (Wr)

Opens a rename dialog pre-filled with the current branch suffix. On confirm, the underlying git branch is renamed (git branch -m), the new branch is pushed to the remote, and the old remote branch is deleted. All branch-keyed app state is migrated to the new name.

Archive (Wa)

Archives the active worktree. The worktree directory is removed, but the git branch is preserved. Archived worktrees remain visible in the tab row with a dim prefix and can be unarchived with u to recreate the worktree from the preserved branch.

Delete (Wd)

Deletes a worktree from disk. This runs git worktree remove and is destructive – uncommitted changes in the worktree are lost. A confirmation prompt is shown before deletion.


Shortcut When Worktrees Pane Is Focused

When the Worktrees pane has focus, the leader prefix is not required. The second key resolves directly:

KeyAction
nCreate a new worktree
rRename worktree
aArchive worktree
dDelete worktree

This shortcut exists because when you are already looking at the worktrees list, the W prefix is redundant. The direct keys are faster and more natural in that context.

Outside the Worktrees pane, these single keys have other meanings (e.g., r runs a command, n may be unbound), so the W leader prefix is required to disambiguate.


Status Bar Feedback

The status bar provides real-time feedback during leader sequences:

  • Before pressing leader key: Status bar shows normal state (mode, branch, model).
  • After pressing W: Status bar shows [W ...] to indicate a leader sequence is pending.
  • After pressing the second key: The action executes and the status bar returns to normal.
  • After pressing Esc: The leader sequence is cancelled and the status bar returns to normal.

This visual feedback ensures you always know whether a leader sequence is in progress, eliminating ambiguity about what your next keystroke will do.

Platform Differences

AZUREAL runs on macOS, Windows, and Linux. The core keybindings are the same across platforms, but modifier keys differ because each platform has different conventions for system-level shortcuts.


Modifier Key Mapping

ActionmacOSWindows / Linux
Copy selectionCmd+CCtrl+C
Cancel agentCtrl+CAlt+C
Archive worktreeCmd+AAlt+A
Delete worktreeCmd+DAlt+D
Select allCmd+ACtrl+A
Save fileCmd+SCtrl+S
UndoCmd+ZCtrl+Z
RedoCmd+Shift+ZCtrl+Y
STT (edit mode)Ctrl+SAlt+S

The pattern is straightforward: where macOS uses Cmd as the primary modifier, Windows and Linux use Ctrl. Where macOS uses Ctrl for secondary actions (like cancelling an agent), Windows and Linux use Alt to avoid collision with Ctrl+C being mapped to copy.


Why Copy and Cancel Differ

On macOS, Cmd+C is the system copy shortcut, so AZUREAL maps copy to Cmd+C and uses Ctrl+C (which has no system meaning on macOS) for cancelling the running agent.

On Windows and Linux, Ctrl+C is both the system copy shortcut and the traditional terminal interrupt signal. AZUREAL maps it to copy (matching user expectation), and moves agent cancellation to Alt+C to avoid the collision.


Modifier Symbol Display

AZUREAL renders modifier keys differently depending on the platform:

macOS uses standard Apple modifier symbols:

SymbolModifier
Control
Option
Shift
Command

Windows and Linux use text labels:

LabelModifier
Ctrl+Control
Alt+Alt
Shift+Shift

These symbols and labels appear in the help overlay, status bar hints, and panel footers.


macOS Option Key Workaround

On macOS, pressing Option + a letter key produces a Unicode character instead of sending the key combination directly. For example:

Key CombinationCharacter Produced
Option+Cc-cedilla
Option+Dpartial differential
Option+Aa-ring
Option+Ssharp-s

This is standard macOS behavior, but it means raw terminal input for Option+letter arrives as a Unicode character, not as an Alt+letter event.

AZUREAL handles this internally by mapping these Unicode characters back to their intended Option+letter combinations. The keybinding system recognizes both the Unicode character and the intended modifier combination, so bindings work correctly regardless.

The help overlay filters out these Unicode alternative representations. You will only see the human-readable Option+letter form, not the raw Unicode character it produces.


Summary

If you are switching between platforms:

  • Non-modifier keys (p, j, k, T, G, ?, etc.) are identical everywhere.
  • Modifier keys follow each platform’s conventions (Cmd on macOS, Ctrl on Windows/Linux).
  • Agent cancel is always one step removed from copy (Ctrl+C on macOS, Alt+C elsewhere).
  • The help overlay (?) always shows the correct bindings for your current platform.

When in doubt, press ? to see exactly what each key does on your system.

Help Overlay

Press ? in command mode to open the help overlay – a centered panel that lists all keybindings organized by section.


What It Shows

The help overlay displays every keybinding defined in the centralized keybinding system, grouped into logical sections:

  • Mode switching – entering and exiting prompt mode, terminal mode, etc.
  • Navigation – scrolling, pane focus cycling, worktree tab switching.
  • Panels – opening the git panel, health panel, projects panel, main branch browser.
  • Toggles – file tree, session list.
  • Worktree operations – leader sequences and direct worktree keys.
  • Clipboard & control – copy, cancel, model cycling, quit.

Each entry shows the key (or key combination) and a short description of the action it performs. Modifier key symbols are rendered in the platform-native format – Apple symbols on macOS, text labels on Windows and Linux (see Platform Differences).


What It Excludes

Modal panels that display their own keybinding hints in a visible footer are excluded from the help overlay. These panels are self-documenting – the footer already tells you what each key does within that panel. Duplicating those bindings in the help overlay would add noise without adding information.

Examples of self-documenting panels:

  • The git panel shows footer hints for staging, committing, pushing, etc.
  • The health panel shows footer hints for navigation and actions.
  • The projects panel shows footer hints for project switching and management.

If a panel is open and you need to know what keys are available, look at its footer. If you are in the main interface and need to know what keys are available, press ?.


Interaction

The help overlay is a read-only display. It does not accept text input or support search/filtering.

Press Esc or ? again to close the overlay and return to command mode.


Source of Truth

The help overlay reads from the same centralized keybinding definitions that lookup_action() uses at runtime. This means the overlay is always accurate – it cannot drift out of sync with actual behavior because both the overlay and the input handler read from the same data.

If a keybinding is added, changed, or removed in the code, the help overlay reflects that change automatically. There is no separate help text to maintain.

Git Worktrees

Worktrees are the organizing unit of all work in AZUREAL. Every feature branch lives inside its own git worktree – a separate working directory with its own checked-out branch, file tree, terminal shell, and agent sessions. This isolation is what makes true concurrent AI-assisted development possible: you can have multiple agents running in parallel across different feature branches without any cross-contamination of files, git state, or conversation history.


How It Works

AZUREAL sits on top of the standard git worktree mechanism. When you create a worktree through AZUREAL, three things happen:

  1. A git worktree is created under <repo>/worktrees/<name>, giving you a full working directory on a new branch named azureal/<name>.
  2. The tab row updates to show the new worktree alongside your existing ones.
  3. A session is created on demand when you send your first prompt or explicitly create one via the session dialog – the SQLite store is not pre-populated at worktree creation time.

From that point on, the worktree is a self-contained development environment. Switching between worktrees (with [/] or by clicking tabs) swaps the file tree, viewer, session history, terminal, and git state all at once.


Main Branch

The main branch (typically main or master) is not a worktree in the traditional sense – it is the original repository checkout. AZUREAL treats it specially: it always appears as the first tab ([★ main]), it cannot be deleted or archived, and its git panel offers pull/commit/push instead of squash/rebase. You can browse and work on main at any time via Shift+M.

See Main Branch Browse for the full details.


Lifecycle

A worktree moves through a simple lifecycle:

Create  -->  Active  -->  Archive or Delete
                ^              |
                |   Unarchive  |
                +--------------+
  • Active worktrees have a working directory on disk and appear as normal tabs.
  • Archived worktrees have their working directory removed but their git branch preserved. They appear dimmed with a diamond prefix and can be restored at any time.
  • Deleted worktrees are fully removed: the working directory, the local branch, the remote branch, and all associated session state.

Chapter Contents

  • Creating Worktrees – How to spin up a new worktree and optionally start an agent session immediately.
  • Managing Worktrees – Renaming, archiving, unarchiving, and deleting worktrees.
  • Main Branch Browse – Working on the main branch without leaving the worktree interface.
  • Worktree Tab Row – The horizontal tab bar: status icons, pagination, unread indicators, and visual states.

Creating Worktrees

Creating a worktree gives you a fully isolated working directory with its own branch, file tree, terminal, and agent sessions. Every worktree lives under <repo>/worktrees/<name> on a branch named azureal/<name>.


The Wn Leader Sequence

Worktree creation uses a two-key leader sequence. Press Shift+W to enter leader mode (the status bar shows W ...), then press n to open the branch dialog. Press Esc at any point to cancel.

This leader pattern keeps destructive worktree operations behind an intentional two-step keypress, preventing accidental triggers from normal typing.


The Branch Dialog

The branch dialog is a centered overlay that lists all available branches in the repository. It has two purposes: creating a brand-new branch or checking out an existing one as a worktree.

Layout

The first row is always “Create new”. Below it, every local and remote branch is listed. Each branch row shows:

  • A checkmark if the branch is already checked out in an active worktree
  • A worktree count showing how many worktrees (active + archived) use that branch
  • The full branch name

Filtering

Start typing to filter the branch list. The filter bar accepts git-safe characters (alphanumeric plus -, _, ., +, @, /, !). The list narrows in real time as you type. Backspace widens the filter, and Left/Right move the cursor within the filter text.

Creating a New Branch

When the “Create new” row is selected (the default), type a name into the filter field and press Enter. AZUREAL will:

  1. Create a git worktree at <repo>/worktrees/<name>
  2. Check it out on a new branch named azureal/<name>
  3. Refresh the tab row and auto-select the new worktree
  4. Enable auto-rebase for the new worktree by default (persisted to .azureal/azufig.toml)

The name you type becomes both the directory name and the branch suffix. For example, typing auth-refactor creates worktrees/auth-refactor on branch azureal/auth-refactor.

If a worktree with that name already exists, creation fails with an error message in the status bar.

Checking Out an Existing Branch

Navigate to an existing branch with j/k and press Enter. If the branch is already checked out in a worktree (shown by the checkmark), AZUREAL switches focus to that worktree instead of creating a duplicate. Otherwise, a new worktree is created from the selected branch.


What Happens After Creation

Creation runs on a background thread so the UI stays responsive. A “Creating worktree…” loading indicator appears while git does its work. Once complete:

  • The tab row gains a new tab for the worktree
  • The new worktree is auto-selected and its file tree loads immediately
  • Auto-rebase is enabled by default, keeping the branch up to date with main (the green R indicator appears on the tab)
  • No session is created yet – the SQLite session store is only populated when you send your first prompt or explicitly create a session via a in the Session pane

From here, the worktree is a self-contained environment. You can open files in the viewer, start a terminal shell, or send a prompt to begin an agent session.

Managing Worktrees

Once a worktree exists, you can rename it, archive it for later, or delete it permanently. All management actions use the W leader sequence: press Shift+W, then the action key. The status bar shows W ... while waiting for the second keypress.

SequenceAction
WnNew worktree
WrRename worktree
WaArchive / unarchive worktree
WdDelete worktree

The main branch cannot be renamed, archived, or deleted.


Renaming (Wr)

Renaming changes the git branch name and migrates all internal state. Press Wr to open a centered dialog with a cyan double border, pre-filled with the current branch suffix (without the azureal/ prefix).

Dialog Controls

  • Type to edit the name. The cursor supports Left/Right movement, Backspace, and character insertion at the cursor position.
  • Enter confirms the rename.
  • Esc cancels.

What Happens on Confirm

  1. All branch-keyed state maps are migrated immediately on the main thread: session files, display events cache, branch slots, active slot, unread sessions, and auto-rebase configuration.
  2. The worktree entry’s branch name updates in-place.
  3. A background thread handles the git operations:
    • git branch -m <old> <new> renames the local branch
    • Pushes the new name to the remote
    • Deletes the old remote branch
    • Sets upstream tracking for the new name
  4. The tab row refreshes with the updated name.

The worktree directory on disk is not renamed – only the git branch changes.


Archiving (Wa)

Archiving removes the worktree’s working directory from disk but preserves the git branch. This is useful for parking a feature branch you want to return to later without cluttering the filesystem.

Press Wa on an active worktree to archive it. The operation runs on a background thread and shows an “Archiving worktree…” loading indicator.

Visual Changes

Archived worktrees remain in the tab row but appear dimmed with a diamond prefix () instead of the normal status circle. They cannot be opened or prompted until unarchived.

Pressing Enter on an archived worktree’s session shows a status message directing you to unarchive first.

Unarchiving

Press Wa again on an archived worktree to restore it. AZUREAL recreates the git worktree from the preserved branch. The “Unarchiving worktree…” loading indicator appears while the working directory is rebuilt.

After unarchiving, the worktree returns to its normal state with full file tree, terminal, and session access. The tab icon reverts from back to the standard status circle.


Deleting (Wd)

Deleting is permanent: it removes the worktree directory, deletes the local git branch, pushes a branch deletion to the remote, and prunes the local remote-tracking ref. All associated session state (session files, branch slots, active slot, unread sessions, auto-rebase config) is cleaned up.

Press Wd to open a centered confirmation dialog with a red double border.

Safety Warnings

Before showing the dialog, AZUREAL checks for potential data loss:

  • Uncommitted changes: runs git status --porcelain on the worktree path (skipped for archived worktrees that have no working directory)
  • Unmerged commits: runs git log main..<branch> --oneline to count commits not yet merged to main

If either condition is found, yellow warning lines appear in the dialog between the question and the action keys:

! 3 uncommitted changes
! 2 commits not merged to main

These warnings are informational – they do not block deletion.

Sole Worktree Dialog

When the worktree is the only one on its branch (the common case), the dialog shows a simple confirmation:

  • y or Enter – confirm deletion
  • Esc or any other key – cancel

Sibling Guard Dialog

When multiple worktrees share the same branch (rare, but possible), git prevents branch deletion while any worktree is still checked out on it. The dialog adjusts to offer two choices:

  • y – delete all sibling worktrees on this branch and the branch itself
  • aarchive only the current worktree (keeps siblings and branch intact)
  • Esc or any other key – cancel

After Deletion

The deletion runs on a background thread. Once complete, the tab row updates, and the selection moves to the nearest remaining worktree. If no worktrees remain, the welcome modal appears.


Cross-Machine Cleanup

On startup and project switch, AZUREAL runs git remote prune origin followed by a cleanup pass that deletes local azureal/* branches which are fully merged to main and have no remote counterpart. This prevents worktrees deleted on one machine from appearing as archived on another.

Main Branch Browse

The main branch (typically main or master) is the original repository checkout and always appears as the first tab in the tab row. While it is not a worktree in the <repo>/worktrees/ sense, AZUREAL lets you browse and work on it with the same interface as any feature worktree.


Entering and Exiting

Press Shift+M from any pane to enter main branch browse mode. The [★ main] tab highlights in yellow (distinct from the AZURE highlight used for feature worktrees) as a visual cue that you are working on the primary branch.

To exit, press Esc or Shift+M again. AZUREAL restores your previous worktree selection and focus state.

Shift+M works globally – from the file tree, viewer, session pane, or input. It also works from inside the git panel, where clicking the ★ main tab or pressing Shift+M opens the main branch’s git view.


Full Functionality

Main browse is not a read-only mode. Everything works the same as it does on a feature worktree:

  • File tree shows the main branch’s working directory
  • Viewer opens and edits files on main
  • Terminal spawns a shell in the repository root
  • Sessions are fully functional – you can create sessions, send prompts, and run agents on main
  • Edit mode works normally for file editing

The main worktree is stored separately from the feature worktrees vec (app.main_worktree), and current_worktree() transparently returns it when browsing_main is true. This means all code paths that operate on “the current worktree” work seamlessly on main.


Git Panel Differences

The git panel (Shift+G) is context-aware and shows different actions depending on whether you are on main or a feature branch:

Main BranchFeature Branch
l Pullm Squash merge to main
c CommitShift+R Rebase onto main
Shift+P Pushc Commit
z StashShift+P Push
Shift+Z Stash popz Stash
Shift+Z Stash pop

Main does not offer squash merge or rebase (you cannot merge main into itself), and feature branches do not offer pull (pulling is done on main, then auto-rebase propagates changes to feature branches).

The different action set and the yellow tab highlight serve as indirect cues that you are operating on the primary branch – there is no separate “main mode” overlay or banner.


Interaction With Tab Switching

The [ and ] keys cycle through feature worktree tabs. When browsing main:

  • Pressing [ exits main browse and selects the last worktree
  • Pressing ] exits main browse and selects the first worktree

This makes it easy to hop between main and your feature branches without reaching for Shift+M.


State Isolation

Entering and exiting main browse saves and restores display events and terminal state, just like switching between feature worktrees. Your main branch session history, file tree expansion state, and terminal shell are all preserved independently from any feature worktree.

switch_project() clears main browse state, so switching projects always starts fresh.

Worktree Tab Row

The tab row is a single horizontal bar at the top of the layout showing all worktrees as clickable tabs. It replaces the older sidebar design, giving more horizontal space to the three-pane layout below.


Layout

The [★ main] tab is always first. Feature worktrees follow in the order they were discovered by git worktree list. Each tab shows a status icon prefix followed by the worktree’s display name (the branch suffix after azureal/).

Tabs are separated by dividers. The divider color is AZURE when the tab row has focus, dark gray otherwise.


Status Icons

Every tab has a leading icon that communicates the worktree’s current state at a glance. Icons follow a strict priority order – the first matching condition wins:

IconColorConditionMeaning
GreenAgent process is runningAn active agent session is streaming
AZUREUnread session finishedA session completed while you were viewing a different worktree
Dim grayWorktree is archivedWorking directory removed, branch preserved
GrayDefault idle stateNo agent running, nothing unread

The running check () always takes priority. If an agent is actively streaming and the session also has unread events, you see the green filled circle, not the half-filled unread indicator.

Unread Tracking

When any agent session finishes on a worktree you are not currently viewing, that worktree’s tab shows in AZURE. The unread indicator clears per-session when you view that specific session, and the branch-level disappears only when all unread sessions on the branch have been viewed. Unread state only clears when the session pane is visible (normal mode or after closing the git panel).


Tab Styling

StateForegroundBackgroundModifier
Active feature worktreeWhiteAZURE (#3399FF)Bold
Active ★ mainBlackYellowBold
Running (not selected)Green
Unread (not selected)AZURE
ArchivedDim gray
Inactive ★ mainYellow
Inactive feature worktreeGray / Dark gray

When the tab row itself has focus, inactive tabs use Gray; when another pane is focused, they use DarkGray.


Keyboard

  • [ / ] – switch to the previous / next worktree tab. These keys are global and work from any pane (file tree, viewer, session, input, terminal). Navigation wraps around at both ends.
  • When browsing main, [ exits to the last worktree and ] exits to the first.

Mouse

Click any tab to select it. Click ★ main to enter main browse mode. Tab hit-test regions are cached during each render for accurate click detection.


Pagination

When tabs do not fit in the available width, they are packed into pages using a greedy algorithm. The page containing the active tab is always shown. A dim N/M indicator appears at the end of the row (e.g., 2/3 means page 2 of 3).

Use { / } (in the git panel) to jump between pages. In normal mode, [ and ] cycle through worktrees and automatically advance the page when crossing a page boundary.


Auto-Rebase Indicator

Worktrees with auto-rebase enabled show an R suffix after their tab label. The R is bold and color-coded to reflect the current rebase state:

ColorMeaning
GreenAuto-rebase enabled, idle
Orange (GIT_ORANGE)RCR (Rebase Conflict Resolution) actively running
BlueRCR complete, awaiting approval

The indicator is rendered as a separate span after the tab label, so it does not interfere with the tab’s own styling. Its width (1 character) is included in the tab width calculation for pagination accuracy.

Auto-rebase is enabled by default for newly created worktrees. Toggle it with a in the git panel’s actions section (feature branches only). The setting is persisted to .azureal/azufig.toml.


The Tab Row Is Not Focusable

The tab row occupies a single row at the top of the layout and is not part of the Tab/Shift+Tab focus cycle. Focus cycles through FileTree -> Viewer -> Session -> Input. The [/] global keys and mouse clicks are the only ways to interact with the tab row.

Agent Sessions

AZUREAL orchestrates AI coding agents by wrapping two CLI tools – Claude Code CLI and OpenAI Codex CLI – behind a unified interface. You interact with both backends through the same prompt input, the same session pane, and the same keybindings. The backend is determined automatically from whichever model you have selected.


Dual Backend Architecture

Two backends exist:

BackendCLI ToolTriggered By
Backend::ClaudeClaude Code CLIAny non-gpt-* model (opus, sonnet, haiku)
Backend::CodexCodex CLIAny gpt-* model (gpt-5.4, gpt-5.3-codex, etc.)

The AgentProcess struct holds both a ClaudeProcess and a CodexProcess. At spawn time, the active model determines which backend handles the request. You never configure the backend directly – switching models with Ctrl+M is all it takes.

Both backends produce the same AgentEvent and DisplayEvent types, so the rest of the application (session pane, session store, rendering) is backend-agnostic. A single session can span prompts to both Claude and Codex models if you switch models mid-conversation.


Process-Per-Prompt Model

Agent processes do not persist between prompts. Each time you press Enter to submit a prompt, a new CLI process is spawned. The process streams its response back as JSON events, and when the response is complete, the process exits. The next prompt spawns a fresh process.

This design exists because Claude Code’s interactive mode uses a full TUI that cannot be driven via stdin. The -p (prompt) mode exits after each response. The spawn overhead is approximately 100-200ms – imperceptible in practice.

Conversation continuity is maintained not by keeping a process alive, but by injecting context from the SQLite session store into each new prompt. See Session Store & Persistence for details on how context injection works.


Chat Bubble Headers

Each response in the session pane is displayed in a chat bubble. The header of each bubble shows the agent name left-aligned and the model ID right-aligned in a subdued style. This makes it easy to see which model produced a given response, especially in sessions that span multiple models.


Chapter Contents

  • Claude Backend – How AZUREAL invokes Claude Code CLI, captures session IDs, and handles permission modes.
  • Codex Backend – How AZUREAL invokes Codex CLI, captures thread IDs, and handles permission modes.
  • Model Switcher – The unified model cycle (Ctrl+M), model colors, backend derivation, and model persistence across sessions.
  • Multi-Agent Concurrency – PID-keyed session slots, running multiple agents per worktree, and slot switching.
  • Session Lifecycle – The full prompt-to-response cycle, from user input through context injection, process spawn, streaming, parsing, and store ingestion.

Claude Backend

The Claude backend wraps the Claude Code CLI (claude command) to execute prompts against Anthropic’s Claude models. AZUREAL uses the CLI’s non-interactive -p mode, which accepts a prompt string and exits after producing a response.


Command Structure

Every Claude invocation follows this pattern:

claude -p "<prompt>" --verbose --output-format stream-json

The flags serve specific purposes:

FlagPurpose
-p "<prompt>"Non-interactive mode. Accepts a single prompt and exits after the response.
--verboseEnables detailed output including tool call information.
--output-format stream-jsonEmits one JSON object per line as events arrive, rather than buffering the entire response.

The prompt string includes any injected context from the session store (see Context Injection). The full prompt is constructed before the process is spawned.


Context Injection (No –resume)

AZUREAL does not use Claude Code’s --resume flag. Instead, conversation continuity is handled entirely through context injection. On each prompt:

  1. The session store builds the conversation context from all prior events via build_context().
  2. The context is formatted and prepended to the user’s prompt inside <azureal-session-context> XML tags.
  3. A fresh claude -p process is spawned with the context-injected prompt.

This approach gives AZUREAL full control over what context the agent sees, enables cross-backend continuity (a session can include both Claude and Codex responses), and avoids dependency on Claude Code’s internal session file format.


Session ID Capture

When a Claude process starts, the CLI emits a subtype:init event in its JSON stream. This event contains the session ID assigned by the Claude backend. AZUREAL captures this ID and associates it with the active session slot.

The session ID is used for display and diagnostics. It is not used for resumption – context injection replaces that role.


Permission Modes

Claude Code supports two permission modes, controlled by configuration:

Dangerously Skip Permissions

claude -p "..." --dangerously-skip-permissions

This flag bypasses all tool-use approval prompts. The agent can read files, write files, execute commands, and perform any other tool action without asking for permission. This is the mode most users run in for unattended workflows.

Approve Mode

Without the --dangerously-skip-permissions flag, Claude Code may pause to request approval for certain tool actions. AZUREAL surfaces these approval requests in the session pane via the AskUserQuestion display (see AskUserQuestion).


Model Selection

The Claude backend serves three models:

ModelAlias
Claude Opusopus
Claude Sonnetsonnet
Claude Haikuhaiku

The --model flag is passed to the CLI based on the currently selected model alias. See Model Switcher for how model selection works.


Streaming and Event Parsing

The --output-format stream-json flag causes Claude Code to emit one JSON object per line as events arrive. AZUREAL reads this stream line by line, parses each JSON object into an AgentEvent, and dispatches it to the event loop. Events include:

  • Init – session start with session ID and model information.
  • AssistantText – incremental text output from the model.
  • ToolUse / ToolResult – tool invocations and their results.
  • UserMessage – the original prompt, echoed back.
  • Error – error conditions reported by the CLI.

These AgentEvent values are converted into DisplayEvent values for rendering in the session pane and persisting to the session store. The conversion is backend-agnostic – both Claude and Codex events feed into the same DisplayEvent pipeline.


Process Lifecycle

Each Claude process follows a simple lifecycle:

  1. Spawn: A new claude -p process is started with the context-injected prompt.
  2. Stream: JSON events are read from stdout and parsed in real time.
  3. Exit: The process exits when the response is complete.
  4. Ingest: The JSONL output file is parsed, events are appended to the SQLite store, and the JSONL file is deleted.

The process does not persist between prompts. See Session Lifecycle for the full end-to-end flow.

Codex Backend

The Codex backend wraps the OpenAI Codex CLI (codex command) to execute prompts against OpenAI’s GPT models. Like the Claude backend, AZUREAL uses a non-interactive execution mode that exits after producing a response.


Command Structure

Every Codex invocation follows this pattern:

codex exec --json "<prompt>"

Like the Claude backend, AZUREAL does not use the Codex CLI’s native resume mechanism. Conversation continuity is handled entirely through context injection from the SQLite session store. Each prompt spawns a fresh process with the full context prepended.

Flag / ArgumentPurpose
execNon-interactive execution mode.
--jsonEmits structured JSON output for machine parsing.

Session ID Capture

When a Codex process starts a new thread, it emits a thread.started event containing a thread_id field. AZUREAL captures this ID and associates it with the active session slot.

The thread ID is used for display and diagnostics. It is not used for resumption – context injection replaces that role, just as with the Claude backend.


Permission Modes

Codex CLI supports two permission modes:

Dangerously Bypass Approvals and Sandbox

codex exec --json --dangerously-bypass-approvals-and-sandbox "<prompt>"

This flag disables all approval prompts and sandbox restrictions. The agent can read files, write files, execute commands, and perform any action without confirmation. This is the Codex equivalent of Claude’s --dangerously-skip-permissions flag.

Full Auto

codex exec --json --full-auto "<prompt>"

Full auto mode allows the agent to operate autonomously while still respecting sandbox boundaries. The agent can proceed without manual approval for standard operations, but destructive or out-of-scope actions may still be restricted. This is a middle ground between fully restricted and fully unrestricted operation.


Model Selection

The Codex backend serves six models:

ModelAlias
GPT-5.4gpt-5.4
GPT-5.3 Codexgpt-5.3-codex
GPT-5.2 Codexgpt-5.2-codex
GPT-5.2gpt-5.2
GPT-5.1 Codex Maxgpt-5.1-codex-max
GPT-5.1 Codex Minigpt-5.1-codex-mini

All models with names starting with gpt- are automatically routed to the Codex backend. See Model Switcher for the full model cycle.


Streaming and Event Parsing

The --json flag causes Codex CLI to emit structured JSON events. AZUREAL reads these events from the process output and converts them into the same AgentEvent and DisplayEvent types used by the Claude backend. The key events include:

  • thread.started – thread creation, carrying the thread_id for session identification.
  • Assistant text – incremental response text from the model.
  • Tool calls and results – file operations, command execution, and their outcomes.
  • Error – error conditions reported by the CLI.

Because both backends produce the same DisplayEvent values, the session pane, session store, and rendering pipeline handle Claude and Codex output identically. You can switch between Claude and Codex models mid-session and the conversation displays seamlessly.


Process Lifecycle

Each Codex process follows the same lifecycle as Claude:

  1. Spawn: A new codex exec process is started with the context-injected prompt.
  2. Stream: JSON events are read and parsed in real time.
  3. Exit: The process exits when the response is complete.
  4. Ingest: Events are appended to the SQLite store and temporary output files are cleaned up.

The process does not persist between prompts. See Session Lifecycle for the full end-to-end flow.

Model Switcher

The model switcher provides a unified way to cycle through all available models across both backends. Pressing Ctrl+M advances to the next model in the pool. The backend (Claude or Codex) is derived automatically from the selected model – there is no separate “switch backend” action.


The Model Cycle

Ctrl+M cycles through nine models in a fixed order, wrapping from the last back to the first:

opus --> sonnet --> haiku --> gpt-5.4 --> gpt-5.3-codex --> gpt-5.2-codex --> gpt-5.2 --> gpt-5.1-codex-max --> gpt-5.1-codex-mini --> (wrap to opus)

The first three models (opus, sonnet, haiku) use the Claude backend. The remaining six (all gpt-* models) use the Codex backend. The transition from haiku to gpt-5.4 automatically switches the backend from Claude to Codex, and the wrap from gpt-5.1-codex-mini back to opus switches it back to Claude.


Backend Derivation

The rule is simple: if the model name starts with gpt-, the Codex backend is used. Everything else uses the Claude backend.

gpt-*  -->  Backend::Codex
*      -->  Backend::Claude

When the backend changes as a result of a model switch, the background agent processor is reset so that it uses the new backend’s event format for parsing.


Model Colors

Each model has an assigned color used in the status bar and session pane headers:

ModelColor
opusMagenta
sonnetCyan
haikuYellow
gpt-5.4Green
gpt-5.3-codexLight Green
gpt-5.2-codexRGB(0, 200, 200)
gpt-5.2Light Cyan
gpt-5.1-codex-maxBlue
gpt-5.1-codex-miniLight Blue

These colors appear in the model badge on the status bar and in the right-aligned model label on chat bubble headers. They provide an at-a-glance visual indicator of which model produced a given response.


Auto-Spawned Processes

When AZUREAL spawns agent processes automatically – such as for Rebase Conflict Resolution (RCR), God File Mitigation (GFM), or Documentation Health (DH) – those processes also follow the currently selected model. If you switch from opus to gpt-5.4 and then trigger a conflict resolution, the RCR agent will use the Codex backend with gpt-5.4.


Model Persistence

Model selection is persisted to the session store so that it survives application restarts and session switches.

How It Works

Each time you press Ctrl+M, a DisplayEvent::ModelSwitch event is injected into the display event stream and appended to the SQLite session store. This event records which model you switched to.

Restoration on Load

When a session is loaded (on startup, project switch, or worktree switch), AZUREAL scans the session’s events in reverse order to determine the model:

  1. ModelSwitch events take priority. These represent explicit user choices made via Ctrl+M. The most recent ModelSwitch event determines the model.
  2. Init events as fallback. If no ModelSwitch event exists, the model is read from the most recent Init event (which records the model that was active when the agent process started).
  3. Default to opus. If the session is empty or contains no recognizable model information, the model defaults to opus.

This means that if you switch models mid-session, close the application, and reopen it, the session will restore to whichever model you last selected – not the model the session was originally started with.

Cross-Backend Sessions

A single session can span both backends. For example, you might start a conversation with opus (Claude), switch to gpt-5.4 (Codex) mid-session, then switch back to sonnet (Claude). The session store records all of these switches, and on reload, the most recent switch is restored. The chat bubble headers show which model produced each response, making the conversation history clear.


Legacy Model Strings

Older sessions may contain the string "codex" as a model identifier (from before the unified model pool was introduced). When encountered during session loading, this is mapped to the first Codex model in the pool (currently gpt-5.4). Similarly, full Claude API model names like "claude-3-5-sonnet-20241022" are recognized and mapped back to their short aliases ("sonnet").

Multi-Agent Concurrency

AZUREAL supports running multiple agent processes simultaneously on the same worktree. Each active process occupies a session slot identified by its OS process ID (PID). This goes beyond the worktree-level isolation described in Git Worktrees – even within a single branch, you can have several agents working in parallel.


PID-Keyed Session Slots

All session state maps are keyed by PID string, not by branch name. This is the fundamental design choice that enables multi-agent concurrency.

Two data structures manage the slot system:

Branch Slots

branch_slots: HashMap<String, Vec<String>>

Maps each branch name to a list of active PID strings. When a new agent process is spawned on a branch, its PID is appended to that branch’s slot list. When a process exits, its PID is removed.

Active Slot

active_slot: HashMap<String, String>

Maps each branch name to the PID of the slot whose output is currently displayed in the session pane. Only one slot can be the active slot per branch at any given time.


Display Behavior

Only the active slot’s output feeds the display pipeline. When multiple agents are running on the same branch, you see the output of whichever slot is currently active. The other slots continue running and their output is drained (read from the process pipe to prevent blocking), but it is not rendered to the session pane.

This means the session pane always shows a single, coherent stream of output, even when multiple agents are working concurrently. You can switch which slot is displayed, but you never see interleaved output from multiple agents.


Slot Lifecycle Rules

The slot system follows a few simple rules:

New Spawns Become Active

When a new agent process is spawned, it always becomes the active slot for its branch. If another agent was already running and displayed, the display switches to the newly spawned process. The previous agent continues running in the background.

Auto-Switch on Exit

When the active slot’s process exits, AZUREAL automatically switches to the last remaining slot on that branch (if any). If no other slots remain, the branch returns to an idle state with no active slot.

Cancel Kills Active Only

When you cancel an agent run (via the cancel keybinding), only the active slot’s process is killed. Other slots on the same branch continue running undisturbed. After cancellation, if other slots remain, the auto-switch rule applies.


Practical Example

Consider a worktree on the feat-auth branch:

  1. You submit a prompt: “Implement the login handler.” An agent spawns with PID 12345. It becomes the active slot.

  2. While PID 12345 is still running, you submit another prompt: “Write tests for the auth module.” A second agent spawns with PID 12346. It becomes the active slot. PID 12345 continues running but its output is no longer displayed.

  3. PID 12346 finishes its response and exits. The active slot auto-switches back to PID 12345, which is still streaming its response.

  4. PID 12345 finishes. No slots remain. The branch is idle.

At each step, the session pane shows exactly one agent’s output. The other agent’s work proceeds in the background.


Session Isolation on Switch

When switching between sessions (changing worktrees, changing projects, or switching which session is viewed in the session list), AZUREAL takes several steps to ensure clean visual transitions:

Cache Clearing

All render caches, animation state, and clickable element maps are cleared immediately on switch. This prevents stale content from a previous session from flickering into view.

Render Sequence Advancement

The render sequence number is advanced, which causes any in-flight render results (computed asynchronously by the background render thread) to be discarded. Results carry the sequence number they were computed for, and the display only accepts results matching the current sequence.

Historic Session Viewing

A viewing_historic_session flag is set when you navigate to a session other than the live session for the current worktree. While this flag is active, live events from running agents are suppressed in the display. This prevents a running agent’s output from appearing in a session you are reviewing from the history.

Slot Ownership Check

An is_viewing_slot() check prevents results from being applied when the currently viewed session does not belong to the active project or worktree. This handles the edge case where a background render completes for project A while you have already switched to project B.


Relationship to Worktree Isolation

Multi-agent concurrency (multiple agents on the same branch) and worktree isolation (agents on different branches) are complementary:

  • Worktree isolation ensures agents on different branches cannot interfere with each other. Each worktree has its own working directory, so file operations are naturally scoped.
  • PID-keyed slots let you run multiple agents on the same branch when you want concurrent work within a single feature. The agents share a working directory, so you should be aware that they may modify the same files.

In most workflows, you will have one agent per worktree. Multi-agent concurrency on a single branch is useful for specific patterns like running a test-writing agent alongside a feature-implementation agent, or submitting a follow-up prompt before the first response completes.

Session Lifecycle

This page describes the complete lifecycle of a single prompt-response exchange, from the moment you press Enter to the moment the response is persisted in the session store and the system is ready for the next prompt.


The Seven Steps

1. User Submits Prompt

The user types a prompt in the input box (prompt mode) and presses Enter. If an agent is already running, the current run is cancelled and the prompt is staged for automatic resubmission once cancellation completes.

2. Context Is Built from the Store

Before spawning the agent process, AZUREAL calls build_context() on the SQLite session store. This function retrieves all prior events for the current session and constructs a context string. The context includes:

  • Previous user messages.
  • Previous assistant responses (text content).
  • Tool call summaries.
  • Any compaction summaries that replaced older events.

The context is formatted as structured text and wrapped in <azureal-session-context> XML tags. This tagged context is prepended to the user’s prompt, giving the agent full conversational history without relying on the CLI’s own session management.

Both backends receive context injection identically – neither uses a CLI-native resume flag.

3. Agent Process Is Spawned

A new CLI process is spawned based on the active backend:

  • Claude: claude -p "<context + prompt>" --verbose --output-format stream-json
  • Codex: codex exec --json "<context + prompt>"

The process runs in the worktree’s directory, so all file operations performed by the agent are scoped to the correct working tree. The process’s PID is registered as a new session slot (see Multi-Agent Concurrency).

4. Streaming JSON Events Are Received and Parsed

The agent process writes JSON events to stdout as they are generated. AZUREAL reads these events line by line in a background thread and sends them to the event loop via a channel.

For Claude, the --output-format stream-json flag produces one JSON object per line. For Codex, the --json flag produces a similar structured output. Both are parsed into the unified AgentEvent type.

Key events during streaming:

EventMeaning
Init / thread.startedSession or thread ID captured.
AssistantTextIncremental text appended to the response.
ToolUseThe agent is invoking a tool (file read, file write, command execution, etc.).
ToolResultThe tool returned a result.
ErrorAn error occurred in the CLI or the model.

5. Events Are Displayed in Real Time

As AgentEvent values arrive, they are converted into DisplayEvent values and appended to the in-memory display event list. The render pipeline picks these up and updates the session pane on the next frame.

Text appears incrementally as the model generates it. Tool calls appear as collapsible sections with their results. The context meter updates to reflect the growing conversation size.

6. Process Exits

When the agent has finished its response, the CLI process exits. AZUREAL detects the exit via the process handle and performs cleanup:

  • The process PID is removed from the branch’s slot list.
  • If this was the active slot, auto-switch to the last remaining slot occurs (if any).
  • The JSONL output file (a temporary file written during streaming) is ready for ingestion.

7. JSONL Parsed, Events Stored, File Deleted

After the process exits, the JSONL output file is parsed into a final, authoritative list of DisplayEvent values. These events are appended to the SQLite session store. Once the store write is confirmed, the JSONL file is deleted.

This two-phase approach (stream for live display, then parse-and-store for persistence) ensures that the session store always contains cleanly parsed events, even if the live stream was interrupted or contained partial JSON lines.


The Cycle Repeats

After step 7, the system is idle and ready for the next prompt. The session store now contains the full history of the conversation, including the most recent exchange. When the user submits the next prompt, the cycle begins again at step 1, with step 2 building context that includes the just-completed exchange.

[User types prompt] --> [Build context] --> [Spawn process] --> [Stream events]
         ^                                                           |
         |                                                           v
         +--- [Ready for next prompt] <-- [Store events] <-- [Process exits]

Error Handling

If the agent process exits with a non-zero status code or emits an error event:

  • The error is displayed in the session pane as a system message.
  • The process PID is still removed from the slot list.
  • Any partial events that were already streamed are still persisted to the store.
  • The system returns to idle, ready for the next prompt.

Transient errors (network issues, rate limits) do not corrupt the session state. The store always reflects what was actually received, and the next prompt starts fresh with the full context.


Why Not Keep the Process Alive?

Claude Code’s interactive mode uses a full terminal UI (TUI) that is designed for human interaction. It cannot be driven programmatically via stdin. The -p flag provides a non-interactive mode, but it exits after each response by design.

Codex CLI’s exec mode behaves similarly – it processes one request and exits.

The spawn-per-prompt approach has practical advantages:

  • Clean process state. Each prompt starts with a fresh process, avoiding accumulated state or memory leaks.
  • Backend flexibility. Switching models mid-session is trivial because each prompt can use a different backend.
  • Failure isolation. A crashed process does not take down the session. The next prompt simply spawns a new one.

The overhead is approximately 100-200ms per spawn, which is negligible compared to the time the model spends generating a response.

Session Store & Persistence

AZUREAL persists all conversation history in a single SQLite database file: .azureal/sessions.azs. This file is the single source of truth for session data – every prompt, every tool call, every agent response, and every compaction summary lives here. There are no external dependencies, no daemon processes, and no cloud sync. Copy the file, and you copy your entire history.


Design Principles

Single-File Storage

One file holds everything. The .azs extension is a standard SQLite database with DELETE journal mode. The custom extension discourages casual tampering – renaming it to .db or .sqlite and opening it in a database browser works fine, but the intent is that AZUREAL owns this file.

Lazy Initialization

The store is not created when you load a project. It is opened (and the file created, if absent) only when you create your first session. Projects that have never had a session will not have a sessions.azs file (though the .azureal/ directory may exist for configuration).

Backend-Agnostic

A single session can span prompts sent to both Claude and Codex backends. The store does not partition by backend – it records events in the order they occurred, regardless of which model produced them.

Context Injection Over Resume

AZUREAL does not use --resume flags to continue conversations. Instead, it reads the session history from the store, wraps it into a context prompt, and spawns a fresh agent process with that context injected. This decouples conversation continuity from any particular backend’s session management.


How It Fits Together

The session store connects several subsystems:

  1. Event ingestion – After an agent process exits, AZUREAL reads the temporary JSONL output file, applies event compaction (truncating verbose tool results to match what the UI displays), strips any injected context prefix, and appends the events to the store.

  2. Context injection – Before spawning a new agent prompt in an existing session, build_context() reads the stored events and build_context_prompt() wraps them in <azureal-session-context> tags. The agent sees the full conversation history; the user sees only their clean prompt in the UI.

  3. Compaction – When accumulated context exceeds 400K characters (~100K tokens), a background compaction agent summarizes the older history into a 2000–4000 character summary, keeping the last few user exchanges verbatim. This prevents context window overflow while preserving conversational continuity.

  4. Completion tracking – When an agent session ends, the store records duration and cost. The session list in the UI renders completion badges (green check for success, red X for failure) based on this data.


Chapter Contents

  • SQLite Store (.azs) – File format, schema tables, session numbering, event storage, and completion persistence.
  • Context Injection – How stored history replaces --resume, the prompt flow, and event stripping on ingestion.
  • Compaction – The character threshold, compaction agent, boundary preservation, auto-continue, and the context meter.
  • Portability – Transferring session data between machines by copying a single file.

SQLite Store (.azs)

All session data lives in a single file: .azureal/sessions.azs. This is a standard SQLite database using DELETE journal mode, accessible by any SQLite client. The .azs extension is intentional – it discourages users from casually browsing or editing the file, signaling that AZUREAL owns the format and schema.


Lazy Creation

The store file is not created when you open a project. It is created lazily on first use – specifically, when you create your first session. Projects that have never had a session will not have a sessions.azs file (though the .azureal/ directory may still exist for configuration files like azufig.toml).


Session Numbering

Sessions are numbered sequentially: S1, S2, S3, and so on. These are display identifiers used in the session list and status bar. The numbering is simple and monotonic – there is no reuse of session numbers after deletion.


Schema

The database contains four tables:

sessions

The primary session record.

ColumnTypeDescription
idINTEGERPrimary key, auto-incremented
nameTEXTUser-assigned session name (or default “Sn”)
worktreeTEXTPath to the git worktree this session belongs to
createdTEXTISO 8601 timestamp of session creation
completedINTEGERBoolean success flag (1 = success, 0 = failure, NULL if still active)
duration_msINTEGERTotal session duration in milliseconds
cost_usdREALAccumulated cost in USD (populated on completion)
last_claude_uuidTEXTUUID of the last Claude JSONL session file (for orphan recovery)

events

Every prompt, response, tool call, and tool result is stored as an event.

ColumnTypeDescription
idINTEGERPrimary key, auto-incremented
session_idINTEGERForeign key to sessions.id (with cascade delete)
seqINTEGERSequence number within the session (monotonically increasing)
kindTEXTEvent type (e.g., UserMessage, AssistantText, ToolCall, ToolResult, Complete)
dataTEXTZstd-compressed JSON event payload (stored as blob despite TEXT type)
char_lenINTEGERCharacter length of the original (uncompressed) event data

A unique constraint on (session_id, seq) prevents duplicate events.

compactions

Compaction summaries that replace older event ranges.

ColumnTypeDescription
idINTEGERPrimary key, auto-incremented
session_idINTEGERForeign key to sessions.id (with cascade delete)
after_seqINTEGERThe sequence number after which events were compacted
summaryTEXTThe 2000–4000 character compaction summary
createdTEXTISO 8601 timestamp of when the compaction was stored

meta

Key-value store for runtime state and schema versioning.

ColumnTypeDescription
keyTEXTMetadata key
valueTEXTMetadata value

The primary key stored here is schema_version, which tracks the database schema version for migrations. Other runtime state such as the active session ID and PID-to-session mappings are held in memory on the App struct, not persisted to the database.


Event Storage and Compaction

Events are not stored verbatim from the agent’s JSONL output. Before serialization, append_events() applies compact_event() to each event, which reduces storage size by truncating event content to match what the UI actually renders:

ToolResult Truncation

ToolTruncation Rule
ReadFirst line + last line only
BashLast 2 lines only
GrepFirst 3 lines only
GlobFile count only (individual paths discarded)
TaskFirst 5 lines only
Default (all others)First 3 lines

ToolCall Input Stripping

Tool call inputs are stripped down to their key field only. Two exceptions:

  • Edit – preserved in full, because the session pane renders diffs from the old_string and new_string fields.
  • Write – summarized rather than stored verbatim, since file contents can be arbitrarily large.

This compaction is applied at ingestion time, not at query time. The stored events are already in their compact form.


Completion Persistence

When an agent process sends a Complete event, AZUREAL calls mark_completed(session_id, duration_ms, cost_usd) on the store. This populates the completed, duration_ms, and cost_usd columns in the sessions table.

The session list in the UI reads these fields to render completion badges:

  • Green check – session completed successfully.
  • Red X – session completed with a failure status.

Sessions that have not yet completed (or are still active) show no badge.

Context Injection

AZUREAL does not use backend-specific resume mechanisms (such as --resume flags) to continue conversations across prompts. Instead, it injects conversation history from the session store directly into each new prompt. This approach is backend-agnostic – the same session can span Claude and Codex prompts without either backend needing to know about the other’s session format.


The Problem with –resume

Backend CLIs typically offer a --resume flag that continues a previous conversation by loading the backend’s own session file. This creates several issues for a multi-backend orchestrator like AZUREAL:

  • Backend lock-in. A session started with Claude cannot be resumed with Codex, and vice versa.
  • Opaque state. The backend owns the session file format, so AZUREAL cannot inspect, compact, or modify the conversation history.
  • Stale context. The backend’s session files may not reflect compaction or event truncation that AZUREAL has applied.

Context injection solves all three. AZUREAL owns the conversation history in its SQLite store and feeds it to whichever backend is selected for the next prompt.


Prompt Flow

When a user sends a prompt in an existing session (one that already has stored events), the following sequence occurs:

1. Build Context

build_context() reads the session’s events from the store and assembles them into a conversation transcript. If compaction summaries exist, they replace the events they cover – the context starts with the summary, then continues with the verbatim events that follow it.

2. Wrap in Context Tags

build_context_prompt() takes the assembled context and wraps it in XML-style tags:

<azureal-session-context>
[assembled conversation history]
</azureal-session-context>

[user's actual prompt]

The tags give the agent a clear signal that the prefixed content is historical context, not a new instruction.

3. Spawn Agent

The agent process is spawned with the wrapped prompt and resume_id = None. From the backend’s perspective, this is a brand-new conversation that happens to start with a detailed context block. There is no dependency on any prior backend session state.

4. UI Display

The UI shows only the user’s clean prompt in the session pane. The injected context prefix is invisible to the user – it exists solely for the agent’s benefit. The user sees their prompt as they typed it, and the agent’s response appears below it as usual.


First Prompt (Empty Session)

When a session has no prior events (the very first prompt), there is no context to inject. The prompt is passed to the agent unchanged – no <azureal-session-context> tags, no wrapping, just the raw user input.


Event Stripping on Ingestion

After the agent process exits, AZUREAL ingests the session’s events from the temporary JSONL output file via store_append_from_jsonl(). During this ingestion, the injected context prefix is stripped from the stored events.

This is critical: without stripping, the context prefix would be stored as part of the first user prompt event, and subsequent context injections would nest – each new prompt would include the previous context prefix inside its own context block, growing unboundedly.

The stripping logic removes the <azureal-session-context>...</azureal-session-context> wrapper from the first event’s content, leaving only the user’s original prompt text. All subsequent events (agent responses, tool calls, tool results) are stored as-is after applying the standard event compaction described in SQLite Store (.azs).

Compaction

Agent conversations accumulate context quickly. A long debugging session with verbose tool results can easily exceed 400K characters in a single sitting. Left unchecked, this would overflow the model’s context window and degrade response quality long before that. AZUREAL’s compaction system addresses this by automatically summarizing older conversation history while preserving recent exchanges verbatim.


Character Threshold

A live character counter – chars_since_compaction – tracks the total characters accumulated in the current session since the last compaction (or since session start, if no compaction has occurred). The threshold is 400,000 characters, which corresponds to roughly 100K tokens.

This counter updates in real-time during streaming. It feeds the context meter displayed on the session pane border (see Context Meter below).


What Happens When the Threshold Is Crossed

When chars_since_compaction exceeds 400K characters mid-turn, the following sequence triggers:

1. Partial Turn Storage

The current turn’s events are stored to SQLite immediately, even though the agent has not finished. This ensures no data is lost in the next steps.

2. Flag Set

The auto_continue_after_compaction flag is set. This tells the system to automatically resume the conversation after compaction completes, without requiring user intervention.

3. Active Process Killed

The running agent process is terminated immediately. This prevents it from piling more content onto an already-overflowing context window. The partial response is preserved via step 1.

4. Compaction Agent Spawned

A background compaction agent is spawned using the currently selected model. This agent receives the conversation history up to the compaction boundary (see Boundary Selection below) and produces a 2,000–4,000 character summary that captures the key decisions, file changes, and current state of the work.

5. Auto-Continue

Once the compaction agent finishes, AZUREAL automatically sends a hidden “Continue.” prompt. This prompt:

  • Uses the new compacted context (summary + preserved recent exchanges).
  • Does not create a user bubble in the session pane – it is invisible to the user.
  • Resumes the agent’s work seamlessly from where it left off.

From the user’s perspective, there may be a brief pause while compaction runs, but the conversation continues without any manual intervention.


Boundary Selection

Compaction does not summarize the entire conversation. It preserves recent exchanges verbatim to maintain conversational coherence.

spawn_compaction_agent() tries compaction_boundary(session_id, from_seq, keep) with progressively smaller keep values (3 → 2 → 1). keep=3 is ideal — it preserves the last 3 user prompts along with all interleaved agent responses, tool calls, and tool results. However, sessions that cross the threshold with ≤3 user messages would never find a boundary at keep=3, leaving compaction stuck. Falling back to keep=2 then keep=1 ensures compaction can always run as long as at least one user message boundary exists.

Everything before the boundary is summarized by the compaction agent. Everything after it is kept verbatim and included in the next context injection as-is.

This means the agent always sees:

  • The compaction summary (covering all older history).
  • The last 1–3 user-agent exchanges in full detail (depending on how many exist since the last compaction).
  • The new prompt.

Guard Rails

Several mechanisms prevent compaction from misbehaving:

Double-Compaction Prevention

A guard prevents a second compaction from being triggered while one is already in flight. If the threshold is crossed again during the compaction agent’s own execution, the system waits for the current compaction to finish before evaluating whether another is needed.

Deferred Spawn

If compaction_boundary() cannot find enough user messages to establish a boundary (e.g., the session has fewer than 3 user prompts), compaction_spawn_deferred is set. This suppresses compaction retries until a new user message arrives, at which point the boundary calculation is re-attempted.

Cross-Backend Fallback

If the compaction agent fails on the primary backend (e.g., Claude returns an error), the system retries with the alternate backend (e.g., Codex). This ensures compaction is not blocked by a single backend’s transient failure.

Empty Output Retry

If the compaction agent returns an empty summary, compaction_retry_needed is set, triggering a re-spawn of the compaction agent. An empty summary would leave the context without any historical record, so this case is always retried.


Context Meter

The session pane border displays a color-coded percentage badge showing how close the current session is to the compaction threshold:

RangeColorMeaning
0–59%GreenPlenty of headroom
60–79%YellowApproaching threshold
80–100%RedCompaction imminent or in progress

The percentage is calculated as:

chars_since_compaction / 400,000 * 100

The meter updates in real-time during streaming, giving continuous visibility into context consumption.

Inactivity Watcher

When the meter reaches 90% or higher and no new events arrive for 20 seconds, a yellow banner appears in the session pane:

Session may be compacting…

This alerts the user that the pause they are experiencing is likely due to an active compaction cycle, not a stalled agent. The banner disappears when events resume.

Portability

The session store is designed for zero-friction portability. Moving your entire conversation history between machines requires copying a single file.


Transferring Sessions

To transfer all session data from one machine to another:

# On the source machine
cp /path/to/project/.azureal/sessions.azs /media/transfer/

# On the target machine
mkdir -p /path/to/project/.azureal/
cp /media/transfer/sessions.azs /path/to/project/.azureal/

That is the complete process. There are no companion files, no indexes to rebuild, and no migration scripts to run.


What the File Contains

The single .azs file contains everything:

  • All sessions – every session ever created in the project, with names, worktree associations, creation timestamps, and completion status.
  • All events – every prompt, agent response, tool call, and tool result, already in their compacted form.
  • All compaction summaries – the full text of every compaction summary generated during the project’s lifetime.
  • Runtime metadata – the schema version (active session ID and PID-to-session mappings are held in memory, not persisted to the store).

There are no external files that the store depends on. The temporary JSONL files produced by agent processes during streaming are ingested into the store on exit and are not needed afterward.


Backend Agnosticism

Sessions in the store are not tied to a specific backend. A single session can contain events from both Claude and Codex prompts, interleaved in chronological order. This means the transferred file works regardless of which backends are available on the target machine – the history is just data, not executable state.

If the target machine only has Claude Code installed (not Codex), all historical Codex events are still visible in the session pane. You simply cannot send new Codex prompts without the Codex CLI. The same applies in reverse.


Cross-Machine Worktree Paths

The sessions table records the worktree path associated with each session. If the project lives at a different absolute path on the target machine (e.g., /home/alice/project vs. /Users/bob/project), the stored worktree paths will not match the new filesystem layout.

AZUREAL resolves this at runtime by matching sessions to worktrees based on the relative worktree name (the branch or directory name), not the absolute path. Sessions created on the source machine will associate with the correct worktrees on the target machine as long as the worktree names match.


Version Control Considerations

The .azureal/ directory is gitignored by default. AZUREAL automatically adds .azureal/ to .gitignore (alongside worktrees/) on first load. This prevents the session store, worktree-level configs, and other runtime files from causing rebase conflicts during multi-worktree development.

To share session history across machines, copy the .azureal/sessions.azs file manually or use a separate sync mechanism.

The Session Pane

The session pane occupies the rightmost 35% of the terminal, spanning the full height from below the worktree tab row down to the status bar. It displays the agent conversation – prompts, responses, tool calls, and tool results – for the currently active session.

Border Titles

The session pane border carries information in three positions:

PositionContent
LeftSession [x/y] – current message position within the session
Center[session name] – the session’s display name in brackets
RightContext usage badge + PID or exit code

Session names: Custom names are preferred for readability. When no custom name has been set, UUIDs are shown in truncated form as [xxxxxxxx-...] to save horizontal space.

PID badge: While an agent process is running, the process ID is shown in green (PID:12345). When the process exits, the badge switches to an exit code: green for exit code 0, red for any non-zero exit code.

Border Styling

The border appearance changes based on context:

  • Focused: AZURE (#3399FF) with double-line border and bold.
  • Unfocused: White with plain single-line border.
  • RCR active: Green with bold border, indicating active conflict resolution. A bottom-border hint shows Ctrl+a Accept/Abort.

The bottom border also displays the current model name, color-coded by model family:

ModelColor
OpusMagenta
SonnetCyan
HaikuYellow
GPT-5.4Green
GPT-5.3-codexLight green
GPT-5.2-codexTeal
GPT-5.2Light cyan
GPT-5.1-codex-maxBlue
GPT-5.1-codex-miniLight blue

Conversation Layout

Messages render in an iMessage-style bubble layout:

  • User messages are right-aligned with an AZURE accent bar and You < header. Text wraps within the bubble width.
  • Assistant messages are left-aligned with a header showing the backend name (Claude > in orange, Codex > in cyan) and model identifier. The header fills the full bubble width with the model name right-aligned.
  • Tool calls appear as timeline nodes beneath assistant messages (see Tool Call Display).

An empty session pane (no session loaded) shows a hint: “Press s to choose a session or a to create one.”

Filtered Messages

Several message types are hidden from the conversation display to reduce noise:

  • Meta messages (isMeta: true) – internal Claude instructions that the user never needs to see.
  • Internal markers<local-command-caveat>, <task-notification>, and <local-command-stdout> tags are stripped before rendering.
  • Rewound/edited user messages – when a user edits a previous message, only the corrected version is shown. Deduplication uses the parentUuid field to identify and suppress superseded messages.
  • TodoWrite tool calls – suppressed from the inline stream because they are rendered separately in the sticky Todo Widget.

Slash Commands

User messages containing <command-name>/xxx</command-name> tags are rendered as centered 3-line magenta banners instead of normal user bubbles. The command name is displayed prominently, making it easy to spot slash command invocations when scrolling through a session.

Compaction Banners

Two banners indicate context compaction status:

  • “Context compacted” – green banner, centered, shown after compaction finishes successfully (both the Compacting and Compacted events produce this).
  • “Session may be compacting…” – yellow warning banner, injected by the inactivity heuristic when context usage is at or above 90% and no events have arrived for 20 seconds.

Hook Display

Hooks (permission checks, tool validations) are captured from multiple sources in the event stream (hook_progress, system-reminder tags, hook_response). They render as dim gray lines near their corresponding tool calls, prefixed with . Consecutive duplicate hooks are deduplicated to prevent visual clutter.

Scrolling and Navigation

  • j / k scroll one line at a time.
  • Up / Down arrows jump to the previous / next message bubble.
  • J / K page scroll (viewport height minus 2 lines of overlap).
  • Alt+Up / Alt+Down jump to the top / bottom of the session.
  • Mouse wheel scrolling works regardless of keyboard focus.
  • The session pane auto-follows new content (pinned to bottom) until the user scrolls up. Scrolling up detaches from the bottom; scrolling back to the end re-attaches.

Session Find

Press / to open an in-session search bar at the bottom of the session pane. Type a query to highlight matches in the conversation. n and N navigate between matches. The search bar shows a current/total match counter. Press Esc to dismiss the search bar; matches remain highlighted until the query is cleared.

What This Chapter Covers

Markdown Rendering

Assistant messages are rendered as styled markdown rather than raw text. The rendering pipeline runs on a background thread – the main event loop submits render requests and polls for completed results, so the draw function itself is cheap and never blocks on parsing or syntax highlighting.


Headers

Three heading levels are supported, each styled with a block character prefix and a distinct color:

SyntaxBlock CharStyle
# H1 (full block)Bold, bright color
## H2 (dark shade)Bold
### H3 (medium shade)Bold

The block character renders at the start of the line, visually anchoring the heading hierarchy. Deeper headings (#### and beyond) are not given special styling – they render as plain text.


Inline Formatting

SyntaxRendering
**bold**Bold modifier applied
*italic*Italic modifier applied
`inline code`Yellow foreground on a dark background

Inline formatting is parsed via the parse_markdown_segments function, which tokenizes text into styled segments. Bold, italic, and code spans can appear within any paragraph, list item, or blockquote.


Code Blocks

Fenced code blocks (triple backtick) receive full treatment:

┌─ rust
│ fn main() {
│     println!("Hello");
│ }
└─

Structure:

  • The opening fence is replaced with a top border (┌─) and a language label in AZURE (#3399FF).
  • Each code line is prefixed with a vertical bar () in dark gray, forming a visual gutter.
  • The closing fence is replaced with a bottom border (└─).
  • Content inside the block is syntax-highlighted using a tree-sitter-based highlighter that supports approximately 25 languages. The language is inferred from the info string after the opening backticks.

Gutter color: The left gutter bar uses an accent color (orange by default) to visually distinguish code blocks from surrounding prose.


Tables

Markdown tables are rendered with box-drawing characters instead of plain pipe characters:

│ Column A │ Column B │ Column C │
├──────────┼──────────┼──────────┤
│ value 1  │ value 2  │ value 3  │

Box-drawing characters used:

CharacterPosition
Vertical cell borders
Left junction (separator row)
Cross junction (separator row)
Right junction (separator row)

Column width clamping: Column widths are pre-scanned and calculated to fit within the bubble width. When the total table width would exceed the available space, columns are proportionally narrowed. Cell content that overflows a clamped column is truncated with an ellipsis (...).

Separator rows: Markdown separator rows (|---|---|) are replaced with box-drawn separator lines using the junction characters above.

Click to expand: Rendered tables are registered as clickable regions. When the user clicks a table, it opens in a full-width popup for easier reading. See Clickable Elements.


Lists

Both bullet and numbered lists are supported:

  • Bullet lists (- item) render with a cyan bullet character, indented from the left margin.
  • Numbered lists (1. item) render with the number preserved, also indented with cyan styling.

Nested lists maintain their indentation level. List items can contain inline formatting (bold, italic, code spans).


Blockquotes

Lines starting with > render as blockquotes:

  • A gray vertical bar appears on the left edge.
  • The quoted text renders in italic.
  • The visual style clearly separates quoted content from the assistant’s own text.

File Paths in Text

When the assistant mentions file paths in its prose (not inside tool calls), the renderer detects them and styles them as underlined orange links. These paths become clickable – clicking one opens the file in the Viewer pane. See Clickable Elements for details.


Verification Paragraphs

Text beginning with Verification: (or bold/italic variants like **Verification:**) is detected and styled distinctly. This handles the common pattern where Claude outputs a verification section summarizing what it checked or confirmed.


Rendering Pipeline

The markdown renderer never runs on the main thread during draw. Instead:

  1. The event loop calls submit_render_request() with the current display events and panel width.
  2. A background render thread parses markdown, runs syntax highlighting, wraps text, and produces a line cache.
  3. The event loop polls poll_render_result() each tick.
  4. The draw function reads from the pre-built cache – just a slice clone and viewport overlay.

When the session pane is resized, the cache width is compared to the new inner width. A mismatch marks the cache as dirty, triggering a new render request on the next loop iteration. The draw function never renders synchronously – it uses whatever cache exists, even if stale by one frame.

Tool Call Display

When an agent invokes a tool (Read, Edit, Bash, Grep, and so on), the session pane renders the invocation as a timeline node beneath the assistant message. Each node shows the tool name, its primary parameter, and a status indicator that updates in real time.


Timeline Layout

Tool calls render as a vertical timeline connected by AZURE (#3399FF) pipe characters:

 ┃
 ┃ ● Read  /src/main.rs
 ┃  │ fn main() {
 ┃  │   (42 lines)
 ┃  └─ }
 ┃
 ┃ ○ Edit  /src/lib.rs

Each tool call node consists of:

  1. Status indicator – a colored circle or symbol (see below).
  2. Tool name – a display-friendly name (e.g., “Read”, “Edit”, “Bash”).
  3. Primary parameter – the most relevant input for that tool type.

The primary parameter is extracted per-tool: file tools (Read, Edit, Write) show the file path; Bash shows the command; Grep shows the search pattern; and so on.


Status Indicators

Each tool call displays one of three status indicators, patched at draw time from the current tool state:

IndicatorColorMeaning
GreenTool completed successfully
Pulsating white/grayTool is currently executing
RedTool failed or returned an error

Pulsation: The pending indicator () cycles through white, gray, dark gray, and back to gray on a timer, creating a subtle animation that signals activity without being distracting. The cycle advances every 2 animation ticks.


Draw-Time Patching

Tool status indicators are not baked into the render cache at render time. Instead, every tool call’s line index and span index are recorded in an animation_line_indices array. During viewport construction (which runs every frame), the viewport builder patches the indicator character and color based on the current state of pending_tool_calls and failed_tool_calls:

  • If the tool’s ID is in pending_tool_calls, patch to with pulsating color.
  • If the tool’s ID is in failed_tool_calls, patch to in red.
  • Otherwise, patch to in green.

This approach means that when a tool completes or fails between full renders, the status updates immediately on the next frame without waiting for a background re-render of the entire line cache.


Error Detection

Tool failure is detected through two mechanisms:

  1. is_error field – the stream-json output includes an is_error boolean on tool results. When true, the tool is added to the failed_tool_calls set.

  2. Fallback heuristic – when the is_error field is absent or ambiguous, the result content is checked for known error patterns:

    • Contains <tool_use_error> tag
    • Starts with "Error..."
    • Contains "ENOENT" (file not found)

    These patterns catch common failure modes that may not set the explicit error flag.


File Tool Paths

Tool calls for file operations (Read, Edit, Write) render the file path with underlined orange styling, making it visually distinct and clickable. Clicking the path opens the file in the Viewer pane. For Edit tool calls, the click also loads the diff context. See Clickable Elements.

When a file path is too long to fit on one line, it wraps across multiple lines. The clickable region spans all wrapped lines, and the click handler knows how many cache lines the path occupies.


Tool Result Display

Beneath the tool call node, a summarized result is shown. Each tool type has its own result format optimized for scannability. See Tool Result Formats for the per-tool breakdown.

The result lines use the same AZURE pipe gutter as the tool call, with indented connector characters ( for continuation, └─ for the final line) to visually group the result with its tool call.


Hook Lines

When a tool call triggers permission hooks or validation checks, the hook output appears as dim gray lines near the tool call. Hooks are prefixed with and show the hook name followed by its output. Consecutive duplicate hook lines are deduplicated.

Tool Result Formats

Each tool type has a purpose-built result summary that balances information density with readability. Results appear beneath their tool call node, indented with AZURE pipe characters and connector lines ( for continuation, └─ for the final line).

When a tool produces no output, the result shows a green checkmark () indicating silent success. For Read tools specifically, empty output shows (empty file) instead.

Failed tool results render entirely in red – both the connector characters and the result text – to make errors immediately visible.


Per-Tool Formats

Read

Displays the file’s first line, a line count summary, and the last non-empty line:

 ┃  │ fn main() {
 ┃  │   (42 lines)
 ┃  └─ }

The file path in the tool call header is an underlined orange clickable link that opens the file in the Viewer. For single-line files, only that line is shown. For two-line files, first and last are shown without the count.

Bash

Shows the last 2 non-empty lines of output, since command results are typically most meaningful at the end:

 ┃  │ Compiling azureal v1.0.0
 ┃  └─ Finished release [optimized] target(s) in 12.34s

When the output is empty or all whitespace, a green checkmark () is shown.

Codex compatibility: The exec_command and write_stdin tool names are treated identically to Bash. Output wrapped in the Codex execution envelope (Chunk ID: ..., Output: ..., Process exited with code ...) is automatically unwrapped to show only the actual output.

Edit

The file path is a clickable link. Beneath the tool call, an inline diff preview shows the changes:

  • Removed lines are styled with gray text on a dim red background.
  • Added lines are syntax-highlighted on a dim green background.
  • Real file line numbers are shown alongside the diff lines for orientation.

Clicking the file path opens the diff in the Viewer pane with the old and new strings pre-loaded.

Write

Displays the file path as a clickable link, followed by a summary line showing the line count, a checkmark, and the first comment found in the content:

 ┃  └─ ✓ 127 lines  // Session pane rendering

The renderer searches for the first line matching a comment pattern (//, #, /*, """, ///, //!) and shows it as a dim italic hint. If no comment is found, the first line of content is used instead.

Grep

Shows the first 3 matching lines, with a count of additional matches:

 ┃  │ src/main.rs:42:    let app = App::new();
 ┃  │ src/lib.rs:10:     pub struct App {
 ┃  │ src/tui/mod.rs:5:  use crate::app::App;
 ┃  └─   (+7 more)

When there are 3 or fewer matches, all are shown without the overflow count.

Glob

Shows the total file count as a single summary line:

 ┃  └─ 23 files

Task (Subagent)

Shows the first 5 lines of the subagent’s response:

 ┃  │ I've analyzed the codebase and found three issues:
 ┃  │ 1. Missing error handling in parse_config
 ┃  │ 2. Unused import in lib.rs
 ┃  │ 3. Test coverage gap in session_store
 ┃  │ All three have been fixed.
 ┃  └─   (+12 more lines)

When the response exceeds 5 lines, the overflow count is shown.

WebFetch

Shows the page title and a content preview (default format – first 3 lines).

WebSearch

Shows the first 3 search results (default format).

LSP

Shows the location and surrounding code context (default format).

Other / Unknown Tools

Any tool not listed above falls back to showing the first 3 lines of output, with an overflow count if there are more.


System Reminder Stripping

Tool results sometimes include <system-reminder> blocks appended by the backend. These are stripped before rendering – only content before the first <system-reminder> tag is shown.


Width Constraints

All result text is truncated to fit within the available bubble width. The maximum text width accounts for the 7-character prefix (┃ └─ or ┃ │), so content is truncated at max_width - 8 characters. Truncated lines end with an ellipsis.

Session List & Search

Pressing s replaces the session pane conversation with a full-pane session browser. The list is scoped to the current worktree – only sessions belonging to the active worktree’s branch are shown.


Layout

Each row in the session list displays:

ElementPositionDescription
Status dotLeftGreen for running sessions, gray for idle
Session nameLeft (after dot)Custom name or truncated UUID
Last modifiedRight-alignedRelative or absolute timestamp
Message countRight (badge)[N msgs] badge showing total messages

The currently selected row is highlighted with an AZURE background and black text. The session name on the selected row renders bold.


KeyAction
j / DownMove selection down one row
k / UpMove selection up one row
JPage down (jump by viewport height)
KPage up (jump by viewport height)
EnterLoad the selected session
aStart a new session
rRename the selected session
s / EscClose the session list and return to conversation

Two-Phase Loading

Opening the session list uses a two-phase approach to avoid blocking the UI:

  1. Immediate: A centered “Loading sessions…” dialog appears on the first frame after s is pressed.
  2. Background: Message counts are loaded from the SQLite session store and the full list renders once data is available.

Message counts are pre-computed in the session store, so loading the list requires no event file parsing.


Name Filter

Pressing / activates a filter bar at the top of the session list. Typing filters sessions by name (case-insensitive substring match). The filter bar has a yellow border when active, gray when inactive. Press Esc to deactivate the filter input (the filter text persists and continues filtering). Press Esc again or s to close the session list entirely.


Typing // (two slashes) switches from name filtering to content search mode. Content search looks inside session event data (stored in the SQLite session store) rather than matching names:

  • Minimum query length: 3 characters (no search runs below this).
  • Result cap: 100 results maximum.

The filter bar shows the search mode prefix (//) and a result count badge on the right side (e.g., 42 results).

Content search results display differently from the normal session list. Each row shows:

  • The session name (or truncated session ID if no custom name exists).
  • A preview of the matching content, truncated to fit the available width.

The selected row uses the same AZURE highlight style. Pressing Enter loads the session containing the selected match.


Rename Dialog

While in the session list, a rename overlay can appear as a centered input box over the list. This allows renaming the selected session without leaving the browser.

Todo Widget

When Claude calls the TodoWrite tool to track task progress, the session pane renders a persistent sticky widget at the bottom showing the current task list. This widget stays anchored to the bottom of the pane regardless of scroll position, providing at-a-glance progress visibility.


Appearance

The widget has a rounded border in dark gray with a bold AZURE title: Tasks. Todo items are listed vertically, each prefixed with a status icon:

IconColorStatus
GreenCompleted
Yellow (pulsating)In progress
Dim grayPending

Pulsation: The in-progress icon () cycles through yellow, light yellow, yellow, and dark gray on a timer, creating a breathing animation that draws attention to the currently active task.


Text Display

The text shown for each todo depends on its status:

  • In-progress items display the activeForm text when it is non-empty. The activeForm uses present tense (e.g., “Building the session parser”) to indicate ongoing work.
  • Completed and pending items display the content text, which uses imperative form (e.g., “Build the session parser”).

Completed items render with dim gray text to visually de-emphasize them, while in-progress and pending items use white text.


Subagent Todos

When a subagent (spawned via the Task tool) has its own todo list, those items appear as indented subtasks beneath the parent task that spawned them:

 ✓ Refactor session store
 ● Implement context injection
   ↳ ○ Parse compaction markers
   ↳ ○ Build injection payload
 ○ Write integration tests

The prefix is rendered in dim gray. Subagent todos are inserted directly after the parent todo item (tracked by parent_idx). If no parent index is available, subtasks are appended after the last main task.


Height and Scrolling

The widget’s height adapts to its content:

  • Content cap: Maximum 20 visual lines of content (plus 2 lines for the top and bottom border, totaling 22 rows maximum).
  • Minimum session space: The widget never consumes so much vertical space that the session content area has fewer than 10 rows.
  • Text wrapping: Todo text wraps within the available width. The height calculation accounts for wrapped lines, so a single long todo item may occupy multiple visual rows.

When the content exceeds the visible area, a scrollbar appears on the right edge:

  • Track: Dark gray characters.
  • Thumb: AZURE characters, sized proportionally to the visible/total content ratio (minimum 1 row).
  • Scrolling: Mouse wheel over the todo widget scrolls its content independently from the session pane. The scroll offset is clamped to valid bounds.

Lifecycle

  • Appears when Claude calls TodoWrite – the current_todos list is populated from the tool call parameters.
  • Stays visible after all tasks are completed. The widget does not auto- dismiss on completion, allowing the user to review the final state.
  • Clears on the next user prompt or when switching sessions. This prevents stale task lists from persisting across unrelated prompts.

Stream Suppression

TodoWrite tool calls are suppressed from the inline session stream. They do not appear as tool call timeline nodes in the conversation. The widget is the sole visual representation of todo state.

AskUserQuestion

When Claude invokes the AskUserQuestion tool, the session pane renders an interactive question prompt as a centered, bordered box. This tool is used when Claude needs clarification or wants the user to choose between options before proceeding.


Visual Layout

The question box renders with magenta borders and a structured layout:

┌────────────────────────────────────────────────────────┐
│ ? Which approach should I take for the refactor?       │
├────────────────────────────────────────────────────────┤
│ 1. Option A                                            │
│    Rename the module and update all imports             │
│ 2. Option B                                            │
│    Create a wrapper module for backward compatibility   │
│ 3. Other (type your answer)                            │
└────────────────────────────────────────────────────────┘

Structure:

  • Top border with magenta box-drawing characters.
  • Question header with a ? icon, rendered in bold white. Long questions wrap within the box width.
  • Separator (├──...──┤) dividing the question from the options.
  • Numbered options – each option has a label in AZURE and an optional description in dim gray beneath it. Descriptions are indented and wrap within the box.
  • Implicit “Other” – an “Other (type your answer)” entry always appears as the last numbered option, allowing free-form responses.
  • Bottom border closing the box.

Multi-select: When the question supports multiple selections, the header icon changes from ? to a checkbox icon to indicate that more than one option can be chosen.


Box Width

The question box width is capped at 60 characters or the panel width minus 4, whichever is smaller. This ensures the box fits comfortably within the session pane without overflowing.


Responding

When the awaiting_ask_user_question flag is true, the user’s next prompt response receives a hidden context prefix that lists the questions and options. The user does not need to restate the question – they simply type a number to select an option or type custom text for the “Other” choice.

The hidden context is invisible to the user in the session pane. It exists solely to give the agent the necessary context to interpret a bare number (like “2”) as a selection from the presented options.


Multiple Questions

Each question in the questions array gets its own separate box. If Claude asks multiple questions in a single AskUserQuestion call, they render as stacked boxes with spacing between them.


Edge Cases

  • No options: When the options field is null or absent, only the “Other” entry appears (numbered as 1).
  • Missing labels: If an option lacks a label field, a ? placeholder is shown.
  • Empty descriptions: Options without descriptions show only the label line with no indented description beneath.
  • Narrow terminal: The box width scales down gracefully. Text wrapping ensures content remains readable even at constrained widths.

Context Meter

The context meter is a color-coded percentage badge displayed on the right side of the session pane’s top border. It shows how much of the context window has been consumed since the last compaction, giving you a real-time sense of how close the session is to needing compaction.


Display

The badge renders as a bold percentage (e.g., 42%) on the right side of the border, immediately before the PID or exit code badge:

╔ Session [5/12] ════ [refactor-auth] ═══════ 67%  PID:4821 ╗

The badge only appears when a session is active. When no session is loaded or the context is empty, the badge is hidden.


Color Thresholds

The badge color changes based on the percentage value:

PercentageColorMeaning
Below 60%GreenPlenty of context remaining
60% to 89%YellowContext usage is elevated
90% and aboveRedContext is nearly full; compaction imminent

Calculation

The percentage represents chars_since_compaction / COMPACTION_THRESHOLD, where:

  • chars_since_compaction is the total character count of all messages in the current session since the last compaction event (or since session start if no compaction has occurred). This counter is synced from the SQLite store on session load and incremented in real time as new prompts and responses arrive.
  • COMPACTION_THRESHOLD is a constant set to 400,000 characters (approximately 100K tokens at 4 characters per token).

The percentage is capped at 100% even if the character count overshoots the threshold before compaction triggers.


Real-Time Updates

The badge updates during streaming without store I/O. On session load, the character count is synced from the SQLite store (the authoritative source). During live streaming, each parsed event and submitted prompt increments the live counter, and the badge is recomputed via update_token_badge_live() – a lightweight function that only recalculates the percentage and color without touching the database.

This two-tier approach (store sync at rest, live counter during streaming) ensures the badge stays accurate without adding I/O overhead to the hot path.


Compaction Inactivity Watcher

When the context percentage reaches 90% or above, AZUREAL starts monitoring for inactivity. If no session events arrive for 30 seconds while the context is at or above 90%, a yellow warning banner is injected into the conversation:

       Session may be compacting...

This heuristic accounts for the fact that the backend may silently compact the context without sending an explicit event. The banner disappears if new events arrive or if a Compacted event confirms that compaction completed.

When the context percentage drops below 90% (for example, after compaction resets the counter), the inactivity watcher resets and the compaction banner state is cleared.


Compaction Trigger

When chars_since_compaction reaches or exceeds the 400,000-character threshold, AZUREAL flags the session for compaction. On the next event loop tick, a background agent is spawned to summarize the conversation. After compaction completes, the character counter resets and the badge drops accordingly. See Compaction for the full compaction lifecycle.

Clickable Elements

The session pane supports mouse-driven interactions on several types of rendered content. Clickable regions are registered during the background render pass and stored as coordinate ranges that the mouse handler checks on each click event.


Tool Call File Paths

File paths in tool call headers (Read, Edit, Write) are rendered as underlined orange text. Clicking a path opens the corresponding file in the Viewer pane.

Read/Write paths: A single click opens the file for viewing with syntax highlighting and line numbers.

Edit paths: A single click opens the diff view in the Viewer, with the old and new strings from the Edit tool pre-loaded. This lets you see exactly what the agent changed without navigating to the file manually.

Clickable region: Each path stores its cache line index, start column, end column, the file path string, and (for Edit tools) the old/new edit strings. When a path wraps across multiple lines, the wrap_line_count field tells the click handler how many cache lines to check.


When the assistant mentions file paths in its prose text (outside of tool calls), the renderer detects and styles them as underlined orange links. These are registered as clickable regions in the same way as tool call paths.

Clicking an assistant-mentioned file path opens it in the Viewer. If the path references a file that was recently edited, the click may also show the diff.


Tables

Rendered markdown tables are registered as clickable regions. Each table stores its cache line range (start and end) and the raw markdown source text.

Clicking anywhere within a rendered table opens a full-width popup that re-renders the table without column width constraints. This is useful for wide tables that had columns truncated to fit within the session pane’s bubble width. The popup renders the table at the full terminal width, showing complete cell content.


Status Bar Center

The status bar’s center section is clickable. Clicking it copies the current status message to the system clipboard. This is useful for grabbing error messages, file paths, or other transient status information that would otherwise require manual selection.

The status bar stores its screen rectangle during each render frame, enabling precise hit-testing for the click.


Click Detection

All clickable regions are tracked through typed tuples stored alongside the render cache:

  • ClickablePath: (line_idx, start_col, end_col, file_path, old_string, new_string, wrap_line_count) – identifies a clickable file path with optional Edit diff context.
  • ClickableTable: (cache_line_start, cache_line_end, raw_markdown_text) – identifies a clickable table region.

On a mouse click, the event handler translates the screen coordinates to cache line and column positions (accounting for scroll offset), then checks against the stored regions. The first matching region triggers the corresponding action.

The File Browser & Viewer

The left half of AZUREAL’s interface is dedicated to browsing and viewing the files in your worktree. It is composed of two panes – the FileTree on the left (15% of terminal width) and the Viewer in the center (50%) – that work together as a unified file exploration system. The FileTree lets you navigate the directory structure, while the Viewer renders file content with full syntax highlighting, markdown preview, image display, and inline diff viewing.


Architecture

┌──────────┬───────────────────────────┐
│          │  [tab1] [tab2] [tab3]     │
│ FileTree │───────────────────────────│
│  (15%)   │                           │
│          │         Viewer (50%)      │
│          │                           │
│          │                           │
└──────────┴───────────────────────────┘

The FileTree always reflects the currently selected worktree. When you switch worktrees via [/] or by clicking a tab in the worktree row, the FileTree swaps to show that worktree’s directory contents, and the Viewer resets to its default state for that worktree.


Capabilities at a Glance

FeatureDescription
File TreeDirectory browser with Nerd Font icons, expand/collapse, filtering options
Syntax HighlightingTree-sitter based highlighting for 24 languages
Markdown PreviewPrettified rendering of .md files with styled headers, tables, and code blocks
Image ViewerTerminal graphics protocol rendering for PNG, JPG, GIF, BMP, WebP, and ICO
Diff ViewerInline diffs from agent Edit tool calls with syntax highlighting
Viewer TabsUp to 12 pinned file tabs across 2 rows
Edit ModeFull text editing with undo/redo, word-wrap, and save
File ActionsAdd, delete, rename, copy, and move files directly from the tree

Interaction Model

The FileTree and Viewer follow the standard focus and navigation model described in Focus & Navigation. In command mode, j/k scroll through the file tree or viewer content, Enter opens a file from the tree into the Viewer, and arrow keys or h/l expand and collapse directories.

The Viewer supports multiple display modes depending on what is loaded:

  • Source view – syntax-highlighted code with line numbers.
  • Markdown preview – rendered markdown without line numbers.
  • Image view – terminal graphics rendering, no scroll or selection.
  • Diff view – inline diffs with red/green backgrounds and real line numbers.
  • Edit mode – full text editing with a dashed border indicator.

Only one display mode is active at a time. The mode is determined automatically by file type, with edit mode toggled manually via e.


Chapter Contents

  • File Tree – The directory browser: Nerd Font icons, filtering options, and scroll indicators.
  • Syntax Highlighting – Tree-sitter grammars, language detection, and capture-to-color mappings.
  • Markdown Preview – Prettified rendering of markdown files with styled elements.
  • Image Viewer – Terminal graphics protocol support for image files.
  • Diff Viewer – Inline diff display from agent edit operations.
  • Viewer Tabs – Pinning files to tabs for quick access.
  • Edit Mode – In-place file editing with undo, redo, and save.
  • File Actions – Creating, deleting, renaming, copying, and moving files from the tree.

File Tree

The File Tree occupies the left 15% of the terminal and is always visible. It displays the directory structure of the currently selected worktree, serving as the primary entry point for opening files in the Viewer.


Border Title

The File Tree’s border title follows the format:

Filetree (worktree_name)                    [pos/total]
  • Left: The literal text Filetree followed by the worktree’s display name in parentheses.
  • Right: A scroll indicator showing the cursor position and total entry count, displayed as [pos/total]. This only appears when the tree content overflows the visible area.

Nerd Font Icons

Every file and directory in the tree is prefixed with an icon. AZUREAL maps approximately 60 file types to specific Nerd Font glyphs, each rendered in the language’s brand color:

File TypeColorExample
Rust (.rs)OrangeRust gear glyph
Python (.py)BluePython logo glyph
JavaScript (.js)YellowJS logo glyph
TypeScript (.ts)BlueTS logo glyph
Go (.go)CyanGo gopher glyph
Markdown (.md)WhiteMarkdown glyph
TOML (.toml)GrayConfig glyph
Directory (expanded)BlueOpen folder glyph
Directory (collapsed)BlueClosed folder glyph

This is a representative subset. The full mapping covers common source files, configs, data formats, images, lock files, and more.

Auto-Detection

Nerd Font availability is detected automatically via detect_nerd_font(). This function runs during the splash screen and works by probing a Private Use Area (PUA) glyph – if the terminal can render it, Nerd Font mode is enabled. If the probe fails, the tree falls back to emoji icons (for example, a folder emoji for directories, a page emoji for generic files). The detection runs once per application launch.


KeyAction
j / DownMove cursor down one entry
k / UpMove cursor up one entry
EnterOpen file in Viewer, or expand/collapse directory
l / RightExpand directory
h / LeftCollapse directory
Alt+UpJump to first sibling in current folder
Alt+DownJump to last sibling in current folder

Double-clicking a file opens it in the Viewer. Double-clicking a directory toggles its expanded/collapsed state.


Options Overlay

Pressing O (Shift+O) while the File Tree is focused opens the Options overlay – a checkbox list for controlling which entries are visible in the tree.

Filter Targets

EntryDefault
Worktrees directoryHidden
.gitHidden
.claudeHidden
.azurealHidden
.DS_StoreHidden
targetHidden
node_modulesHidden

Each entry is a “Hide” toggle. Checked entries are hidden from the tree; unchecked entries are visible. Navigate the list with j/k and toggle with Space or Enter. Press Esc or O again to close the overlay.

Persistence

Filter settings are persisted to the project’s azufig.toml configuration file. Changes take effect immediately and survive application restarts. See Project Config for details on the config file format.


Directory Behavior

Directories display with an expand/collapse indicator. When expanded, their children are indented below them with a visual tree guide. Collapsed directories show only the directory name with a closed-folder icon.

The tree is populated from disk on worktree selection and updated by the file watcher when changes occur. See File Watcher for details on how filesystem events propagate to the tree.


Worktree Scoping

The File Tree is always scoped to a single worktree. When you switch worktrees (via [/], clicking a worktree tab, or pressing Shift+M for main browse), the tree reloads from the new worktree’s root directory. There is no cross-worktree file browsing – each worktree is an isolated view of its own working directory.

Syntax Highlighting

AZUREAL uses tree-sitter for all syntax highlighting. Unlike regex-based highlighters, tree-sitter parses source code into a full abstract syntax tree (AST), producing accurate highlighting that correctly handles nested structures, multi-line strings, and language-specific edge cases.


Supported Languages

Twenty-four language grammars are bundled:

LanguageExtensions
Rust.rs
Python.py, .pyw, .pyi
JavaScript.js, .mjs, .cjs, .jsx
TypeScript.ts, .mts, .cts
TSX.tsx
JSON.json, .jsonc
TOML.toml
Bash.sh, .bash, .zsh
C.c, .h
C++.cpp, .hpp, .cc, .cxx, .hxx, .hh
Go.go
HTML.html, .htm
CSS.css
Java.java
Ruby.rb
Lua.lua
YAML.yml, .yaml
Markdown.md, .markdown
Scala.scala, .sc
R.r, .R
Haskell.hs
PHP.php
SQL.sql
Perl.pl, .pm

Perl is registered with an empty highlight query, so .pl and .pm files are recognized but rendered as plain text without syntax coloring.

Files with unrecognized extensions are displayed as plain text without highlighting.


Language Detection

Language detection uses two strategies depending on context:

File Viewer

When a file is opened in the Viewer, the language is determined by file extension lookup. The file’s extension is matched against the table above. This is a direct map with no ambiguity – each extension maps to exactly one grammar.

Session Code Blocks

When rendering fenced code blocks in the Session pane (agent responses), the language is determined by the code fence token – the string after the opening triple backticks. For example, ```rust selects the Rust grammar, ```python selects Python, and so on. Unrecognized or missing fence tokens result in plain-text rendering.


Capture-to-Color Mappings

Tree-sitter grammars produce captures – named tokens like keyword, string, comment, and function. AZUREAL maps 26 capture names to specific colors:

CaptureColor
attributeLight blue / Lavender
commentGray (dim)
constantAZURE (blue)
constant.builtinYellow
constructorAZURE (blue)
embeddedWhite
escapeMagenta
functionBlue
function.builtinBlue
function.methodBlue
keywordMagenta
labelAZURE (blue)
numberYellow
operatorWhite
propertyWhite
punctuationWhite
punctuation.bracketWhite
punctuation.delimiterWhite
stringGreen
string.specialGreen
tagAZURE (blue)
typeYellow
type.builtinYellow
variableWhite
variable.builtinMagenta
variable.parameterOrange

These mappings produce a consistent visual language across all supported grammars. Keywords and control flow are always in the magenta family, strings are always green, and comments are always dimmed – regardless of the source language.


Dual Instances

AZUREAL runs two independent tree-sitter instances:

  1. App instance (main thread) – Used for highlighting file content in the Viewer and for edit-mode syntax cache updates. This instance runs on the main application thread and processes files as they are opened or edited.

  2. Render instance (background thread) – Used for highlighting code blocks in Session pane rendering. Because session content can contain many code blocks across multiple languages, this instance runs on the dedicated render thread to avoid blocking the main event loop.

Both instances share the same grammar set and color mappings. They are fully independent in memory, so there is no synchronization overhead between them.


Performance

Tree-sitter parsing is incremental. When a file is edited, only the changed region of the AST is re-parsed rather than the entire file. This makes syntax highlighting in Edit Mode fast even for large files.

In the Viewer (read-only mode), the entire file is parsed once on load and the highlight result is cached. In edit mode, the syntax cache is invalidated and regenerated per edit version – not per frame – so rapid typing does not cause redundant re-parses.

Markdown Preview

When the Viewer opens a .md or .markdown file, it switches from plain syntax-highlighted source to a prettified markdown preview. This mode renders markdown elements with styled formatting, making documentation files readable without leaving the terminal.


Rendered Elements

Headers

Headers are rendered with a single block prefix character that varies by level:

█ H1 Heading       (AZURE, bold, underlined)
▓ H2 Heading       (AZURE, bold)
▒ H3 Heading       (Green, bold)
░ H4-H6 Heading    (Green)

The descending block density (FULL > DARK SHADE > MEDIUM SHADE > LIGHT SHADE) creates visual weight that distinguishes heading levels at a glance. Header text is rendered bold (H1-H3) with H1 additionally underlined.

Bullets and Lists

  • Unordered lists render with standard bullet characters, indented per nesting level.
  • Ordered lists render with their original numbering preserved.

Nested lists maintain correct indentation relative to their parent.

Blockquotes

Blockquotes are prefixed with a vertical bar on each line:

┃ This is a blockquote.
┃ It can span multiple lines.

The BOX DRAWINGS HEAVY VERTICAL character provides a clean visual margin. Nested blockquotes stack the bar prefix (one bar per nesting level).

Code Blocks

Fenced code blocks are rendered with syntax highlighting using the tree-sitter grammar identified by the code fence token. A ```rust block is highlighted as Rust, ```python as Python, and so on. Blocks without a fence token or with an unrecognized token render as plain text.

Code blocks are visually distinct from the surrounding prose through indentation and background contrast.

Tables

Tables are rendered with box-drawing characters for borders:

┌──────────┬───────────┐
│ Column A │ Column B  │
├──────────┼───────────┤
│ value 1  │ value 2   │
│ value 3  │ value 4   │
└──────────┴───────────┘

Column widths are calculated from the content. Header rows are visually separated from data rows by a horizontal rule.

Inline Styling

MarkdownRendering
**bold**Bold text
*italic*Italic text
`code`Styled inline code span

Inline styles nest correctly – ***bold italic*** produces bold italic text.


Line Numbers

Markdown preview mode renders with no line numbers. The gutter width is set to zero, giving the rendered content the full width of the Viewer pane. This matches the reading-oriented intent of markdown preview – line numbers are useful for source code, not for documentation.


Edit Mode Interaction

When you press e to enter Edit Mode on a markdown file, the preview is replaced with the raw markdown source rendered with standard syntax highlighting (using the Markdown tree-sitter grammar). This shows the actual markup characters – #, *, `, |, and so on – so you can edit them directly.

Exiting edit mode (via Esc) returns to the prettified preview, re-rendering the file with any changes you made.

Image Viewer

When you open an image file in the Viewer, AZUREAL renders it directly in the terminal using a graphics protocol. This lets you preview images without leaving the application.


Supported Formats

FormatExtensions
PNG.png
JPEG.jpg, .jpeg
GIF.gif
BMP.bmp
WebP.webp
ICO.ico

Files with these extensions are detected as images and routed to the image viewer automatically. Other binary files are not rendered.


Terminal Graphics Protocols

Image rendering uses the ratatui-image crate, which supports three terminal graphics protocols in order of preference:

Kitty Graphics Protocol

The Kitty protocol transmits image data as base64-encoded payloads directly to the terminal. It supports full-color rendering at the terminal’s native resolution and is the highest-quality option. Terminals that support this protocol include Kitty, WezTerm, and Ghostty.

Sixel

Sixel is an older graphics protocol supported by a wide range of terminals (mlterm, foot, xterm with Sixel enabled, and others). It encodes images as six-pixel-tall horizontal bands. Color depth varies by terminal, but modern implementations typically support 256 or more colors.

Halfblock Fallback

If neither Kitty nor Sixel is available, the image viewer falls back to halfblock rendering. This uses the Unicode half-block character to encode two vertical pixels per terminal cell, with foreground and background colors representing the top and bottom pixel respectively. The result is lower resolution than native graphics protocols but works in any terminal that supports 24-bit color.

Protocol Detection

The graphics protocol is auto-detected once on the first image load. The detection probes the terminal’s capabilities and selects the best available protocol. The result is cached for the remainder of the session – subsequent image loads skip detection and use the cached protocol immediately.


Viewport Behavior

Images are auto-fitted to the Viewer viewport. The image is scaled to fill the available space while preserving its aspect ratio. If the image is smaller than the viewport, it is displayed at its native size (no upscaling).

Unlike source files, image view has no scrolling, no text selection, and no cursor. The image is a static render that fills the viewport.


Edit Mode

Edit mode is not available for image files. Pressing e on an image has no effect. Images are view-only.

Diff Viewer

When an agent makes file edits via the Edit tool, the Viewer switches to diff mode to display the changes inline. Diffs are syntax-highlighted and color-coded, giving you immediate visual feedback on what the agent changed.


When Diffs Appear

Diffs are generated from Edit tool calls in agent sessions. When the Session pane displays a tool call that modified a file, selecting that tool call (or navigating to it) loads the corresponding diff into the Viewer. The Viewer title updates to reflect the file being diffed.


Visual Format

Diffs are displayed as inline diffs – removed and added lines are shown interleaved at their actual file positions, not in a side-by-side layout.

Removed Lines

  • Text color: Dark grey.
  • Background: Dim red.
  • Syntax highlighting: None. Removed lines are rendered in flat grey text to visually de-emphasize them.

Added Lines

  • Text color: Full syntax highlighting (using the file’s tree-sitter grammar).
  • Background: Dim green.
  • Syntax highlighting: Yes. Added lines receive the same highlighting they would have in normal file view, making it easy to read the new code.

Unchanged Lines

Context lines surrounding the diff are rendered with normal syntax highlighting and no background color, providing visual anchoring for the changes.


Line Numbers

Diff view displays real file line numbers in the gutter. These correspond to the line numbers in the actual file on disk, not sequential diff-line indices. This means you can reference a specific line number from the diff and find it directly in the source file.


When an agent session contains multiple Edit tool calls, you can cycle through them:

KeyAction
Alt+LeftPrevious edit
Alt+RightNext edit

Each navigation step loads the next (or previous) diff into the Viewer, updating the displayed file, line numbers, and diff highlights.


Scroll Correction

When a diff is loaded, the Viewer auto-scrolls to the diff location so the changed lines are visible immediately. The scroll calculation accounts for word-wrap – if long lines in the file are wrapped across multiple visual rows, the scroll offset is adjusted so the diff target appears at the correct screen position rather than being pushed offscreen by wrapped lines above it.

This ensures that regardless of file length or line wrapping, navigating to a diff always places the changes in view.

Viewer Tabs

Viewer Tabs let you pin frequently accessed files for quick switching. Up to 12 files can be tabbed at once, displayed in two rows of six across the top of the Viewer pane.


Tab Bar Layout

The tab bar renders inside the Viewer border at rows 1 and 2 (immediately below the top border). Each row holds up to 6 tabs at a fixed width. The layout looks like this when tabs are active:

┌─ Viewer ──────────────────────────────┐
│ [main.rs] [lib.rs] [mod.rs]          │
│ [config.toml] [README.md]            │
│───────────────────────────────────────│
│                                       │
│  (file content starts here)           │
│                                       │
└───────────────────────────────────────┘

When tabs are present, the file content area shifts down by 2 rows to accommodate the tab bar. When no tabs exist, those rows are reclaimed by the content area.


Keybindings

KeyAction
tSave current file to a new tab
Alt+tOpen tab dialog
xClose current tab

Saving a Tab

Pressing t while viewing a file saves that file’s path to a new tab. The tab appears at the next available slot. If the file is already tabbed, no duplicate is created.

Tab Dialog

Alt+t opens a tab dialog overlay that shows all current tabs in a navigable list. Navigate with j/k, select with Enter, or jump directly to a tab with number keys 1-9. You can also close tabs with x from within the dialog.

Closing Tabs

x closes the currently active tab. If the closed tab was the one being viewed, the Viewer switches to the next available tab. If no tabs remain, the Viewer returns to its default empty state.


Tab Limit

A maximum of 12 tabs is enforced (6 per row, 2 rows). Attempting to save a 13th tab displays a status message indicating the tab limit has been reached. Close an existing tab with x to free a slot before adding a new one.


Tab Persistence

Viewer tabs are associated with the current worktree. Switching worktrees does not carry tabs across – each worktree maintains its own tab set. Tabs persist for the duration of the session but are not saved to disk across application restarts.

Edit Mode

Edit mode turns the Viewer into a text editor. You can make changes to files directly within AZUREAL without switching to an external editor or the embedded terminal.


Entering and Exiting

KeyAction
eEnter edit mode (from command mode, with a file open in the Viewer)
EscExit edit mode and return to command mode

Edit mode is only available for text files. Pressing e on an image has no effect. For markdown files, entering edit mode replaces the prettified preview with raw syntax-highlighted source (see Markdown Preview).


Visual Indicator

When edit mode is active, the Viewer border changes from a solid line to a dashed line. This provides an immediate visual distinction between read mode (solid border) and edit mode (dashed border), matching the visual language used elsewhere in AZUREAL for mutable state.


Text Editing

Edit mode supports standard text editing operations:

KeyAction
Any printable characterInsert at cursor
BackspaceDelete character before cursor
DeleteDelete character after cursor
Left / RightMove cursor one character
Up / DownMove cursor one visual line
HomeMove cursor to start of line
EndMove cursor to end of line
EnterInsert newline

Word-Boundary Wrapping

Long lines are wrapped at word boundaries to fit the Viewer width. The cursor navigates through visual lines (wrapped rows), not just source lines. This means:

  • Up/Down moves through wrapped visual lines, not source lines. If a single source line wraps into three visual rows, pressing Down three times moves through all three rows before reaching the next source line.
  • Home moves to the start of the current source line. End moves to the end of the current source line.

The wrap-aware cursor ensures that navigation feels natural regardless of line length.


Undo and Redo

PlatformUndoRedo
macOSCmd+ZCmd+Shift+Z
Linux / WindowsCtrl+ZCtrl+Y

The undo/redo stack records each editing action (insertions, deletions, replacements). The stack is capped at 100 entries – once the cap is reached, the oldest entries are discarded as new ones are added.

Undo steps back through the history one action at a time. Redo moves forward through undone actions. Performing a new edit after an undo discards all redo-able entries beyond the current point (standard undo-tree behavior).


Saving

PlatformSave
macOSCmd+S
Linux / WindowsCtrl+S

Pressing save opens a confirmation dialog before writing to disk. Confirm with Enter (or y) to write, or cancel with Esc (or n) to return to editing without saving.


Discarding Changes

When you press Esc to exit edit mode while unsaved changes exist, a discard dialog appears:

  • Discard (Enter or y) – Discards all changes and returns to read-only view with the original file content.
  • Cancel (Esc or n) – Returns to edit mode so you can continue editing or save first.

If there are no unsaved changes, Esc exits edit mode immediately without a dialog.


Syntax Highlighting in Edit Mode

Syntax highlighting remains active during editing. The highlighting cache is invalidated and regenerated per edit version – each time the buffer changes, the tree-sitter AST is incrementally re-parsed and the highlight cache is updated. Crucially, this happens per version, not per frame, so rapid typing does not cause redundant re-parses.


Mouse Support

  • Click positions the cursor at the clicked location. The click coordinates are converted from screen space to source-line and source-column, accounting for word-wrap offsets.
  • Drag creates a text selection from the click point to the drag endpoint. See Text Selection & Copy for details on selection behavior in edit mode.

Speech-to-Text Integration

When Speech-to-Text is used while edit mode is active, the transcribed text is inserted at the current cursor position rather than being placed in the prompt input. This lets you dictate directly into the file you are editing.

File Actions

The File Tree supports direct file and directory manipulation through a set of single-key actions. All file actions operate on the currently highlighted entry in the tree and are available in command mode when the File Tree has focus.


Action Summary

KeyActionDescription
aAddCreate a new file or directory
dDeleteDelete the highlighted entry
rRenameRename the highlighted entry
cCopyCopy the highlighted entry to another location
mMoveMove the highlighted entry to another location

Add (a)

Pressing a opens an inline text input at the bottom of the File Tree pane. Type the name of the new file or directory to create.

Creating a file: Type a filename (e.g., utils.rs) and press Enter. The file is created as an empty file inside the directory that currently has focus. If the cursor is on a file, the new file is created in that file’s parent directory.

Creating a directory: Append a trailing / to the name (e.g., helpers/) and press Enter. The directory is created, and the tree refreshes to show it.

Press Esc to cancel without creating anything.


Delete (d)

Pressing d on a highlighted file or directory triggers a confirmation prompt:

Delete "filename"? (y/N)

The default is No – pressing Enter without typing y cancels the deletion. You must explicitly press y to confirm. This prevents accidental data loss from a stray keypress.

Deleting a directory removes it recursively, including all of its contents.


Rename (r)

Pressing r opens an inline text input pre-filled with the current name of the highlighted entry. Edit the name as needed and press Enter to confirm the rename.

The rename operates within the same directory – it changes the entry’s name but does not move it. To move a file to a different directory, use the Move action instead.

Press Esc to cancel without renaming.


Copy (c)

Copy is a two-step operation:

Step 1: Grab the Source

Press c on the entry you want to copy. The entry is marked as the copy source with a visual indicator: a solid border around the name rendered in magenta.

┃filename.rs┃

The solid-border magenta highlight persists as you navigate the tree, reminding you that a copy operation is in progress.

Step 2: Paste at Target

Navigate to the target directory and press Enter to paste. The source file (or directory) is copied into the target directory, preserving the original name. If a file with the same name already exists at the target, the operation fails with a status message.

Recursive directory copy is supported. Copying a directory duplicates its entire contents, including all subdirectories and files.

Cancelling

Press Esc at any point to cancel the copy operation. The source highlight is removed and the tree returns to normal.


Move (m)

Move works identically to Copy in its two-step flow, with two differences:

  1. The visual indicator uses a dashed border in magenta instead of a solid border:

    ╎filename.rs╎
    
  2. After pasting at the target, the original entry is removed from its source location. Move is effectively a copy-then-delete.

Step 1: Grab the Source

Press m on the entry you want to move. The dashed-border magenta highlight appears.

Step 2: Paste at Target

Navigate to the target directory and press Enter. The entry is moved to the new location.

Cancelling

Press Esc to cancel the move. The source highlight is removed and no filesystem changes occur.


Visual Summary

OperationBorder StyleBorder Color
Copy sourceSolid (┃name┃)Magenta
Move sourceDashed (╎name╎)Magenta

The distinct border styles make it possible to tell at a glance whether you are in a copy or move operation.


Error Handling

All file actions report errors via the status bar. Common error scenarios:

ScenarioMessage
Name already exists (add/rename)File already exists
Target already exists (copy/move)Target already exists
Permission deniedPermission denied
Delete confirmation declined(no message, action cancelled silently)

Failed operations leave the filesystem unchanged.

The Git Panel

The git panel is AZUREAL’s dedicated interface for repository operations. It replaces the normal TUI layout with a git-specific view where you can stage files, commit with AI-generated messages, rebase, squash merge, resolve conflicts, and push – all without leaving the terminal. Toggle it with Shift+G.


Design Philosophy

AZUREAL treats git as a first-class workflow rather than something you shell out for. The git panel reuses the same 3-pane layout structure as normal mode but repurposes each pane for git operations. Every action is context-aware: the available keybindings change depending on whether you are on the main branch or a feature branch, and the panel always shows exactly the operations that make sense for your current state.

The panel also integrates AI at two key points: commit message generation (which uses your selected model to write conventional commit messages from the diff) and conflict resolution (which spawns a Claude session to resolve rebase conflicts interactively). These are not separate tools – they are woven into the standard git workflow so that AI assistance is a natural part of every merge and commit.


Visual Identity

The git panel uses its own color palette to make the mode switch immediately obvious:

ElementColor
Focused bordersGIT_ORANGE (#F05032)
Unfocused bordersGIT_BROWN (#A0522D)

These replace the AZURE (#3399FF) accent used in normal mode. The shift from blue to orange is an instant visual cue that you are in git context.


Entering and Exiting

KeyAction
Shift+GToggle git panel on/off

When you enter the git panel, the layout transforms and the changed files list and commit log populate from the current worktree’s git state. When you exit, the normal layout is restored exactly as you left it.


Context-Aware Actions

The actions available in the git panel depend on which branch is active. On main, you get pull/commit/push/stash/stash pop. On feature branches, you get squash merge/rebase/commit/push/stash/stash pop. A few actions are always available regardless of branch.

On Main Branch (5 actions)

KeyAction
lPull
cCommit
Shift+PPush
zStash
Shift+ZStash pop

On Feature Branch (6 actions)

KeyAction
mSquash merge into main
Shift+RRebase onto main
cCommit
Shift+PPush
zStash
Shift+ZStash pop

Always Available

KeyContextAction
rAny paneRefresh git state
aActions focused, feature branch onlyToggle auto-rebase
sActions focusedAuto-resolve settings

Chapter Contents

  • Layout & Navigation – The 3-pane git layout, focus cycling, worktree switching, and status bar.
  • Changed Files & Staging – File status indicators, staging/unstaging, discarding changes, and the UI-only staging model.
  • Commit with AI Messages – AI-generated conventional commit messages, the commit editor, and commit+push.
  • Squash Merge – The multi-step squash merge workflow from feature branch to main.
  • Rebase & Auto-Rebase – Manual rebase, automatic background rebase, and the rebase status indicator.
  • Conflict Resolution (RCR) – The Rebase Conflict Resolution overlay and interactive Claude-assisted resolution.
  • Auto-Resolve Settings – Configuring which files are automatically resolved via 3-way union merge.
  • Pull & Push – Pulling, pushing, force-push with lease detection, and post-operation state refresh.

Layout & Navigation

The git panel reuses AZUREAL’s 3-pane layout structure but fills each pane with git-specific content. The result is a purpose-built git interface that feels familiar because it shares the same spatial organization as normal mode.


Pane Layout

╔════════════════════════════════════════════════════╗
║  [* main] │ [○ feat-a] │ [● feat-b]   (tab bar)  ║
╠═══════════╦════════════════════════╦═══════════════╣
║  Actions  ║                        ║               ║
║───────────║       Viewer           ║  Commit Log   ║
║  Changed  ║    (diffs / editor)    ║               ║
║  Files    ║                        ║               ║
╠═══════════╩════════════════════════╩═══════════════╣
║  GIT: branch-name (Tab/Shift+Tab: cycle | hints)  ║
╚════════════════════════════════════════════════════╝

Top: Worktree Tab Bar

The same tab row from normal mode, but rendered with the git color palette (GIT_ORANGE for selected, GIT_BROWN for unselected). You can switch between worktrees within the git panel using [ and ] – the entire panel updates to reflect the newly selected worktree’s git state.

The left sidebar is divided into two sections stacked vertically:

  • Actions (top) – Lists the available git operations for the current branch context. The actions shown here change between main and feature branches (see The Git Panel for the full action table).
  • Changed Files (bottom) – Lists all modified, added, deleted, renamed, and untracked files with their staging status. See Changed Files & Staging for details.

Center: Viewer

The viewer pane serves double duty. In its default state, it shows diffs for selected files. When you press c to commit, it transforms into the commit editor with the AI-generated message. During conflict resolution, it displays the conflict overlay.

Right: Commit Log

The commit log shows the branch’s commit history. On feature branches, this is scoped to branch-only commits (git log main..HEAD). On main, it shows the full history. Unpushed commits render in green; pushed commits render dimmed.

The bottom border of the commit log displays divergence badges showing how far the branch has diverged from main and from its remote tracking branch:

 ↑2 ↓0 main  ↑1 ↓3 remote

These badges are color-coded: the main badge uses red when behind and green when only ahead; the remote badge uses yellow when behind and cyan when only ahead.

Bottom: Git Status Box

A full-width git status box (3 rows, GIT_ORANGE double border) sits below the three panes. Its title bar contains keybinding hints in the same style as the prompt title bar. The content area shows operation result messages (green for success, red for errors). A minimal 1-row status bar sits beneath it, showing Git: <worktree> on the left and CPU/PID badge on the right.


Focus Cycling

Three panes participate in the focus cycle:

OrderPaneDescription
1ActionsThe git operations list
2Changed FilesThe file list with staging controls
3Commit LogThe branch commit history

Tab moves focus forward through this cycle (Actions, Files, Commits, Actions, …). Shift+Tab moves backward. The viewer pane does not participate in the cycle – it updates reactively based on selections in the other panes.

The focused pane is indicated by a GIT_ORANGE (#F05032) border. Unfocused panes use GIT_BROWN (#A0522D).


Worktree Switching

KeyAction
[Switch to previous worktree
]Switch to next worktree
{Jump one page backward in worktree list
}Jump one page forward in worktree list

Switching worktrees within the git panel refreshes all panes: the actions list updates for the new branch context, the changed files list repopulates, the viewer clears, and the commit log reloads. You do not need to exit the git panel to work on a different branch.


Entering and Exiting

Shift+G toggles the git panel from any context. On entry, the panel loads the git state for the currently selected worktree. On exit, the normal layout restores with all prior pane state intact – the file tree position, viewer content, and session scroll position are all preserved.

Changed Files & Staging

The Changed Files section occupies the bottom half of the git panel’s left sidebar. It lists every file with uncommitted changes and provides controls for staging, unstaging, and discarding those changes. AZUREAL uses a UI-only staging model – staging state is tracked entirely within the interface, not read from or written to the git index until commit time.


File Status Indicators

Each file in the list is prefixed with a single-character status indicator, color-coded by change type:

CharMeaningColor
MModifiedYellow
AAdded (new tracked file)Green
DDeletedRed
RRenamedCyan
?UntrackedMagenta

These characters and colors match the conventions of git status --short, making the display immediately readable for anyone familiar with git.


Staged vs. Unstaged Appearance

The visual treatment of each file entry changes based on its staging state:

Staged Files

  • File path rendered in normal text with an underline
  • Status character uses its normal color (from the table above)
  • Diff stats (+N / -M) shown in normal intensity

Unstaged Files

  • File path rendered in strikethrough with DarkGray color
  • Status character dimmed
  • Diff stats dimmed

This visual distinction makes it possible to see at a glance which files will be included in the next commit and which will be left out.


Default Staging Behavior

All files default to staged when the changed files list loads. This matches the most common intent: you typically want to commit everything. The staging controls exist for the cases where you need to exclude specific files from a commit.

Because staging is UI-only, it has no effect on the actual git index. AZUREAL does not run git add or git reset when you toggle staging. The staging state is applied at commit time, when AZUREAL stages exactly the files marked as staged in the UI before running git commit.


Staging Controls

KeyContextAction
sChanged Files focusedToggle stage/unstage for the selected file
Shift+SChanged Files focusedStage or unstage all files at once

Pressing s on a staged file unstages it (strikethrough + dim). Pressing s again restages it (underline + normal). Shift+S is a bulk toggle: if any files are unstaged, it stages all; if all are staged, it unstages all.


Discarding Changes

KeyContextAction
xChanged Files focusedDiscard changes to the selected file

Pressing x prompts for inline confirmation directly in the file list entry. The prompt displays Discard <path>? [y/n] in the file’s row. Pressing y confirms the discard; pressing n or Esc cancels. Other keys are ignored while the confirmation is active.

The discard mechanism depends on the file type:

File TypeGit Command
Tracked (modified/deleted)git restore <path>
Untrackedgit clean -f <path>

Discarding is irreversible – there is no undo. The inline confirmation exists specifically to prevent accidental data loss.


Title Bar

The Changed Files section title summarizes the current state:

Files (12, 10✓, +84 / -31)

The components are:

ComponentMeaning
12Total number of changed files
10✓Number of files currently staged
+84 / -31Aggregate lines added and removed across all files

This gives you a quick summary without needing to scan every file in the list.

Commit with AI Messages

Pressing c in the git panel triggers a commit workflow that uses your selected AI model to generate a conventional commit message from the staged diff. The message appears in an editable text area in the viewer pane, where you can review it, modify it, and confirm the commit – all without leaving the git panel.


The Commit Flow

When you press c, the following sequence runs:

  1. Stage – All files marked as staged in the UI are staged in the git index.
  2. Diff – AZUREAL captures the staged diff (git diff --staged).
  3. Generate – A background thread spawns to send the diff to the selected AI model with instructions to produce a conventional commit message.
  4. Display – The generated message fills an editable text area in the viewer pane.

Steps 1-3 happen automatically. You interact starting at step 4.


AI Backend Selection

The backend used for message generation depends on your currently selected model:

Model PatternBackendCommand
gpt-*Codexcodex exec --ephemeral
All othersClaudeclaude -p

The --no-session-persistence flag is passed to Claude to prevent the creation of .jsonl session files for these ephemeral generation requests.

Cross-Backend Fallback

If the primary backend fails (network error, timeout, API issue), AZUREAL automatically retries with the alternate backend. A Claude failure retries with Codex; a Codex failure retries with Claude. This fallback is transparent – you see the generated message regardless of which backend produced it.


The Commit Editor

Once the AI generates a message, the viewer pane transforms into a commit editor:

╔═══════════════════════════════════════╗
║  feat: add retry logic for API calls  ║
║                                       ║
║  Implement exponential backoff with   ║
║  jitter for transient API failures.   ║
║  Maximum of 3 retries before          ║
║  propagating the error to the caller. ║
╚═══════════════════════════════════════╝

The editor supports full text editing with word-wrap. You can rewrite the message entirely, tweak a word, or accept it as-is.

Commit Editor Keybindings

KeyAction
EnterCommit with the current message
Cmd+P / Ctrl+PCommit and push in one action
Shift+EnterInsert a newline (for multi-line messages)
EscCancel the commit

Both Enter and Cmd+P use a deferred pattern: a loading popup renders immediately while the git operation runs in the background. This prevents the UI from appearing frozen during the commit or push.


Message Cleanup

The AI-generated message goes through post-processing before it appears in the editor:

  • Markdown code fences are stripped. Some models wrap their output in triple-backtick fences; these are removed so the message is plain text suitable for a git commit.
  • Leading/trailing whitespace is trimmed.

The result is a clean conventional commit message ready for review.


Conventional Commit Format

The AI is instructed to produce messages following the conventional commit specification:

<type>(<optional scope>): <description>

<optional body>

Common types include feat, fix, refactor, docs, test, chore, and ci. The model infers the appropriate type from the diff content. You can always override the type or any other part of the message in the editor before confirming.

Squash Merge

Squash merge is the primary workflow for landing feature branches into main. Pressing m on a feature branch runs a multi-step background operation that rebases the branch, squash merges it into main, and pushes the result – all with progress feedback in the status bar.


Keybinding

KeyContextAction
mGit panel, feature branch, Actions focusedSquash merge into main

This action is only available on feature branches. It does not appear in the actions list when you are on main.


The Multi-Step Process

Squash merge is not a single git command. AZUREAL runs a carefully ordered sequence to ensure a clean, linear history on main:

Step 1: Abort Stale State

Any leftover rebase or merge state is cleaned up (git rebase --abort, git merge --abort) to prevent interference from a previously interrupted operation.

Step 2: Stash Dirty Working Tree

If the working tree has uncommitted changes, they are stashed automatically. The stash is popped on success and failure exit paths. On conflict, the stash remains until the conflict is resolved (popped after RCR accept or abort) so your uncommitted work is never lost.

Step 3: Rebase Feature onto Main

The feature branch is rebased onto main using exec_rebase_inner. This ensures that any upstream changes on main are incorporated before the merge, avoiding merge conflicts at the squash step.

Step 4: Push Rebased Branch

The rebased feature branch is pushed to the remote. If the branch has diverged (which it will after rebasing), --force-with-lease is used automatically.

Step 5: Pull Main

AZUREAL switches to main and runs git pull --ff-only to ensure main is up to date before merging.

Step 6: Squash Merge

git merge --squash <feature-branch>

This stages all of the feature branch’s changes as a single set of changes on main, without creating a merge commit yet.

Step 7: Commit with Rich Message

The squash merge commit is created with a structured message. The commit subject follows the format feat: merge <branch> into main, and the body includes the individual commit messages from the feature branch as bullet points:

feat: merge auth-refactor into main

- feat: add JWT token validation
- fix: handle expired refresh tokens
- refactor: extract auth middleware

Step 8: Push Main

The merged main branch is pushed to the remote.

Step 9: Pop Stash

If changes were stashed in Step 2, they are popped back onto the working tree.


Progress Feedback

The status bar displays progress messages as each phase executes:

  1. Rebasing onto main...
  2. Pushing rebased branch...
  3. Merging into main...
  4. Pushing to remote...

The entire operation runs in a background thread, keeping the UI responsive.


On Conflict

If the rebase step encounters a conflict, the squash merge pauses and opens the conflict overlay (the RCR flow). See Conflict Resolution (RCR) for details on how conflicts are resolved.

If the conflict is resolved successfully, the squash merge automatically resumes from where it left off. The continue_with_merge flag ensures that accepting the conflict resolution proceeds directly to the merge step without requiring you to restart the operation.


On Success: Post-Merge Dialog

After a successful squash merge, AZUREAL displays a PostMergeDialog that offers two options for the feature worktree:

  • Archive – Remove the worktree’s working directory but keep the branch. The worktree appears dimmed in the tab row and can be restored later.
  • Delete – Fully remove the worktree, its local branch, and its remote branch.

This dialog appears because after a squash merge, the feature branch’s work has been incorporated into main and the branch is typically no longer needed.

Rebase & Auto-Rebase

AZUREAL provides two ways to rebase feature branches onto main: a manual rebase triggered by keybinding, and an automatic background rebase that keeps branches up to date without intervention. Both use the same underlying rebase logic and conflict handling.


Manual Rebase

KeyContextAction
Shift+RGit panel, feature branch, Actions focusedRebase onto main

Pressing Shift+R rebases the current feature branch onto main. The rebase uses the --onto form with the merge-base fork point, which produces a clean linear history even when the branch has been previously rebased.

Dirty Working Tree Handling

If the working tree has uncommitted changes, AZUREAL stashes them before rebasing and pops the stash on all exit paths:

  • Success – Stash popped after rebase completes.
  • Conflict – Stash popped after conflict resolution (accept or abort).
  • Failure – Stash popped after rebase abort.

You never need to worry about losing uncommitted work during a rebase.

Push After Rebase

After a successful rebase, the branch will have diverged from its remote tracking branch (because rebase rewrites commit history). AZUREAL detects this automatically and uses --force-with-lease instead of a regular push. The status bar appends “(force-pushed)” to confirm the force push occurred.

--force-with-lease is used rather than --force because it refuses to overwrite the remote branch if someone else has pushed to it since your last fetch. This prevents accidentally destroying work on shared branches.


Auto-Rebase

Auto-rebase is a background process that automatically rebases eligible worktrees onto main at regular intervals. Toggle it with a in the git panel actions.

Toggle

KeyContextAction
aGit panel, Actions focusedToggle auto-rebase on/off

The setting is persisted per-worktree in <worktree_path>/.azureal/azufig.toml under the [git] section as auto-rebase = "true", so it survives restarts.

How It Works

When auto-rebase is enabled, AZUREAL checks all worktrees every 2 seconds. For each worktree, it evaluates whether a rebase is appropriate. A worktree is skipped if any of the following conditions are true:

ConditionReason
Claude session is runningRebase would disrupt the agent’s working state
RCR (conflict resolution) is activeA conflict is already being handled
Working tree is dirtyUncommitted changes would complicate the rebase
Git panel is open for that worktreeUser may be in the middle of a manual operation

If none of these conditions apply, the worktree is eligible and auto-rebase proceeds.

Outcomes

Auto-rebase produces one of two outcomes:

Rebased (no conflicts)

The branch is rebased cleanly. AZUREAL automatically pushes the rebased branch (with --force-with-lease) and displays a green success dialog for 2 seconds before it auto-dismisses. No user action is required.

Conflict detected

AZUREAL switches to the conflicted worktree and opens the conflict overlay (the RCR flow). See Conflict Resolution (RCR) for the full resolution workflow.


Tab Row Indicator

When auto-rebase is enabled, each worktree tab in the tab row displays a colored R indicator showing the rebase state:

ColorState
GreenIdle – auto-rebase is enabled and no action is in progress
OrangeRCR active – a conflict is being resolved for this worktree
BlueApproval pending – conflict resolution is complete and awaiting your approval

This indicator is visible at all times, not just in the git panel, so you can monitor rebase status from normal mode as well.

Conflict Resolution (RCR)

RCR – Rebase Conflict Resolution – is AZUREAL’s workflow for handling rebase conflicts. Rather than dropping you into a manual conflict editor, RCR spawns a Claude session dedicated to resolving the conflict, with a structured overlay that guides the process from detection through resolution and approval.


When RCR Triggers

RCR activates whenever a rebase encounters a conflict. This can happen during:

  • A manual rebase (Shift+R)
  • A squash merge (which rebases as its first step)
  • An auto-rebase cycle

In all three cases, the same conflict overlay appears.


The Conflict Overlay

When a conflict is detected, the viewer pane displays a conflict overlay with a red border:

╔══════════ REBASE CONFLICT ══════════╗
║                                     ║
║  Conflicted files:                  ║
║    src/auth/middleware.rs            ║
║    src/auth/token.rs                ║
║                                     ║
║  Auto-merged files:                 ║
║    src/lib.rs                       ║
║    src/config.rs                    ║
║                                     ║
║  [y] Resolve with Claude            ║
║  [n] Abort rebase                   ║
║                                     ║
╚═════════════════════════════════════╝

The overlay lists two categories of files:

  • Conflicted files – Files with merge conflicts that need resolution.
  • Auto-merged files – Files that git merged successfully without conflicts.

Two options are presented:

KeyAction
yResolve with Claude – spawn an RCR session
nAbort rebase – run git rebase --abort and return to the pre-rebase state

The RCR Session

Pressing y spawns a Claude session specifically for conflict resolution. This session is distinct from your normal development sessions:

  • Session name: [RCR] <branch-name> (e.g., [RCR] azureal/auth-refactor)
  • Working directory: The feature worktree where the rebase is in progress
  • Session pane theme: Green-themed borders, replacing the normal AZURE borders, to make it visually distinct from regular sessions

Claude receives the conflict context – the conflicted file list, the conflict markers, and the surrounding code – and begins working through the resolution. The session streams in real time in the session pane, just like any other Claude session.

Interactive Follow-Up

RCR sessions are fully interactive. While Claude is resolving conflicts (or after it finishes), you can send follow-up prompts to refine the resolution. For example, you might ask Claude to keep a specific side of a conflict, adjust a merged implementation, or explain what caused the conflict.


The Approval Dialog

When Claude exits (either by completing the resolution or when you stop it), an approval dialog appears with a green border:

KeyAction
y or EnterAccept resolution
nAbort rebase
EscDismiss dialog (re-show with Ctrl+A)

Accept (y / Enter)

Accepting the resolution performs the following:

  1. The RCR session file is deleted (cleanup).
  2. The stash is popped (restoring any uncommitted changes from before the rebase).
  3. If the rebase was the first step of a squash merge (continue_with_merge flag is set), the squash merge automatically proceeds from where it left off.

Abort (n)

Aborting runs git rebase --abort, which restores the branch to its pre-rebase state. The stash is popped and no changes are made to the branch history.

Dismiss (Esc)

Dismissing the dialog hides it without taking any action. The rebase remains in its mid-conflict state. You can re-show the approval dialog at any time by pressing Ctrl+A. This is useful if you want to inspect the resolved files before deciding.


RCR and Squash Merge Integration

When a squash merge triggers RCR (because the rebase step hit a conflict), the continue_with_merge flag is set internally. This flag tells the approval handler that accepting the resolution should not just complete the rebase but also continue with the remaining squash merge steps (merge into main, commit, push). The entire squash merge resumes automatically – you do not need to re-trigger it.


RCR and Auto-Rebase Integration

When auto-rebase detects a conflict, it switches to the affected worktree and opens the conflict overlay. The tab row indicator for that worktree changes from green (idle) to orange (RCR active), and then to blue (approval pending) once Claude finishes. See Rebase & Auto-Rebase for the indicator color table.

Auto-Resolve Settings

Auto-resolve is a mechanism that handles certain rebase conflicts automatically, without spawning an RCR session or requiring any user interaction. It works by maintaining a configurable list of files that can be safely resolved via 3-way union merge – keeping both sides of the conflict with no conflict markers.


How Auto-Resolve Works

When a rebase encounters conflicts, AZUREAL checks whether all conflicted files are in the auto-resolve list. If they are, the conflict is resolved automatically using git merge-file --union for each file. If even one conflicted file is not in the auto-resolve list, auto-resolve does not apply and the full RCR flow takes over.

The Union Merge Strategy

git merge-file --union <current> <base> <other>

The --union flag performs a 3-way merge that keeps both sides of every conflict. Rather than inserting conflict markers (<<<<<<<, =======, >>>>>>>), it concatenates both versions. This works well for files where both sides’ changes are additive and order does not matter – documentation files, changelogs, and configuration files are typical examples.

Looping Through Commits

A rebase replays commits one at a time. Auto-resolve does not just handle the first conflicted commit – it loops through all subsequent commits in the rebase, auto-resolving any that have only auto-resolvable conflicts. The loop continues until either the rebase completes or a commit has a conflict involving a file not in the auto-resolve list. At that point, the rebase pauses and hands off to the RCR flow for manual resolution.


Default Auto-Resolve Files

The following files are configured for auto-resolve by default:

File
AGENTS.md
CHANGELOG.md
README.md
CLAUDE.md

These are documentation and configuration files that agents frequently modify in parallel across branches. Because changes to these files are typically additive (new entries, new sections), the union merge strategy produces correct results in the vast majority of cases.


Settings Overlay

Press s in the git panel actions to open the auto-resolve settings overlay:

╔══════ Auto-Resolve Files ══════╗
║                                ║
║  [x] AGENTS.md                 ║
║  [x] CHANGELOG.md              ║
║  [x] README.md                 ║
║  [x] CLAUDE.md                 ║
║  [ ] Cargo.toml                ║
║                                ║
║  j/k: navigate                 ║
║  Space: toggle                 ║
║  a: add file                   ║
║  d: remove file                ║
║  Esc: save & close             ║
║                                ║
╚════════════════════════════════╝

Settings Keybindings

KeyAction
j / kNavigate up/down in the file list
SpaceToggle the selected file on/off
aAdd a new file to the list (prompts for file path)
dRemove the selected file from the list
EscSave changes and close the overlay

Changes are saved immediately on close.


Persistence

The auto-resolve file list is stored per-worktree in <worktree_path>/.azureal/azufig.toml under the [git] section. Each file is a separate key with an auto-resolve/ prefix:

[git]
"auto-resolve/AGENTS.md" = "true"
"auto-resolve/CHANGELOG.md" = "true"
"auto-resolve/README.md" = "true"
"auto-resolve/CLAUDE.md" = "true"

This configuration is per-worktree, so different worktrees can have different auto-resolve lists. Editing the TOML file directly has the same effect as using the settings overlay.

Pull & Push

Pull and push are the simplest operations in the git panel. They map directly to git pull and git push with one important enhancement: AZUREAL automatically detects diverged branches and switches to --force-with-lease when a regular push would be rejected.


Pull

KeyContextAction
lGit panel, main branch, Actions focusedPull from remote

Pull runs git pull on the main branch. It is only available when you are on main – feature branches are kept in sync with main through rebasing, not pulling.

After the pull completes, the changed files list and commit log refresh automatically to reflect any new changes.


Push

KeyContextAction
Shift+PGit panel, any branch, Actions focusedPush to remote

Push works on any branch – main or feature. The behavior adapts based on the branch state.

Pre-Push Pull (Main Only)

On main/master branches, AZUREAL runs git pull --rebase before pushing to incorporate any upstream changes. This is skipped on feature branches – they are kept in sync with main via the auto-rebase system, and pulling on a feature branch whose remote was already squash-merged could corrupt HEAD state.

Regular Push

If the local branch is ahead of its remote tracking branch and has not diverged (no rewritten history), AZUREAL runs:

git push -u origin <branch>

The -u flag sets the upstream tracking reference on first push.

Force Push with Lease

If the local branch has diverged from its remote tracking branch – which happens after a rebase rewrites commit history – a regular push would be rejected. AZUREAL detects this condition and automatically uses:

git push --force-with-lease -u origin <branch>

The status bar appends (force-pushed) to confirm that a force push was performed rather than a regular push.

--force-with-lease is a safer alternative to --force. It refuses to overwrite the remote branch if someone else has pushed commits that you have not fetched. This prevents accidentally destroying work on shared branches while still allowing you to push rebased history.


Post-Operation Refresh

Both pull and push trigger a full state refresh after completion:

  • The changed files list is repopulated from git status.
  • The commit log is reloaded to reflect any new or removed commits.
  • The divergence badges on the commit log border are recalculated.

This ensures the git panel always shows current state after any remote operation.

The Embedded Terminal

AZUREAL includes a PTY-based embedded terminal that acts as a portal directly into your shell. Rather than switching to a separate terminal application to run commands, you can toggle a terminal pane inline, type commands, see full-color output, and return to your normal workflow – all without leaving the TUI.


How It Works

The terminal is built on portable-pty, a cross-platform pseudo-terminal library. When you open the terminal, AZUREAL spawns your detected shell inside a real PTY, meaning programs that expect a terminal (colored output, interactive prompts, curses-based tools) work correctly. The terminal pane renders the PTY output using ansi-to-tui for full ANSI color support and vt100 for cursor positioning.

Each worktree gets its own terminal shell instance. When you switch worktrees, the terminal switches to that worktree’s shell (spawning one if it does not already exist), with the working directory set to the worktree’s root. This keeps every branch’s terminal activity isolated.


Two Modes

The terminal operates in two distinct input modes:

  • Terminal command mode – The terminal is visible and you can interact with AZUREAL’s keybindings normally. Global keys like G, H, M, P, and bracket navigation all work. Press t to drop into type mode.
  • Terminal type mode – All keystrokes are forwarded directly to the PTY. AZUREAL keybindings are suspended. Press Esc to return to command mode.

This separation means you never accidentally send a keystroke to the wrong target. See Terminal Modes for the full breakdown.


Visual Indicators

When the terminal pane is active (focused), its border turns azure to clearly indicate where input is going. The border returns to its default color when you move focus elsewhere.

The terminal pane sits at the bottom of the layout. Its height is adjustable with + and - in terminal command mode, ranging from 5 to 40 lines.


Chapter Contents

  • Terminal Modes – The two input modes, their keybindings, and how to switch between them.
  • Shell Integration – How AZUREAL detects and launches your shell across platforms.
  • Terminal Features – Color rendering, cursor positioning, resizing, mouse interaction, and clipboard support.

Terminal Modes

The embedded terminal uses two input modes to cleanly separate AZUREAL navigation from shell interaction. At any given moment, the terminal is in exactly one of these modes.


Terminal Command Mode

Terminal command mode is the default state when the terminal pane is open. You can see the terminal output and interact with AZUREAL normally.

Entering Command Mode

FromKeyEffect
Any pane (terminal closed)T (Shift+T)Toggle terminal open in command mode
Terminal type modeEscExit type mode, enter command mode

Available Keys in Command Mode

All global keybindings remain active:

KeyAction
GOpen Git panel
HOpen Health panel
MBrowse main branch
POpen Projects panel
TToggle terminal (close)
[ / ]Switch worktrees
rOpen run commands
tEnter terminal type mode
pClose terminal / refocus prompt
EscClose terminal
+Increase terminal height
-Decrease terminal height

The p key provides a quick way to dismiss the terminal and return focus to the prompt. It works from terminal command mode and also from any mode where the prompt is tabbed away.

Resizing

The terminal pane height is adjustable in command mode:

  • + increases height by one line.
  • - decreases height by one line.
  • Height is clamped between 5 lines (minimum) and 40 lines (maximum).

The resize takes effect immediately and the terminal content reflows to match the new dimensions.


Terminal Type Mode

Type mode forwards all keystrokes directly to the PTY. AZUREAL keybindings are completely suspended – everything you type goes to the shell.

Entering Type Mode

FromActionEffect
Terminal command modePress tEnter type mode
Any modeClick inside terminal paneEnter type mode, reposition cursor

Clicking inside the terminal pane is a shortcut that both enters type mode and moves the cursor to the clicked position within the shell’s input line.

Available Keys in Type Mode

KeyAction
EscExit type mode (return to command mode)
Alt+Left or Ctrl+LeftWord navigation backward
Alt+Right or Ctrl+RightWord navigation forward
All other keysForwarded to PTY

Word navigation sends readline-compatible escape sequences to the PTY: \x1bb for backward and \x1bf for forward. This works in bash, zsh, and other readline-based shells.

Enter Key Behavior

The Enter key sends \r (carriage return) to the PTY, not \n (line feed). This matches standard terminal behavior and ensures proper command execution across all shells.


Mouse Interaction

Mouse input works in both modes:

ActionEffect
Click inside terminalEnter type mode, reposition cursor
Mouse dragSelect text with auto-scroll
Mouse wheelScroll through terminal history
Cmd+C / Ctrl+C (with selection)Copy selected text to clipboard

Text selection operates in scrollback-adjusted absolute coordinates, meaning selections remain accurate even when scrolled through history. Dragging past the top or bottom edge of the terminal pane triggers automatic scrolling.

When Cmd+C or Ctrl+C is pressed with an active selection, the selected text is copied to the system clipboard. Without a selection, the standard interrupt signal is sent to the PTY instead.


Quick Reference

T (Shift+T)       Toggle terminal open/closed
t                  Enter type mode (keystrokes go to shell)
Esc                Exit type mode / close terminal
p                  Close terminal / refocus prompt
+/-                Resize terminal height (5-40 lines)
Click              Enter type mode + reposition cursor
Drag               Select text
Cmd+C / Ctrl+C     Copy selection (or interrupt if no selection)

Shell Integration

AZUREAL automatically detects and launches the appropriate shell for your platform. No manual configuration is required – the terminal works out of the box on macOS, Linux, and Windows.


Shell Detection

Unix (macOS and Linux)

On Unix systems, AZUREAL reads the SHELL environment variable to determine which shell to launch. If SHELL is not set, it falls back to /bin/bash.

Most systems set SHELL to the user’s login shell (e.g., /bin/zsh on modern macOS, /bin/bash on most Linux distributions). The terminal inherits this shell along with its configuration files (.bashrc, .zshrc, etc.).

Windows

Windows shell detection follows a prioritized chain:

  1. pwsh.exe (PowerShell 7+) – tried first.
  2. powershell.exe (Windows PowerShell 5.1) – tried if pwsh.exe is not found.
  3. COMSPEC / cmd.exe – final fallback.

Each candidate is verified by checking its exit status before accepting it. If a shell binary exists but fails to start, detection moves to the next candidate.

PowerShell is launched with the -NoLogo flag to suppress the startup banner, keeping the terminal output clean.


Environment Setup

Regardless of platform, AZUREAL sets one environment variable before spawning the shell:

TERM=xterm-256color

This tells programs running inside the terminal that 256-color output is supported. Most modern CLI tools (ls with colors, git diff, syntax highlighters) respect this variable and produce colored output automatically.


Per-Worktree Shells

Each worktree runs its own independent shell instance. The shell’s working directory is set to the worktree’s root directory at launch:

WorktreeShell CWD
main<repo>/
feature-auth<repo>/worktrees/feature-auth/
bugfix-render<repo>/worktrees/bugfix-render/

When you switch between worktrees, the terminal switches to that worktree’s shell. If the worktree does not have a shell running yet, one is spawned on first access. Shells persist for the lifetime of the worktree session – if you switch away and come back, your shell history and state are still there.


PTY Initialization Order

The embedded terminal uses portable-pty to manage the pseudo-terminal. The PTY setup follows a specific initialization order that is critical for reliability:

  1. Clone the readertry_clone_reader() obtains a handle for reading PTY output.
  2. Take the writertake_writer() obtains a handle for sending input to the PTY.
  3. Spawn the shellspawn_command() launches the detected shell inside the PTY.
  4. Drop the slavedrop(pair.slave) releases the slave side of the PTY pair.

Steps 1 and 2 must happen before step 3. Attempting to clone the reader or take the writer after the command has been spawned can lead to race conditions or missed output. Dropping the slave after spawn ensures the PTY correctly detects when the shell exits.

Terminal Features

The embedded terminal goes beyond basic text I/O. It supports full ANSI colors, accurate cursor positioning, dynamic resizing, and mouse-driven text selection with clipboard integration.


Full Color Support

Terminal output is rendered with complete ANSI color support via the ansi-to-tui crate. This translates ANSI escape sequences into styled TUI spans, meaning you see the same colors in AZUREAL’s terminal that you would see in a standalone terminal emulator:

  • Standard 16 colors (bold/dim variants)
  • 256-color palette
  • 24-bit true color (RGB)
  • Text attributes: bold, italic, underline, strikethrough, inverse

Programs like git diff, ls --color, bat, delta, and other color-aware CLI tools render correctly without any special configuration beyond the TERM=xterm-256color environment variable that AZUREAL sets automatically.


Cursor Positioning

The terminal tracks cursor position using a vt100 parser that interprets cursor movement escape sequences in real time. This means programs that reposition the cursor – progress bars, interactive prompts, top-style dashboards – render accurately.

The cursor position is displayed as a blinking indicator in the terminal pane when in type mode, showing exactly where the next character will be inserted.


Dynamic Resizing

The terminal pane dynamically resizes to match its allocated dimensions in the layout. Resizing happens in two scenarios:

  1. Manual resize – Pressing + or - in terminal command mode adjusts the pane height between 5 and 40 lines.
  2. Layout reflow – When the overall terminal window is resized, the terminal pane adjusts its width (and potentially height) to fit the new layout dimensions.

On each resize, the PTY is notified of the new dimensions so that programs running inside it can reflow their output correctly. This means full-screen programs like vim, htop, or less adapt to the available space.


Text Selection

Text selection in the terminal uses mouse drag with automatic scrolling:

  1. Click and drag to start a selection. The selected text is highlighted.
  2. Drag past the top or bottom edge of the pane to auto-scroll through the terminal history while extending the selection.
  3. Release to finalize the selection.

Selections are tracked in scrollback-adjusted absolute coordinates. This means that if you scroll up into history, select text, and then the terminal scrolls further, the selection remains anchored to the correct text.

Clipboard Copy

With an active selection:

  • macOS: Cmd+C copies the selected text to the system clipboard.
  • Linux / Windows: Ctrl+C copies the selected text to the system clipboard.

Without an active selection, the same key combination sends the standard interrupt signal (SIGINT) to the PTY, which is the expected behavior for canceling a running command.


Scrollback History

The terminal maintains a scrollback buffer that you can navigate with the mouse wheel. Scrolling up moves through command history and output; scrolling down returns toward the current output. The scrollback buffer preserves all output from the shell session.


Quick Reference

FeatureDetails
Color supportANSI 16, 256, and 24-bit true color
Cursor trackingvt100 parser for accurate positioning
Resize range5 to 40 lines (+/- keys)
SelectionMouse drag with auto-scroll
ClipboardCmd+C / Ctrl+C with active selection
ScrollbackMouse wheel navigation
Per-worktreeIndependent shell per worktree
EnvironmentTERM=xterm-256color set automatically

Projects Panel

AZUREAL supports working across multiple repositories through its Projects panel. Each project is a git repository registered with AZUREAL, and you can switch between them while preserving the full state of every project – running agent sessions, file trees, terminal shells, and all. This makes it possible to manage an entire portfolio of repositories from a single AZUREAL instance.


How It Works

Projects are stored persistently in ~/.azureal/azufig.toml under the [projects] section. Each project entry records the repository path and a display name. AZUREAL loads this list on startup and provides a panel for switching between projects, adding new ones, and managing the list.

When you switch projects, AZUREAL takes a snapshot of the current project’s entire state and restores the target project’s snapshot. Agent processes continue running in the background for the project you leave – their output is captured to session files even though it is not displayed until you switch back.


Automatic Registration

When AZUREAL launches inside a git repository, it automatically registers that repository as a project. The display name is derived from the git remote URL when available, falling back to the folder name if no remote is configured. You never need to manually add the repository you launched from.


Opening the Panel

ContextKeyEffect
Worktree viewPOpen Projects panel
Startup (not in a git repo)AutomaticProjects panel shown full-screen

When AZUREAL starts outside of a git repository, the Projects panel appears automatically as a full-screen view with a prompt to initialize a new repository or select an existing project.


Auto-Pruning

On load, AZUREAL silently prunes the project list: any entry whose directory no longer exists or is no longer a valid git repository is removed from the configuration file. This keeps the project list clean without manual intervention.


Chapter Contents

  • Managing Projects – Adding, removing, renaming, initializing, and switching between projects.
  • Parallel Projects – How project state is snapshotted and restored, and what happens to background processes.
  • Background Processes – How agent exit events are handled for non-active projects, and activity status indicators.

Managing Projects

The Projects panel provides all the operations needed to maintain your project list: adding repositories, switching between them, renaming display names, initializing new repos, and removing entries.


Panel Actions

KeyAction
EnterSwitch to selected project
aAdd a project by path
dDelete project from list
nRename display name
iInitialize a new git repository
EscClose panel (only if a project is loaded)
Ctrl+QQuit AZUREAL

Switching Projects (Enter)

Pressing Enter on a project in the list performs a full project switch:

  1. Validation – The target directory is checked to confirm it is still a valid git repository. If it is not, the entry is pruned and an error message appears.
  2. State snapshot – The current project’s full state is captured (see Parallel Projects for what is included in the snapshot). Agent processes continue running in the background – their output is captured to session files and handled via the background exit flow.
  3. Reload – AZUREAL reloads with the target project’s repository, restoring its snapshot if one exists.

The switch is effectively a full context change: the file tree, worktrees, sessions, terminals, viewer tabs, and git state all update to reflect the new project.


Adding a Project (a)

Pressing a opens a path input dialog. Enter the absolute path to a git repository. AZUREAL validates that the path points to a valid git repository before adding it to the project list. If validation fails, an error message explains why.

The display name for an added project is derived automatically from the git remote URL. If no remote is configured, the folder name is used.


Deleting a Project (d)

Pressing d removes the selected project from AZUREAL’s project list. This only removes the entry from ~/.azureal/azufig.toml – it does not delete the repository from disk. Your files, branches, and commit history remain untouched.


Renaming a Project (n)

Pressing n opens a text input to change the selected project’s display name. The display name is what appears in the project list and in status messages. It has no effect on the actual repository directory or git configuration.


Initializing a Repository (i)

Pressing i prompts for a directory path. If the path is left blank, AZUREAL uses the current working directory. A new git repository is initialized at that location via git init, and the new repository is automatically added to the project list.

This is primarily useful when AZUREAL starts outside of a git repository and presents the full-screen project panel. You can initialize a repo right from the panel without dropping to a shell.


Closing the Panel (Esc)

Pressing Esc closes the Projects panel and returns to the normal worktree view. This only works if a project is currently loaded – if AZUREAL started without a project (outside a git repo), the panel cannot be dismissed until you select or initialize a project.

Ctrl+Q quits AZUREAL entirely, regardless of whether a project is loaded.

Parallel Projects

AZUREAL does not shut down background projects when you switch away from them. Agent sessions continue running, output is captured, and the full project state is preserved in a snapshot. When you switch back, everything is restored exactly as you left it.


Project Snapshots

When you switch away from a project, AZUREAL captures its entire state into a ProjectSnapshot. When you switch back, the snapshot is restored and the project resumes as if you never left.

What Is Snapshotted

The snapshot includes everything needed to reconstruct the project’s UI and runtime state:

ComponentDetails
Display eventsThe rendered session output for each worktree
WorktreesAll worktree metadata and their current states
File treeExpanded/collapsed nodes, selected file
Viewer tabsOpen files, scroll positions, active tab
Branch-to-slot mapsWhich session slot belongs to which branch
Unread sessionsWhich worktrees have new agent output since last viewed
TerminalsShell state for each worktree
Run commandsConfigured run commands and their states
PresetsPrompt presets configured for the project

Stale Slot Cleanup

When a snapshot is restored, AZUREAL performs a cleanup pass. Session slots that reference branches or worktrees that no longer exist are pruned from the snapshot. This handles the case where a branch was deleted externally (e.g., by another developer or a CI system) while the project was in the background.


Background Agent Sessions

Agent processes (Claude sessions) continue running in the background when you switch to a different project. Their behavior while backgrounded:

  • Output is captured – The agent’s responses are written to the session file on disk, ensuring nothing is lost.
  • Output is not rendered – The display events for the background project are not updated in real time. There is no wasted rendering work for a project you are not looking at.
  • On switch back – The snapshot is restored and the session pane reflects the current state of all sessions, including any output produced while the project was backgrounded.

This means you can start a long-running agent task in one project, switch to another project to do different work, and come back later to find the results waiting for you.


Activity Status Icons

Each project in the project list displays an activity status icon drawn from the same symbol set used in the worktree tab row. The icon reflects the aggregate status of all worktrees in the project.

Priority Order

Status icons follow a priority hierarchy (highest to lowest):

  1. Running – At least one agent is actively executing.
  2. Failed – At least one agent has failed.
  3. Waiting – At least one agent is waiting for user input.
  4. Pending – At least one agent is queued to start.
  5. Completed – All agents have finished successfully.
  6. Stopped – All agents are stopped.

The highest-priority status among all worktrees determines the icon shown for the project.

Active vs. Background Status

  • Active project – Status is derived from live worktree statuses in real time.
  • Background projects – Status is derived by checking the project’s snapshot against the set of currently running sessions. This provides an accurate status without needing to render or process the background project’s full state.

Background Processes

When you switch away from a project, its agent sessions keep running. AZUREAL tracks these background processes and handles their lifecycle events – including exit – without requiring the project to be active.


Background Exit Handling

When an agent session finishes in a background project, AZUREAL’s handle_claude_exited() function is responsible for processing the event. The handler checks the slot_to_project map to determine whether the exiting session belongs to the active project or a background one.

For Background Projects

When the exiting session belongs to a background project, the handler:

  1. Updates the snapshot – The project’s ProjectSnapshot is modified directly. The session’s branch_slots and active_slot entries are updated to reflect the completed/failed state.
  2. Marks unread – The worktree that owned the session is flagged as having unread output. When you switch back to the project, the worktree tab will show the unread indicator.
  3. Status message – A status bar message is shown, prefixed with the project’s display name, so you know which background project had an agent finish. For example: [my-api] Claude session completed.

For Active Projects

When the exiting session belongs to the active project, normal exit handling applies – the session pane updates, the tab row status icon changes, and the status bar reflects the result. No snapshot manipulation is needed because the active project’s state is live.


Process Continuity

Agent processes are not killed when you switch projects. All running sessions continue executing in the background. Their output is captured to session files on disk and handled via the background exit flow (see above). When you switch back, the project snapshot is restored and includes any output that was produced while the project was backgrounded.

Note: Session file capture ensures no output is ever lost, even though the background project’s display is not being updated in real time.


Monitoring Background Activity

You do not need to switch to a project to check on its agents. The project list in the Projects panel shows activity status icons for every project, including background ones:

Icon meaningWhat it tells you
RunningAt least one agent is still executing
FailedAn agent encountered an error
WaitingAn agent needs user input
CompletedAll agents finished successfully
StoppedAll agents are stopped

Background project status is determined by checking the project’s snapshot against the set of currently running sessions. This is a lightweight check that does not require restoring the full project state.


Practical Workflow

A typical multi-project workflow looks like this:

  1. Start an agent task in Project A (e.g., “refactor the auth module”).
  2. Press P to open the Projects panel.
  3. Switch to Project B and work on something else.
  4. Glance at the project list periodically – Project A’s icon shows “Running”.
  5. When Project A’s icon changes to “Completed”, switch back.
  6. The snapshot restores. The session pane shows the agent’s full output. The worktree tab has an unread indicator. Everything is exactly as if you had been watching the whole time.

Speech-to-Text

AZUREAL includes a fully local speech-to-text engine that lets you dictate prompts instead of typing them. Audio is captured from your default input device, transcribed on-device via whisper.cpp, and inserted at the cursor position in the prompt or edit buffer. No audio ever leaves your machine.


How It Works

The speech pipeline runs through four stages:

  1. Capture – Audio is recorded from the default input device via the cpal library (CoreAudio on macOS, WASAPI on Windows). Raw samples arrive as f32 at the device’s native sample rate and channel count.
  2. Preprocessing – Multi-channel audio is mixed down to mono, then resampled to 16kHz (Whisper’s expected input rate).
  3. Transcription – The accumulated audio buffer is fed to whisper.cpp with Greedy { best_of: 1 } decoding. On macOS, Metal GPU acceleration is used automatically; on Windows, CUDA GPU acceleration is used.
  4. Insertion – The transcribed text is inserted at the current cursor position with smart spacing: a space is prepended if the cursor is not at the start of a line or immediately after a space.

Toggle Recording

Press Ctrl+S while in prompt mode or edit mode to start recording. Press Ctrl+S again to stop recording and trigger transcription.

On Windows and Linux, the edit mode binding is Alt+S instead of Ctrl+S (because Ctrl+S is used for file save in edit mode on those platforms). The prompt mode binding remains Ctrl+S on all platforms.

The stop keybinding resolves from any focus state or mode while recording is active. You do not need to navigate back to the prompt to stop – Ctrl+S will always stop an active recording regardless of where focus currently sits.


Visual Feedback

While recording is active, two visual indicators appear:

  • Magenta border – The prompt or edit buffer border turns magenta to signal that the microphone is live.
  • REC / … prefix – The status area shows REC while audio is being captured and ... while transcription is in progress.

A progress indicator also appears in the status bar during the transcription phase, since Whisper processing takes a moment depending on the length of the recording.


Resource Efficiency

The speech subsystem is designed to consume zero resources when not in use:

  • Background thread – The audio processing thread blocks on mpsc::recv() when idle. It consumes no CPU until a recording is started.
  • Lazy model loading – The WhisperContext is not created at startup. It is loaded on the first use of speech-to-text, so users who never dictate pay no memory cost.

Once loaded, the Whisper context remains in memory for the duration of the session to avoid repeated model load times.


Whisper Model

AZUREAL uses the ggml-small.en Whisper model, stored at:

~/.azureal/speech/ggml-small.en.bin

This file is approximately 466 MB. If the model file is missing, AZUREAL shows an error with download instructions. You must download the model manually before first use:

mkdir -p ~/.azureal/speech && curl -L -o ~/.azureal/speech/ggml-small.en.bin \
  https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en.bin

The small.en model provides a good balance between transcription accuracy and speed for English-language input.


Quick Reference

Ctrl+S    Toggle recording on/off (prompt mode, or edit mode on macOS)
Alt+S     Toggle recording on/off (edit mode on Windows/Linux)
Ctrl+S    Stop recording from ANY focus/mode (always resolves)
DetailValue
Audio librarycpal (CoreAudio on macOS, WASAPI on Windows)
Transcription enginewhisper.cpp (Metal GPU on macOS, CUDA GPU on Windows)
Sample pipelinef32 -> mono mixdown -> 16kHz resample
Decoding strategyGreedy { best_of: 1 }
Model file~/.azureal/speech/ggml-small.en.bin (~466 MB)
Idle CPU usageZero (thread blocks on mpsc::recv)

Run Commands

Run Commands let you save and execute shell commands from anywhere in AZUREAL. Instead of switching to a terminal to run your build, test, or deploy scripts, you define them once and trigger them with a couple of keystrokes. Commands can be scoped globally (available in every project) or locally (specific to a single project).


Opening the Picker

Press r (global keybinding) to open the run command picker. If only one command is defined, it executes immediately without showing the picker.

Press Shift+R to open the new command dialog directly, bypassing the picker.


The Picker

The picker lists all defined run commands. Each entry shows its position number, name, and a G (global) or P (project) scope badge.

KeyAction
j / kNavigate up and down
1-9Quick-select by position number
EnterExecute the highlighted command
eEdit the highlighted command
dDelete the highlighted command (prompts y/n confirmation)
aAdd a new command

Creating a Command

The new command dialog (opened via Shift+R or a from the picker) has two fields:

  1. Name – A short label for the command (e.g., “Build”, “Test”, “Deploy”).
  2. Command / Prompt – The shell command or natural-language prompt to execute.

Tab cycles focus between the Name field and the Command/Prompt field.

Command Mode vs Prompt Mode

Inside the Command/Prompt field, pressing Tab toggles between two input modes:

  • Command mode – The text is treated as a raw shell command and executed directly (e.g., cargo build --release).
  • Prompt mode – The text is treated as a natural-language description. AZUREAL spawns a [NewRunCmd] agent session that asks Claude to generate the appropriate shell command. Once the session completes, the generated command is automatically reloaded into your run command list.

Dual Scope

Run commands support two scopes:

ScopeConfig FileAvailability
Global~/.azureal/azufig.tomlEvery project
Project.azureal/azufig.toml (in project root)Current project only

Press Ctrl+S in the dialog to toggle between global and project scope.

Both scopes are merged in the picker – global commands appear first, followed by project-local commands. Each command shows a G (global) or P (project) scope badge.


Storage Format

Commands are stored under the [runcmds] table in the respective azufig.toml file. Keys are prefixed with a position number that determines their order in the picker:

[runcmds]
1_Build = "cargo build"
2_Test = "cargo test"
3_Deploy = "./scripts/deploy.sh"

The position prefix (1_, 2_, etc.) controls the display order and maps to the 1-9 quick-select keys in the picker.


Quick Reference

r          Open run command picker (or execute if only 1 defined)
Shift+R    Open new command dialog
Ctrl+S     Toggle global/project scope (in dialog)
Tab        Cycle fields / toggle Command vs Prompt mode (in dialog)
1-9        Quick-select in picker
Enter      Execute selected command
e          Edit selected command
d          Delete selected command (y/n)
a          Add new command from picker

Preset Prompts

Preset Prompts are reusable prompt templates that you can insert into the prompt buffer with a single keybinding. They are useful for frequently repeated instructions – things like “review this file for bugs”, “add tests for the selected function”, or “refactor to reduce complexity”. You can define up to 10 presets, each accessible by a dedicated Alt+number shortcut.


Quick-Select

The fastest way to use a preset is the direct shortcut:

KeyAction
Alt+1 through Alt+9Insert preset 1-9 directly
Alt+0Insert preset 10 directly

These shortcuts work in prompt mode only and skip the picker entirely. The preset text is inserted into the prompt buffer immediately.


The Picker

Press Alt+P in prompt mode to open the preset prompt picker. The picker shows all defined presets with their position numbers and scope badges.

KeyAction
1-9, 0Quick-select by number
EnterInsert the highlighted preset
aAdd a new preset
eEdit the highlighted preset
dDelete the highlighted preset (prompts y/n confirmation)

Each preset in the picker displays a G (global) or P (project) badge indicating its scope.


Dual Scope

Presets support two scopes, just like Run Commands:

ScopeConfig FileBadge
Global~/.azureal/azufig.tomlG
Project.azureal/azufig.toml (in project root)P

Press Ctrl+S in the preset dialog to toggle between global and project scope.

Global presets are available everywhere. Project-local presets are specific to the current project and override global presets at the same position number.


Hint Display

A hint is shown in the prompt title bar reminding you that preset prompts are available. This serves as a passive reminder of the feature without requiring you to memorize the keybinding.


Quick Reference

Alt+P         Open preset prompt picker (prompt mode only)
Alt+1 - Alt+9 Quick-select preset 1-9 (prompt mode only)
Alt+0         Quick-select preset 10 (prompt mode only)
Ctrl+S        Toggle global/project scope (in dialog)

Health Panel

The Health Panel is a diagnostic overlay that scans your codebase for structural problems – files that have grown too large, documentation that has fallen behind. It gives you a quick read on the health of your project and provides one-click actions to fix what it finds, powered by concurrent agent sessions.

Open the Health Panel with Shift+H from anywhere in AZUREAL.


Layout

The Health Panel appears as a centered modal overlay, sized at 55% x 70% of the terminal (minimum 50 columns by 16 rows). The title bar reads “Health: <worktree>”, showing the name of the currently active worktree.

The panel uses a green accent color (Rgb(80, 200, 80)) for its UI elements – borders, highlights, and active indicators.


Tab Bar

The panel contains two tabs, displayed in a horizontal tab bar at the top:

TabPurpose
God FilesFinds source files exceeding 1000 lines of production code
DocumentationMeasures doc-comment coverage across all source files

Press Tab to switch between tabs. The panel remembers which tab you were viewing and reopens on that tab the next time you open it.


Auto-Refresh

While the Health Panel is open, file changes on disk trigger an automatic debounced rescan with a 500ms delay. If you or an agent modifies a file, the panel updates its results without requiring a manual refresh.


Scope Mode

Press s while the Health Panel is open to enter Scope Mode, which lets you restrict which directories the panel scans. See Scope Mode for details.


Chapter Contents

  • God Files – The God Files tab: detection logic, results list, and modularization actions.
  • Documentation Health – The Documentation tab: coverage scoring, per-file breakdown, and doc-generation sessions.
  • Scope Mode – Restricting the scan to specific directories.

God Files

The God Files tab identifies source files that have grown beyond a maintainable size. Any source file exceeding 1000 lines of production code appears in this list. You can select one or more files and spawn concurrent agent sessions to break them into well-structured modules.


Detection Logic

Line Counting

The scanner counts production lines of code, not total file length. For Rust files, any #[cfg(test)] block and its contents are excluded from the count. This means a 1200-line Rust file with a 300-line test module registers as 900 production lines and does not appear in the results.

For all other languages, the entire file length is counted.

Source Root Detection

The scanner looks for well-known source directories at the project root:

src/  lib/  crates/  packages/  app/  cmd/  pkg/  internal/  ...

Approximately 16 directory names are recognized. If none are found, the scanner falls back to scanning the entire project root.

Excluded Directories

Approximately 75 directories are automatically skipped during scanning. These include build output directories (target/, node_modules/, dist/, .git/, etc.) and other non-source paths that would produce false positives.

Recognized Extensions

The scanner recognizes approximately 75 source file extensions (.rs, .py, .ts, .js, .go, .java, .c, .cpp, and many more). Files with unrecognized extensions are ignored.


Results List

Detected god files are displayed in a sorted list, ordered by line count descending (largest files first). Each entry shows the file path relative to the project root and its production line count.


Keybindings

KeyAction
j / kNavigate up / down one entry
J / KPage down / page up
Alt+Up / Alt+DownJump to top / bottom
SpaceToggle checkbox on the highlighted file
aToggle all checkboxes (select all / deselect all)
vView all checked files as Viewer tabs (up to 12)
Enter or mModularize all checked files

Modularization

When you press Enter or m with one or more files checked, AZUREAL spawns agent sessions to break those files into smaller modules.

Module Style Selection

If any of the checked files are Rust or Python, a dialog appears asking you to choose a module style:

LanguageOption AOption B
RustFile-based module root (foo.rs + foo/bar.rs)Directory module (foo/mod.rs + foo/bar.rs)
PythonPackage (foo/__init__.py + submodules)Single-file split

For other languages, the dialog is skipped and modularization proceeds immediately.

Parallel Execution

Each checked file spawns its own agent session tagged with [GFM] (God File Modularization). All sessions run concurrently. The agent receives the file contents and instructions to decompose it into well-structured, appropriately sized modules while preserving all existing behavior.

You can monitor progress in the session pane – each [GFM] session appears as a separate conversation.


Quick Reference

Shift+H       Open Health Panel
Tab           Switch to Documentation tab
j/k           Navigate results
J/K           Page down / page up
Alt+Up/Down   Jump to top / bottom
Space         Toggle checkbox
a             Toggle all checkboxes
v             View checked files as tabs
Enter / m     Modularize checked files
s             Enter Scope Mode

Documentation Health

The Documentation tab measures doc-comment coverage across your codebase. It scans every source file for documentable items and checks whether each one has a preceding doc comment. The result is a per-file and overall coverage score that tells you where documentation is missing.


What Gets Scanned

The scanner identifies the following documentable items using a line-based heuristic (no AST parsing):

Item Kind
fn
struct
enum
trait
const
static
type
impl
mod

For each item found, the scanner checks whether the lines immediately preceding it contain a /// or //! doc comment. If a doc comment is present, the item is counted as documented. If not, it is counted as undocumented.

This is a line-based heuristic, not a full AST analysis. It works well for standard Rust code formatting but may produce inaccurate results for unusual layouts.


Overall Score

The top of the Documentation tab shows an overall documentation coverage score as a percentage. The score header is color-coded:

Score RangeColor
80% and aboveGreen
50% to 79%Yellow
Below 50%Red

Per-File Breakdown

Below the overall score, each scanned file is listed individually with its own coverage percentage. Files are sorted by coverage ascending – the files with the worst documentation appear first, making it easy to identify where attention is most needed.


Keybindings

KeyAction
j / kNavigate up / down one entry
J / KPage down / page up
Alt+Up / Alt+DownJump to top / bottom
SpaceToggle checkbox on the highlighted file
aCheck all files that are not already at 100% coverage
vView all checked files as Viewer tabs (up to 12)
EnterSpawn documentation sessions for all checked files

Documentation Sessions

When you press Enter with one or more files checked, AZUREAL spawns agent sessions to add missing documentation. Each file gets its own session tagged with [DH] (Documentation Health).

All [DH] sessions run concurrently. The agent prompt instructs Claude to add doc comments to all undocumented items in the file without modifying any existing code. Only documentation is added – no refactoring, no formatting changes, no logic changes.

You can track progress in the session pane, where each [DH] session appears as a separate conversation.


Quick Reference

Shift+H       Open Health Panel
Tab           Switch to God Files tab
j/k           Navigate results
J/K           Page down / page up
Alt+Up/Down   Jump to top / bottom
Space         Toggle checkbox
a             Check all non-100% files
v             View checked files as tabs
Enter         Spawn [DH] sessions for checked files
s             Enter Scope Mode

Scope Mode

Scope Mode lets you restrict which directories the Health Panel scans. By default, the panel scans the entire source tree. If your project has directories you want to exclude from health analysis (vendored code, generated files, third-party dependencies), Scope Mode lets you explicitly choose which directories to include.


Entering Scope Mode

Press s while the Health Panel is open. This is a panel-level keybinding that works from either the God Files or Documentation tab.


The Scope Interface

Scope Mode opens the File Tree in a special selection mode. The interface looks and works like the normal file tree, but with green visual treatment to distinguish it:

  • Green double-line border surrounds the file tree.
  • The title bar reads “Health Scope (N dirs)”, where N is the number of currently selected directories.
  • Green highlights mark directories that are included in the scan scope.

Directory Inheritance

When you include a directory, all of its subdirectories are automatically included. You do not need to select each subdirectory individually. Selecting src/ implicitly includes src/models/, src/views/, and everything else nested beneath it.


Keybindings

KeyAction
j / kNavigate the file tree
EnterToggle the highlighted directory in or out of scope
EscSave the current scope and return to the Health Panel

Persistence

When you press Esc to leave Scope Mode, two things happen:

  1. The selected scope is saved to azufig.toml in the project’s .azureal/ directory. The scope persists across Health Panel opens and AZUREAL restarts.
  2. The Health Panel immediately rescans using the updated scope, refreshing both the God Files and Documentation tabs with results filtered to the selected directories.

Quick Reference

s             Enter Scope Mode (from Health Panel)
Enter         Toggle directory in/out of scope
Esc           Save scope to azufig.toml and rescan
j/k           Navigate file tree

Completion Notifications

AZUREAL sends native desktop notifications when agent sessions complete. This lets you work in other applications while agents run and get notified the moment a response is ready – without polling the terminal.


What Triggers a Notification

A notification fires for every session exit, not just the currently focused session. If you have three agents running in parallel across different worktrees, you receive a notification when each one finishes.

The notification content varies based on the exit condition:

ConditionBody Text
Context compaction“Compacting context”
Normal completion“Response complete”
Error exit“Exited with error”
Process terminated“Process terminated”

The notification title follows the format worktree:session_name, making it easy to identify which agent finished from the notification banner alone.


Platform Details

macOS

  • Notifications sent via the notify-rust crate.
  • An .icns icon file is embedded in the binary at compile time via include_bytes!().
  • On first launch, AZUREAL creates a minimal .app bundle at ~/.azureal/AZUREAL.app containing the extracted icon. This bundle is what macOS associates with the notification, allowing the branded icon to appear.
  • Notification permissions are automatically enabled by writing to the ncprefs.plist file, avoiding the need for users to manually grant permissions through System Settings on first use.
  • The .app bundle is created once and reused on subsequent launches.
  • Notification sound: “Glass”.

Windows

  • Notifications are sent via PowerShell WinRT toast APIs. The notify-rust crate is not used on Windows because its custom .app_id() requires a registered AppUserModelID (AUMID), which silently drops toasts when unregistered.
  • PowerShell’s own pre-registered AUMID is used instead, ensuring reliable delivery.
  • CREATE_NO_WINDOW (0x08000000) prevents a console window from flashing when the PowerShell process spawns.
  • Toast XML uses appLogoOverride with ~/.azureal/AZUREAL_toast.png for a crisp branded icon (PNG renders clearly in toasts; .ico renders blurry).

Linux

  • Notifications sent via the notify-rust crate (same as macOS).

Implementation

Each notification is dispatched on a fire-and-forget background thread that never blocks the event loop. If the notification system is unavailable or the send fails, the failure is silently ignored – notifications are a convenience feature and must never interfere with the core TUI experience.


Quick Reference

DetailValue
TriggerEvery session exit (all sessions, not just focused)
Title formatworktree:session_name
macOSnotify-rust + .app bundle + Glass sound
WindowsPowerShell WinRT toast + PNG icon
Linuxnotify-rust
ThreadingFire-and-forget background thread
PlatformmacOS, Windows, and Linux

Debug Dump

Debug Dump exports a snapshot of AZUREAL’s internal state to a text file for troubleshooting. The output contains parsing statistics, event breakdowns, rendered output samples, and recent event history – everything needed to diagnose rendering or parsing issues. Sensitive data is automatically obfuscated before writing.


Creating a Dump

Press Ctrl+D to start a debug dump. A two-phase process follows:

  1. Naming dialog – A text input appears asking for a name. Type a short identifier (e.g., “broken-render”, “missing-tool-result”) and press Enter.
  2. Dump execution – The dump runs on the next frame after the dialog closes. This ensures the dialog itself does not appear in the captured state.

The output file is saved to:

.azureal/debug-output_{name}

For example, entering “broken-render” produces .azureal/debug-output_broken-render in the project root. Entering an empty name produces .azureal/debug-output.

The name is used directly in the filename, so short hyphenated identifiers are usually the easiest to search for and share.


Contents

The dump file includes the following sections:

SectionDescription
Parsing statsCounts of parsed events by type, error rates, timing data
Event breakdownSummary of event types seen during the session
Last 5 eventsThe five most recent events in full detail
Full rendered outputThe complete rendered session pane content as it appears on screen

This gives you both the raw data (events, stats) and the visual result (rendered output) in a single file, making it straightforward to identify where parsing diverges from rendering.


Obfuscation

Debug dumps are designed to be safe to share. All sensitive content is replaced using deterministic word substitution – each unique token in the output is mapped to a replacement word, and the same token always maps to the same replacement. This means:

  • File paths, variable names, and code content are replaced with neutral words.
  • The structure of the output is fully preserved – you can still see event boundaries, nesting, formatting, and layout.
  • Tool names, event types, parsing statistics, and structural metadata are preserved verbatim, since these are needed for diagnosis.

The deterministic mapping means that if the same variable name appears in multiple places in the dump, it will have the same replacement word everywhere, so patterns and relationships remain visible even in obfuscated output.


Quick Reference

Ctrl+D        Open debug dump naming dialog
Enter         Confirm name and write dump
Esc           Cancel
DetailValue
Output path.azureal/debug-output_{name} (no extension)
Execution timingNext frame after dialog close
ObfuscationDeterministic word replacement
Preserved verbatimTool names, event types, parsing stats, structure
ReplacedFile paths, code content, variable names, user text

Configuration

AZUREAL uses two TOML configuration files, both named azufig.toml. One lives in your home directory and holds global preferences that apply everywhere. The other lives inside each project and holds project-specific settings. Both use the same format conventions, and both are optional – AZUREAL runs with sensible defaults when neither file exists.


Two-File Model

FileLocationScope
Global~/.azureal/azufig.tomlAll projects, all worktrees
Project.azureal/azufig.toml (at main worktree root)One project, shared across all its worktrees

The global config stores your API key, the path to your Claude Code binary, your permission mode, your registered projects list, and any run commands or preset prompts you want available in every project. The project config stores project-local overrides: file tree hidden entries, health panel scan scope, git auto-rebase rules, and project-specific run commands and preset prompts.

There is no merging or inheritance between the two files. Global run commands and project run commands both appear in the run commands list, but they are loaded from their respective files independently.


Format Conventions

Both files follow the same TOML conventions:

  • Section headers use single-bracket [section] notation (e.g., [config], [git], [filetree]).
  • Key-value pairs use key = "value" format. Keys that qualify as TOML bare keys (alphanumeric, dashes, underscores) are written unquoted. Values are always quoted strings.
  • Numbered prefixes are used for ordered entries in [runcmds] and [presetprompts]: 1_Build = "cargo build", 2_Test = "cargo test". The numeric prefix determines display order; the part after the underscore becomes the display name.
  • Every section uses #[serde(default)] in the Rust deserialization, so missing sections are silently filled with empty defaults. You never need to include a section you have no keys for.

Write Pattern

AZUREAL never edits config files in place. The write cycle is always load-modify-save: read the entire file into a struct, update the relevant field, serialize the full struct back to disk. This avoids partial writes, preserves all sections, and keeps the file in a consistent state even if the process is interrupted.


Project Config Location

The project-level azufig.toml always lives at the main worktree root inside .azureal/. Because git worktrees share a common .git directory, the project config is effectively shared by all worktrees in the project. You do not create separate configs per worktree.

The .azureal/ directory is gitignored by default – AZUREAL automatically adds .azureal/ to .gitignore (alongside worktrees/) on first load. This prevents the session store, worktree-level configs, and other runtime files from causing rebase conflicts.


Chapter Contents

  • Global Config – The ~/.azureal/azufig.toml file: API key, Claude path, permission mode, registered projects, global run commands, and global preset prompts.
  • Project Config – The .azureal/azufig.toml file: file tree hidden entries, health scan scope, git auto-rebase and auto-resolve settings, project-local run commands, and project-local preset prompts.

Global Config

The global configuration file lives at ~/.azureal/azufig.toml. It holds settings that apply across all projects: your API key, the path to the Claude Code binary, your permission mode, the list of registered projects, and any global run commands or preset prompts.


Location

~/.azureal/
  azufig.toml    <-- global config
  AZUREAL.app/   <-- macOS app bundle (if present)
  speech/        <-- Whisper model files

The ~/.azureal/ directory is created automatically on first launch if it does not exist. The config file itself is created when you first register a project or save a setting through the UI.


Sections

[config]

Core application settings.

[config]
anthropic_api_key = "sk-ant-..."
claude_executable = "/usr/local/bin/claude"
default_permission_mode = "ignore"
KeyDescriptionDefault
anthropic_api_keyAnthropic API key. Used by Claude Code CLI for authentication.(none)
claude_executableAbsolute path to the Claude Code CLI binary. AZUREAL uses this to spawn agent processes.Auto-detected from $PATH
codex_executableAbsolute path to the Codex CLI binary.Auto-detected from $PATH
default_permission_modePermission mode for agent sessions. Valid values: "approve", "ignore", "ask"."ignore"
verboseEnable verbose logging.false

The default_permission_mode controls how Claude Code handles tool use permissions:

  • "ignore" – Claude Code skips permissions entirely (--dangerously-skip-permissions).
  • "approve" – Claude Code approves all tool use automatically.
  • "ask" – Claude Code asks for permission on each tool use (default Claude behavior).

[projects]

Registered projects. Each entry maps a display name to a filesystem path.

[projects]
AZUREAL = "~/AZUREAL"
Website = "~/projects/website"

Keys are display names shown in the Projects Panel. Values are paths to the project root (the directory containing .git). Tilde (~) is expanded at runtime. Paths must point to a valid git repository.

When you register a project through the Projects Panel in the UI, it writes an entry here. When you unregister a project, the entry is removed.

[runcmds]

Global run commands available in every project. Entries use numbered prefixes to preserve display order.

[runcmds]
1_Build = "cargo build --release"
2_Test = "cargo test"
3_Lint = "cargo clippy -- -W clippy::all"

The prefix format is N_Name, where N is a positive integer and Name is the display label shown in the run commands menu. The value is the shell command to execute.

Global run commands appear alongside project-local run commands in the run commands list. See Run Commands for how these are executed.

[presetprompts]

Global preset prompts available in every project. Same prefix format as run commands.

[presetprompts]
1_Review = "Review this file for bugs and suggest improvements."
2_Explain = "Explain what this code does, step by step."
3_Refactor = "Refactor this code for clarity without changing behavior."

The prefix format is N_Name, where N determines order and Name is the display label. The value is the prompt text that gets injected into the input field when selected.

Global preset prompts appear alongside project-local preset prompts. See Preset Prompts for how these are used in the UI.


Example File

A complete global config:

[config]
anthropic_api_key = "sk-ant-api03-xxxxxxxxxxxxxxxxxxxx"
claude_executable = "/opt/homebrew/bin/claude"
default_permission_mode = "ignore"

[projects]
AZUREAL = "~/AZUREAL"
Webapp = "~/projects/webapp"
Infra = "~/work/infrastructure"

[runcmds]
1_Build = "cargo build --release"
2_Test = "cargo test -- --nocapture"
3_Format = "cargo fmt --all"

[presetprompts]
1_CodeReview = "Review this code for correctness, performance, and style."
2_WriteTests = "Write comprehensive tests for this module."

Project Config

The project-level configuration file lives at .azureal/azufig.toml relative to the main worktree root. It holds settings scoped to a single project: file tree hidden entries, health panel scan directories, git automation rules, and project-specific run commands and preset prompts.


Location

<project-root>/
  .azureal/
    azufig.toml      <-- project config
    sessions.azs     <-- session store (SQLite)
  .git/
  src/
  ...

The .azureal/ directory is gitignored by default – AZUREAL automatically adds .azureal/ to .gitignore on first load to prevent the session store and runtime files from causing rebase conflicts.

Because git worktrees share a common working tree root, the project config file at the main worktree root is shared by all worktrees in the project. You do not maintain separate configs per worktree.


Sections

[filetree]

Controls which entries are hidden in the file tree pane.

[filetree]
hidden = ["worktrees", ".git", "target", "node_modules", ".DS_Store"]
KeyDescriptionDefault
hiddenArray of file/directory names to hide from the file tree.["worktrees", ".git", ".claude", ".azureal", ".DS_Store", "target", "node_modules"]

Entries are matched by exact name against the filename component (not the full path). If a directory is named target, adding "target" hides it at every level of the tree. This is a display-only filter – hidden entries still exist on disk and are accessible via the embedded terminal.

[healthscope]

Directories included in health panel scans. Also aliased as [godfilescope] for backward compatibility.

[healthscope]
dirs = ["src", "crates", "lib"]
KeyDescriptionDefault
dirsArray of directory paths to include when scanning for god files and documentation coverage.[] (scans entire project)

When this section is absent or empty, the health panel scans the entire project tree. When populated, only the listed directories are scanned. This is useful for large monorepos where you want to focus health checks on specific crates or modules. See Scope Mode for how to configure this interactively from the health panel UI.

[runcmds]

Project-local run commands. Same format as the global [runcmds] section.

[runcmds]
1_Dev = "cargo run -- --dev"
2_Migrate = "sqlx migrate run"
3_Seed = "cargo run --bin seed"

Project-local run commands appear in the run commands menu alongside global ones. They are only visible when this project is active.

[presetprompts]

Project-local preset prompts. Same format as the global [presetprompts] section.

[presetprompts]
1_FixLint = "Fix all clippy warnings in this file."
2_AddDocs = "Add documentation comments to all public items in this module."

Project-local preset prompts appear alongside global ones in the preset prompts menu and are only visible when this project is active.

[git]

Git automation settings. This section uses a flat key-value map. Auto-rebase and auto-resolve settings are stored per-worktree – each worktree has its own .azureal/azufig.toml with its own [git] section. They are not stored in the project-level config with per-branch keys.

Auto-Rebase

When enabled for a worktree, AZUREAL automatically rebases the worktree’s branch onto main every 2 seconds (when idle).

[git]
auto-rebase = "true"

The key auto-rebase is set to "true" to enable or removed to disable. Auto-rebase is deferred while an agent is actively streaming to avoid interrupting work in progress.

See Rebase & Auto-Rebase for how auto-rebase integrates with the git panel.

Auto-Resolve

Per-file auto-resolve settings for merge conflicts. When enabled for a file, AZUREAL automatically resolves conflicts in that file during rebase operations using a union merge strategy (keeping both sides’ changes).

[git]
"auto-resolve/AGENTS.md" = "true"
"auto-resolve/CHANGELOG.md" = "true"
"auto-resolve/README.md" = "true"
"auto-resolve/CLAUDE.md" = "true"

Keys follow the pattern auto-resolve/<filename>. Values are "true" to enable auto-resolve. The default list includes AGENTS.md, CHANGELOG.md, README.md, and CLAUDE.md.

See Auto-Resolve Settings for details.


Example File

A complete project config:

[filetree]
hidden = ["worktrees", ".git", "target", "node_modules", ".DS_Store", ".azureal"]

[healthscope]
dirs = ["src", "crates"]

[runcmds]
1_Dev = "cargo run"
2_Test = "cargo test"
3_Check = "cargo check --all-targets"

[presetprompts]
1_Modularize = "Break this file into smaller modules, one feature per file."
2_Optimize = "Profile and optimize the hot path in this function."

[git]
auto-rebase = "true"
"auto-resolve/AGENTS.md" = "true"
"auto-resolve/CHANGELOG.md" = "true"

Architecture & Internals

AZUREAL is a single-process, multi-threaded Rust application built on ratatui and crossterm. This section documents the internal architecture for contributors and users who want to understand how the application works under the hood.


Design Philosophy

Mostly-Stateless

Runtime state is derived from git, not stored in custom databases. Worktrees come from git worktree list. Branches come from git branch. The active backend is derived from the selected model. Close the application and reopen it – the UI reconstructs itself from the repository.

Persistent state is minimal by design:

  • Two azufig.toml files – global and project-level configuration in TOML format. See Configuration.
  • One SQLite database.azureal/sessions.azs holds all session history. See Session Store.
  • Temporary JSONL files – agent session output, parsed during streaming and ingested into SQLite on completion, then deleted.

Single-Process, Multi-Threaded

There is no daemon, no server, and no IPC between separate binaries. Everything runs in one process. Background work is offloaded to dedicated threads that communicate with the main event loop via mpsc channels:

ThreadPurpose
Input readerReads crossterm terminal events and forwards them to the main loop
AgentProcessorParses agent JSONL events in the background
Render threadPerforms markdown parsing, syntax highlighting, and text wrapping
FileWatcherMonitors filesystem changes via notify crate
Terminal rxReads PTY output from the embedded terminal (conditional)

Event-Driven

The main loop is a classic event loop: wait for an event, process it, optionally redraw the screen. Events come from multiple sources (keyboard, mouse, agent output, file changes, timers) and are all funneled through channels into a single-threaded dispatcher. This avoids locks on shared state and keeps the rendering path simple.


Key Abstractions

App

The central state struct. Holds all UI state, all worktree state, the session store handle, the agent process manager, and references to every background thread’s channel. The main loop owns the single App instance and passes mutable references to event handlers.

AgentProcess

Manages the lifecycle of agent CLI processes. Holds both a ClaudeProcess and a CodexProcess. At spawn time, the selected model determines which backend is invoked. The process streams JSON events on stdout, which are forwarded to the AgentProcessor thread for parsing. A separate reader thread also writes events to a temporary JSONL file for post-exit ingestion into the session store.

DisplayEvent

The unified event type that both backends produce after parsing. The session pane, session store, and render pipeline all consume DisplayEvent values. This abstraction decouples the UI from any specific backend’s wire format.

SessionStore

The SQLite persistence layer. Handles event ingestion, context retrieval, compaction, and completion tracking. See Session Store & Persistence for the full schema and lifecycle.


Thread Communication

All inter-thread communication uses std::sync::mpsc channels. There are no mutexes on hot paths. The main loop drains channels on each iteration, processes all pending messages, then decides whether to redraw.

Input Reader ──→ [mpsc] ──→ Main Loop
AgentProcessor ──→ [mpsc] ──→ Main Loop
FileWatcher ──→ [mpsc] ──→ Main Loop
Terminal rx ──→ [mpsc] ──→ Main Loop (conditional)
Main Loop ──→ [mpsc] ──→ Render Thread
Render Thread ──→ [mpsc] ──→ Main Loop (rendered output)

Chapter Contents

  • Event Loop – The main event loop: input handling, event batching, draw throttling, and adaptive timing.
  • Render Pipeline – Background rendering: markdown parsing, syntax highlighting, sequence numbers, viewport caching, and deferred initial renders.
  • Performance – Performance rules and invariants: what must never happen in the render path, caching strategies, and CPU budget targets.
  • File Watcher – Filesystem monitoring: notify backends, noise filtering, event coalescing, and the three-phase parse+render pipeline.

Event Loop

The main event loop is the heart of AZUREAL. It runs on the main thread, processes all events from every source, and decides when to redraw the screen. The design prioritizes input responsiveness above all else – keystrokes must feel instant even while agents are streaming thousands of events per second.


Input Reader Thread

Terminal input (keyboard, mouse, resize) is read on a dedicated background thread. This thread calls crossterm::event::read() in a blocking loop and forwards each event to the main loop via an mpsc channel. The dedicated thread exists so that the main loop never blocks on I/O – it can always drain pending events and proceed to rendering.


Event Sources

The main loop receives events from multiple channels:

SourceChannelContents
Input readerinput_rxKeyboard, mouse, and resize events
AgentProcessoragent_rxParsed DisplayEvent values from agent JSONL
FileWatcherwatcher_rxSessionFileChanged and WorktreeChanged events
Terminal rxterminal_rxPTY output bytes (only polled when terminal_mode is active)
Render threadrender_rxCompleted rendered output with sequence numbers

Event Batching

The loop does not process one event and then redraw. Instead, it drains all pending events from every channel before considering a redraw. This is critical for throughput: if an agent emits 50 events between frames, processing them one at a time would mean 50 separate redraws. Batching collapses them into one.

The drain order is: input events first (highest priority), then agent events, then file watcher events, then render completions. Input always wins.

Claude Event Cap

As a safety guard, the loop processes at most 10 agent events per tick. This prevents a burst of agent output from starving input handling. If more than 10 events are pending, the remaining events are processed on the next tick.


Motion Discard

Mouse motion events (MouseEventKind::Moved) are dropped immediately upon receipt. These events fire at high frequency (every pixel of mouse movement) and carry no useful information for AZUREAL’s UI. Discarding them at the earliest possible point prevents them from consuming processing time or triggering unnecessary redraws.


Conditional Terminal Polling

The embedded terminal’s PTY output channel (terminal_rx) is only polled when terminal_mode is active. When the terminal is hidden, its channel is ignored entirely. This avoids waking the main loop for terminal output that would not be displayed.


Cached Terminal Size

The terminal dimensions (columns and rows) are cached in the App struct and updated only on resize events. Every component that needs the terminal size reads the cached value rather than calling crossterm::terminal::size(), which involves a system call. The cache is invalidated and refreshed whenever a Resize event arrives.


Fast-Path Input (macOS)

On macOS, AZUREAL uses fast_draw_input() for text input rendering. This function writes the input field directly to the terminal via VT escape sequences, bypassing ratatui’s full terminal.draw() call entirely.

The performance difference is significant:

PathLatency
fast_draw_input()~0.1ms
terminal.draw()~18ms

This means keystrokes in the input field are echoed to the screen roughly 180 times faster. The fast path is used only for input field updates where the rest of the screen has not changed. Any event that requires a full layout recalculation falls back to the normal draw path.

This optimization is macOS-only. On Windows, direct VT writes conflict with the console input parser. On Linux, the standard draw path is fast enough that the optimization is not needed, though it may be enabled in the future.


Extended Typing Deferral

When the user is actively typing, AZUREAL suppresses terminal.draw() calls for 300ms after the last keystroke. During this window, only fast_draw_input() updates the input field. If no keystroke arrives within 300ms, the next tick triggers a full redraw to sync the rest of the UI.

This prevents the expensive full-draw path from running on every keystroke during rapid typing, while ensuring the screen stays current during pauses.


Force Full Redraw

Certain layout changes require a complete screen repaint:

  • Opening or closing the git panel (Shift+G)
  • Opening or closing overlay panels (health, projects, help)
  • Terminal mode toggle

These events set a force_full_redraw flag that bypasses all draw suppression and throttling, ensuring the layout transition renders immediately with a full terminal.clear() before the draw.


Pre-Draw Event Drain with Abort

Immediately before calling terminal.draw(), the loop performs one final drain of the input channel. If any keyboard event is found during this drain, the draw is aborted and the event is processed instead. This prevents the situation where a keystroke arrives just before a draw begins, forcing the user to wait 18ms for the draw to complete before their input is processed.


Adaptive Draw Throttle

The draw rate adapts to what is happening:

StateTarget FPSFrame Interval
User interaction (typing, scrolling, navigation)30 fps~33ms
Idle streaming (agent output, no user input)5 fps~200ms

During idle streaming, there is no reason to redraw at 30fps – the agent’s output arrives in bursts and the user is just watching. Dropping to 5fps saves significant CPU. The moment a keystroke or mouse event arrives, the throttle switches back to 30fps for immediate responsiveness.


Adaptive Poll Timeout

The crossterm::event::poll() timeout also adapts:

StatePoll Timeout
Busy (recent input, active streaming)16ms
Idle (no input, no streaming)100ms

A shorter poll timeout means faster event pickup at the cost of more CPU usage. The 100ms idle timeout lets the CPU sleep longer when nothing is happening.


Background Refreshes

File tree discovery and worktree list refresh run in the background and do not block the event loop. When a WorktreeChanged event arrives from the file watcher, the refresh is scheduled but does not freeze the UI while scanning the filesystem.


Streaming Deferrals

Two operations are deferred while an agent is actively streaming:

  • Auto-rebase – Rebasing while an agent is writing files would corrupt the agent’s working state. Auto-rebase waits until the stream completes.
  • Health panel refresh – Scanning the filesystem during active streaming would produce inaccurate results and waste I/O bandwidth. The refresh is queued and runs after the stream ends.

Render Pipeline

The render pipeline transforms raw agent events into the styled, wrapped, syntax-highlighted text that appears in the session pane. Because this work is computationally expensive – markdown parsing, syntax highlighting, and Unicode text wrapping all have nontrivial cost – it runs on a dedicated background thread, decoupled from the main event loop.


Background Render Thread

A dedicated render thread receives raw content from the main loop, performs all parsing and styling, and sends the rendered output back. This keeps the main loop’s draw path fast: it simply blits pre-rendered lines to the terminal rather than parsing markdown or highlighting code inline.

Main Loop ──→ [render request + seq] ──→ Render Thread
                                              │
                                         parse markdown
                                         highlight code
                                         wrap text
                                              │
Render Thread ──→ [rendered output + seq] ──→ Main Loop

Sequence Numbers

Every render request carries a monotonically increasing sequence number. When the rendered output arrives back on the main loop, the sequence number is checked against the latest submitted request. If the returned sequence is stale (a newer request was submitted while this one was processing), the result is discarded.

This is the “latest-wins” policy. It handles the common case where the user scrolls rapidly or an agent emits multiple events in quick succession – only the most recent render matters, and older results are dropped without being displayed.


Incremental Renders

When new agent events arrive during an active session, the render pipeline does not re-render the entire conversation. It renders only the newly appended events, using a zero-clone approach that avoids copying previously rendered content.

The rendered output for older events is retained in a cache. New events are rendered, and their output is appended to the cache. This means the cost of rendering is proportional to the number of new events, not the total conversation length.


Render Submit Throttle

The main loop does not submit a render request on every single agent event. A minimum interval of 50ms is enforced between render submissions. Events that arrive within this window are batched and submitted together on the next tick.

This prevents the render thread from being overwhelmed during high-throughput agent output (e.g., a large file write that produces hundreds of events in milliseconds).


Viewport Cache

The session pane displays a scrollable view of the rendered conversation. Rather than re-slicing the full rendered output on every frame, the viewport is cached as a pre-computed slice of lines.

The viewport cache is invalidated and rebuilt only when:

  • The user scrolls (scroll position changes).
  • New content is appended (total line count changes).
  • An animation tick fires (e.g., a streaming cursor blink).

If none of these conditions are met, the cached viewport slice is reused directly, and the draw call simply blits it to the terminal buffer with no additional processing.


Deferred Initial Render

When loading a session with a long history (200+ events), rendering all events upfront would cause a visible delay before the session pane becomes interactive. AZUREAL handles this by deferring the initial render:

  1. On session load, only the last 200 events are rendered immediately.
  2. The session pane becomes interactive as soon as these are ready.
  3. If the user scrolls to the top of the conversation, the remaining older events are rendered on demand.

This means opening a session with 2000 events feels as fast as opening one with 200 – the user sees the most recent content immediately and only pays the rendering cost for older content if they scroll back to view it.


What Gets Rendered

The render thread handles three categories of work:

Markdown Parsing

Agent responses contain markdown (headings, lists, code blocks, bold/italic text, links). The render thread parses this into styled spans that ratatui can display with correct formatting.

Syntax Highlighting

Code blocks in agent responses are syntax-highlighted using the appropriate language grammar. The SyntaxHighlighter instance is created once and reused across renders – it is never created in the render path, because initialization takes 100ms+ (see Performance).

Text Wrapping

All rendered text is pre-wrapped to the current terminal width. This wrapping is performed once during rendering, not during drawing. The draw path receives already-wrapped lines and writes them directly to the terminal buffer.

If the terminal is resized, the wrapping width changes and a full re-render is triggered to rewrap all content at the new width.

Performance

AZUREAL is designed to feel instant. The performance budget is tight: CPU must stay below 5% during scrolling, input must be reflected in under 1ms on macOS, and the application must remain responsive even while agents stream thousands of events per second. This page documents the performance rules and invariants that make this possible.


The Core Rule

Never create expensive objects in the render path.

The render path is everything between “an event arrives” and “pixels appear on screen.” Any operation in this path directly impacts perceived responsiveness. The single most important rule is that expensive initialization – syntax highlighter creation (100ms+), file I/O, network calls, process spawning – must happen outside the render path, either at startup or on a background thread.


Performance Invariants

These are not guidelines. They are invariants that the codebase enforces:

1. Cache Rendered Output

The rendered_lines_cache stores the fully styled, wrapped output of the session pane. Drawing reads from this cache. Only new events trigger render work; previously rendered content is never re-rendered unless the terminal width changes.

2. Decouple Animation from Content Cache

Animations (streaming cursor blink, progress indicators) are patched into the viewport at draw time without invalidating the content cache. The cursor blink does not trigger a re-render of the entire conversation – it patches a single cell in the viewport slice.

3. Skip Redraw When Nothing Changed

Scroll operations return a boolean indicating whether the scroll position actually changed. If the user is already at the bottom and presses Down, scroll returns false and no redraw occurs. This prevents wasted frames on no-op inputs.

4. Pre-Format Expensive Data at Load Time

Data that is expensive to format (file tree entries, session list items, status bar components) is formatted once when loaded or changed, not on every draw call. The draw path reads pre-formatted strings.

5. Never Use .wrap() on Pre-Wrapped Content

Text wrapping is performed once during rendering. The ratatui Paragraph widget is given pre-wrapped lines and is not configured with .wrap(), which would perform a redundant wrapping pass on already-wrapped content.

6. Cache Edit Mode Highlighting Per Version

When the user edits a file in edit mode, syntax highlighting is cached per edit version (a monotonically increasing counter). Highlighting is recomputed only when the content changes, not on every cursor movement or draw cycle.

7. File I/O is Safe on the Render Thread

The render thread may read files from disk (e.g., for syntax detection or grammar loading), but file I/O is never performed on the draw path. The distinction matters: the render thread runs in the background and does not block frame output. The draw path runs on the main thread and must complete within the frame budget.


CPU Budget

ScenarioTarget CPUNotes
Idle (no input, no streaming)<1%100ms poll timeout, no draw calls
Scrolling<5%Viewport cache hit, no re-render
Active typing<3%fast_draw_input() on macOS, deferred full draw
Agent streaming<8%5fps draw throttle, incremental render
Agent streaming + user scrolling<12%30fps draw, viewport cache rebuild

These targets assume a modern machine (2020+ CPU). The primary lever for CPU reduction is draw throttling – the adaptive 5fps/30fps system described in Event Loop is the single largest contributor to low idle CPU usage.


What To Watch For

Common performance mistakes and how AZUREAL avoids them:

SyntaxHighlighter::new() in a Loop

Creating a syntax highlighter loads grammar definitions and compiles them. This takes 100ms+ and must happen once, at startup. The highlighter is stored as a long-lived value and reused across all renders.

Allocating in the Draw Path

The draw path should allocate as little as possible. Pre-rendered lines are stored as Vec<Line> and sliced by reference for the viewport. No new String or Vec allocations occur per frame for content that has not changed.

Unnecessary Full Redraws

A full terminal.draw() call costs ~18ms. The fast-path input optimization (~0.1ms) and the skip-redraw-on-no-change logic exist specifically to avoid paying this cost when it is not needed. Every code path that might trigger a redraw must justify why a full draw is necessary rather than a partial update or no update at all.

Blocking the Main Loop

The main loop must never block. All I/O (file reads, process spawning, git commands) runs on background threads or is dispatched asynchronously. A blocked main loop means frozen input handling, which is the most user-visible performance failure.

File Watcher

AZUREAL monitors the filesystem for changes using the notify crate. A dedicated FileWatcher thread watches for modifications to agent session files and project source files, forwarding classified events to the main loop via an mpsc channel. This drives the three-phase incremental parse+render pipeline that keeps the session pane up-to-date during live agent streaming.


Backend

The notify crate selects a platform-native backend automatically:

PlatformBackendMechanism
macOSkqueueKernel event queue
LinuxinotifyFilesystem notification API
WindowsReadDirectoryChangesWWin32 directory change notifications

All three backends are event-driven and impose negligible CPU overhead when idle.


Watch Targets

The file watcher monitors two categories of paths:

Session JSONL Files

Watched non-recursively. When an agent is running, it writes events to a temporary JSONL file. The watcher detects writes to this file and emits SessionFileChanged events, triggering incremental parsing and rendering.

Worktree Directory

Watched recursively. The current worktree’s directory tree is monitored for any file creation, modification, or deletion. Changes emit WorktreeChanged events, which trigger a background file tree refresh and may update the file viewer if the changed file is currently displayed.


Noise Filtering

Not all filesystem events are meaningful. The watcher discards events from paths that match known noise patterns before forwarding them to the main loop:

PatternReason
/target/Rust build artifacts, extremely high churn during compilation
/.git/Git internal files, updated on every commit/checkout
/node_modules/JavaScript dependencies, irrelevant to project source
.DS_StoremacOS Finder metadata
.swp, .swo, ~Vim/editor swap and backup files

Filtering happens at the watcher thread level, before events reach the main loop’s channel. This keeps the channel clean and avoids waking the main loop for events that would be immediately discarded.


Event Coalescing

Filesystem events often arrive in bursts. A single cargo build can produce hundreds of file change events in /target/ within milliseconds. Even after noise filtering, legitimate events can arrive in rapid succession (e.g., an agent writing multiple files in sequence).

The watcher coalesces events within a 200ms window:

  • At most one SessionFileChanged event per 200ms window.
  • At most one WorktreeChanged event per 200ms window.

If multiple raw events arrive within the window, they are collapsed into a single coalesced event. This prevents the main loop from processing redundant file tree refreshes or re-parsing the same JSONL file multiple times in rapid succession.


Graceful Fallback

If the notify backend fails to initialize (e.g., the system has exhausted its inotify watch limit on Linux, or kqueue file descriptors are unavailable), the watcher falls back to stat()-based polling with a 500ms interval.

In polling mode, the watcher periodically checks the modification time and size of watched files. This is less efficient than event-driven watching but provides the same functionality. A log message is emitted when fallback mode activates, so the user can diagnose and fix the underlying issue (typically by increasing /proc/sys/fs/inotify/max_user_watches on Linux).


Three-Phase Parse+Render Pipeline

The file watcher is the entry point for the incremental session update pipeline. When a SessionFileChanged event arrives, three phases execute in sequence:

Phase 1: Change Detection

The file watcher detects that the agent’s JSONL output file has been modified. On event-driven backends, this is immediate. On the stat() fallback, detection latency is up to 500ms.

Phase 2: Incremental Parse

The refresh_session_events() function seeks to its last known file offset (session_file_parse_offset) and reads only the newly appended lines from the JSONL file. Each line is parsed as a JSON event and converted to a DisplayEvent. Previously processed content is not re-read or re-parsed.

This is the key to performance during long agent sessions. A session with 10,000 events that appends one new event pays the cost of parsing one line, not 10,000.

Phase 3: Incremental Render

The newly parsed DisplayEvent values are sent to the render thread, which renders only the new content. The rendered output is appended to the existing cache. The viewport is updated to reflect the new content (auto-scrolling to the bottom if the user was already at the bottom).

This three-phase pipeline means that the cost of processing a new agent event is constant regardless of session length. A session with 100 events and a session with 100,000 events both process new events in the same amount of time.

Platform Support

AZUREAL runs on macOS, Linux, and Windows. The core feature set is identical across all three platforms – agent sessions, worktrees, the session pane, the file viewer, the git panel, and the embedded terminal all work everywhere. The differences are in input handling, GPU availability, terminal protocol support, and platform-specific integrations.


PlatformRecommendedAlso tested
macOSKittyGhostty, Alacritty, WezTerm, Terminal.app
LinuxKittyGhostty, Alacritty, WezTerm, Konsole
WindowsWindows Terminal

Kitty and Windows Terminal deliver the best overall experience – both for input handling and for rendering fidelity. They produce the cleanest interpretation of AZUREAL’s box-drawing characters, Unicode glyphs, and styled borders, resulting in pixel-perfect pane separators, tab bars, and dialog frames. Kitty also provides the Kitty keyboard protocol on macOS/Linux for unambiguous key reporting, while Windows Terminal provides full ConPTY support with reliable mouse and key event handling. Other listed terminals work well – AZUREAL automatically provides Alt+ fallback bindings for key combinations that require the Kitty protocol, though some terminals may show minor visual artifacts in complex border intersections or half-block character rendering.


Platform Matrix

FeaturemacOSLinuxWindows
Agent sessions (Claude + Codex)YesYesYes
Git worktreesYesYesYes
Session store (SQLite)YesYesYes
File watcher backendkqueueinotifyReadDirectoryChangesW
Embedded terminalPTYPTYConPTY
Speech-to-text (Whisper)Metal GPUCPU onlyCUDA GPU
Kitty keyboard protocolYesYesNo
fast_draw_input()YesNoNo
.app bundleYesN/AN/A
NotificationsNSUserNotificationD-Bus (notify-rust)Toast (PowerShell)
Modifier key for destructive actionsCmdCtrlAlt

Key Bindings by Platform

AZUREAL adapts its modifier key usage to each platform’s conventions:

ActionmacOSLinuxWindows
CopyCmd+CCtrl+CCtrl+C
SaveCmd+SCtrl+SCtrl+S
UndoCmd+ZCtrl+ZCtrl+Z
Cancel agentAlt+C
Archive worktreeAlt+A

On macOS, Cmd is the primary modifier. On Linux, Ctrl fills the same role. On Windows, destructive actions use Alt to avoid conflicts with the console input system. See Platform Differences for the full keybinding mapping.


Build Dependencies

All platforms require the Rust toolchain, LLVM/Clang, and CMake for the Whisper speech-to-text dependency. Platform-specific build requirements:

PlatformAdditional Requirements
macOSXcode Command Line Tools
Linuxlibclang-dev, cmake
WindowsLLVM.LLVM, CMake, Ninja, LIBCLANG_PATH environment variable, NVIDIA CUDA Toolkit

See Requirements for installation instructions.


Terminal Protocol Support

AZUREAL uses the Kitty keyboard protocol on macOS and Linux for improved key event accuracy. This protocol distinguishes between key press and key release events and provides unambiguous reporting of modifier combinations.

On Windows, the Kitty protocol is not enabled because it conflicts with mouse event handling in Windows Terminal. Windows uses the standard console input API instead, which provides adequate key reporting for all supported features.


Chapter Contents

  • macOS – Primary platform: Metal GPU, .app bundle, notifications, Cmd key bindings, fast-path input, and Option+letter remapping.
  • Linux – Full support: CPU-only Whisper, Ctrl key bindings, build dependencies, and Kitty protocol.
  • Windows – ConPTY terminal, PowerShell shell detection, Alt key bindings, MSVC Whisper fixes, path canonicalization, and NTFS junctions.

macOS

macOS is AZUREAL’s primary development platform. It receives the most testing, has the most platform-specific optimizations, and is the only platform with GPU- accelerated speech-to-text and native notification support.


Metal GPU for Whisper

On macOS, the Whisper speech-to-text engine runs on the Metal GPU. This provides significantly faster transcription compared to CPU inference on Linux and Windows. The Metal backend is selected automatically when available – no configuration is required.

GPU inference means lower latency for speech-to-text: dictating a prompt and getting the transcription back feels near-instant on Apple Silicon machines.


.app Bundle

AZUREAL creates a .app bundle at ~/.azureal/AZUREAL.app. This bundle is auto-created on first launch and serves two purposes:

Activity Monitor Integration

macOS identifies processes by their bundle. Without a .app bundle, AZUREAL appears in Activity Monitor under whatever terminal emulator launched it (e.g., “Terminal” or “iTerm2”). With the bundle, it appears as AZUREAL with its own branded icon, making it easy to identify in process lists and the Dock.

Process Identity via proc_pidpath()

macOS uses proc_pidpath() to resolve a process’s executable path for display purposes. AZUREAL re-execs itself through the .app bundle so that proc_pidpath() returns the bundle path rather than a bare binary path. This ensures consistent process identification across Activity Monitor, lsof, and other system tools.

The bundle contains a branded icon and a minimal Info.plist. It is regenerated if missing or outdated.


Notifications

Completion notifications are delivered via NSUserNotification. When an agent session completes while AZUREAL is not the focused application, a system notification appears with the session name and completion status.

Permission is requested automatically on first use. If the user has disabled notifications for AZUREAL in System Preferences, the notification call silently fails with no error.


Cmd Key Bindings

macOS uses Cmd as the primary modifier, matching platform conventions:

KeybindingAction
Cmd+CCopy selected text to clipboard
Cmd+SSave current file (edit mode)
Cmd+ZUndo last edit (edit mode)

These bindings feel native to macOS users and do not conflict with terminal control sequences (Ctrl+C sends SIGINT, which is a different action).


Option+Letter Unicode Remapping

On macOS, pressing Option+letter produces Unicode characters (e.g., Option+A produces “a” with a diacritical mark). AZUREAL remaps these back to their ASCII equivalents when processing input, so Option+letter keybindings work as expected rather than inserting Unicode characters into the input field.


fast_draw_input()

The fast-path input rendering optimization is macOS-only. When the user types in the input field and no other part of the screen needs updating, fast_draw_input() writes the input field directly to the terminal via VT escape sequences, bypassing ratatui’s full terminal.draw() call.

PathLatency
fast_draw_input()~0.1ms
terminal.draw()~18ms

This gives macOS users the most responsive typing experience. The optimization relies on direct VT writes that work reliably with macOS terminal emulators (Terminal.app, iTerm2, Kitty, Alacritty, WezTerm) but conflict with the Windows console input parser, which is why it is not available on Windows.


Kitty Keyboard Protocol

AZUREAL enables the Kitty keyboard protocol on macOS for improved key event reporting. This protocol provides:

  • Unambiguous key identification (distinguishing Tab from Ctrl+I, Enter from Ctrl+M, etc.).
  • Separate key press and release events.
  • Accurate modifier reporting.

The recommended terminal on macOS is Kitty, which has full protocol support. AZUREAL has also been tested in Ghostty, Alacritty, WezTerm, and Terminal.app. Terminal.app does not support the Kitty protocol, but AZUREAL falls back gracefully to standard key reporting with Alt+ alternatives for affected bindings.


Build Notes

macOS builds require Xcode Command Line Tools for Clang headers and CMake for the Whisper build:

xcode-select --install
brew install cmake

On Apple Silicon (M1/M2/M3/M4), builds produce ARM64 binaries natively. No Rosetta translation is needed.

Linux

Linux is a fully supported platform. Every feature in AZUREAL works identically to macOS, with two exceptions: Whisper runs on the CPU rather than a GPU, and fast_draw_input() is not enabled (the standard draw path is used for all rendering). There are no missing features or degraded capabilities.


CPU-Only Whisper

The Whisper speech-to-text engine runs on the CPU on Linux. There is no Metal equivalent, and CUDA/Vulkan GPU backends are not currently integrated. Transcription is slower than on macOS with Metal but remains usable for typical prompt dictation.

For best results, use the ggml-small.en.bin model. Larger models (medium, large) produce better transcriptions but increase latency on CPU inference.


Build Dependencies

Linux builds require libclang-dev and cmake:

Debian/Ubuntu:

sudo apt install libclang-dev cmake

Fedora/RHEL:

sudo dnf install clang-devel cmake

Arch Linux:

sudo pacman -S clang cmake

These are needed for the whisper-rs crate, which compiles whisper.cpp from source during the Rust build.


Ctrl Key Bindings

Linux uses Ctrl as the primary modifier, following standard terminal conventions:

KeybindingAction
Ctrl+CCopy selected text to clipboard
Ctrl+SSave current file (edit mode)
Ctrl+ZUndo last edit (edit mode)

These are the same bindings as macOS with Cmd replaced by Ctrl. The full keybinding set is otherwise identical across both platforms.


Kitty Keyboard Protocol

AZUREAL enables the Kitty keyboard protocol on Linux, just as on macOS. The recommended terminal on Linux is Kitty, which has full protocol support. AZUREAL has also been tested in Ghostty, Alacritty, WezTerm, and Konsole. Other terminals with protocol support include Foot.

Terminal emulators that do not support the protocol (e.g., GNOME Terminal, older xterm) fall back to standard key reporting with no loss of functionality. The only difference is that certain ambiguous key combinations (like Tab vs Ctrl+I) may not be distinguishable without the protocol – AZUREAL provides Alt+ fallback bindings for these cases.


File Watcher

The file watcher uses inotify on Linux. This is efficient and event-driven but subject to a system-wide watch limit. If AZUREAL logs a message about watch initialization failure and falls back to polling, increase the inotify limit:

# Check current limit
cat /proc/sys/fs/inotify/max_user_watches

# Increase temporarily
sudo sysctl fs.inotify.max_user_watches=524288

# Increase permanently
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

The default limit on many distributions (8192) is insufficient for large projects with deep directory trees. The recommended value of 524288 is used by VS Code, JetBrains IDEs, and other file-watching applications.


Embedded Terminal

The embedded terminal uses standard PTY (pseudo-terminal) on Linux, the same as macOS. Shell detection follows $SHELL and defaults to /bin/bash if unset. All terminal features – color support, click-to-position, mouse drag selection, auto-scroll, and mouse wheel scrolling – work identically to macOS.


Notifications

System notifications are delivered via the notify-rust crate, which uses the system’s D-Bus notification service (e.g., notify-send). When an agent session completes while AZUREAL is not the focused application, a desktop notification appears with the session name and completion status. Most Linux desktop environments and notification daemons (GNOME, KDE, dunst, mako) support this out of the box.


Distribution Notes

AZUREAL is tested on Ubuntu 22.04+ and Fedora 38+. It should work on any modern Linux distribution with glibc 2.31+ and a terminal emulator that supports 256 colors. Musl-based distributions (Alpine) are untested.

Windows

Windows is a supported platform with full feature parity, though several subsystems require platform-specific implementations and workarounds due to differences in the terminal model, filesystem, and input handling.


ConPTY Terminal Backend

The embedded terminal uses ConPTY (Windows Console Pseudo Terminal) via the portable-pty crate. ConPTY is the modern Windows equivalent of Unix PTY, available on Windows 10 1809+ and all Windows 11 versions.

Shell Detection

AZUREAL detects the available shell in preference order:

  1. pwsh.exe – PowerShell 7+ (cross-platform PowerShell)
  2. powershell.exe – Windows PowerShell 5.1 (ships with Windows)
  3. cmd.exe – Command Prompt (fallback)

The first available shell is used. PowerShell 7+ is preferred because it supports modern terminal features and ANSI escape sequences more reliably than the legacy Windows PowerShell.

Enter Key Behavior

PowerShell expects carriage return (\r) rather than line feed (\n) for line submission. AZUREAL sends \r when the Enter key is pressed in the embedded terminal on Windows. This is transparent to the user but differs from the Unix behavior where Enter sends \n.

Terminal Title Reassertion

Claude Code CLI calls SetConsoleTitle() during execution, which overwrites AZUREAL’s terminal title. AZUREAL reasserts its own title (AZUREAL @ <project> : <branch>) after every draw frame, ensuring the title stays correct both during and after agent execution.


CUDA GPU Whisper with MSVC Fixes

Whisper runs on the CUDA GPU on Windows, providing GPU-accelerated transcription (requires an NVIDIA GPU and the CUDA Toolkit). The whisper-rs crate requires additional patches to compile with the MSVC toolchain:

  • Layout tests disabled – MSVC produces different struct layouts than GCC/ Clang, causing layout assertion failures in the vendored whisper.cpp bindings. These tests are disabled on Windows.
  • Enum type fixes – Certain C enum types require explicit size annotations to match MSVC’s default enum representation.

These fixes are applied in AZUREAL’s vendored copy of whisper-rs and do not affect runtime behavior.

Build Dependencies

winget install LLVM.LLVM Kitware.CMake Ninja-build.Ninja Nvidia.CUDA

After installing LLVM, set the LIBCLANG_PATH environment variable:

[Environment]::SetEnvironmentVariable("LIBCLANG_PATH", "C:\Program Files\LLVM\bin", "User")

Set CMAKE_GENERATOR=Ninja in your environment so CMake uses Ninja instead of the Visual Studio generator (required for correct CUDA include path propagation).

Restart your terminal after setting these variables.


Alt Key Bindings

Windows uses Alt as the modifier for destructive or significant actions, rather than Cmd (macOS) or Ctrl (Linux). This avoids conflicts with the Windows console input system, where Ctrl+C is intercepted by the console host before it reaches the application.

KeybindingAction
Alt+CCancel running agent
Alt+AArchive current worktree

Standard Ctrl bindings that do not conflict with the console (Ctrl+S, Ctrl+Z) work normally.


No Kitty Keyboard Protocol

The Kitty keyboard protocol is not enabled on Windows. Windows Terminal’s implementation of the protocol conflicts with mouse event reporting, causing mouse clicks and scrolling to be misinterpreted. AZUREAL uses the standard Win32 console input API instead, which provides adequate key and mouse event reporting for all features.

This means that certain ambiguous key combinations (Tab vs Ctrl+I, Enter vs Ctrl+M) cannot be distinguished on Windows. AZUREAL’s keybinding system accounts for this by avoiding these ambiguous bindings.


No fast_draw_input()

The fast_draw_input() optimization (direct VT escape sequence writes to bypass ratatui’s draw call) is not available on Windows. Direct VT writes conflict with the Windows console input parser, which processes VT sequences differently than Unix terminal emulators.

All rendering on Windows goes through the standard terminal.draw() path. The ~18ms draw latency is acceptable on Windows because there is no competing fast-path expectation.


Path Canonicalization

Windows paths returned by std::fs::canonicalize() include the \\?\ extended- length prefix (e.g., \\?\C:\Users\name\project). This prefix is valid but causes issues with tools that do not expect it.

AZUREAL uses the dunce crate to strip the \\?\ prefix from canonicalized paths, producing standard C:\Users\name\project paths throughout the application. This ensures compatibility with git, Claude Code CLI, and any other external tools invoked by AZUREAL.


NTFS Junctions for Session Linking

On Unix systems, AZUREAL uses symbolic links for session file references between worktrees. Windows NTFS does not support unprivileged symlink creation (it requires the SeCreateSymbolicLinkPrivilege unless Developer Mode is enabled).

AZUREAL uses NTFS junctions instead, which do not require elevated privileges. Junctions function identically to directory symlinks for AZUREAL’s purposes – the file watcher and session parser follow them transparently.


File Watcher

The file watcher uses ReadDirectoryChangesW on Windows, the native Win32 API for monitoring directory changes. This is event-driven and efficient, similar to inotify on Linux and kqueue on macOS.

Unlike inotify, ReadDirectoryChangesW does not have a configurable watch limit – it is bounded by available memory. The graceful fallback to stat()-based polling is still implemented but rarely needed on Windows.


The recommended terminal on Windows is Windows Terminal, which ships with Windows 11 and is available on Windows 10 via the Microsoft Store. It provides full ConPTY support, true-color output, and reliable mouse event reporting.

The legacy conhost.exe (the default console host prior to Windows Terminal) has limited ANSI support and is not recommended.

CLI Reference

AZUREAL is invoked from the command line as azureal. With no arguments, it launches the TUI. Subcommands provide headless operations for session and project management.


Usage

azureal [OPTIONS] [SUBCOMMAND]

Running azureal with no arguments launches the full TUI interface inside the current terminal. AZUREAL expects to be run from within a git repository (or with a registered project – see Projects Panel).

Global Options

FlagDescriptionDefault
-o, --output <format>Output format: table, json, or plaintable
-v, --verboseEnable verbose outputoff
--config <path>Path to config file(auto-detected)

These flags can be placed before or after any subcommand.


Self-Installation

The AZUREAL binary is self-installing. On first run, it detects whether it has been installed to a standard location on your PATH. If not, it copies itself to the appropriate system or user-local bin directory:

PlatformInstall Path
macOS/usr/local/bin/azureal (falls back to ~/.local/bin/azureal)
Linux/usr/local/bin/azureal (falls back to ~/.local/bin/azureal)
Windows%USERPROFILE%\.azureal\bin\azureal.exe

After the self-install completes, azureal is available globally. Subsequent runs skip the install step. See Installation for the full details.


Subcommands

azureal session

Session management subcommands for worktree-based sessions.

azureal session archive <name>

Archive a worktree session. This removes the worktree’s working directory from disk while preserving the git branch. The worktree appears dimmed in the tab row and can be restored later.

azureal session archive my-feature

This is equivalent to pressing Wa or a (with the worktree focused) inside the TUI. The branch azureal/my-feature remains in the local repository and can be pushed to a remote as usual.

azureal session unarchive <name>

Unarchive a previously archived session. This recreates the worktree directory from the preserved branch, restoring it to an active state.

azureal session unarchive my-feature

After unarchiving, the worktree reappears as a normal tab in the TUI with its full session history intact (stored in the SQLite session store, not in the worktree directory).

azureal session list

List all sessions. Alias: azureal session ls.

azureal session list
azureal session list --project /path/to/project --all
FlagDescription
-p, --projectFilter by project path
-a, --allShow archived sessions too

azureal session new

Create a new session.

azureal session new -p "Fix the login bug"
azureal session new -p "Add tests" --name my-session --project /path
FlagDescription
-p, --promptInitial prompt for the agent (required)
-d, --projectProject path (defaults to current directory)
-n, --nameCustom session name

azureal session status <name>

Show the status of a session.

azureal session stop <name>

Stop a running session.

FlagDescription
-f, --forceForce stop (SIGKILL instead of SIGTERM)

azureal session delete <name>

Delete a session and its worktree.

FlagDescription
-y, --yesSkip confirmation prompt

azureal session resume <name>

Resume a stopped or waiting session.

FlagDescription
-p, --promptAdditional prompt to send

azureal session logs <name>

Show session logs/output.

FlagDescription
-f, --followFollow output in real-time
-l, --linesNumber of lines to show (default: 50)

azureal session diff <name>

Show diff for a session’s worktree.

FlagDescription
--statShow stat only (files changed summary)

azureal session cleanup

Clean up worktrees from completed/failed/archived sessions.

FlagDescription
-d, --projectProject path (defaults to current directory)
--delete-branchesAlso delete the associated git branches
-y, --yesPerform cleanup without confirmation
--dry-runOnly show what would be cleaned up

azureal project

Project management subcommands.

azureal project list

List all registered projects. Alias: azureal project ls.

azureal project show [project]

Show details for a project. Defaults to the current directory if no project is specified.

azureal project remove <project>

Remove a project from tracking (does not delete the repository).

FlagDescription
-y, --yesSkip confirmation prompt

azureal project config

Show or update project configuration.

FlagDescription
-p, --projectProject path (defaults to current directory)
--main-branchSet the main branch name

Shortcut Commands

Several session operations have top-level shortcuts:

ShortcutEquivalent
azureal list (or azureal ls)azureal session list
azureal new -p "prompt"azureal session new -p "prompt"
azureal status <name>azureal session status <name>
azureal diff <name>azureal session diff <name>

Logging

AZUREAL uses tracing-subscriber with env-filter for structured logging. Log output is controlled through the standard RUST_LOG environment variable.

Setting the Log Level

# Errors only (default when RUST_LOG is unset)
RUST_LOG=error azureal

# Warnings and errors
RUST_LOG=warn azureal

# Info-level logging
RUST_LOG=info azureal

# Debug logging (verbose)
RUST_LOG=debug azureal

# Trace logging (extremely verbose)
RUST_LOG=trace azureal

Module-Level Filtering

env-filter supports per-module log levels, which is useful for isolating specific subsystems without drowning in output from everything else:

# Debug logging for the session store, info for everything else
RUST_LOG=info,azureal::session_store=debug azureal

# Trace the event loop only
RUST_LOG=warn,azureal::event_loop=trace azureal

Logs are written to stderr, so they do not interfere with TUI rendering. When diagnosing issues, redirect stderr to a file:

RUST_LOG=debug azureal 2> azureal.log

Debug Dump

The Ctrl+D keybinding inside the TUI triggers a debug dump – a snapshot of internal state written to a named text file in .azureal/. This is covered in detail at Debug Dump. The dump is useful for filing bug reports or inspecting runtime state without attaching a debugger.


Version

azureal --version

Prints the version number and exits.


Summary

CommandDescription
azurealLaunch the TUI
azureal tuiLaunch the TUI (explicit subcommand)
azureal --versionPrint version and exit
azureal session listList all sessions
azureal session new -p "prompt"Create a new session
azureal session status <name>Show session status
azureal session stop <name>Stop a running session
azureal session delete <name>Delete a session and its worktree
azureal session archive <name>Archive a worktree (remove directory, keep branch)
azureal session unarchive <name>Unarchive a worktree (recreate directory from branch)
azureal session resume <name>Resume a stopped session
azureal session logs <name>Show session logs
azureal session diff <name>Show worktree diff
azureal session cleanupClean up completed/failed worktrees
azureal project listList registered projects
azureal project show [project]Show project details
azureal project remove <project>Remove project from tracking
azureal project configShow/update project configuration
Environment VariableDescription
RUST_LOGControls log verbosity via tracing-subscriber env-filter

Complete Keybinding Reference

This page is the canonical reference for every keybinding in AZUREAL. It is organized by context – the mode or panel that must be active for the keybinding to apply. For conceptual explanations of the modal input system, see Keybindings & Input Modes.

Platform note: This reference uses macOS modifier notation (Cmd, Ctrl, Alt/Option). On Windows and Linux, substitute Ctrl for Cmd and Alt for Ctrl in most cases. See Platform Differences for the full mapping table.


Global (Command Mode)

These keybindings are available in command mode across the main worktree view. They do not fire during text input (prompt mode, edit mode, terminal type mode, or active filter/search inputs).

KeyActionNotes
Ctrl+QQuitPrompts confirmation if agents are running
Ctrl+DDebug dumpOpens naming dialog, then writes state snapshot to file
Ctrl+C / Alt+CCancel agentKills the active agent in the current slot. macOS uses Ctrl+C; Windows/Linux uses Alt+C
Cmd+C / Ctrl+CCopy selectionCopies from whichever pane has an active text selection. macOS uses Cmd+C; Windows/Linux uses Ctrl+C
Ctrl+MCycle modelRotates through available models: opus, sonnet, haiku, gpt-5.4, and others
?Help overlayOpens the help overlay showing all keybindings for the current context
pEnter prompt modeFocuses the input area for typing a prompt. Closes the terminal if it is open
TToggle terminalOpens or closes the embedded terminal pane
GGit panelOpens the git panel overlay
HHealth panelOpens the health panel overlay
MBrowse main branchSwitches view to the main branch (read-only browse)
PProjects panelOpens the projects panel overlay
rRun commandOpens the run-command picker, or executes directly if only one command is registered
RAdd run commandOpens the dialog to register a new run command
[ / ]Switch worktree tabMoves to the previous/next worktree tab. Wraps around at both ends
Tab / Shift+TabCycle pane focusCycles focus through FileTree, Viewer, Session, and Input in order

Worktree Actions

Worktree operations use either a W leader key sequence (press W, then the action key) or a direct key when the worktree tab row is focused. See Leader Sequences for details on the leader key mechanism.

KeyAction
Wn / nCreate new worktree
Wr / rRename worktree
Wa / aArchive or unarchive worktree (toggles based on current state)
Wd / dDelete worktree

Input / Prompt Mode

Active when the input area is focused for typing a prompt to an agent. The input area border turns yellow to indicate prompt mode.

KeyAction
EnterSubmit prompt
Shift+EnterInsert newline
EscReturn to command mode
Ctrl+S / Alt+SToggle speech-to-text
Alt+PPreset prompts picker
Alt+1 through Alt+9, Alt+0Quick-select preset
Up / DownHistory prev/next
Alt+Left / Alt+RightWord navigation
Ctrl+WDelete word

Session Pane (Command Mode)

These keybindings apply when the session pane is focused and you are in command mode (not typing a prompt).

KeyAction
j / kScroll line
J / KPage scroll
Up / DownJump to next/prev message
Shift+Up / Shift+DownJump to next/prev prompt
Alt+Up / Alt+DownTop / bottom
aNew session
sToggle session list
/Search text
n / NNext/prev search match

Session List Overlay

The session list is a modal overlay for browsing, loading, and managing saved sessions. It appears over the session pane when s is pressed.

KeyAction
j / kNavigate
J / KPage scroll
EnterLoad session
aNew session
rRename session
/Filter by name
//Content search
s / EscClose

File Tree

These keybindings apply when the file tree pane is focused.

KeyAction
j / k / Up / DownNavigate
h / LeftCollapse / go to parent
l / RightExpand directory
EnterOpen file / toggle directory
SpaceToggle directory
Alt+Right / Alt+JRecursive expand
Alt+Left / Alt+KRecursive collapse
Alt+Up / Alt+DownJump to first/last sibling
aAdd file/directory
dDelete
rRename
cCopy (clipboard mode)
mMove (clipboard mode)
OOptions overlay

Viewer

These keybindings apply when the file viewer pane is focused in its default read-only mode.

KeyAction
j / kScroll line
J / KPage scroll
Alt+Up / Alt+DownTop / bottom
Alt+Left / Alt+RightCycle through edits
eEnter edit mode
Cmd+A / Ctrl+ASelect all
tSave to tab
Alt+TTab picker dialog
xClose current tab
EscClose viewer

Edit Mode

Active when the viewer is in edit mode (entered with e from the viewer). The viewer border changes to indicate edit mode is active.

KeyAction
Any printable keyInsert text
Backspace / DeleteDelete character
Arrow keysMove cursor
Home / EndLine start/end
Cmd+S / Ctrl+SSave
Cmd+Z / Ctrl+ZUndo
Cmd+Shift+Z / Ctrl+YRedo
Ctrl+S / Alt+SSpeech-to-text
EscExit edit mode

Terminal Command Mode

Active when the terminal pane is visible and focused, but you have not entered type mode. The terminal border turns azure. All global keybindings continue to work in this mode.

KeyAction
tEnter type mode
EscClose terminal
+ / -Resize height
All globalsWork normally

Terminal Type Mode

Active when the terminal is in type mode (entered with t from terminal command mode). All keystrokes are forwarded to the PTY except the escape and navigation overrides listed below.

KeyAction
EscExit type mode
Alt+Left / Alt+RightWord navigation
Ctrl+Left / Ctrl+RightWord navigation
All other keysForward to PTY

Git Panel

The git panel is a modal overlay opened with G. It has three focus areas – Actions (pane 0), Files (pane 1), and Commits (pane 2) – cycled with Tab/Shift+Tab. The Viewer pane always shows the diff for the selected file or commit but is not a separate focus target. Some keybindings are context-dependent based on which area is focused and whether the current worktree is the main branch or a feature branch.

Actions Pane

KeyActionContext
lPullMain branch only
mSquash mergeFeature branch only
RRebaseFeature branch only
cCommitAny branch
PPushAny branch
zStashAny branch
ZStash popAny branch
aToggle auto-rebaseFeature branch only
sAuto-resolve settingsActions pane only

Files Pane

KeyAction
j / kNavigate
sToggle stage/unstage
SStage/unstage all
xDiscard changes
Enter / dView diff

Commits Pane

KeyAction
j / kNavigate
Enter / dView diff

Panel-Level

KeyAction
Tab / Shift+TabCycle pane focus
[ / ]Switch worktree
{ / }Jump page
rRefresh
Cmd+A / Ctrl+ASelect all (viewer)
Cmd+C / Ctrl+CCopy selection
J / PageDownPage down diff
K / PageUpPage up diff
EscClose panel

Commit Editor

The commit editor appears when c is pressed in the git panel actions pane. It provides a text input area for composing a commit message (optionally pre-filled with an AI-generated message).

KeyAction
EnterCommit
Cmd+PCommit + push
Shift+EnterInsert newline
EscCancel

Health Panel

The health panel is a modal overlay opened with H. It has two tabs – God Files and Documentation – switched with Tab. Some keybindings differ by tab.

Both Tabs

KeyAction
j / kNavigate
J / KPage scroll
Alt+Up / Alt+DownJump top/bottom
SpaceToggle check
aToggle all
vView checked as tabs

Tab-Specific

KeyActionTab
Enter / mModularizeGod Files – spawn an agent to split the selected file
Enter / mDocumentDocumentation – spawn an agent to document the selected item

Panel-Level

KeyAction
sScope mode
TabSwitch tab
EscClose panel

Projects Panel

The projects panel is a modal overlay opened with P. When AZUREAL starts outside of a git repository, this panel appears automatically as a full-screen view.

KeyAction
j / kNavigate
EnterSwitch to project
aAdd project
dDelete from list
nRename display name
iInitialize git repo
EscClose
Ctrl+QQuit

Quick Reference by Key

The table below lists every single-key binding and its meaning across different contexts. This is useful for answering “what does this key do?” when you are unsure of your current context.

KeyCommand ModeSession PaneFile TreeViewerGit PanelHealth PanelProjects Panel
aNew sessionAdd fileAuto-rebase / Toggle allToggle allAdd project
cCopy (clipboard)Commit
dDeleteView diffDelete project
eEdit mode
iInit git repo
jScroll downNavigate downScroll downNavigate downNavigate downNavigate down
kScroll upNavigate upScroll upNavigate upNavigate upNavigate up
lExpandPull (main)
mMove (clipboard)Squash mergeModularize/Document
nNext matchRename
pPrompt mode
rRun commandRenameRefresh
sSession listStage / Auto-resolve / ScopeScope mode
tSave to tab
vView as tabs
xClose tabDiscard changes
zStash
?Help overlay
/Search

Platform Modifier Summary

For quick reference, here is the modifier key mapping between platforms. The full explanation is at Platform Differences.

ActionmacOSWindows / Linux
CopyCmd+CCtrl+C
Cancel agentCtrl+CAlt+C
SaveCmd+SCtrl+S
UndoCmd+ZCtrl+Z
RedoCmd+Shift+ZCtrl+Y
Select allCmd+ACtrl+A
STT (edit/prompt)Ctrl+SAlt+S

Non-modifier keys (p, j, k, T, G, ?, /, etc.) are identical on all platforms. When in doubt, press ? to see the correct bindings for your system.