• .claude/skills/javascript/SKILL.md

    From Rob Swindell (on Windows 11)@VERT to Git commit to main/sbbs/master on Sunday, May 24, 2026 15:52:35
    https://gitlab.synchro.net/main/sbbs/-/commit/f27b2e8270997abb2ada9598
    Modified Files:
    .claude/skills/javascript/SKILL.md
    Log Message:
    .claude/skills/javascript: document constant namespaces and terminal-capability checks

    Two new subsections after load()/require(): one breaks down where
    constants come from (host-injected globals like LOG_*/INVALID_SOCKET, class-static properties like FileBase.DETAIL/SORT and CryptContext.ALGO_*,
    and JS-library files under exec/load/ that need load()/require()); the
    other distinguishes console.term_supports(USER_UTF8) (live connection capability) from user.settings & USER_UTF8 (stored preference) and
    explains when to use which.

    Triggered by C-vs-JS namespace confusion (bare UTF8 in sbbsdefs.h vs
    USER_UTF8 in exec/load/userdefs.js, same (1<<29) value) hit during
    LLM-guru language-output work.

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

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Windows 11)@VERT to Git commit to main/sbbs/master on Friday, May 29, 2026 15:31:38
    https://gitlab.synchro.net/main/sbbs/-/commit/30e601ade1688cc1a957bb92
    Modified Files:
    .claude/skills/javascript/SKILL.md
    Log Message:
    skills/javascript: note the chat_llm LLM-Guru module family

    Add the chat_llm engine, chat_llm_irc adapter, llm_tools, and llm_index to
    the stock exec/*.js inventory, plus wiki links to the new module/config/
    howto pages now published on wiki.synchro.net.

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

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Friday, May 29, 2026 23:59:56
    https://gitlab.synchro.net/main/sbbs/-/commit/d4613a65265ef3fe028b36a7
    Modified Files:
    .claude/skills/javascript/SKILL.md
    Log Message:
    javascript skill: how to syntax-check/test a side-effecting module

    Document the guard-and-load() idiom: gate a module's load-time entry
    point (server loop, socket connect, greeting) behind a NO_MAIN-style
    sentinel so it can be load()ed under jsexec for syntax-checking and unit-testing its helpers without running the side effect. Cites the
    stock CHAT_LLM_NO_STANDALONE / CHAT_LLM_IRC_NO_MAIN guards. Notes that syncjslint.js is a style linter (jslint) with false positives on valid SpiderMonkey regex, not a reliable syntax gate.

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Friday, May 29, 2026 23:59:57
    https://gitlab.synchro.net/main/sbbs/-/commit/a923481d60d196ea331efafd
    Modified Files:
    .claude/skills/javascript/SKILL.md
    Log Message:
    skills/javascript: document INI trailing-comment type-dependency

    Synchronet .ini value parsing strips a trailing "; comment" only for single-token value types (boolean via isTrue, enum via parseEnum, and integer/float/datetime via the numeric parse). STRING values keep the
    whole post-'=' text (truncsp only), so an inline comment after a
    string-valued key becomes part of the value -- the trap that corrupts URL/path/name settings. File.iniGetObject() reads every value as a raw
    string and thus never strips inline comments.

    Add a "Reading INI files (and the trailing-comment trap)" section with
    the per-type table, the rationale (single-token values have no
    delimiter ambiguity; strings do), and the history behind the
    single-token support (cceb1fbb8 isTrue after FozzTexx's sexpots.ini
    report; 7346893d6 enum-after-comment).

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Thursday, June 04, 2026 19:01:13
    https://gitlab.synchro.net/main/sbbs/-/commit/ee6689ca9a399aa3c5ad1cc6
    Modified Files:
    .claude/skills/javascript/SKILL.md
    Log Message:
    javascript skill: document js.on_exit() scope + forced-termination semantics

    js.on_exit() evaluates its handler string in the script's GLOBAL scope (it compiles the string against the global object), so a handler function nested in another function is unreachable and throws silently at exit. It also fires on forced termination (operator-terminate / SIGTERM), which -- unlike a thrown exception -- does NOT run catch/finally (verified empirically via SIGTERM + file markers). New 'Script lifecycle: exit handlers' section covers the global-scope requirement, the finally-vs-on_exit distinction, and the out-of-process testing caveat.

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Thursday, June 04, 2026 19:01:13
    https://gitlab.synchro.net/main/sbbs/-/commit/a915d2d944ed7b316e36e030
    Modified Files:
    .claude/skills/javascript/SKILL.md
    Log Message:
    javascript skill: correct js.on_exit scope (door child-scope vs jsexec)

    The earlier note said on_exit evaluates "in the GLOBAL scope" -- true only for jsexec/login/timed-event modules (host evaluates against js_glob and recurses child scopes). Doors and most bbs.exec/;exec/js.exec invocations run in a fresh child js_scope (exec.cpp:595) and the host evaluates on_exit against THAT scope (exec.cpp:701) with no recursion. Registration captures the scope where js.on_exit is called -- top level = js_scope, inside a function = that function's call object -- so a handler registered inside main() is filed where a
    door's EvalOnExit(js_scope) never looks and silently never runs, even though the
    same code passes under jsexec (which recurses). This shipped as a live bug.

    Rewrite: register on_exit at top level (not inside a function); prefer try/finally for clean-unwind cleanup (a door disconnect is a clean unwind) and use on_exit only as the forced-terminate backstop; note that a jsexec wrapper does not reproduce a door's on_exit scope, so finally is the dependable path.

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Saturday, June 06, 2026 15:23:28
    https://gitlab.synchro.net/main/sbbs/-/commit/90570a43c9e774e639c3ab04
    Modified Files:
    .claude/skills/javascript/SKILL.md
    Log Message:
    skills/javascript: document inkey timed input + inactivity model

    Add a "Timed / non-blocking key input, and the inactivity model"
    subsection to the input/output coverage: console.inkey(mode, timeout)
    semantics (timeout in ms; K_NONE returns "" vs K_NUL returns null on
    timeout; 1-char string on a key), the getkey-enforces-idle /
    inkey-doesn't trap, and the live console inactivity properties (max_getkey_inactivity, getkey_inactivity_warning, last_getkey_activity). Verified against inkey.cpp, getkey.cpp, js_console.cpp, scfglib1.c (NOINP=0x0100, default max_getkey_inactivity=300).

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Sunday, June 07, 2026 18:41:23
    https://gitlab.synchro.net/main/sbbs/-/commit/e9e5884dba659e4eec2e8ee0
    Modified Files:
    .claude/skills/javascript/SKILL.md
    Log Message:
    skills/javascript: load() caller-scope trap + uselect/printfile/directory gotchas

    Lessons captured from Synchronet door work:
    - load('file') runs in the CALLER's scope, so its top-level vars become locals of
    whatever function called load() -- a top-level / on_exit handler can't see a
    value load()ed inside main(); capture it into a reachable scope (the silent
    "quetzal is not defined" ReferenceError).
    - console.uselect: the title is auto-prefixed with "Select " (pass "a Game", not
    "Select a Game"); the display call's number argument is the DEFAULT item index.
    console.line_counter = 0 discards a pending auto-pager prompt before a clear. - console.printfile renders Ctrl-A codes + ANSI/CP437 by default; P_PCBOARD is
    only for PCBoard @X codes (it would misread a literal @).
    - directory() defaults to GLOB_MARK, so directory entries come back with a trailing
    '/' (self-identifying). The CWD is process-global (always ctrl/, since sbbs is
    multi-threaded) -- which is why no chdir is exposed to BBS JS; build absolute
    paths from js.exec_dir / system.*_dir.

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Thursday, June 11, 2026 15:38:02
    https://gitlab.synchro.net/main/sbbs/-/commit/4804fef3b8d8cbf70a850169
    Modified Files:
    .claude/skills/javascript/SKILL.md
    Log Message:
    javascript skill: document terminal-control abstraction + K_CTRLKEYS vs K_EXTKEYS

    Two lessons from the Z-Machine v6 door work:

    - Terminal control sequences (cursor show/hide, etc.) are abstracted in
    ansiterm_lib.js (CSI ?25h/l via ansiterm.send) -- reach for that before
    hand-writing escape codes.

    - Function/cursor key input: inkey/getkey pre-translate the arrow/Home/End
    escapes into control codes (TERM_KEY_*), conflating them with Ctrl-letters.
    K_EXTKEYS still yields those conflated codes and has no F-key codes; K_CTRLKEYS
    passes control keys through AND leaves the escape sequences RAW so you can parse
    the cterm forms yourself (ESC[A/B/C/D, ESC[11~..24~) and keep arrows/F-keys
    distinct. getbyte() is the raw alternative but drops idle-disconnect + UTF-8.

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Monday, July 06, 2026 20:44:06
    https://gitlab.synchro.net/main/sbbs/-/commit/371c3e49b542335fac8e210c
    Modified Files:
    .claude/skills/javascript/SKILL.md
    Log Message:
    javascript skill: five API gotchas learned from real module work

    - MsgBase.remove_msg(): passing a header object silently returns false
    with an empty last_error; pass the message number/offset/ID.
    - bbs.text() accepts the string identifier (via text_id_map), and a
    text.dat format string can carry more %-specifiers than the name
    suggests (NodeMsgFmt has three, including the message body) -- count
    them in ctrl/text.dat before format()ing, or the leftover %s renders
    literally.
    - bbs.compare_ars() evaluates the current user only; use the User
    object's compare_ars() to test any user (e.g. other nodes' useron),
    plus how to find a program's execution_ars and enumerate in-use nodes.
    - Emit line breaks with console.newline() rather than a hand-written
    "\r\n", same terminal-correctness principle as console.creturn().
    - Don't copy P_NOPAUSE onto printfile() calls reflexively: it disables
    the default pagination, which is only right when the surrounding flow
    provides the pause.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Tuesday, July 21, 2026 21:53:27
    https://gitlab.synchro.net/main/sbbs/-/commit/8c2fba616c53be965f97ca60
    Modified Files:
    .claude/skills/javascript/SKILL.md
    Log Message:
    javascript skill: document the 512-byte line cap on readAll/readln

    File.readAll() and File.readln() both default to maxlen=512. Because readAll
    is a loop over readln, and readln is fgets(buf, len+1, fp), an over-long line is SPLIT rather than truncated: fgets leaves the remainder in the stream, so
    it comes back as the next array element. The array quietly desynchronizes
    from the file's lines, nothing throws, and f.error stays 0.

    Hit this reading a 20MB data/gitpush.jsonl whose largest payload is ~160KB: a scan for the longest line reported exactly 512 bytes, and JSON.parse threw on the fragments. Only files whose lines all fit in 512 bytes behave correctly, which is what makes it look intermittent.

    Also note that load('json_lines.js') is the right tool for .jsonl and
    .ndjson, but that its own max_line_len default is 4096 -- still too small for records this size, so it has to be passed explicitly.

    Three baseline agents given the un-amended skill all got this right, but each did it by reading js_file.cpp first; the section is here to save that detour, and to carry the json_lines.js default, which none of them had reason to hit.

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