<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="repartee — A modern terminal IRC client built with Ratatui, Tokio, and Rust. Scripting, theming, encrypted logging, and more.">
<meta name="keywords" content="irc, terminal, tui, client, chat, repartee, ratatui, tokio, rust">
<meta name="author" content="outragedevs">
<meta property="og:title" content="Architecture — repartee">
<meta property="og:description" content="A modern terminal IRC client built with Ratatui, Tokio, and Rust. Inspired by irssi, designed for the future.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://outragedevs.github.io/repartee/">
<meta property="og:image" content="https://outragedevs.github.io/repartee/images/chat.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{title}} — repartee">
<meta name="twitter:description" content="A modern terminal IRC client built with Ratatui, Tokio, and Rust.">
<meta name="twitter:image" content="https://outragedevs.github.io/repartee/images/chat.png">
<title>{{title}} — repartee</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<button class="hamburger" aria-label="Toggle navigation">
<span></span>
<span></span>
<span></span>
</button>
<div class="page-wrapper">
<aside class="sidebar">
<div class="sidebar-header">
<a href="index.html" class="brand">repartee</a>
<span class="brand-tagline">Documentation</span>
</div>
<nav class="sidebar-nav">
<ul>
<li><a href="index.html">Home</a></li>
</ul>
<div class="nav-section">
<span class="nav-section-title">Getting Started</span>
<ul>
<li><a href="installation.html">Installation</a></li>
<li><a href="first-connection.html">First Connection</a></li>
<li><a href="configuration.html">Configuration</a></li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Reference</span>
<ul>
<li><a href="commands.html">Commands</a></li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Scripting</span>
<ul>
<li><a href="scripting-getting-started.html">Getting Started</a></li>
<li><a href="scripting-api.html">API Reference</a></li>
<li><a href="scripting-examples.html">Examples</a></li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Customization</span>
<ul>
<li><a href="theming.html">Theming</a></li>
<li><a href="theming-format-strings.html">Format Strings</a></li>
<li><a href="logging.html">Logging & Search</a></li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Usage</span>
<ul>
<li><a href="web-frontend.html">Web Frontend</a></li>
<li><a href="sessions.html">Sessions & Detach</a></li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Project</span>
<ul>
<li><a href="architecture.html" class="active">Architecture</a></li>
<li><a href="faq.html">FAQ</a></li>
</ul>
</div>
</nav>
<div class="sidebar-footer">
Built with <a href="https://www.rust-lang.org">Rust</a>
·
<a href="https://github.com/outragedevs/repartee">GitHub</a>
</div>
</aside>
<div class="sidebar-overlay"></div>
<div class="content-wrapper">
<main class="content">
<h1>Architecture</h1>
<p>Technical overview of repartee's internal design.</p>
<h2>Technology stack</h2>
<table>
<thead>
<tr>
<th>Component</th>
<th>Technology</th>
</tr>
</thead>
<tbody><tr>
<td>Language</td>
<td>Rust 2024 edition</td>
</tr>
<tr>
<td>TUI framework</td>
<td>ratatui 0.30+ with crossterm backend</td>
</tr>
<tr>
<td>Async runtime</td>
<td>tokio (full features)</td>
</tr>
<tr>
<td>IRC protocol</td>
<td><code>irc-repartee</code> crate v1.3.1</td>
</tr>
<tr>
<td>Scripting</td>
<td>Lua 5.4 via mlua 0.11</td>
</tr>
<tr>
<td>Storage</td>
<td>SQLite via rusqlite (bundled)</td>
</tr>
<tr>
<td>Encryption</td>
<td>AES-256-GCM via aes-gcm</td>
</tr>
<tr>
<td>Error handling</td>
<td>color-eyre + thiserror</td>
</tr>
<tr>
<td>Config</td>
<td>TOML via serde</td>
</tr>
</tbody></table>
<h2>TEA architecture</h2>
<p>repartee follows The Elm Architecture (TEA) pattern:</p>
<pre><code>Model → Message → Update → View
</code></pre>
<ul>
<li><strong>Model</strong>: <code>AppState</code> — all application state (buffers, connections, config)</li>
<li><strong>Message</strong>: Events from IRC, keyboard, mouse, timers</li>
<li><strong>Update</strong>: Event handlers that transform state</li>
<li><strong>View</strong>: ratatui rendering functions that read state</li>
</ul>
<p>State is UI-agnostic — the <code>state/</code> module has no ratatui imports.</p>
<h2>Module structure</h2>
<pre><code>src/
main.rs # Entry point, fork, terminal setup
app.rs # Main event loop, App struct
constants.rs # APP_NAME and global constants
config/ # TOML config loading
state/ # Application state (buffers, connections, sorting)
session/ # Detach/reattach infrastructure
mod.rs # Socket paths, session listing, PID liveness
protocol.rs # Shim ↔ backend message types (bincode framing)
shim.rs # Terminal shim (relay loop, splash, input)
writer.rs # SocketWriter (impl Write for socket output)
irc/ # IRC connection, event handling, formatting
events.rs # IRC message → state update
mod.rs # Connection setup, CAP negotiation, SASL
cap.rs # IRCv3 CAP framework
isupport.rs # ISUPPORT parameter parsing
batch.rs # IRCv3 BATCH (netsplit/netjoin)
sasl_scram.rs # SASL SCRAM-SHA-256 implementation
extban.rs # Extended ban types ($a:account, etc.)
flood.rs # Flood protection
netsplit.rs # Netsplit detection
ignore.rs # Ignore list matching
formatting.rs # IRC formatting helpers
ui/ # TUI rendering
layout.rs # Screen layout + regions
buffer_list.rs # Left sidebar
nick_list.rs # Right sidebar
chat_view.rs # Message display
input.rs # Command input + tab completion
status_line.rs # Bottom status bar
topic_bar.rs # Top topic display
message_line.rs # Single message rendering
styled_text.rs # Format string → ratatui spans
theme/ # Theme loading + format string parser
scripting/ # Lua scripting engine
engine.rs # ScriptEngine trait + ScriptManager
api.rs # Event names
event_bus.rs # Priority-ordered event dispatch
lua/ # Lua 5.4 backend (mlua)
storage/ # SQLite logging
db.rs # Database schema + migrations
writer.rs # Batched async writer
query.rs # Search + read queries
crypto.rs # AES-256-GCM encryption
types.rs # LogRow, StoredMessage
commands/ # Command system
parser.rs # /command arg parsing
registry.rs # Command registration
handlers_irc.rs # IRC commands (/join, /msg, etc.)
handlers_ui.rs # UI commands (/clear, /close, etc.)
handlers_admin.rs # Admin commands (/set, /reload, etc.)
docs.rs # Command documentation loader
</code></pre>
<h2>Session architecture</h2>
<p>On startup, repartee forks into two processes:</p>
<pre><code>repartee
├── Backend (child) # headless daemon — IRC, state, socket listener
│ └── Unix socket # ~/.repartee/sessions/{pid}.sock
└── Shim (parent) # terminal bridge — renders UI, forwards input
</code></pre>
<p>The <strong>backend</strong> runs the tokio event loop, manages IRC connections, state, scripts, and logging. It listens on a Unix socket for shim connections.</p>
<p>The <strong>shim</strong> captures terminal events (keyboard, mouse, resize) and sends them to the backend as <code>ShimMessage</code> variants. The backend renders ratatui frames and sends raw terminal output back as <code>MainMessage::Output</code>. Communication uses length-prefixed bincode serialization.</p>
<p>On <strong>detach</strong>, the shim exits (shell gets its prompt back). The backend continues running. On <strong>reattach</strong>, a new shim connects to the socket. The shim sends a <code>TerminalEnv</code> snapshot (dimensions, font size, terminal type env vars) so the backend can adapt to the new terminal.</p>
<p>On <strong>SIGHUP</strong> (terminal closed unexpectedly), the backend auto-detaches instead of crashing.</p>
<h2>Event flow</h2>
<ol>
<li><strong>Terminal events</strong> (keyboard, mouse) arrive via crossterm <code>event::poll</code> in a <code>spawn_blocking</code> thread (or via socket from the shim)</li>
<li>Events are sent through a tokio mpsc channel to the main loop</li>
<li><strong>IRC events</strong> arrive via the <code>irc</code> crate's async reader, converted to <code>IrcEvent</code> enum variants</li>
<li>The main loop in <code>App::run()</code> processes all events sequentially, updating <code>AppState</code></li>
<li>After each event batch, the UI is re-rendered from the current state</li>
</ol>
<h2>IRC connection layer</h2>
<p>Each server connection spawns:</p>
<ul>
<li>An async reader task that receives IRC messages and sends <code>IrcEvent</code> to a shared mpsc channel</li>
<li>Messages are sent through the <code>irc</code> crate's <code>Sender</code> stored in the connection state</li>
</ul>
<p>All connections share a single event channel, with events tagged by <code>connection_id</code>.</p>
<h2>Scripting architecture</h2>
<pre><code>ScriptManager
└── Vec<Box<dyn ScriptEngine>>
└── LuaEngine (mlua)
├── Per-script Lua environments (sandboxed)
├── Event handlers (priority-sorted, per-event)
└── Command handlers
</code></pre>
<p>The <code>ScriptEngine</code> trait allows adding new languages (Rhai, etc.) by implementing the trait and registering with <code>ScriptManager</code>.</p>
<h2>IRCv3 support</h2>
<p>repartee implements a comprehensive set of IRCv3 capabilities negotiated during connection via CAP LS/REQ/ACK:</p>
<table>
<thead>
<tr>
<th>Capability</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><code>multi-prefix</code></td>
<td>Multiple mode prefixes per nick (e.g. <code>@+nick</code>)</td>
</tr>
<tr>
<td><code>extended-join</code></td>
<td>Account name and realname in JOIN messages</td>
</tr>
<tr>
<td><code>server-time</code></td>
<td>Message timestamps from the server</td>
</tr>
<tr>
<td><code>account-tag</code></td>
<td>Account name on every message</td>
</tr>
<tr>
<td><code>cap-notify</code></td>
<td>Server-side capability change notifications</td>
</tr>
<tr>
<td><code>away-notify</code></td>
<td>Real-time away status changes</td>
</tr>
<tr>
<td><code>account-notify</code></td>
<td>Account login/logout notifications</td>
</tr>
<tr>
<td><code>chghost</code></td>
<td>Real-time ident/hostname changes</td>
</tr>
<tr>
<td><code>echo-message</code></td>
<td>Authoritative echo of sent messages</td>
</tr>
<tr>
<td><code>invite-notify</code></td>
<td>Notifications for channel invites</td>
</tr>
<tr>
<td><code>batch</code></td>
<td>Grouped message batches (netsplit/netjoin)</td>
</tr>
<tr>
<td><code>userhost-in-names</code></td>
<td>Full user@host in NAMES replies</td>
</tr>
<tr>
<td><code>message-tags</code></td>
<td>Arbitrary IRCv3 message metadata</td>
</tr>
</tbody></table>
<p>SASL mechanisms supported: PLAIN, EXTERNAL (client certificate), SCRAM-SHA-256.</p>
<p>WHOX (extended WHO) is auto-detected and used to populate account names and full user@host for nick entries.</p>
<h2>Storage pipeline</h2>
<pre><code>AppState::add_message()
→ log_tx (tokio mpsc sender)
→ WriterTask (batches 50 rows / 1 second)
→ SQLite (WAL mode, FTS5 index)
</code></pre>
<p>Messages flow from the UI thread to the writer task asynchronously, ensuring zero UI blocking on disk I/O.</p>
<nav class="page-nav">
<a href="sessions.html" class="page-nav-link prev">
<span class="page-nav-label">← Previous</span>
<span class="page-nav-title">Sessions & Detach</span>
</a>
<a href="faq.html" class="page-nav-link next">
<span class="page-nav-label">Next →</span>
<span class="page-nav-title">FAQ</span>
</a>
</nav>
<footer class="site-footer">
Built with <a href="https://www.rust-lang.org">Rust</a> ·
<a href="https://github.com/outragedevs/repartee">GitHub</a> ·
MIT License
</footer>
</main>
</div>
</div>
<script>
(function() {
const hamburger = document.querySelector('.hamburger');
const sidebar = document.querySelector('.sidebar');
const overlay = document.querySelector('.sidebar-overlay');
function toggleSidebar() {
hamburger.classList.toggle('active');
sidebar.classList.toggle('open');
overlay.classList.toggle('visible');
document.body.style.overflow = sidebar.classList.contains('open') ? 'hidden' : '';
}
function closeSidebar() {
hamburger.classList.remove('active');
sidebar.classList.remove('open');
overlay.classList.remove('visible');
document.body.style.overflow = '';
}
hamburger.addEventListener('click', toggleSidebar);
overlay.addEventListener('click', closeSidebar);
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && sidebar.classList.contains('open')) {
closeSidebar();
}
});
})();
</script>
</body>
</html>