• src/syncterm/Wren.adoc sr

    From Deuc¿@VERT to Git commit to main/sbbs/m on Monday, April 27, 2026 16:09:00
    https://gitlab.synchro.net/main/sbbs/-/commit/4adbdc8d6d1dbafb8877cd67
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/console.wren src/syncterm/scripts/load/wrentest.wren src/syncterm/scripts/syncterm.wren src/syncterm/term.c wren_bind.c wren_host.c wren_host.h wren_host_internal.h
    Log Message:
    SyncTERM: Wren HookHandle + main-loop hook compaction

    Foreign HookHandle returned by Hook.on*/Hook.every Ä no Wren-side
    constructor, so scripts can only remove their own hooks. Carries
    per-entry metrics (callCount, totalRuntime, min/maxRuntime) timed
    through xp_timer().

    Hook + timer entries are now heap-allocated structs reached through
    pointer arrays in state.hooks[]/state.timers[]. HookHandle.remove()
    releases the fn handle and links the entry onto a cleanup queue; wren_host_compact() drains that queue from the doterm() outer loop,
    shifts the entry out of its dispatch array, and frees regex
    resources. The struct itself stays alive until both compaction has
    run and Wren's GC has fired the foreign-class finalizer, so removed
    handles keep returning sensible metric reads until the script drops
    them.

    The unified wren_hook_entry struct discriminates hook vs timer via
    ev (extended with WREN_HOOK_TIMER), letting the dispatch
    infrastructure share one path for the lifetime + cleanup machinery.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Tuesday, April 28, 2026 04:20:00
    https://gitlab.synchro.net/main/sbbs/-/commit/e2a8307110280f8f5bef30e7
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/console.wren syncterm.wren src/syncterm/wren_bind.c
    Log Message:
    SyncTERM: Wren console Ä /mods command + REPL.modules

    Adds REPL.modules, a foreign static getter that walks
    modules->entries directly to enumerate every module currently
    loaded into the VM (including core, every embedded module, every
    user script, and anything pulled in via import). Skips empty
    slots and tombstones (key is UNDEFINED_VAL for both); non-string
    keys are filtered defensively.

    console.wren grows a /mods command that calls REPL.modules,
    sorts via a byte-wise stringLT_ helper, and prints the result.
    Wren's String doesn't implement <, so List.sort()'s default
    {|a, b| a < b} comparator aborts on string lists; the helper does
    ASCII-safe byte comparison and is reusable for other string sorts
    that come up later.

    Wren.adoc refreshed: documents the new Hook.dispatch_ contract
    ("hooks must run synchronously; wrap parking work in
    Fiber.new {...}.call()"), corrects the Modal Input section's
    description of nextEvent-from-a-hook (now detected and reported,
    not silently hung), describes REPL.eval's actual statement-keyword pre-classifier (was still describing the old try-expression-first
    flow), and adds REPL.modules + /mods + /? + /q to the command
    tables.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Tuesday, April 28, 2026 04:20:00
    https://gitlab.synchro.net/main/sbbs/-/commit/e2528de46b9d76fdceb6ea0d
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/console.wren syncterm.wren src/syncterm/term.c wren_bind.c wren_host.c wren_host.h wren_host_internal.h
    Log Message:
    SyncTERM: Wren API audit + Directory rework + doc completeness pass

    Three threads, committed together because they overlap in the same files:

    API shape audit
    ---------------
    * Input.next, Input.poll, Input.nextEvent Ä converted from getters
    to methods (Input.next(), Input.poll(), Input.nextEvent()). The
    rule "getters are for things that feel like variable access, not
    things that feel like they're doing something" Ä these block,
    poll, or park a fiber, so they're actions.
    * Directory.list Ä converted from method to a foreign Map getter.
    The directory's contents read like a property; indexing
    `Cache.list["RIP"]` returns the File or Directory handle for that
    name (or null), which composes naturally for tree traversal:
    `Cache.list["RIP"].list["icons.dat"]`.

    Directory rework
    ----------------
    * Directory.create(name) Ä now actually creates the file (was a
    no-op File-object factory). Uses C11 exclusive-create
    (fopen("wbx")) for race-free atomic creation. Returns null on
    any failure (file exists, invalid name, path too long, OS reject).
    * Directory.createDir(name) Ä added. Mirrors create() for
    subdirectories via MKDIR (which is naturally exclusive).
    * Directory.delete(name) Ä added. Parent-acts-on-child shape:
    removes the named entry (regular file or empty directory only;
    refuses symlinks, devices, FIFOs). Returns bool.
    * File.delete() Ä removed. The instance-method-that-zombies-its-
    receiver shape was awkward; Directory.delete(name) covers the
    case from the parent.
    * Directory.list now returns Files AND Directories Ä the C
    implementation always built a Map keyed by name with File values
    for regular files; this extends it to also emit Directory values
    for subdirectories, matching the documented intent.

    Live-handle registry
    --------------------
    A successful Directory.delete shouldn't leave outstanding handles
    to the removed entry quietly operating on stale paths. Each
    wren_file and wren_directory now self-registers on a doubly-linked
    list rooted on wren_host_state. Helpers fs_register_*,
    fs_unregister_*, fs_kill_*, fs_invalidate_subtree.

    Two layers of staleness protection on every File / Directory
    operation:

    1. dead flag Ä set by fs_invalidate_subtree when a parent's
    delete removes the entry (or marks an ancestor). file_check /
    dir_check (called at the top of every method) abort the fiber
    on dead.
    2. Per-call fexist() / isdir() Ä catches deletions that bypassed
    Directory.delete (other process, the user, another script).
    On miss: fs_kill_*(handle) (mark dead + unregister) + throw.

    Open-file exemption: a File between open() and close() skips
    the fexist() check (its fd is authoritative Ä Unix lets reads/
    writes continue past unlink, and Windows refuses to delete
    open files at all). fs_invalidate_subtree skips fp != NULL
    entries on the same logic. fn_File_close re-runs fexist()
    after fclose; if the path is gone, the handle becomes dead.

    Wren.adoc completeness pass
    ---------------------------
    Stale "see ciolib.h" references replaced with full reference
    content:
    * Codepage Ä every entry described, _b suffix explained.
    * Key Ä full grouped tables (ASCII / cursor / modified Insert-
    Delete / modified arrows / function keys with all four modifier
    columns / synthetic markers).
    * Font Ä full 46-row table including the 1-31 unnamed-in-Wren
    slots that are still reachable numerically; "thin"/"swiss"
    font-style annotations explained.
    * Screen.supports, Screen.videoFlags Ä every flag described.
    * ConnType, Emulation, BBSListType, ScreenMode, AddressFamily,
    MusicMode, RipVersion, Parity, FlowControl, LogLevel, ExtAttr,
    LastColumnFlag, LogMode, StatusDisplay Ä all converted from
    bare name lists to descriptive tables.

    Corrections to wrong descriptions:
    * sxScroll Ä SIXEL scroll mode (not "smooth scroll" / DECSCLM).
    * blinkAltChars Ä repurposes attribute bit 7 to select the alt
    character set (not "animate alt-char-set on blink interval").
    * StatusDisplay Ä VT320 DECSSDT semantics (host-writable status
    line, not "verbose status showing connected host").

    Worked example replaced. The "auto-respond to a prompt" example
    was using onInput + manual line buffering with a logic bug that
    only checked for prompts on LF (so "Logon: " Ä which has no
    trailing LF Ä never matched). Replaced with a Hook.onMatch
    two-liner; added a smaller per-byte BEL-counter example that
    demonstrates onInput correctly without the broken pattern.

    Anchors added: [[hook-events]], [[modal-input]], [[codepage]], [[filename-policy]], [[directory-handle-staleness]] so the
    existing <<...>> cross-refs resolve.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Tuesday, April 28, 2026 06:00:00
    https://gitlab.synchro.net/main/sbbs/-/commit/feab4f3a1cac2087b0260c95
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/connected/console.wren src/syncterm/scripts/syncterm.wren wrentest.wren src/syncterm/term.c wren_bind.c wren_host.c wren_host.h wren_host_internal.h
    Log Message:
    SyncTERM: Wren result-queue framework + CTerm.suspended

    Generic completion queue: callable C-side request data + fiber
    handle + deliver/free callbacks travel through one mutex-protected
    FIFO, drained at the top of each doterm() iteration. The drainer
    walks each entry, skips fibers where Fiber.isDone (cached primitive
    handle), builds the Wren foreign right before wrenCall, and
    releases the handle + frees the data after. Workers can push from
    any thread; delivery is owner-thread only.

    Input.nextEvent now flows through the queue: dispatch_key/dispatch_mouse
    push an input_result carrying the raw key code or mouse_event and
    transfer the parked fiber handle. One-iteration latency on
    delivery, but the wrenCall is no longer fired mid-foreign-stack.

    Replaces the implicit "parking on Input.nextEvent claims the
    screen" behavior with an explicit CTerm.suspended Bool. Backed
    by a doterm() local; while true, the wire pump halts and bytes
    pile up in the conn buffer until the TCP window fills and the
    remote sees backpressure.

    When the suspend flag transitions back to false, doterm() credits
    the byte pump with all the bytes that would have drained at the
    emulated rate during the suspended window. Those bytes burst past
    the speed gate one per pump iteration until the credit runs out;
    the visible output catches up to where it would have been with no
    suspend. No-op when speed emulation is disabled.

    Adds T06 to wrentest.wren that exercises the queue end-to-end:
    parks a fiber on Input.nextEvent, ungets a sentinel key, sets +
    clears CTerm.suspended around the resume, and verifies the fiber
    captured the right KeyEvent. Also flips console.wren's launcher
    hook to the filtered Hook.onKey(Key.wrenConsole) form.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Tuesday, April 28, 2026 06:25:00
    https://gitlab.synchro.net/main/sbbs/-/commit/572a7ff6bf903c063edc99a6
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/wrentest.wren src/syncterm/term.c wren_host.c wren_host.h
    Log Message:
    SyncTERM: Hook.onInput String returns + filter spillover

    Extend Hook.onInput so a handler can return a String to replace
    the input byte with the string's bytes (up to 256; bigger
    replacements log a runtime error and the byte passes through).
    Bool true still drops, anything else still passes through.

    Decouple the wire-side buffer from recv_byte_buffer: a separate
    wire_buffer holds raw conn_recv_upto bytes, and the filter runs
    them into recv_byte_buffer until either input exhausts or output
    fills. When a replacement won't fit, the filter pauses on that
    input byte; the unprocessed wire-side tail stays parked in
    wire_buffer until the next recv_bytes() call drains something out
    and frees room for it. Spillover means recv_byte_buffer never has
    to grow past BUFFER_SIZE even with aggressive expansion.

    wren_host_dispatch_input now returns int Ä KEEP/DROP/N Ä and writes
    replacement bytes into a caller-provided buffer. The caller (wren_filter_input) commits N bytes only if they fit, otherwise
    backs out without consuming the input byte.

    Adds an LFCRLF hook to wrentest.wren that ticks a counter when it
    fires; report_() asserts the counter is positive, exercising the
    new WREN_TYPE_STRING dispatch branch end-to-end.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Tuesday, April 28, 2026 09:22:00
    https://gitlab.synchro.net/main/sbbs/-/commit/cf86f51e84049319c38da7a8
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/connected/console.wren src/syncterm/scripts/wrentest.wren
    Log Message:
    SyncTERM: WrenConsole.register for module-defined REPL commands

    Modules can plug in their own /<name> entries via
    WrenConsole.register(name, help, fn). The handler runs with the raw
    argument string (everything after the first separating space, or ""
    if none) inside a Fiber so a runtime abort surfaces as a logged
    error rather than tearing the console out from under itself.
    Re-registering a name overwrites; names can't contain spaces.

    /? now lists registered commands as continuation lines under the
    built-in "commands:" row, each annotated with the help text. WrenConsole.unregister(name) drops a registration (idempotent); WrenConsole.commands returns the sorted list of currently-registered
    names for tests + tooling.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Tuesday, April 28, 2026 09:22:00
    https://gitlab.synchro.net/main/sbbs/-/commit/b743852183205e720d9cc8f1
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/syncterm.wren wrentest.wren src/syncterm/wren_bind.c
    Log Message:
    SyncTERM: File.readLine + File.writeLine

    readLine() reads from the current offset to the first LF (0x0A) or
    EOF and returns the bytes with any trailing LF removed. Offset
    advances past the LF on a hit, or to EOF if none found. Returns
    null when already at EOF so a typical loop terminates cleanly; a
    blank line is the empty string, distinct from EOF.

    writeLine(s) writes the bytes of s at the current offset, then
    appends an LF. Offset advances past the LF. No special-casing if
    s already ends in LF Ä writeBytes() is the way to opt out of the
    trailing-LF behavior.

    Implementation chunks reads through a 512-byte buffer with
    geometric growth on long lines, so a 100GB file with short lines
    doesn't allocate the whole remainder up front.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Tuesday, April 28, 2026 17:47:00
    https://gitlab.synchro.net/main/sbbs/-/commit/9f1ed0221b7e8d4c67967ede
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/syncterm.wren wrentest.wren src/syncterm/wren_bind.c
    Log Message:
    SyncTERM: File.sha1 / File.md5 + missing docs and tests

    File.sha1 and File.md5 hash the file's full content via xpmap and
    the existing src/hash sha1.c / md5.c. Zero-length files are
    special-cased to an empty buffer because xpmap rejects 0-sized
    maps. Returned as raw digest bytes (Wren strings are byte-safe)
    so they compare directly against SFTPEntry.hash from the
    sha1s@syncterm.net / md5s@syncterm.net SFTP extensions; format hex
    yourself if you need it for display.

    Also catches Wren.adoc + wrentest.wren up to recent work that
    shipped without docs / tests:

    - New Wren.adoc sections for Platform, Timer (+ TimerElapsed), SFTP
    (+ FileFlag, SFTPEntry, SFTPStat, SFTPHandle, SFTPError, and the
    shared async-op pattern used by Timer / SFTP / Input.nextEvent).
    File doc gets the sha1 / md5 row added.

    - wrentest.wren coverage:
    Platform.name returns non-empty String.
    File.sha1 / File.md5 of an empty file (exercises the
    zero-length code path) and of "hello" (exercises xpmap).
    Timer.trigger(ms=0) parks a fiber, the doterm sweep marks it
    past-due, the drain resumes with a TimerElapsed.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Thursday, April 30, 2026 00:47:00
    https://gitlab.synchro.net/main/sbbs/-/commit/5c5edcef06b965f5b689ae14
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/syncterm.wren ui_app.wren ui_demo.wren ui_pane.wren ui_widget.wren ui_widget_test.wren wrentest.wren src/syncterm/wren_bind.c wren_bind_screen.c wren_bind_screen.h
    Log Message:
    SyncTERM: Wren Input.wake + App.post; UI fit-and-finish; doc the language

    New host primitive: Input.wake(fiber, value) Ä queue a fiber resumption
    on the same result queue Input.nextEvent and Timer.trigger drain
    through. Safe to call from Hook.onInput; the resume happens on the
    next main-loop drain, so a network-driven app (IRC, ticker, log
    viewer) can wake a UI fiber parked on Input.nextEvent when remote
    bytes change visible state. If the target is also the parked-fiber
    slot, wake clears it (compared via wrenValuesSame on the underlying
    Value, since handles wrapping the same fiber are distinct pointers
    but equal Values) so the next Input.nextEvent re-arms cleanly.

    App.post() / App.post(value) wraps Input.wake against a captured
    _runFiber; App.onPost=(fn) is the user-visible handler.

    Container.focusStep_ now returns false when the only focusable child
    is already focused, so a Pane wrapping a single ListView (or any
    nested single-focusable Container) no longer traps Tab inside itself
    Ä Tab bubbles up to the parent. New regression test in
    ui_widget_test.

    Pane.helpButtonRect_ suppresses the [?] button when neither onHelp
    nor helpText is wired. A button that does nothing is worse than no
    button. Demos that want the button now set helpText with relevant
    key hints (gallery, Checkbox, RadioGroup, SpinBox, TextInput, Form).

    Wren.adoc gains two top-level chapters before Quick Start: a Wren
    Language Reference (literals, statement-termination rules,
    classes/fields scope, fibers, modules, common pitfalls) and a Wren
    Standard Library reference (System / Object / Class / Bool / Null /
    Num / String / List / Map / Range / Sequence / Fiber / Fn) so other
    LLMs pointed at this doc don't have to chase wren.io fragments.
    Also fixes asciidoctor's `...` -> ellipsis substitution wherever
    three dots are Wren range / slice syntax (escaped via \\...).
    Documents Input.wake, App.post / App.onPost, the popStatus z-order
    (below modals), and the gatesActiveLayer two-axis layer model.
    Updates check.on glyph reference (now û, not þ).

    wrentest gains T07: Input.wake delivers two values (a foreign
    KeyEvent and a String) to fibers parked on plain Fiber.yield (no Input.nextEvent registration), exercising both the result-queue
    plumbing and the WrenHandle pin/release across types.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, May 03, 2026 21:44:00
    https://gitlab.synchro.net/main/sbbs/-/commit/76a3f347979081df508310b6
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/connected/keys_default.wren src/syncterm/scripts/syncterm.wren ui_list.wren ui_list_test.wren ui_pane.wren ui_widget.wren src/syncterm/term.c wren_bind.c wren_bind_conn.c wren_bind_conn.h
    Log Message:
    SyncTERM: move Alt-M to Wren; add ListView padding + Pane auto-sizing

    The Alt-M music-mode picker is now a Wren Hook.onKey handler in keys_default.wren that opens a Pane + ListView modal. The C
    case-block in term.c is gone; music_control() itself stays for
    the Alt-Z popup-menu's SM_MUSIC entry.

    C primitives added so the script can do its job:
    * CTerm.music = i (setter; clamps to legal range)
    * Host.musicNames (List<String> built from music_names[])
    * Host.musicHelp (returns music_helpbuf)

    UI library changes the picker exposed:
    * ListView always reserves a 1-cell padding between the frame and
    the items on the side that doesn't have a scrollbar (both sides
    when no scrollbar is shown at all). The previous behaviour let
    long items butt against the frame.
    * Widget.preferredWidth / preferredHeight -- new base getters
    returning null ("no preference, fill what's given"). ListView
    overrides them with the smallest cell budget that displays every
    item without truncation.
    * Pane.fitContent() sizes the pane around its single child's
    preferred size, including the title bar's required width and
    the corner-button cluster. titleAsBar mode reserves a 1-cell
    padding around the title (`title + 4`); frameTitle mode uses
    the existing `title + 6` (corners + brackets + spaces).
    * Pane.centerOnScreen() repositions the pane after fitContent.

    The Alt-M handler is now ~15 lines: build the list, add to pane,
    fit + center, run. No hardcoded widths, no manual inner-bounds
    math.

    Wren.adoc updated with the new accessors, the Widget / Pane /
    ListView additions, the title-mode geometry rules, and a small
    auto-sized list-in-pane example mirroring the music picker.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Monday, May 04, 2026 11:53:00
    https://gitlab.synchro.net/main/sbbs/-/commit/838332e35280aa8865a3ed7f
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/sftp_app.wren ui_list.wren ui_popup.wren
    Log Message:
    SyncTERM: ListView gets type-to-search, Ctrl-F/G, click-activate, tag mode

    Type-to-search: any printable codepoint grows a rolling buffer and
    jumps to the first item whose searchTextFor_ starts with it (case-
    insensitive ASCII fold). No-match falls back to just the new char.
    Buffer resets on any nav / activation key.

    Ctrl-F: prompts via a new compact Find popup (3 rows tall, title in
    the top frame border, full innerBounds row for the input, no buttons), case-insensitive substring search wrapping the list. Ctrl-G repeats
    the last query.

    Click-to-activate: button1Click on a row both selects and fires
    onSelect, matching UIFC's ulist.

    Tag mode: opt-in via selectionMode = "tag". Per-item flags toggled
    by Space; tagged getter returns the indices. 1-cell marker column
    uses theme tag.on / tag.off glyphs.

    searchTextFor_(item) is the subclass hook for what users type
    against; defaults to formatItem(item, 1024). BrowserListView and
    QueueListView override it to point at the bare filename instead of
    the chip-prefixed display line.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Monday, May 04, 2026 13:07:00
    https://gitlab.synchro.net/main/sbbs/-/commit/9480df8001d2548b3d9bbf0b
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/connected/keys_default.wren src/syncterm/scripts/syncterm.wren ui_popup.wren src/syncterm/syncterm.c term.c wren_bind.c wren_bind_conn.c wren_bind_conn.h wren_host.c wren_host.h wren_host_internal.h
    Log Message:
    SyncTERM: move disconnect cluster (Alt-X / Alt-H / Ctrl-Q / [X]) to Wren

    The four hangup-and-quit keys are now driven from
    keys_default.wren via a new DisconnectFlow helper that raises a
    Confirm popup ("Disconnect... Are you sure?") and, on yes, calls Conn.endSession(exitApp). doterm() picks the request up at the
    top of its next iteration, runs the (UI-free) C cleanup, and
    either returns to the bbslist (Alt-H / Ctrl-Q) or exits
    syncterm (Alt-X / window-close).

    Ctrl-Q is gated to text-mode terminals (curses / ANSI) at module-
    load time via the new Host.textTerminal predicate; graphical
    backends keep Ctrl-Q as a normal control byte.

    C-side check_hangup is now pure cleanup Ä the confirm popup and
    screen save/restore moved to Wren, the only caller was doterm,
    and the syncmenu's SM_DISCONNECT / SM_EXIT cases are now
    deduped onto the same primitive. check_exit keeps its UIFC
    "Are you sure you want to exit?" popup because bbslist + menu.c
    ESC handlers reach it from outside the disconnect-cluster path
    where Wren has already asked.

    Wren bindings: Conn.endSession(exitApp), Host.textTerminal, Key.ctrlA..Key.ctrlZ (full set; not just the two I happened
    to need), Popup.onDismiss=(fn) so a fresh App can drive a
    standalone Confirm without an enclosing run loop.

    Pending-disconnect drain runs at the top of the doterm outer
    loop after wren_result_drain Ä the parked DisconnectFlow fiber
    resumes during the result drain and calls Conn.endSession from
    there, not from a wren_host_dispatch_key frame, so a single
    post-drain check is what makes the hangup land in the same
    iteration as the user's Yes click.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 09:24:00
    https://gitlab.synchro.net/main/sbbs/-/commit/86562f55ac003f4ac4256fee
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/menu_bbs_editor.wren menu_settings_ui.wren menu_sort_profiles.wren menu_ui.wren ui_popup_test.wren
    Log Message:
    wren: keep owning menus behind nested dialogs

    MenuUi choice helpers dismissed their pane before returning a selected
    value. Repeating editors therefore opened prompts and submenus over the
    main screen instead of over the menu that owned the selected action.

    Add callback forms that run the selected action while the choice pane
    remains on the modal stack, then dismiss it after nested UI returns.
    Use them throughout settings, web-list, font, palette, logging, and sort profile editors so each parent remains visible in its inactive state.

    Document the stacking behavior and cover the callback lifetime with a modal-stack regression test.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 09:24:00
    https://gitlab.synchro.net/main/sbbs/-/commit/514458c7a94fc3970b174f26
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/menu/main_menu.wren Log Message:
    wren: update main menu hints with focus

    The Wren main menu rendered directory editing commands in the footer
    regardless of which pane owned focus. SyncTERM Settings consequently
    advertised F2, copy, paste, insert, and delete operations which do not
    apply to that menu.

    Track the active hint set in the classic footer. Retain the directory
    commands and conditional paste hint for the directory, while showing
    only Help and Exit for SyncTERM Settings, matching UIFC's flag-driven
    bottom line.

    Preserve the current menu's hint set while editing the comment and
    restore the destination menu's hints when comment editing ends.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 09:24:00
    https://gitlab.synchro.net/main/sbbs/-/commit/a4abcbd3d70efae9752a948d
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/menu/main_menu.wren Log Message:
    wren: repaint comment editor on focus changes

    The comment input selected its centered inactive or left-aligned edit
    rendering from the parent footer's focus. The footer did not gate its
    child's activity, so a focus change did not invalidate the cached input surface. Leaving an unchanged comment could therefore leave the edit
    lightbar visible until an unrelated update forced a repaint.

    Make the footer a focus gate so its input repaints immediately when the
    footer gains or loses focus. Restore the established full-row comment
    colors and the normal-background margins around the active input.

    Use codepoint count when centering comments so multibyte text agrees
    with the input cursor and the cell-oriented painter.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 09:24:00
    https://gitlab.synchro.net/main/sbbs/-/commit/a51e13fee68a1ca689b960d6
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/menu/main_menu.wren src/syncterm/scripts/ui_input.wren ui_input_test.wren
    Log Message:
    wren: restore comment replace-all entry

    UIFC entered an existing comment through K_EDIT with its current text
    in an initial replace-all state. Typing or deleting replaced the
    complete value, while cursor movement or mouse positioning preserved it
    and continued ordinary editing. The Wren editor always appended at the
    end and kept the whole field in input colors.

    Add an explicit transient select-all state to TextInput and activate it
    when the main-menu comment receives focus. Keep replacement edits to one onChange callback and dismiss the state through navigation, clipboard,
    help, and focus-traversal paths.

    Highlight only the existing comment while replacement is pending, then
    restore normal menu colors after the cursor is positioned. Document the
    new TextInput contract and cover replacement, deletion, movement, mouse positioning, and value reassignment.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 09:24:00
    https://gitlab.synchro.net/main/sbbs/-/commit/efc8c15aa023d5de7bec3e7a
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/menu/main_menu.wren src/syncterm/scripts/menu_bbs_editor.wren ui.wren ui_input.wren ui_input_test.wren ui_popup.wren ui_popup_test.wren
    Log Message:
    wren: restore K_EDIT prompt behavior

    The UIFC menu passed K_EDIT for 34 existing-value inputs. The initial
    Wren migration only assigned those values to TextInput, causing typing
    to append at the end. This affected directory fields, program settings,
    sort profiles, web lists, font names, palette components, and repeated
    Find queries.

    Add SelectOnFocusInput to preserve normal TextInput assignment while
    providing UIFC-style existing-value editing. Select the value whenever
    the widget gains focus, highlight only selected text, and return to
    ordinary colors after cursor movement. Use it for Prompt, Find, the
    palette component editor, and the main-menu comment editor.

    Reselect palette values after a percent reset, export the new input and
    Find through the public UI module, and update the Wren documentation.
    Replace the popup test that expected append-at-end behavior with
    replacement and movement coverage.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 09:24:00
    https://gitlab.synchro.net/main/sbbs/-/commit/18566cfa9a000da6a7c06113
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/connected/font_pick.wren src/syncterm/scripts/menu_bbs_editor.wren menu_settings_ui.wren ui_draw_test.wren ui_list.wren ui_pane.wren ui_picker.wren
    Log Message:
    ui: centralize shadow-aware pane sizing

    The connection settings editor retained a nearly full-screen
    rectangle after its UIFC list was replaced. Fixed-size Help panes
    also failed to reserve the two columns needed by the Wren shadow
    renderer.

    Make Pane own constrained content layout. Measure children again
    after assigning their viewport so ListView scrollbar overhead is
    included, provide shared screen-fitting operations for natural and
    fixed sizes, and leave ListView responsible only for its natural
    dimensions.

    Use the shared layout for the directory editor, generic and font
    list pickers, File Locations, and Build Options. Add pane geometry
    tests and document the current sizing contracts.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 09:24:00
    https://gitlab.synchro.net/main/sbbs/-/commit/c97924540b81e05079330cb5
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/ui_input.wren ui_input_test.wren ui_popup_test.wren
    Log Message:
    ui: show the end of long prompt values

    Prompt initializes a TextInput value before assigning its bounds. The
    cursor was placed at the end, but the horizontal viewport remained at
    zero because no field width existed yet. This displayed the value prefix
    while clamping the visible cursor into the field, then jumped on the
    first cursor movement.

    Recalculate the TextInput viewport whenever its width is assigned or
    changed. Start from the unscrolled position before ensuring visibility
    so long values consistently show their trailing characters and a cell
    for the insertion cursor.

    Cover both direct TextInput layout and the long initial-value Prompt
    path, and document the viewport contract.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 09:24:00
    https://gitlab.synchro.net/main/sbbs/-/commit/11d04f312ebf17c9954dcb16
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/menu_ui.wren ui_popup.wren ui_popup_test.wren
    Log Message:
    ui: size prompts for their input fields

    MenuUi gave every prompt a fixed 34-column minimum and otherwise
    sized it only from the label. This left URI, path, password, modem,
    and other long-capacity fields unnecessarily narrow. UIFC instead
    included the maximum input length and capped the result to the screen.

    Add Prompt.sizeForInput and have the MenuUi wrappers use maxLen as
    the requested field width. Keep the compact minimum for short fields,
    size direct Prompt helpers from their initial values, and correct popup
    bounds so an oversized request cannot exceed the standard margins.

    Add coverage for compact and full-width menu prompts, input layout,
    and right-shadow clearance. Document the shared sizing contract.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 09:24:00
    https://gitlab.synchro.net/main/sbbs/-/commit/81ab6d872552f58b9e865a20
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/menu_settings_ui.wren src/syncterm/uifcinit.c uifcinit.h wren_bind_fs.c src/uifc/filepick.c filepick.h
    Log Message:
    ui: select the current font in the file picker

    Font Details always opened the native file picker at the process
    current directory. Replacing a configured font therefore discarded the
    useful location and selection already stored in the font record.

    Allow the single-file picker initial path to name an existing file. It
    opens the containing directory and initializes the file-list cursor and
    scroll position to the file basename. Directory initial paths and the multi-file picker retain their existing behavior.

    Pass the configured font path from the Wren menu, falling back to the
    current directory only for an unset font slot, and document the expanded Host.pickFile contract.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 13:28:00
    https://gitlab.synchro.net/main/sbbs/-/commit/6c09071ae20ae8d08f241919
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/picker/file_picker.wren src/syncterm/scripts/ui_list.wren ui_list_test.wren
    Log Message:
    wren: fix picker list focus and positioning

    Keep both sides of the file picker in the normal active palette while
    showing the selection lightbar only in the list that owns focus. Preserve
    the existing always-visible selection default for other ListView callers.

    When a selection is restored before list bounds are known, center it during initial layout if it falls outside the first viewport. Continue using the existing minimal scrolling behavior for subsequent keyboard navigation.

    Document both ListView controls and cover focused rendering and initial viewport positioning in the Wren UI tests.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 13:28:00
    https://gitlab.synchro.net/main/sbbs/-/commit/73886f9d8d8507216cdf7aed
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/ui_help.wren ui_help_test.wren ui_list.wren ui_list_test.wren ui_logview.wren ui_logview_test.wren
    Log Message:
    wren: improve scrollbar mouse navigation

    Make ListView track clicks jump to the proportional viewport position
    instead of consuming the click without moving. Shift its selection with
    the viewport so the focused lightbar remains on the same screen row.

    Treat wheel events over a scrollbar as page navigation in ListView, Help,
    and LogView while retaining their existing item or line increments over content. Leave button drag handling unchanged so each control preserves
    its current text-selection or thumb-drag behavior.

    Document the mouse rules and add coverage for track clicks and page-wheel navigation across all three scrollbar consumers.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 13:28:00
    https://gitlab.synchro.net/main/sbbs/-/commit/f56a1ff1d1f3b79c4cd61c96
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/picker/file_picker.wren src/syncterm/scripts/ui_draw.wren ui_draw_test.wren ui_list.wren ui_list_test.wren
    Log Message:
    wren: correct scrollbar click handling

    Map scrollbar track clicks through the inverse of the thumb placement calculation so a one-cell thumb lands on the clicked row instead of the
    row above it.

    Treat the separator column between list content and the scrollbar as
    inert. The picker keeps its inactive-pane focus behavior while routing
    only actual scrollbar clicks through the scrollbar handler.

    Document the interaction and cover both cases with Wren tests.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 13:28:00
    https://gitlab.synchro.net/main/sbbs/-/commit/b252c42d8aeb661e76a3954e
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/menu/menu_host_ui.wren Log Message:
    wren: center host progress text

    Host progress panes painted every status line at the first interior
    column. This left connection messages visibly off-center even though
    the pane itself was centered.

    Wrap status text to the padded interior width, size the pane from the
    wrapped row count, and center every resulting line. The retained App
    repaints its full-screen backdrop before each update, so replacement
    panes can resize without leaving the previous frame behind.

    Document the progress-pane layout for menu VM implementations.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 13:28:00
    https://gitlab.synchro.net/main/sbbs/-/commit/6d5032ddff4500878c4e210f
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/connected/scrollback_view.wren src/syncterm/scripts/wrentest.wren
    Log Message:
    wren: isolate scrollback mouse events

    The connected scrollback viewer added its events to the active CTerm
    mask. When a BBS enabled mouse tracking, raw motion kept waking and
    repainting the viewer, while drag move/end events could be absent and
    strand text selection.

    Replace the mask while the viewer is active with exactly its drag and
    wheel events, including the complete drag sequence. Restore the exact
    caller mask on exit instead of reconstructing it from CTerm state. This
    also preserves subscriptions belonging to a surrounding Wren App.

    Document and test the viewer mask.

    Fixes ticket 270

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Monday, July 20, 2026 11:51:00
    https://gitlab.synchro.net/main/sbbs/-/commit/f99bbe4180ffe4156ce8b815
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/menu/main_menu.wren Log Message:
    wren: initialize the startup menu backdrop

    Startup web-cache progress runs after the menu VM is initialized but
    before MainMenu.run() creates the interactive application. The status
    pane therefore saved and overlaid the uninitialized text-mode screen.

    Have the built-in main_menu module paint only its full-screen backdrop
    and title bar when the module loads. Startup alerts and progress panes
    then save and restore initialized menu chrome without constructing the
    footer or loading the directory.

    Document the startup rendering order.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Monday, July 20, 2026 11:51:00
    https://gitlab.synchro.net/main/sbbs/-/commit/793574a598a0d1140900f72c
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/picker/file_picker.wren src/syncterm/scripts/sftp_app.wren ui_list.wren ui_list_test.wren
    Log Message:
    wren: preserve list view across refreshes

    ListView.items= reset the selection and scrollTop whenever a caller
    rebuilt its row labels. Callers then restored the selected index, causing ensureVisible_() to place that row at the bottom of the viewport.

    Preserve the selected index and viewport when replacing a populated
    list, clamping only when the replacement is shorter. Add resetItems() for actual collection navigation and use it when changing file-picker and
    SFTP directories.

    Document the two replacement modes and test preservation across both an
    item refresh and subsequent layout.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Monday, July 20, 2026 11:51:00
    https://gitlab.synchro.net/main/sbbs/-/commit/e90c365ca2b71fd852f1bc21
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/connected/transfer_app.wren transfer_pick.wren src/syncterm/scripts/menu_ui.wren ui.wren ui_popup.wren ui_popup_test.wren
    Log Message:
    wren: restore compact text entry dialogs

    Ordinary menu text entry used the general Prompt popup, which repeated
    matching title and field labels, added padded message rows, and exposed
    OK and Cancel buttons despite Enter and Escape already submitting and cancelling the field.

    Add a UIFC-style LinePrompt with the label and input sharing one interior
    row. Use it for menu editing and transfer filename entry, while retaining Prompt for host dialogs that carry a separate explanatory message.

    Center the custom layout without Pane's single-child fitting helper so
    its calculated input bounds are not replaced with the full pane interior. Document the widget and cover its geometry and dismissal behavior.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Monday, July 20, 2026 11:51:00
    https://gitlab.synchro.net/main/sbbs/-/commit/7d66cd8aec7cdab6c6f4a2e4
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/menu/main_menu.wren src/syncterm/scripts/auto/picker/file_picker.wren src/syncterm/scripts/classic_theme.wren syncterm.wren syncterm_picker.wren ui_app.wren ui_widget_test.wren src/syncterm/wren_bind.c wren_bind_fs.c wren_bind_picker.c wren_bind_picker.h wren_menu_host.c wren_picker_host.c
    Log Message:
    wren: apply the configured theme to every App

    Apps outside the main menu previously inherited Theme.default, while
    the menu and picker wired ClassicTheme independently. This gave
    connected and standalone dialogs different selected-button colors from
    the rest of SyncTERM.

    Expose the six configured palette indexes as read-only Host.themeColors
    in all three Wren VMs. Have App.new() build ClassicTheme.current from
    them. Remove the menu and picker-specific theme injection and picker
    request color copy while retaining explicit app.theme overrides and live settings updates.

    Document the program-wide default and test that a new App receives the configured Classic Theme.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Monday, July 20, 2026 11:51:00
    https://gitlab.synchro.net/main/sbbs/-/commit/dbd75c66a2f45238f716fe04
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/menu/menu_host_ui.wren src/syncterm/scripts/auto/picker/file_picker.wren src/syncterm/scripts/ui_draw.wren ui_draw_test.wren ui_help.wren ui_list.wren ui_logview.wren ui_pane.wren ui_popup.wren ui_popup_test.wren ui_style.wren ui_style_test.wren ui_widget_test.wren
    Log Message:
    wren: classify pane frames by purpose

    Frame selection was exposed as single versus double, and Popup forced
    every dialog into the single-line family. This made alerts, prompts, and standalone choices diverge from UIFC, where control-bearing lists and
    inputs use double-line borders while Help and status displays use
    single-line borders.

    Replace framePreset and the visual glyph prefixes with the semantic
    frameKind values control and display. Default Pane and Popup to control,
    then select display explicitly for Help, transient status overlays, and
    startup progress. Reject unknown kinds instead of silently treating them
    as single-line frames.

    Give scrollbar separators their own glyph identifier and update the file picker's structural dividers to use the control family. Document the new
    theme contract and test the registry, frame rendering, validation, and
    widget assignments.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Monday, July 20, 2026 11:51:00
    https://gitlab.synchro.net/main/sbbs/-/commit/f59f9a6274e8b80389e490b6
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/auto/menu/menu_host_ui.wren src/syncterm/scripts/ui_progress.wren ui_progress_test.wren
    Log Message:
    wren: preserve web progress column alignment

    The web-cache formatter pads each list name to a 20-cell field, but the
    menu host centered every complete status row independently and resized
    the pane from the longest current row. Different state and counter
    lengths therefore moved the name and colon columns between rows and
    updates.

    Restore the fixed 74-column progress pane used by the UIFC presentation,
    capped to the normal screen margins. Render every formatted and wrapped
    row from one padded left edge so the C formatter's columns remain
    stable.

    Move the reusable text body into the existing progress module, document
    the geometry, and test unequal status rows with aligned name columns.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Monday, July 20, 2026 16:56:00
    https://gitlab.synchro.net/main/sbbs/-/commit/efef12fb850785555fea36d3
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/ui_app.wren ui_widget.wren ui_widget_test.wren
    Log Message:
    Redraw complete UI trees after theme changes

    Replace top-level dirty marks with non-bubbling recursive widget-tree invalidation. Theme previews previously repainted modal and root containers without invalidating their cached descendants, leaving inactive widgets in
    the previous theme until another state change dirtied them.

    Include transient status overlays in complete redraws and parent them to the App so they inherit its selected theme. Document the repaint contract and
    cover descendants in both the background root and modal stack.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Tuesday, July 21, 2026 09:28:00
    https://gitlab.synchro.net/main/sbbs/-/commit/5651d5592e5a5ffb7b320a04
    Modified Files:
    src/syncterm/Wren.adoc src/syncterm/scripts/ui_app.wren ui_widget_test.wren
    Log Message:
    Discard modals left by failed handlers

    Snapshot the App modal stack before each synchronous event dispatch.
    When a handler aborts after pushing a dialog, remove frames outside the pre-dispatch common prefix so an orphaned pane cannot consume the next
    Escape or Backspace.

    Keep modals that the failed handler successfully dismissed, preserving completed close operations and the existing Key.quit recovery behavior. Document the error-boundary contract and add regression coverage for the
    next Escape after an abort.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net