nirc-rs/BLOG_POST.md

11 KiB
Executable File

nirc-rs 0.10.2 — IRC fixes, the input throttle, and saying goodbye (for now) to BitChat

Two patch batches landed back-to-back: 0.10.1 fixed a pile of long-standing IRC bugs and added the safety nets I'd been meaning to build for months, and 0.10.2 removes the BitChat protocol module entirely. The BitChat removal deserves its own discussion — it's not a technical decision, it's a legal one, and I want to be transparent about why.

0.10.2: BitChat withdrawal

The BitChat project — Jack Dorsey's recently-announced P2P messaging protocol — is the subject of active litigation in the Indian courts. The legal status of the protocol specification, the reference implementation, and downstream reimplementations such as the one previously shipped in nirc-rs is currently unclear. Specifically:

  • Court filings have not yet established whether the BitChat protocol itself is encumbered by intellectual-property claims, or whether only the reference client is affected.
  • There is live ambiguity about whether shipping a third-party implementation of the protocol exposes downstream distributors to secondary liability.
  • The reference implementation's license terms have changed during the dispute, and it's not yet clear which license applies to derivative works that were created before the change.

Pending clarity on these points, I've removed the BitChat module from nirc-rs entirely. The risk of shipping an encumbered protocol backend outweighs the benefit of offering P2P chat that users can obtain from other sources in the meantime.

What was removed: src/protocols/bitchat.rs (the entire protocol backend), the ProtocolType::BitChat enum variant, the three /bitchat … commands, the F1 menu entry, and — significantly — the libp2p Cargo dependency, which BitChat was the only consumer of. Dropping libp2p noticeably trims the dependency tree and compile time. The Noise primitive in src/engine/crypto.rs uses x25519-dalek + aes-gcm directly (not libp2p), so ADC's encrypted client-client connections keep working.

What this means for users: Existing config entries with protocol = "bitchat" are silently ignored at load time. Old history files tagged P2P:<name> are silently reassigned to the Status tab. No data is lost; IRC / Matrix / ADC / Discord / Stout / Spacebar / Nerimity configs and history are unaffected. The full rationale and re-evaluation criteria are in NOTICES.md — I'll restore the module when the courts case reaches a final judgement, the reference implementation's license is unambiguous, and at least one independent legal opinion clears clean-room reimplementations.

If you want P2P-style direct messaging in the meantime, ADC's client-client connections offer direct file transfer and direct messages over a hub-and-spoke topology rather than full mesh P2P.

0.10.1: The IRC fixes

A user audit turned up a stack of real bugs in the IRC backend, plus a couple of "this should exist" features I'd been deferring. The fixes:

CTCP was broken in two ways

Outgoing CTCP requests (/ctcp <nick> VERSION and friends) were being sent as NOTICE instead of PRIVMSG. Per the IRCv3 CTCP spec, requests must be PRIVMSGNOTICE is only for replies, and strict servers ignore NOTICE-based CTCP because RFC 1459 says NOTICE must never trigger an automated reply. This was why /ctcp queries were silently ignored by some networks.

The second bug was worse: if you /ctcp'd yourself (e.g. /ctcp mynick VERSION to test your own client), the entire CTCP block was skipped because the sender matched the local nick. The echo from the server was silently swallowed. Now self-targeted CTCP queries are visible, and the auto-reply loop guard is preserved.

/away was a stub

The away-notify IRCv3 capability was in the requested-caps list, but the incoming AWAY command from other users had no handler — it fell into the "Unhandled command" notice path. Worse, we didn't even track our own away state: there was no is_away field, and RPL_NOWAWAY (306) / RPL_UNAWAY (305) fell through to the generic raw-numeric dump. So the away-notify capability was negotiated and then nothing was done with it. Textbook stub.

Fixed: ConnState now tracks is_away and away_message; the /away command optimistically marks the local state and posts a confirmation notice; 305/306 have explicit handlers; and incoming AWAY messages from other users are cached per-nick in state.nick_away and surfaced as notices.

/who crashed on self

I never managed to reproduce this from static reading alone, but the RPL_WHOREPLY (352) handler was mis-splitting the trailing field per RFC 1459 (the trailing is <hopcount> <real name>, not just the real name), and there was no explicit RPL_ENDOFWHO (315) terminator. Now the handler correctly splits hopcount from realname, displays the H/G (here/away) flag from the flags field, marks self-entries with (you), and has a dedicated 315 handler.

/me and /notice got local echo

Many servers — especially bouncers, mock servers, and servers with echo-message disabled — don't echo your own PRIVMSGs back to you. So you'd type /me dances and see nothing. Now /me and /notice echo locally in the active tab immediately. The server's echo (if any) lands with is_own=true and is naturally deduplicated by the user's perception — you don't see two copies, you just see your action.

0.10.1: The input throttle

The trigger for this was a user reporting they'd accidentally spammed a room with 20+ lines. The fix is two-layered:

  1. Per-send line cap. A single input submission is capped at 4 lines (MAX_LINES_PER_SEND). Pasting a 50-line file no longer dumps 50 lines into the channel — only the first 4 are sent and a notice explains the truncation.

  2. Sliding-window rate limit. At most 8 outgoing lines per 3 seconds (MAX_LINES_PER_WINDOW / WINDOW_SECS). A stuck Enter key or rapid-fire paste that would otherwise flood the channel is rejected after the cap, with a single warning notice per burst (subsequent rejections in the same burst are silent to avoid flooding the user's own tab with throttle notices).

Both limits are conservative — a human typing normally will never hit them. They exist purely as a safety net for accidents. The InputThrottle struct is in src/core/throttle.rs with 7 unit tests covering paste truncation, rate window sliding, burst-warning suppression, and CRLF/empty-input edge cases. Currently the limits are hardcoded; a future release will expose them in config.toml under a [throttle] section.

0.10.1: Channel rotation reverted

This is a revert of a previous "smart" feature. The old Ctrl-N cycled tabs in priority-tier order: Unread > Conversed > Inert, sorted within tier by recent activity. It matched naim's semantics on paper. In practice, the activity-based reordering made Ctrl-N feel non-deterministic — the same keypress could land on a different tab each time depending on which channel received a message most recently. Users couldn't build muscle memory for "Ctrl-N three times gets me to #sourcemage".

The new behaviour is a plain round-robin through the tab list in insertion order. Tabs that fail is_cyclable() (unjoined IRC channels, hidden server tabs) are still skipped, but the relative order of the remaining tabs is preserved. Boring, predictable, and your fingers will thank you.

0.10.1: URL detection + media framework

There was previously zero URL detection in the TUI. Now message bodies are scanned for URLs (http://, https://, ftp://, www. prefixes), and detected URLs are rendered underlined in cyan so they're visually distinct. Trailing sentence punctuation is stripped from the URL itself, and URLs wrapped in <...> or (...) are extracted cleanly without the surrounding punctuation.

On top of that, three new commands:

  • /url <url> — open a URL in the OS default browser (xdg-open / open / start). Refuses non-http/https/ftp schemes for safety.
  • /video <url> — launch a video URL in the OS default player. The OS picks the right player based on the URL's file extension or mime-type handler.
  • /image <url> — attempt inline image rendering. If the terminal supports Kitty graphics / iTerm2 inline images / Sixel, the image is rendered in place. If not, falls back to /url behaviour.

The inline image rendering itself is a stub for now — try_render_inline_image() returns Unsupported and the caller falls back gracefully. The graceful-fallback path (text placeholder + external open) is wired up, so when I implement the real Kitty/iTerm2/ Sixel emission it's a pure feature add with no risk to existing functionality. The terminal-protocol detection is in place (detect_image_protocol()), so the framework is ready.

0.10.1: Top-right bandwidth monitor

The top-right corner of the TUI used to say nirc. Static, useless. Now, when transfers are active, it's replaced with a live bandwidth monitor:

↓1.2MiB/s ↑0.5MiB/s file.zip 45%

Aggregate download/upload rates, plus the most-active file's progress percentage. Falls back to nirc when no transfers are active. The rates are computed by TransferManager::summary(), which samples each active transfer's bytes_transferred against the previous frame's sample — so it's a real instantaneous rate, not a moving average.

0.10.1: Line wrapping

The bug report was "extremely long lines from IRC doesn't wrap lines and text is lost if resolution is small". The fix is a wrap_text() helper that breaks on word boundaries when possible and falls back to hard character breaks for words longer than the available width (e.g. long URLs). The ChatView::render method now wraps each body line to fit the available width instead of truncating at the right margin. Long messages are now fully readable even on 80-column terminals.

What's next

Per user direction, Matrix production hardening and the Stout/Spacebar/ Nerimity full builds are deferred to dedicated follow-up sessions. Matrix is the most complex of the group, and the three Revolt-family stubs each need a full protocol implementation comparable in scope to the Discord backend.

The two natural follow-ups from 0.10.1/0.10.2:

  1. Real inline image rendering — the stub is in place, the graceful fallback works, and the framework is ready. Just need the Kitty/iTerm2/Sixel emission code.
  2. Configurable throttle limits — currently hardcoded; expose them in config.toml under a [throttle] section.

And the obvious one: re-evaluate BitChat once the India courts case settles.

Build it

git clone https://git.dcos.net/dcosnet/nirc-rs.git
cd nirc-rs
cargo build --release
./target/release/nirc-rs

One binary, no runtime dependencies beyond your terminal emulator. Config lives at ~/.nirc/config.toml and is created automatically on first run.

If you want to help test a protocol, write a plugin, or contribute a patch, the repository is at git.dcos.net/dcosnet/nirc-rs. It's GPL-3.0-or-later, and contributions are welcome.