Skip to content

Announce, shared directory, packaging, and terminal-client fixes - #5

Merged
kevinelliott merged 3 commits into
mainfrom
claude/retro-client-server-a7nw8a
Aug 15, 2026
Merged

Announce, shared directory, packaging, and terminal-client fixes#5
kevinelliott merged 3 commits into
mainfrom
claude/retro-client-server-a7nw8a

Conversation

@kevinelliott

@kevinelliott kevinelliott commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • A burrow can announce itself to Looking Glass (on by default, inert until advertise_host is set) and the SPA/TUI share one directory crate so listings do not invent online counts or uptime.
  • Terminal clients catch up: TUI chat actually shows new messages, 2FA login works, agreements require explicit consent, and rabbit dm / rabbit who cover what the wire already had.
  • One-command stack plus deb/apk/rpm/brew packaging, so running or installing the server side is no longer three hand-typed commands from three documents.

Test plan

  • ctl config set advertise_host … then confirm announce logs and a listing on the configured tracker
  • Looking Glass (SPA and TUI) opens on rabbithole.directory and names its source; missing uptime/online shows as absent, not zero
  • announce_enabled false stamps noindex on .well-known without a restart
  • TUI chat tails new messages; rabbit login --totp and rabbit agree behave as documented
  • just up brings up burrow + Looking Glass; packaging scripts still produce the expected artifacts

Note

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_host is set), with explicit canonical JSON signing and a noindex feature tag in .well-known when announcing is disabled.

Introduces rabbithole-directory so the SPA and TUI share one discovery path (rabbithole.directory with 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; rabbit gains --totp, explicit agree, dm subcommands, richer who, 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.

kevinelliott and others added 3 commits August 12, 2026 03:47
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.
@kevinelliott
kevinelliott merged commit 6a7b715 into main Aug 15, 2026
14 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread apps/cli/src/main.rs
c.agreement_accept().await?;
}
// Track it so `status` stays accurate; never accept on their behalf.
s.pending_agreement = welcome.agreement.clone();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1b33081. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant