Use Jerboa 0.2.8 toolchain

ober

a2bf52377c653fe767c222cbd157fe48bbb1ddae

diff --git a/.build.yml b/.build.yml
index 6a5b8bf..3d866bc 100644
--- a/.build.yml
+++ b/.build.yml
@@ -17,7 +17,7 @@ packages:
   - unzip
   - zip
 sources:
-  - https://git.sr.ht/~lisp/jerboa#37426d9bb88e0cef607141605ae4bc928d5ccb24
+  - https://git.sr.ht/~lisp/jerboa#v0.2.8
   - https://git.sr.ht/~lisp/jerboa-emacs
 artifacts:
   - jerboa-emacs/jerboa-emacs-0.1.0.jpkg
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 485ddb3..aac5607 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,7 +10,7 @@ permissions:
   contents: read
 
 env:
-  JERBOA_VERSION: v0.2.3
+  JERBOA_VERSION: v0.2.8
   JERBUILD_TOOL: ${{ github.workspace }}/.jerboa/bin/jerbuild
 
 jobs:
diff --git a/Makefile b/Makefile
index 221c1b2..8de6c1b 100644
--- a/Makefile
+++ b/Makefile
@@ -226,7 +226,7 @@ all:
 vendor-deps:
 	@echo "=== Vendoring dependencies into $(VENDOR) ==="
 	@mkdir -p $(VENDOR)
-	@for repo in $(VENDOR_REPOS); do \
+	@set -e; for repo in $(VENDOR_REPOS); do \
 	  case "$$repo" in \
 	    jerboa) url="$(JERBOA_REPO)"; rev="$(JERBOA_REV)" ;; \
 	    jerboa-shell) url="$(JERBOA_SHELL_REPO)"; rev="$(JERBOA_SHELL_REV)" ;; \
@@ -266,7 +266,7 @@ vendor-deps:
 	done
 	@echo "=== Vendoring jsh dependencies into $(JSH_VENDOR) ==="
 	@mkdir -p $(JSH_VENDOR)
-	@for repo in $(JSH_VENDOR_REPOS); do \
+	@set -e; for repo in $(JSH_VENDOR_REPOS); do \
 	  case "$$repo" in \
 	    jerboa-coreutils) rev="$(JSH_COREUTILS_REV)" ;; \
 	    jerboa-awk) rev="$(JSH_AWK_REV)" ;; \
diff --git a/dependencies.lock b/dependencies.lock
index 9a6a378..49b7c9d 100644
--- a/dependencies.lock
+++ b/dependencies.lock
@@ -1,6 +1,6 @@
 # Reviewed immutable Git inputs for vendor-deps. Update each revision in a
 # dedicated dependency-review change; release builds fail on any mismatch.
-JERBOA_REV := c109e3129996afa90705e0bcef72f46cc82c7705
+JERBOA_REV := aecb3f873c58569f19ec62d8ac34901c49c496f9
 JERBOA_SHELL_REV := a861227afb4a6e343a358b5bfec1700989ee479d
 JERBOA_PCRE2_REV := 00ed6cba49c8b6a477da575e8acd1eea993c911e
 JERBOA_SCINTILLA_REV := 96745c5ea69c95f1c4fa2ce80f43e9cdf59a55a6
@@ -50,8 +50,7 @@ TREE_SITTER_HTML_REV := 5a5ca8551a179998360b4a4ca2c0f366a35acc03
 TREE_SITTER_LUA_REV := 534c461d2b75b0887ec968ef9635f4460b0878b7
 TREE_SITTER_SCHEME_REV := c6cb7c7d7a04b3f5d999c28e2e9c0c31b2d50ece
 
-JERBOA_TOOLCHAIN_VERSION := v0.2.3
-JERBOA_TOOLCHAIN_MACOS_ARM64_SHA256 := c57d4e3f22e28c50a2a9d039d0b326dc929024a1f071653d8a043a93b7a37e97
-JERBOA_TOOLCHAIN_LINUX_AMD64_SHA256 := 19e1e8a2633e71f44508cc3889a17f5ecec60dcc583a2e055fb580dc04785ff2
-JERBOA_TOOLCHAIN_LINUX_ARM64_SHA256 := 897cc2fbff1822ca879f2c5d650e701a4fef8da55798de40c7d2b079a698b273
-JERBOA_TOOLCHAIN_FREEBSD_AMD64_SHA256 := c6325ccbd88568fcf9d66fb89a8cd361632f4d7c0f53ebd0ec667600c71d7e15
+JERBOA_TOOLCHAIN_VERSION := v0.2.8
+JERBOA_TOOLCHAIN_MACOS_ARM64_SHA256 := 0ffdc558c9e70b9f81e6fd9ad3c98281bbc8d577c70f18e0a20db873b8d459aa
+JERBOA_TOOLCHAIN_LINUX_AMD64_SHA256 := 87e7a3b9d308b99d6844eb8135ba2c2ed523828965daf3f2a34f9fe0fd1e0d50
+JERBOA_TOOLCHAIN_FREEBSD_AMD64_SHA256 := 0623616083b4e9e96bd8b162f75bedaccc138339ab3569be518472ebb20be27c
diff --git a/docs/improve-kimi3.md b/docs/improve-kimi3.md
index 8485ee5..1a4fc51 100644
--- a/docs/improve-kimi3.md
+++ b/docs/improve-kimi3.md
@@ -154,10 +154,10 @@ editing — line numbers drift.**
 
 ## 4. Unused Jerboa stdlib features (robustness/perf/correctness wins)
 
-Vendored jerboa is **1,089 commits behind** `~/mine/jerboa` (vendor @ 37426d9,
-2026-06-14; upstream @ 1e43b3df, 2026-07-29). Bump `dependencies.lock` deliberately —
-most modules below already exist in the vendored copy, so no bump is required unless
-noted. Currently used: sugar, srfi/13, sort, misc/{string,process,ports,channel,list,
+Vendored jerboa is pinned to **v0.2.8** (`dependencies.lock` commit
+`aecb3f873c58569f19ec62d8ac34901c49c496f9`). Bump `dependencies.lock`
+deliberately when a newer release is required. Currently used: sugar, srfi/13,
+sort, misc/{string,process,ports,channel,list,
 completion,rwlock,memo,wg,shuffle,rbtree,pqueue,number,barrier,atom}, text/{json,hex,
 diff,glob}, crypto/digest, format, net/{request,uri}, pregexp, srfi/{1,19}, stm, os/
 {signal,fdio}, iter, engine, amb, repl, native-loader.
diff --git a/plan-android.md b/plan-android.md
index 856dcbc..aed5e35 100644
--- a/plan-android.md
+++ b/plan-android.md
@@ -10,7 +10,7 @@
 
 2. **Qt6 Widgets DO work on Android.** Despite common advice to use QML, Qt6Widgets has an `android` QPA plugin and can render on Android. It won't look native, but for an Emacs-like editor, that's fine — nobody expects Material Design.
 
-3. **The static binary approach is well-proven.** The existing Docker pipeline already produces a fully self-contained ELF binary with embedded boot files and memfd loading. The same approach could target Android's NDK toolchain.
+3. **The static binary approach is well-proven.** The existing Podman pipeline already produces a fully self-contained ELF binary with embedded boot files and memfd loading. The same approach could target Android's NDK toolchain.
 
 4. **The FFI architecture is clean.** The three-layer design (C++ shim → C callback bridge → Scheme FFI) has no fundamental desktop-only assumptions. The SPSC ring buffer for GC-safe callbacks is platform-agnostic.
 
@@ -74,8 +74,8 @@ This is the "real" solution but requires:
    - Gradle project wraps the native `.so` into an APK
    - `androiddeployqt` automates most of this
 
-5. **Adapt the Dockerfile/build pipeline for Android targets**
-   - New Dockerfile (or Makefile target) using Android NDK instead of Alpine musl
+5. **Adapt the Containerfile/build pipeline for Android targets**
+   - New Containerfile (or Makefile target) using Android NDK instead of Alpine musl
    - Same WPO + boot-file embedding strategy
    - Output: `libjemacs-qt.so` instead of `jemacs-qt` ELF binary
 
@@ -131,7 +131,7 @@ The fundamental architecture of jerboa-emacs (embedded Chez + static Qt + C FFI 
 
 - `jemacs-qt-main.c` — Entry point adaptation (JNI or Qt Android main)
 - `Makefile` — New `android-qt` target
-- `Dockerfile` — New Android NDK variant (or separate `Dockerfile.android`)
+- `Containerfile` — New Android NDK variant (or separate `Containerfile.android`)
 - `build-binary-qt.ss` — Target machine type (`tarm64le` instead of `ta6le`)
 - `support/vendor-overrides/qt_shim.cpp` — Minor `#ifdef __ANDROID__` guards if needed
 - `src/jerboa-emacs/qt/app.ss` — Android lifecycle hooks
diff --git a/src/jerboa-emacs/qt/lsp-client.ss b/src/jerboa-emacs/qt/lsp-client.ss
index 36adfaf..2071cd7 100644
--- a/src/jerboa-emacs/qt/lsp-client.ss
+++ b/src/jerboa-emacs/qt/lsp-client.ss
@@ -114,7 +114,7 @@
            #f))))))
 
 (def (lsp-read-headers port)
-  "Read HTTP-style headers, return Content-Length value or #f."
+  "Read HTTP-style headers, return decimal Content-Length value or #f."
   (let loop ((content-length #f))
     (let ((line (read-line port)))
       (cond
@@ -132,7 +132,16 @@
                                         #\return))
                          (substring val-str 0 (- (string-length val-str) 1))
                          val-str))
-                (len (string->number clean)))
+                (len (let ((nchars (string-length clean)))
+                       (let parse ((i 0) (n 0))
+                         (cond
+                           ((= i nchars) (and (> nchars 0) n))
+                           ((char-numeric? (string-ref clean i))
+                            (parse (+ i 1)
+                                   (+ (* n 10)
+                                      (- (char->integer (string-ref clean i))
+                                         (char->integer #\0)))))
+                           (else #f))))))
            (loop (or len content-length))))
         ;; Other headers — skip
         (else (loop content-length))))))
@@ -150,10 +159,10 @@
     (write-subu8vector body-bytes 0 content-length port)
     (force-output port)))
 (def (lsp-report-write-failure! context pending-id)
-  "Mark the LSP server dead after a failed write and notify the UI handler."
+  "Mark the LSP server dead after a transport failure and notify the UI handler."
   (when pending-id
     (lsp-take-pending! pending-id))
-  (jemacs-log! "LSP: write failed during " context)
+  (jemacs-log! "LSP: transport failed during " context)
   (with-catch (lambda _ (void))
     (lambda ()
       (when *lsp-process*
@@ -165,7 +174,7 @@
     (when handler
       (let ((params (make-hash-table)))
         (hash-put! params "type" 1)
-        (hash-put! params "message" (string-append "LSP write failed: " context))
+        (hash-put! params "message" (string-append "LSP transport failed: " context))
         (handler params)))))
 
 ;;;============================================================================
@@ -369,7 +378,7 @@
           (lambda ()
             (when *lsp-process*
               (with-catch
-                (lambda (e) (void))
+                (lambda (e) (lsp-report-write-failure! "reader" #f))
                 (lambda ()
                   (lsp-drain-messages! *lsp-process*))))))
         ;; Send initialize request
@@ -380,7 +389,7 @@
   "Stop the LSP server — send shutdown, then exit, then kill process."
   (when *lsp-process*
     ;; Send shutdown request (don't wait for response)
-    (with-catch (lambda (_e) (void))
+    (with-catch (lambda (_e) (lsp-report-write-failure! "shutdown" #f))
       (lambda ()
         (let ((msg (make-hash-table)))
           (hash-put! msg "jsonrpc" "2.0")
diff --git a/support/ensure-jerboa.sh b/support/ensure-jerboa.sh
index 8f46558..411165a 100755
--- a/support/ensure-jerboa.sh
+++ b/support/ensure-jerboa.sh
@@ -30,7 +30,6 @@ else
     case "$os-$arch" in
         Darwin-arm64) target=macos-arm64 ;;
         Linux-x86_64|Linux-amd64) target=linux-amd64 ;;
-        Linux-aarch64|Linux-arm64) target=linux-arm64 ;;
         FreeBSD-amd64|FreeBSD-x86_64) target=freebsd-amd64 ;;
         *)
             echo "ERROR: unsupported platform for Jerboa release artifacts: $os $arch" >&2
@@ -41,7 +40,7 @@ else
 fi
 
 case "$target" in
-    macos-arm64|linux-amd64|linux-arm64|freebsd-amd64) ;;
+    macos-arm64|linux-amd64|freebsd-amd64) ;;
     *)
         echo "ERROR: unsupported release target: $target" >&2
         exit 1
@@ -62,7 +61,6 @@ locked_version=$(awk '$1 == "JERBOA_TOOLCHAIN_VERSION" && $2 == ":=" { print $3;
 case "$target" in
     macos-arm64) digest_key=JERBOA_TOOLCHAIN_MACOS_ARM64_SHA256 ;;
     linux-amd64) digest_key=JERBOA_TOOLCHAIN_LINUX_AMD64_SHA256 ;;
-    linux-arm64) digest_key=JERBOA_TOOLCHAIN_LINUX_ARM64_SHA256 ;;
     freebsd-amd64) digest_key=JERBOA_TOOLCHAIN_FREEBSD_AMD64_SHA256 ;;
 esac
 expected=$(awk -v key="$digest_key" '$1 == key && $2 == ":=" { print $3; exit }' "$lock")