nirc-rs/BLOG_POST.md

242 lines
11 KiB
Markdown
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 `PRIVMSG``NOTICE` 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 `PRIVMSG`s 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
```sh
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.