ANetBBS Changelog
Versions are internal build numbers. Public releases are tagged
separately. Current release: v1.0b2.58 (July 2026). Full release: August 1 2026.
v1.0a2.159 — Fix QWK art scatter: strip record-boundary \n; pipe colors in terminal (June 2026)
- Root cause: QWK
0xE3record separators are stored as\nin the DB. When the body has cursor-positioning sequences, those\ncharacters were inserting spurious line breaks between records, moving the cursor to the wrong row before the next\x1b[r;cHcould reposition it. Each record's content landed on the wrong visual row, producing the scattered-fragment display. web/render_msg.pyrender_msg_body/render_msg_body_rich: Now usesto_html()fromfeatures/ansi_html.py(VT renderer) instead of the local streaming_ansi_to_htmlthat silently dropped cursor-pos sequences. When cursor-pos sequences are detected after CSI-fix and pipe-code conversion,\nchars are stripped before calling the VT renderer. Flat-art and plain-text messages continue to use the streaming path.features/bbs_ui.pyread_echo_area: (1) Removed_BLOCK_ARTfallback — only messages with detected cursor-positioning sequences trigger full-screen raw display; flat art goes to scrollable ANView. (2) For cursor-pos art:\nstripped frombody_unicodebefore streaming to terminal. (3) Added|NNpipe color → ANSI SGR conversion applied tobody_unicodebefore display (handles Synchronet/Mystic pipe codes in message signatures and art).
v1.0a2.158 — Strip SAUCE record from ANSI art before rendering (June 2026)
echomail/qwk.py_parse_messages_dat: Strip the SAUCE metadata record at QWK import time. At the first\x1a(Ctrl+Z) byte in the decoded body, truncate — everything from\x1aonward is the binary SAUCE header (title, author, group, date, resolution fields) that, when passed to a renderer, appears as random ANSI sequences and scatters content across the display. Existing DB messages are handled at display time.features/bbs_ui.pyread_echo_area: Strip the SAUCE record from the body before CSI fix and CP437 decode. Applies to all messages already in the DB regardless of when they were imported.features/ansi_html.pyto_html: Strip the SAUCE record for string input. The bytes-input path already handled\x1aSAUCEstripping; this adds the same protection for the common string path used when rendering messages from the DB.
v1.0a2.157 — ANSI art full-width VT renderer + block-char detection (June 2026)
features/ansi_html.py_to_html_vt: VT renderer now renders every row to the full WIDTH (80 columns) instead of stopping at that row'smax_c. Previously, rows with sparse content (art that writes only a few characters at specific columns) appeared as narrow bars in the browser; now all rows are consistently 80 chars wide, preserving the 2-D portrait shape.features/ansi_html.pyto_html: Added_HAS_BLOCK_ARTdetection. When the body contains CP437 block/half-block characters (█ ▄ ▀ ░ ▒ ▓ or latin-1 mojibake equivalents U+00B0-B2, U+00DB-DF) together with any ANSI escape, the VT renderer is used instead of the streaming renderer. Covers flat ANSI art (no cursor-positioning) that previously got the fast streaming path.features/bbs_ui.pyread_echo_area: (1) Addedfto_HAS_CPOSregex (HP-style cursor-pos alias forH, was missing). (2) Added block-char detection as a fallback: CP437 block chars in latin-1 mojibake form trigger raw terminal display even when no cursor-pos sequences are detected. (3) Raw terminal output now converts\nto\r\nso flat-art rows return to column 0 on each new line.
v1.0a2.156 — ANSI art terminal raw display; web CSS fix; ANView colors (June 2026)
features/bbs_ui.pyread_echo_area: Detect cursor-positioning sequences in the decoded body. If found (ANSI art), display raw to the terminal (\x1b[2J→ stream body →\x1b[25;1Hprompt) so full 2-D positioning and colors render exactly as the sender intended — matches Synchronet/SyncTERM behavior. Plain-text messages (no cursor positioning) still use the ANView scrollable frame.features/anedit.pylaunch_aneview: Change ANSI strip from removing ALL escape sequences to removing only cursor-positioning sequences ([H f A B C D G J K s u r]). SGR color/attribute codes (\x1b[...m) are now preserved so plain-text colored messages display with colors inside the ANView frame.features/anedit.py_ViewerScreen.draw_text: New ANSI-aware override. Uses_ansi_trunc()helper to truncate lines at_TWvisible characters (ignoring escape sequences) and\x1b[Kto erase trailing space rather than.ljust(). Prevents SGR sequences from being sliced mid-sequence and eliminates visible-width miscounts from ANSI bytes.features/anedit.py_ansi_trunc: New helper — truncates a string with embedded ANSI sequences to a maximum ofmaxlenvisible characters while preserving all escape sequences.templates/echomail/read.html: Fix CSS for ANSI body display. Changedwhite-space: pre-wrap(allowed long art rows to wrap mid-row, scrambling the image) towhite-space: prewithoverflow-x: auto. Removed the per-spanwhite-space: pre-wrapoverride. Fixedline-height: 1.0(was 1.5/1.2, adding unwanted gaps between art rows).
v1.0a2.155 — CP437 decode in ANView; VT ANSI renderer for web; scroll fix (June 2026)
features/anedit.pylaunch_aneview: Body was displayed as latin-1 mojibake (Ûinstead of█). Apply the same CP437 re-decode as the web path (encode latin-1 → bytes → decode cp437, keeping control chars 0x01–0x1F as-is) before stripping ANSI sequences. Block/line-drawing chars now render as proper Unicode in UTF-8 terminal sessions.features/anedit.pyANView._handle+_scroll_view: UP/DOWN arrows now scroll the viewport by 1 line immediately (was: moved cy without visible text change until cy left viewport). Space scrolls down a page. PgUp/PgDn/Home/End/Ctrl+Home/End all work. Replaced non-ASCII↑↓hint chars with ASCIIUp/Dn— SyncTERM displayed them as??.features/ansi_html.py: Added virtual-terminal renderer_to_html_vt. When cursor-positioning sequences (\x1b[H,\x1b[A/B/C/D,\x1b[2J, etc.) are detected in the body, routes to the VT renderer which maintains an 80-column cell grid, processes all cursor moves, and outputs the correct 2-D layout. Streaming renderer kept for plain-text messages. Result: ANSI art on the web viewer displays with correct positioning instead of as a scrambled linear stream.
v1.0a2.154 — Terminal ANSI fix, no-pager reader, ANView message viewer (June 2026)
features/bbs_ui.py: Three improvements to the terminal echomail reader. (1) Apply the same CSI-split fix from v1.0a2.153 at view time so QWK0xE3artifacts ([1;45metc.) don't appear in the terminal. (2) Remove the 18-line page-break (-- more (Enter, Q=back to list) --) — messages scroll freely. (3) AddR=ReplyandN=New Msgoptions after reading; both launch ANEdit with the appropriate pre-fills in the current area.features/anedit.py: Added_ViewerScreen,ANView, andlaunch_aneview. ANView is a read-only ANEdit-frame viewer: navigation keys scroll, R/N/Q exit with action codes, bottom border shows viewer hints, status bar showsRead-only · Ln:X/Y, cursor is hidden.
v1.0a2.153 — Fix ANSI sequences split anywhere by QWK line separator (June 2026)
echomail/qwk.py_parse_messages_dat+web/render_msg.pyrender_msg_body/render_msg_body_rich: v1.0a2.152 only fixed the case where QWK0xE3fell between ESC and[. The separator can fall anywhere in a CSI sequence — in the parameter string too (e.g.\x1b[1;\n37m). Replaced the narrow\x1b\n→\x1bstring replace withre.sub(r'\x1b\n?\[[0-9;?\n]*[@-~]', lambda m: m.group(0).replace('\n',''), ...)which strips\nfrom any position within a matched escape sequence, covering all split positions in one pass.
v1.0a2.152 — Fix ANSI sequences split by QWK line separator (June 2026)
echomail/qwk.py_parse_messages_dat: QWK byte0xE3(line separator) can fall between ESC (0x1B) and[when an ANSI CSI sequence straddles a QWK line boundary. After\xe3→\nconversion the sequence became\x1b\n[1;45m, which_ansi_to_html's regex couldn't match. Added.replace('\x1b\n', '\x1b')after the separator conversion to rejoin split sequences before storage.web/render_msg.pyrender_msg_body/render_msg_body_rich: same\x1b\n→\x1breplacement at render time so all messages already in the database (imported before this fix) render correctly without a migration.
v1.0a2.151 — Fix ANSI rendering in web echomail viewer (June 2026)
web/render_msg.py_decode_charset: CP437 maps byte0x1B(ESC) to U+2190 (←), breaking_ansi_to_html's\x1b\[regex so all ANSI color sequences leaked as visible text. Fix: after CP437 decode, restore bytes0x01–0x1Fto their original control-char code points. CP437 is a single-byte codec so azip(raw, decoded)rebuild is safe and exact.
v1.0a2.150 — QWK overhaul: nine bug fixes (June 2026)
Full audit and repair of the QWK subsystem.
poller.py: stamp_qwk_conf_numon outbound messages beforeQWKClient.poll(). Without it, all outbound echomail went to conference 0 (netmail) — hub discarded it.qwk_user.pyupload(): REP body decode now useslatin-1+replace('\xe3', '\n')— CP437 decode turned 0xE3 into π instead of a newline.qwk_user.pyupload(): addednetwork_id=area.network_idtoEchomailMessage—NOT NULLcolumn was unset;IntegrityErrorwas silently swallowed and all REP replies lost.qwk_user.pyupload(): movedEchoArea.queryoutside the parse loop (was running one DB query per message).qwk_user.py_build_qwk_blob: fixed body line separator\r\n→\xe3; encodingcp437→latin-1. QWK readers saw blank bodies.qwk_user.py_last_read_map:r.last_read_id→r.last_message_id. AttributeError was swallowed; every download included all messages ever.qwk.py_ftp_download: hub_id candidates now listed before packet_id — for DOVE-Net you downloadVERT.qwk, notANET.qwk.qwk.py_parse_messages_dat: skip killed messages (active_flag == 0xE2) after reading body bytes; also added defensive\r\n/\rnormalization.qwk.py_clean_body: extract@CHRS:/@CHARSET:value and return aschrskey — previously discarded, so UTF-8 messages from Synchronet displayed as garbled CP437._import_messagealready acceptedmsg_data.get('chrs'), it just never received it.
v1.0a2.149 — Echomail area sysop-only flag + security levels (June 2026)
- Echomail
EchoAreaForm: addedis_sysop_onlyBooleanField;min_access_levelalready existed in the form but wasn't exposed in the template. - Area edit form (
echomail/admin/area_form.html): Active, Subscribed, Sysop Only checkboxes now shown together in a flags box; Min Access Level field alongside with hint text. - Areas list (
echomail/admin/areas.html): Status column replaced with four separate columns — Active ✓/✗, Subscribed ✓/✗, Sysop Only 🔒/—, Level badge (color-coded by range). new_arearoute: now savesis_sysop_onlyandmin_access_levelon area creation.web/echomail.py: extracted_check_area_access(area)helper; applied to area view, thread, compose, and next-unread routes. Area index and compose dropdown filter by bothis_sysop_onlyandmin_access_level <= user.access_level.- New templates:
auth/verify_sent.html,auth/verified.html,auth/resend_verification.html. - Admin nav: new "SMTP / Email" link above Settings divider.
6 new web games
- Klondike Solitaire — full rules, undo, auto-foundation, win detection.
- Video Poker (Jacks or Better) — 5-card draw, pay table, multi-bet, hold mechanic.
- Texas Hold'em vs CPU — 4-player table, easy/medium/hard bluffing AI, full 7-card hand eval.
- Blackjack — hit/stand/double/split, 4-deck shoe, chip-click bet UI, dealer to 17.
- Slot Machines — 3 themed machines (Classic Bars, Lucky Fruits, Retro BBS) with animated reels.
- Galaga — Canvas arcade: enemy formations, dive attacks, tractor-beam boss capture, particle FX, touch controls.
v1.0a2.146 — Fix dial-out directory DB lookup in terminal context (June 2026)
- Root cause of dial-out directory not syncing:
_load_directory()in
features/dialout.pyqueried the DB without a Flask app_context. Terminal
processes have no ambient Flask context so the query raised silently
(except pass), always falling back to the hardcoded DEFAULT_DIRECTORY.
Addedwith _app().app_context():— same pattern as bbs_ui.py / menu_engine.py.
Both PeerBbs (web directory) and DialoutDestination entries now appear on
SSH/telnet.
v1.0a2.145 — BBS Directory: edit local entries + remove Who's Online (June 2026)
- Local BBS entries in the web admin (
/bbses/admin) can now be edited via a
modal form — all fields: name, hostname, port, web URL, location, software,
FTN address, description, active toggle. Previously delete/disable only. - Removed finger/who's-online polling entirely:
_do_finger,_refresh_peer,
background refresh, online count badges, "Who's on" button, admin columns.
view.htmlis now a clean BBS detail/connect page.
v1.0a2.144 — Dial-out dir sync + User access flags (June 2026)
- Terminal dial-out menu now reads from PeerBbs (web BBS Directory Local tab)
as primary source, with DialoutDestination as secondary. Entries added via
the web BBS Directory are now visible on SSH/telnet. - New
user_access_flagstable; sysop can suspend echomail, MRC, IRC, games,
QWK downloads, or file downloads per user from/admin/users/<id>/manage.
New table — upgrade-safe, no migration needed. Suspended users see a clear
message and are returned to the menu.
v1.0a2.143 — Fix ANSI menu action: @PAUSE@ pagination + end-of-screen hold (June 2026)
@PAUSE@in ANSI body now splits into pages; user presses any key to advance.
Previously the code passed through as literal text on screen.ansimenu action now forces a final key-wait after the screen displays so
the menu doesn't immediately redraw and erase the content._show_ansi_screengainsforce_pause=Falseparam;_act_ansipassesTrue.