ANetBBS Changelog
Versions are internal build numbers. Public releases are tagged
separately. Current release: v1.0b2.33 (July 2026). Full release: August 1 2026.
v1.0a2.166 — Fix ZMODEM first-attempt failures (June 2026)
features/xfer.py: Removed--escapefrom ZMODEMsend_flags. With--escape,szsends a ZSINIT frame to negotiate extended character escaping; SyncTERM (and many other terminal emulators) responds with ZRINIT instead of ZACK, stalling the handshake and causing sz to fail or retry. Without--escapethe handshake is a clean ZRQINIT → ZRINIT → ZFILE sequence and transfers succeed on the first attempt.szstill escapes XON, XOFF, and DLE by default.--binaryis kept to prevent newline translation of binary data.
v1.0a2.165 — (superseded — do not use)
v1.0a2.164 — Fix /imsg/directory crash + RSS short-URL + title word-wrap (June 2026)
templates/imsg/directory.html: Fixed 500 crash on/imsg/directory—current_appis not a Jinja2 global in Flask, causingUndefinedErrorfor any admin visiting the page. Replaced withurl_for('admin.registry_self'). Also corrected the endpoint name (admin.registry_self_registerdoesn't exist).web/rss.py: Addedredirect_bpwithGET /r/<item_id>— unauthenticated 302 redirect to the original RSS article URL. No new DB model; usesRssItem.idas the short code.features/bbs_ui.py_rss_view_item: Replaced truncated raw link with a short URL ({web_base}/r/{item.id}) that always fits in 80 columns. Title now word-wraps at 74 cols instead of hard-truncating.web_app.py: Registeredredirect_bp.
v1.0a2.161 — Fix mixed-art \n stripping; 80-col viewer; ASCII status bar (June 2026)
features/anedit.pylaunch_aneview: Changed\nstrip logic — only strips for pure flat block art (block chars present, no cursor-pos sequences). Cursor-pos art (including mixed flat+cursor-pos like ConstructiveChaos) keeps\nso that flat header sections (e.g. the CONSTRUCTION logo) aren't collapsed into a single overflowing row. Cursor-pos absolute positioning is unaffected by artifact\nbetween sequences.features/anedit.py_ViewerScreen: Fixed all widths from_W=79to80— header bar, status bar, and_ansi_trunclimit. Prevents the rightmost art column from being clipped.features/anedit.py_ViewerScreen.draw_status: Replaced↑↓Unicode arrows (rendered as??in SyncTERM) withPgUp/PgDnASCII text.web/render_msg.py: Same condition change as terminal — strip\nonly for flat block art without cursor-pos. Fixes ConstructiveChaos logo being missing in web view.
v1.0a2.160 — VT renderer for terminal; flat-art \n fix; borderless ANView (June 2026)
features/ansi_html.py: Refactored VT engine into_run_vt(text)(returns cell grid). Addedto_ansi_lines(text, width=80)— runs body through VT renderer and returns a list of terminal-ready ANSI-coloured strings. Added_HEX_TO_FG/_HEX_TO_BGreverse-palette maps._to_html_vtnow calls_run_vtinternally.features/anedit.py_ViewerScreen: Removed box-drawing border. Row 1 = dark header bar (subject), rows 2–23 = 22-line content area (up from 17), row 24 = status/hints bar. No side borders or separator lines.features/anedit.pyANView: Overrides_ensure_visible,_scroll_view,_pgup,_pgdn,_doc_start,_doc_endto use the new 22-line viewport.features/anedit.pylaunch_aneview: Now handles CP437 decode + pipe-code conversion + art detection internally. Strips\nfor cursor-pos or block-art bodies before callingto_ansi_lines(). All message types (ANSI art, cursor-pos art, plain text) render in the scrollable ANView — the raw full-screen display path is gone.features/bbs_ui.py: Removed cursor-pos detection and raw display path. All messages route tolaunch_aneview().web/render_msg.py:render_msg_body/render_msg_body_richnow also strip\nfor flat block-art (CP437 block chars, no cursor-pos) before VT rendering. Fixes horizontal-bar scatter for flat art in web view.echomail/qwk.py: At import time, if body contains ESC (\x1b),\xe3record-boundary bytes are stripped (not converted to\n). Real line structure in ANSI art comes from embedded\r\n. Plain-text bodies unchanged (\xe3→\n).
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.