Announce, shared directory, packaging, and terminal-client fixes - #5
Conversation
Auditing the CLI and TUI against what has shipped turned up defects, not just gaps. The defects first, because two of them are lockouts. **The TUI chat pane could not show new messages.** It mapped every line into a `List` and rendered it *stateless* — ratatui draws those from index 0 — while seeding 50 lines of history at startup. So on any terminal shorter than the backlog you saw the oldest 50 lines and every message that arrived after landed below the fold, permanently invisible. The comment above it read "tail to fit", which is what it was supposed to do. `chatlog.rs` computes the window as arithmetic and tests it: follow the newest line by default, and once you scroll back, hold position so incoming messages don't yank you away from what you're reading. PgUp/PgDn/↑/↓/Home/End drive it, the title shows how many lines are below when you're scrolled up, and the buffer is bounded — a terminal client is a window on a conversation, not an archive, and an unbounded Vec in a long-lived session is a slow leak. **Accounts with 2FA could not sign in from any native client.** `AuthPassword::with_totp` has existed since the 2FA slice; nothing ever called it. `rabbit login --totp 123456` now works, via a new `Client::auth_password_totp` (the old signature stays, delegating). **The CLI silently accepted burrows' agreements.** `login` printed "commands will auto-accept — read it with `rabbit status`", but `status` had no idea the agreement existed, and `reconnect()` accepted it on the user's behalf before every command. Accepting terms nobody was shown is not consent. The agreement is now stored on the session, printed in full by `status`, and accepted only by an explicit `rabbit agree` — which prints it first unless you pass `--yes`. **`rabbit dm`** — list, read, send. The DM family has been on the wire since Wave 2 with no CLI surface at all: a terminal user could read every board and file on a burrow but not the message someone sent them. Reading marks read (that is what reading means), with `--keep-unread` for scripts, and an end-to-end encrypted DM says so instead of printing a blank line that looks like a bug. Verified live between two accounts: send, unread count, read, mark-read, reply. **`rabbit who`** was discarding three fields the wire already gave it — presence state, away message, and the identity key. It shows them now, with the same key glyph the web roster uses. Also: the README claimed the TUI has DMs (it does not), and `contrib/burrow.service` pointed at a stale GitHub URL — which matters now because it is about to be baked into every distro package. 49 tests across the terminal crates, clippy clean.
…191.0
Running "the server side" of RabbitHole meant three hand-typed commands from
three different documents, and there was no way to install it that wasn't
"clone the repo and build". Both are now one command.
`just up` (scripts/stack.sh) runs the burrow with the web client plus the
Looking Glass tracker, and one Ctrl-C stops both. Two details cost real
debugging: burrow resolves a *relative* `--web-root` under `--data-dir`, so a
relative path silently 404s every page (WEB_ROOT is absolute now); and a
frontend build failure must not take the server down with it, since the burrow
still answers on QUIC and WebSocket with no SPA at all.
packaging/nfpm.yaml describes the file list once and yields deb + apk + rpm
rather than a debian/ tree plus an APKBUILD plus a .spec, three toolchains to
keep in agreement about the same list. nfpm does not expand ${VAR} inside
contents[].src, so scripts/package.sh sed-renders the config to a temp file
first. Verified: a real 13.6 MB arm64 .deb with the right control metadata.
The maintainer scripts deliberately do not enable or start a service, and
never delete /var/lib/burrow on removal. A burrow holds accounts, boards,
files and an identity key; an uninstall that eats them is a data-loss bug
wearing a package manager's clothes.
The Homebrew formula consumes the archives release.yml already publishes, so
there is no second artifact to build, and its test asserts the version matches
rather than that the binary merely exists, which would pass on a stale tarball.
Also fixes a real blocker: crates/ui-web/index.html now passes post-MVP
wasm-opt feature flags. Without them `trunk build --release` failed outright,
which means release SPA builds were impossible.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…thout inventing facts. A burrow nobody can discover is a burrow nobody joins, so announce is on by default (inert until advertise_host is set) and the SPA and TUI parse the same listings — uptime and online counts stay optional so a missing field is not shown as zero. The wasm download is size-tuned so finding a place does not cost a fat payload.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1b33081. Configure here.
| c.agreement_accept().await?; | ||
| } | ||
| // Track it so `status` stays accurate; never accept on their behalf. | ||
| s.pending_agreement = welcome.agreement.clone(); |
There was a problem hiding this comment.
Agreement lost on every reconnect
High Severity
reconnect no longer calls agreement_accept, but agreement is only tracked per server session and ChatSend still requires agreed. After rabbit agree clears the local flag, the next rabbit say opens a fresh session that starts unagreed, so lobby chat fails with Forbidden. reconnect also always copies welcome.agreement into pending_agreement, so a cleared accept flag is overwritten in memory whenever Welcome still includes the configured text.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1b33081. Configure here.


Summary
advertise_hostis set) and the SPA/TUI share one directory crate so listings do not invent online counts or uptime.rabbit dm/rabbit whocover what the wire already had.Test plan
ctl config set advertise_host …then confirm announce logs and a listing on the configured trackerannounce_enabled falsestampsnoindexon.well-knownwithout a restartrabbit login --totpandrabbit agreebehave as documentedjust upbrings up burrow + Looking Glass; packaging scripts still produce the expected artifactsNote
Medium Risk
Changes touch signed outbound announce traffic and agreement consent behavior, plus broad release/Docker packaging; core auth paths are extended but heavily tested in-tree.
Overview
Adds Looking Glass self-announce: burrows periodically POST Ed25519-signed descriptors to configured trackers (default on, idle until
advertise_hostis set), with explicit canonical JSON signing and anoindexfeature tag in.well-knownwhen announcing is disabled.Introduces
rabbithole-directoryso the SPA and TUI share one discovery path (rabbithole.directorywith tracker fallback), honest handling of missing online/uptime fields, and a small total JSON parser for nested glass replies.Distribution expands: release CI builds arm64 Linux, nfpm deb/apk/rpm, Docker ships burrow + looking-glass + built SPA;
rabbitgains--totp, explicitagree,dmsubcommands, richerwho, and stops auto-accepting agreements on reconnect. TUI fixes lobby chat to follow the newest lines and opens the server browser on the wide directory by default.Reviewed by Cursor Bugbot for commit 1b33081. Bugbot is set up for automated code reviews on this repo. Configure here.