ANetBBS Changelog — Beta History (pre-v1.0.0)
Archived history of every internal beta build number (v1.0a1.1 through
v1.0b2.239) from before ANetBBS's v1.0.0 full release in August 2026.
Preserved as-is — a real record of what changed at each step during
development — but split out of the main CHANGELOG.md
once it passed 6,000 lines, to keep that file scannable for the
v1.0.0-onward history that actually matters day to day. Newest-first,
same as the main changelog.
v1.0b2.163 — Who's Online font-path leak + "Default Theme" fixes (July 2026)
- FIX: Who's Online sometimes showed a user "on" /static/fonts/Ac437_IBM_VGA_9x16.woff instead of their real page — a lazily-loaded font fetch could be the last request recorded for a session. Static asset requests no longer update presence.
- FIX: setting a theme as the site's "Default Theme" in Admin had no effect — nothing ever read that flag. Both the web UI and terminal UI now actually fall back to it, and their "Default" theme-picker labels show which theme that really is instead of a hardcoded "Classic Green".
v1.0b2.162 — Tagline picker selection visibility: give up on reverse-video (July 2026)
Third attempt at the same bug, reported live each time via screenshot: the selected row in the tagline picker was still invisible after both (1) adding an explicit color to compete with the reverse-video highlight, and (2) removing that color to match every other lightbar row's convention of relying on reverse-video alone. Both were still invisible on the user's terminal (SyncTERM), which means reverse-video + bold itself doesn't render usably on that client, regardless of what color the row text has. Stopped guessing at the SGR interaction and sidestepped reverse-video for this row entirely: it now explicitly cancels the wrapper's escape codes and draws its own > marker in a plain bright color instead, which doesn't depend on how any given client's reverse-video happens to interact with bold.
v1.0b2.161 — Wide-terminal row overflow, and the real selected-row visibility fix (July 2026)
- FIX: on a wide (132-col) terminal, the "Compose Echomail" area-picker lightbar corrupted itself after scrolling down then back up — stray fragments ("tegory", "S Scene", "neral") left on screen. Root cause: the row's Name-column width was computed as
terminal_width - 28, undercounting the row's real overhead (tag + spacing + category = 37 visible chars, not 28) by 9 characters. On a 132-col terminal each row silently overflowed the line and the terminal auto-wrapped it onto the next line; a later partial redraw (scrolling) only overwrote part of that wrapped-in text, leaving the rest behind. Fixed the width math; added a test that measures every rendered row's real visible width against the terminal width at 132 columns. - FIX (real fix this time): v1.0b2.160's tagline-picker color fix (adding an explicit bold-white foreground to the row text) was still invisible when selected, confirmed via a second live screenshot. The actual cause was the opposite of the first guess — an explicit foreground color competing with the reverse-video highlight is what's invisible, not the lack of one. Every other lightbar row in the app already avoids this by leaving the selected row's main text uncolored (relying purely on the reverse-video wrapper against default terminal colors) while only non-selected rows get an explicit color; the tagline picker now follows that same convention.
v1.0b2.160 — Tagline picker: defer to send time, fix invisible selected row (July 2026)
- FIX: the terminal tagline picker showed up before the user even started typing — reported live ("it should not ask you about a tag line until you send, when you send it should bring up the tagline"). Moved from a pre-editor prompt to a
tagline_pickercallback that ANEdit itself calls once, at actual send time (Ctrl+W//send). Removed the now-redundant/tagslash command and its toggle state — the picker is the only path now, and it always runs (if the pool isn't empty) right before finalizing a send, never on abort. - FIX: the selected row in the tagline picker rendered as a blank, unreadable highlighted bar — the row text had no explicit foreground color, and reverse-video + bold with no explicit color apparently collapses to white-on-white in real terminal clients (SyncTERM, confirmed live via screenshot). Every other lightbar row in the app already colors its text for this exact reason; this was the one that didn't.
v1.0b2.159 — Real root cause: the tagline seed file never shipped in ANY release tarball (July 2026)
Found after v1.0b2.158's fix still didn't work live: build-release.sh builds its file list from git (tracked + untracked-but-not-ignored), and .gitignore's runtime-data rule was a bare data/ — which git matches against every directory named data anywhere in the tree, not just the intended top-level data/ (sysop DB/uploads/mail spools). That silently caught anetbbs/data/ too, which holds bundled shipped content, not runtime state — so anetbbs/data/default_taglines.txt (~200 taglines) never made it into v1.0b2.157 or .158's tarball. The seed step's open() always hit a missing file on a real install, silently logged and skipped, so /tag correctly reported "no taglines available" — the pool was genuinely empty on every deployed copy, no matter which compose path you used. Every test in the suite passed throughout, because tests run against the repo checkout, which still has the file locally — none of them exercised "what actually ends up in the tarball."
Fixed by anchoring the rule to /data/ and adding explicit entries for the two genuine runtime-state directories that were incidentally relying on the old broad pattern (anetbbs/games/sbbs_doors/data/, vendor/games/anetsims/data/). Also added a small regression test that shells out to git check-ignore/git ls-files to catch this exact class of "silently never shipped" bug going forward, since the existing test suite structurally couldn't.
v1.0b2.158 — Fourth tagline call site: replying from inside an echo area (July 2026)
v1.0b2.157 wired the tagline picker into three terminal compose points (_post_compose, _send_pm, _compose_echomail), but missed a fourth: replying to (or starting a new message from) an echo area's message list — reached by reading an area, then pressing R or N inside the ANView reader — calls launch_anedit() directly from inside read_echo_area(), a separate code path. Reported live ("I still dont see an add tagline option in terminal when sending an echomail", then confirmed via /tag reporting "no taglines available" from that exact screen). Fixed; new regression test drives the real reply flow end-to-end.
v1.0b2.157 — Tagline picker (browse & choose), compose-echomail lightbar, bad-area visibility (July 2026)
- Taglines now work as a scrollable picker in both terminal and web, not a blind random pick — you browse the pool and choose one, or skip. Terminal uses the same lightbar as everywhere else; web uses a visible multi-row listbox instead of a checkbox.
- FIX: composing a message in the terminal never actually asked whether to add a tagline —
/tagwas the only way in, and it wasn't even listed in ANEdit's real help screen (only in an unused internal help string). Added an active picker at all three terminal compose points, and added/tagto the real help screen too. - FIX: the "Compose echomail" area picker (network → area → message) used the old numbered list with a
-- more (Enter / Q) --page break every 17 areas. Now a scrollable lightbar, matching how areas are already browsed when reading. - Added visibility for echomail dropped because a known area is unsubscribed/deactivated — previously silently discarded with no record at all (unlike the existing "unknown area" case). Both reasons now share the same Bad Areas admin review queue, tagged by reason, with a re-subscribe action.
v1.0b2.156 — Message-board ANView fix, actually wired this time (July 2026)
v1.0b2.155's message-board fix targeted read_thread()/list_threads() (class-body methods), but both were dead code — BBSMenuUI.list_threads gets reassigned near the bottom of bbs_ui.py to a different implementation (_list_threads_v2, calling read_thread_v2) that shadows them at every real call site, so the original fix never actually ran. Found via a live terminal capture still showing the old --MORE-- pager after the .155 build. Fixed the actually-reachable read_thread_v2 to use ANView, and removed the now-confirmed-dead read_thread/list_threads methods. New end-to-end regression test drives the real list_threads() entry point (not just the function in isolation) to guard against this exact class of bug recurring.
v1.0b2.155 — Shared tagline pool, and ANView for message boards (July 2026)
- Added a shared, sysop-editable pool of ~200 taglines. Opt in per message with a checkbox (web) or
/tag(terminal) — works across local boards, private messages, netmail, and echomail. Distinct from the existing fixed per-user FTN tagline, which still auto-appends unconditionally to netmail/echomail. - FIX: terminal message boards used the old page-break
[MORE]pager instead of the scrollable ANView reader already used for echomail/private messages. Board threads now render through the same CP437/ANSI-aware pipeline, and reply/new-thread shortcuts work the same way they already do when reading echomail. (Note: this fix was incomplete — see v1.0b2.156.)
v1.0b2.154 — File area fixes, per-network netmail options, and poll-in-progress visibility (July 2026)
- FIX: file area "Delete" silently saved instead of deleting (a hidden form field always overrode the button)
- Added bulk actions for file areas (multi-select, select-by-network, bulk enable/disable/subscribe/delete)
- FIX: TIC log showed a false "nodelist import failed" error on ordinary (non-nodelist) files
- Added per-network Crash/Hold/Direct netmail delivery defaults and a packet-header password field (separate from the BinkP session password)
- Added visibility for in-progress BinkP polls (previously showed nothing until a poll finished) — an admin can now see a poll is running, with a live elapsed-time counter and partial transcript
v1.0b2.153 — Release-readiness audit: 10 install/update fixes ahead of the August 1 full release (July 2026)
Top-to-bottom pass over install.sh, update.sh, and their generated configs, prompted by wanting the BinkP fix (v1.0b2.147-152) to be the last "oh, our side is broken" surprise before the full release. All 10 findings below were fixed, in priority order:
- FIX (CRITICAL): install.sh computed a mode-specific
WEB_BIND(e.g.127.0.0.1for a reverse-proxied test-mode install) but only ever displayed it — never wrote it to.env.deploy/serve.pytherefore always fell back to its own0.0.0.0default regardless of install mode, silently defeating test mode's entire "gunicorn binds localhost-only, nothing reaches the LAN" security promise. Now written to.env; also added to.env.exampleso update.sh's existing key-backfill mechanism picks it up on upgrades of already-affected installs. - FIX (HIGH): update.sh's rollback-on-failed-update path restored
.env, the database, and systemd units, but never the application code itself — a bad update that failed its health check left the broken new code in place, so the "rollback" just restarted the same broken code. Added a pre-update code snapshot (excludes logs/data/venv, ~28MB) and restore-on-failure. - FIX (HIGH): install.sh's fresh-install
anetbbs.service(telnet/ssh/rlogin/FTP) never grantedCAP_NET_BIND_SERVICE, so FTP (port 21) would silently fail to bind if a sysop later setFTP_ENABLED=true— until their next update.sh run self-healed it. Now granted at install time, matching what update.sh already does. - FIX (MEDIUM): install.sh had no disk-space preflight check at all; update.sh gained one after a real disk-full corruption incident. Added the same check to install.sh.
- FIX (MEDIUM): the MRC bridge config self-heal path (update.sh) and
mrc/bridge/config.example.jsonstill defaulted to the old unencryptedmrc_port: 5000/use_ssl: false, stale since v1.0a2.111 changed the real default to5001/SSL. install.sh itself already had it right. - FIX (MEDIUM): re-running install.sh against an existing install silently reset the sysop's password (if the same username was retyped) or created a second, duplicate admin account (if it wasn't) — no warning either way. Existing accounts are now preserved unless
--forceis passed. - FIX (LOW): neither script checked for
systemctlbefore assuming systemd — added a clear fail-fast message instead of scattered "command not found" errors partway through a 9-step install. - FIX (LOW): update.sh's backup directory was created with default (world-readable) permissions and only
chmod 0700'd after.env.bak(SECRET_KEY, DB credentials) and everything else was already written into it. Now locked down at creation. - FIX (LOW): install.sh's nginx CVE-2026-42945 check tried to distinguish "vulnerable version, exploitable config" from "vulnerable version, safe config" using a crude "config contains both
rewriteandset" heuristic. Verified the CVE itself against NVD/vendor advisories (real, CVSS 9.2, confirmed version range) but the heuristic doesn't match the actual trigger condition (an unnamed PCRE capture + literal?in a rewrite's replacement, followed by another rewrite/if/set) and could have told a genuinely vulnerable config it was safe. Dropped the exploitability guess; now always recommends patching when the version is in range. - Documented
install.sh --uninstall(and--defaults/--force) in the README — previously undocumented anywhere despite being a real, working flag.
No behavior changes to the running BBS itself — every fix in this release is in the install/update tooling. Full test suite (1032 tests) still green throughout.
v1.0b2.152 — AKA announcement fix, plus a netmail send/receive correctness pass (July 2026)
Sysop-noticed oddity: some inbound sessions' M_ADR line only listed the default Fidonet address instead of all four configured network identities, while every outbound session correctly listed all four. Root cause: the AKA-lookup fix in v1.0b2.147 disposed the database connection before reading each network's address/domain fields instead of after, a detached-instance hazard that usually goes unnoticed (the values are normally already in memory) but can intermittently fail and silently fall back to a single default address. Moved the disposal to after all the fields are read. No automated regression test added for this one — reliably reproducing the exact timing needed to trigger a real SQLAlchemy detached-instance failure would need a heavier real-database test fixture than this module's existing mock-based ones; the fix itself is a straightforward reordering, safe by inspection.
Also did a netmail send/receive correctness pass while in this code (prompted by the M_GOT fix above). Found: the FMPT/TOPT kludges that carry a point system's point number (e.g. the ".5" in "1200:1/2.5") were parsed on receive and then silently discarded — our own outbound side never puts the point number inside the INTL kludge itself, only in these separate fields, so any point-addressed netmail lost its point number on import. The inbound listener also had its own separate, redundant re-derivation of the sender/recipient addresses that didn't use the (now-fixed) parser output at all. Both fixed; 4 new regression tests, each verified to fail without its corresponding fix.
v1.0b2.151 — Found it: M_GOT was sending a hard-coded 0 instead of the file's real timestamp (July 2026)
The actual root cause of the multi-month BinkP resend loop, found by comparing our code line-by-line against binkd's own real source. When ANetBBS receives a file and acknowledges it with M_GOT, the reply is supposed to echo back the file's name, size, AND timestamp — ours sent the timestamp as a literal 0 instead of the real value, in both the inbound listener and the outbound poller.
binkd's own matching logic (tfile_cmp() in its prothlp.c) requires an exact match on all three fields before it will recognize our M_GOT as acknowledging the file it sent, and only then does it remove the file from its own outbound queue. A real timestamp looks like 1784314217 — it never equals 0, so that match silently failed on every single file, every single session, regardless of anything else going on. The hub kept re-offering its entire backlog every poll because it never once got a M_GOT it could actually match, no matter how correctly ANetBBS otherwise received and acknowledged every file.
This fully explains the resend loop on its own — independent of session timing or the end-of-batch handshake work in v1.0b2.148-150, which remain valid improvements but were not the actual fix. 4 new regression tests, each verified to fail without the fix.
v1.0b2.150 — Respond faster after a BinkP transfer instead of waiting out most of a minute first (July 2026)
Real measurement against a real Fidonet hub showed the TCP connection consistently dying ~15 seconds after its last file, well under either side's configured wait (120s inbound, 60s outbound) — meaning our own confirmatory end-of-batch signal (v1.0b2.148) was often being sent well after that window had already closed. Shrunk the wait to 5 seconds on both the inbound listener and outbound poller, so that signal has a real chance of reaching the hub while the link is still alive.
This is a genuine improvement to session responsiveness, verified with 2 new regression tests (each confirmed to fail without the fix) — but it's not confirmed to be the actual fix for the hub's resend loop. Direct inspection of binkd's own source turned up a more likely explanation: binkd deletes a file from its outbound queue the instant it receives our M_GOT, independent of anything that happens afterward in the session — and we've confirmed via transcript that our M_GOT has always been sent correctly. That points toward a stuck or misconfigured outbound-queue file on the hub's own system, not something fixable in our protocol timing. Investigation continues.
v1.0b2.149 — Diagnostic logging: confirm whether the post-transfer M_EOB is actually reaching the hub (July 2026)
Follow-up to v1.0b2.148: the Fidonet hub's backlog still isn't shrinking even with that fix active. _send_cmd() logs its transcript line before attempting the actual socket write, so a failed write (e.g. the peer already closed the connection) can look identical in the transcript to a successful send. Added explicit success/failure logging around the proactive post-transfer M_EOB in both the inbound listener and outbound poller, to tell the two cases apart on the next poll. No behavior change.