<!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="Web Frontend — 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" class="active">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">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>Web Frontend</h1>
<p>repartee includes a built-in web frontend that runs alongside the terminal UI. Access your IRC sessions from any browser — desktop or mobile — with real-time bidirectional sync.</p>
<div style="text-align: center; margin: 16px 0;">
<a href="https://www.youtube.com/watch?v=okU4WKF5GDI" target="_blank">
<img src="https://img.youtube.com/vi/okU4WKF5GDI/maxresdefault.jpg" alt="Repartee Web Frontend Demo" style="max-width: 100%; border-radius: 8px; border: 1px solid var(--border);">
</a>
<p style="color: var(--text-muted); font-size: 13px; margin-top: 6px;">TUI (left) | Mobile web (center) | Desktop web (right) — 1:1 state sync.</p>
</div>
<h2>Enabling</h2>
<p>The web frontend is disabled by default. To enable it, set a password in <code>~/.repartee/.env</code> and enable it in <code>config.toml</code>:</p>
<p><strong>1. Set the login password:</strong></p>
<pre><code class="language-bash">echo 'WEB_PASSWORD=your-secret-password' >> ~/.repartee/.env
</code></pre>
<p><strong>2. Enable in config:</strong></p>
<pre><code class="language-toml">[web]
enabled = true
port = 8443
</code></pre>
<p>repartee auto-generates a self-signed TLS certificate on first launch. Open <code>https://localhost:8443</code> in your browser and accept the certificate warning.</p>
<h2>Configuration</h2>
<p>All web settings live under the <code>[web]</code> section in <code>config.toml</code> and can be changed at runtime with <code>/set</code>:</p>
<table>
<thead>
<tr>
<th>Setting</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><code>web.enabled</code></td>
<td><code>false</code></td>
<td>Enable the web server</td>
</tr>
<tr>
<td><code>web.bind_address</code></td>
<td><code>127.0.0.1</code></td>
<td>Bind address (use <code>0.0.0.0</code> for LAN access)</td>
</tr>
<tr>
<td><code>web.port</code></td>
<td><code>8443</code></td>
<td>HTTPS port</td>
</tr>
<tr>
<td><code>web.tls_cert</code></td>
<td><em>(auto)</em></td>
<td>Path to TLS certificate (PEM). Empty = self-signed</td>
</tr>
<tr>
<td><code>web.tls_key</code></td>
<td><em>(auto)</em></td>
<td>Path to TLS private key (PEM). Empty = self-signed</td>
</tr>
<tr>
<td><code>web.password</code></td>
<td><em>(from .env)</em></td>
<td>Login password (set via <code>WEB_PASSWORD</code> in <code>.env</code>)</td>
</tr>
<tr>
<td><code>web.session_hours</code></td>
<td><code>24</code></td>
<td>Session duration before re-login required</td>
</tr>
<tr>
<td><code>web.theme</code></td>
<td><code>nightfall</code></td>
<td>Default theme (<code>nightfall</code>, <code>catppuccin-mocha</code>, <code>tokyo-storm</code>, <code>gruvbox-light</code>, <code>catppuccin-latte</code>)</td>
</tr>
<tr>
<td><code>web.timestamp_format</code></td>
<td><code>%H:%M</code></td>
<td>Timestamp format (chrono strftime syntax)</td>
</tr>
<tr>
<td><code>web.line_height</code></td>
<td><code>1.35</code></td>
<td>CSS line-height for chat messages</td>
</tr>
<tr>
<td><code>web.nick_column_width</code></td>
<td><code>12</code></td>
<td>Nick column width in characters</td>
</tr>
<tr>
<td><code>web.nick_max_length</code></td>
<td><code>9</code></td>
<td>Max nick display length before truncation</td>
</tr>
</tbody></table>
<p>Settings changed via <code>/set web.*</code> apply immediately to all connected web clients.</p>
<h2>Features</h2>
<p>The web frontend provides full 1:1 parity with the terminal UI:</p>
<ul>
<li><strong>All buffer types</strong> — server, channel, query, DCC chat</li>
<li><strong>Real-time sync</strong> — messages, nick changes, joins, parts, quits, topic changes, mode changes</li>
<li><strong>Bidirectional buffer switching</strong> — switch a buffer on web and the TUI follows, and vice versa</li>
<li><strong>Command execution</strong> — run any <code>/command</code> from the web input (output visible on web)</li>
<li><strong>Tab completion</strong> — nicks, <code>/commands</code>, and <code>/set</code> setting paths</li>
<li><strong>Nick list</strong> — grouped by mode (ops, voiced, regular), away status</li>
<li><strong>Activity indicators</strong> — unread counts and color-coded activity levels</li>
<li><strong>Mentions</strong> — highlight tracking with mention count badge</li>
<li><strong>Theme picker</strong> — switch themes live (5 built-in themes)</li>
<li><strong>Multiline input</strong> — paste multiline text, each line sent separately</li>
<li><strong>Persistent sessions</strong> — page refresh reconnects automatically (session stored in browser)</li>
</ul>
<h2>Desktop Layout</h2>
<p>The desktop layout mirrors the terminal UI:</p>
<pre><code>┌─────────────────────────────────────────────────────┐
│ Topic bar │
├──────────┬─────────────────────────────┬────────────┤
│ Buffers │ Chat area │ Nick list │
│ │ 14:23 @ferris❯ Hello! │ @ferris │
│ (status) │ 14:24 alice❯ Hey there │ alice │
│ 1.#rust │ │ bob │
│ 2.#help │ │ │
├──────────┴─────────────────────────────┴────────────┤
│ [kofany(+i)] [#rust(+nt)] [Lag: 42ms] [Act: 3,4] │
│ ❯ [Message input... ] [➤] │
│ [● ● ● ● ●] theme picker │
└─────────────────────────────────────────────────────┘
</code></pre>
<h2>Mobile Layout</h2>
<p>On screens narrower than 768px, the layout switches to a mobile-optimized view:</p>
<pre><code>┌──────────────────────────┐
│ ☰ #rust (+nt) — Welc… 👥│ top bar
├──────────────────────────┤
│ 14:23 @ferris❯ Has any… │ inline nicks
│ 14:24 alice❯ Yeah, it's… │
├──────────────────────────┤
│ [kofany|Act: 3,4,7] │ compact status
│ [Message... ] ➤ │ input
└──────────────────────────┘
</code></pre>
<p><strong>Mobile features:</strong></p>
<ul>
<li><strong>Inline chat</strong> — nicks appear inline with the message (no right-aligned column) to maximize horizontal space</li>
<li><strong>Slide-out buffer list</strong> — tap the ☰ hamburger or swipe right from anywhere to open the channel/buffer list</li>
<li><strong>Slide-out nick list</strong> — tap the 👥 button or swipe left from anywhere to open the nick list</li>
<li><strong>Auto-close panels</strong> — tapping a buffer in the slide-out switches to it and closes the panel automatically</li>
<li><strong>Touch-friendly</strong> — large tap targets, swipe gestures, no accidental horizontal scroll</li>
<li><strong>Viewport fitting</strong> — uses <code>100dvh</code> to properly fill the screen on iOS Safari and Android Chrome (accounts for browser chrome)</li>
<li><strong>No auto-zoom</strong> — focusing the input field does not trigger iOS Safari's auto-zoom behavior</li>
<li><strong>Notch-safe</strong> — respects <code>safe-area-inset-bottom</code> on iPhones with home indicators</li>
</ul>
<h2>Custom TLS</h2>
<p>For production use (or to avoid browser certificate warnings), provide your own TLS certificate:</p>
<pre><code class="language-toml">[web]
tls_cert = "/path/to/fullchain.pem"
tls_key = "/path/to/privkey.pem"
</code></pre>
<p>Let's Encrypt certificates work out of the box.</p>
<h2>Remote Access</h2>
<p>To access the web frontend from other devices on your network:</p>
<pre><code class="language-toml">[web]
bind_address = "0.0.0.0" # listen on all interfaces
port = 8443
</code></pre>
<p>Then open <code>https://your-machine-ip:8443</code> from your phone or another computer.</p>
<h2>Security</h2>
<ul>
<li><strong>HTTPS only</strong> — all traffic is encrypted via TLS</li>
<li><strong>Password authentication</strong> — HMAC-SHA256 verified login</li>
<li><strong>Rate limiting</strong> — brute-force protection with progressive lockout</li>
<li><strong>Session tokens</strong> — time-limited, stored in browser localStorage</li>
<li><strong>No external dependencies</strong> — the web UI is compiled to WASM and embedded in the binary; no CDN requests, no external scripts</li>
</ul>
<nav class="page-nav">
<a href="logging.html" class="page-nav-link prev">
<span class="page-nav-label">← Previous</span>
<span class="page-nav-title">Logging & Search</span>
</a>
<a href="sessions.html" class="page-nav-link next">
<span class="page-nav-label">Next →</span>
<span class="page-nav-title">Sessions & Detach</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>