Fix make static-qt: resolve Scheme import conflicts and link jsh coreutils

ober

47a3b9d91ac3c89316b28319050ba367c482b3a1

diff --git a/Makefile b/Makefile
index e85385d..dcddfcc 100644
--- a/Makefile
+++ b/Makefile
@@ -254,6 +254,7 @@ SCI_SRC      ?= $(HOME)/mine/chez-scintilla
 QT_SRC       ?= $(HOME)/mine/chez-qt
 QTSHIM_SRC   ?= $(HOME)/mine/gerbil-qt
 COREUTILS_SRC ?= $(HOME)/mine/jerboa-coreutils
+JSH_COREUTILS_LIB ?= $(JSH_SRC)/rust-coreutils/target/x86_64-unknown-linux-musl/release/libjsh_coreutils.a
 
 DEPS_IMAGE := jemacs-deps:$(ARCH)
 
@@ -297,6 +298,7 @@ build-jemacs-qt-static: check-root
 	cp /src/vendor/jerboa-shell/embed-crypto.c /deps/jsh/ 2>/dev/null; \
 	cp /src/vendor/jerboa-shell/embed-crypto.h /deps/jsh/ 2>/dev/null; \
 	cp /src/vendor/jerboa-shell/ffi-shim.c /deps/jsh/ 2>/dev/null; \
+	cp /src/vendor/jerboa-shell/libcoreutils.c /deps/jsh/ 2>/dev/null; \
 	if [ -f /src/vendor/jerboa-shell/crypto_stub.c ]; then \
 	  gcc -c -O2 /src/vendor/jerboa-shell/crypto_stub.c -o /tmp/jemacs-build/crypto_stub.o; \
 	fi; \
@@ -370,6 +372,7 @@ build-jemacs-qt-static: check-root
 	CHEZ_QT_DIR=/deps/chez-qt \
 	CHEZ_QT_SHIM_DIR=/deps/gerbil-qt/vendor \
 	COREUTILS_DIR=/deps/coreutils \
+	JSH_COREUTILS_LIB=/deps/jsh/libjsh_coreutils.a \
 	TREE_SITTER_INCLUDE=/opt/tree-sitter-include \
 	TREE_SITTER_LIB=/opt/tree-sitter-lib \
 	TREE_SITTER_GRAMMARS=/opt/tree-sitter-grammars \
@@ -389,8 +392,10 @@ linux-static-qt-docker:
 	  -v $(JERBOA)/lib/std:/host-jerboa-std:ro \
 	  -v $(COREUTILS_SRC)/lib:/host-coreutils:ro \
 	  -v $(JSH_SRC)/src:/host-jsh-src:ro \
+	  -v $(JSH_COREUTILS_LIB):/host-jsh-coreutils.a:ro \
 	  $(DEPS_IMAGE) \
 	  sh -c "apk add --no-cache libvterm-dev libvterm-static >/dev/null 2>&1; \
+	         cp /host-jsh-coreutils.a /deps/jsh/libjsh_coreutils.a; \
 	         cp -a /host-jsh-src/. /deps/jsh/src/; \
 	         cp -a /host-coreutils/. /deps/coreutils/; \
 	         find /deps/coreutils -name '*.sls' -exec sed -i 's/(load-shared-object #f)/(void)/g' {} +; \
diff --git a/build-binary-qt.ss b/build-binary-qt.ss
index 2f75bb0..7d4d4f3 100644
--- a/build-binary-qt.ss
+++ b/build-binary-qt.ss
@@ -456,6 +456,15 @@ echo OK"
       (display "Error: jsh ffi-shim.c compilation failed\n")
       (exit 1))))
 
+;; jsh libcoreutils (terminal width/height/raw-mode for ls, top patches)
+(when jemacs-static?
+  (let* ((jsh-root (path-parent jsh-dir))
+         (cmd (format "gcc -c -O2 -o jemacs-qt-jsh-coreutils.o ~a/libcoreutils.c -Wall 2>&1"
+                      jsh-root)))
+    (unless (= 0 (system cmd))
+      (display "Error: libcoreutils.c compilation failed\n")
+      (exit 1))))
+
 ;; jsh embed-crypto (pure C crypto for embed encryption — no external deps)
 (when jemacs-static?
   (let* ((jsh-root (path-parent jsh-dir))
@@ -554,16 +563,18 @@ echo OK"
                           ts-shim-obj ts-queries-obj ts-lib-dir ts-gram-dir))
          (crypto-stub (if (file-exists? "/tmp/jemacs-build/crypto_stub.o")
                        "/tmp/jemacs-build/crypto_stub.o" ""))
+         ;; jsh Rust coreutils static library (musl build, pre-compiled on host)
+         (jsh-coreutils-lib (or (getenv "JSH_COREUTILS_LIB") ""))
          (cmd (format "g++ -static -Wl,--export-dynamic -o jemacs-qt \
-jemacs-qt-main.o jemacs-qt-chez-shim.o jemacs-qt-pcre2-shim.o jemacs-qt-jsh-ffi.o \
+jemacs-qt-main.o jemacs-qt-chez-shim.o jemacs-qt-pcre2-shim.o jemacs-qt-jsh-ffi.o jemacs-qt-jsh-coreutils.o \
 jemacs-qt-embed-crypto.o jemacs-qt-ssh-agent-stub.o ~a \
 jemacs-qt-pty-shim.o jemacs-qt-vterm-shim.o jemacs-qt-repl-shim.o jemacs-qt-jerboa-landlock.o jemacs-qt-sci-stubs.o \
 qt_static_symbols.o \
-~a ~a ~a ~a ~a \
+~a ~a ~a ~a ~a ~a \
 -L~a -lkernel -llz4 -lz \
 -lvterm -lm -ldl -lpthread -luuid -lncurses -lstdc++ 2>&1"
                       crypto-stub
-                      libqt-shim qt-plugins ts-link qt-libs pcre2-libs
+                      libqt-shim qt-plugins ts-link qt-libs pcre2-libs jsh-coreutils-lib
                       chez-dir)))
     (printf "  ~a~n" cmd)
     (unless (= 0 (system cmd))
@@ -591,7 +602,7 @@ qt_static_symbols.o \
       "jemacs_qt_scheme_boot.h" "jemacs_qt_jemacs_qt_boot.h"
       "jemacs-qt-all.so" "qt-main.so" "qt-main.wpo" "jemacs-qt.boot")
     (if jemacs-static?
-        '("jemacs-qt-jsh-ffi.o" "jemacs-qt-embed-crypto.o"
+        '("jemacs-qt-jsh-ffi.o" "jemacs-qt-jsh-coreutils.o" "jemacs-qt-embed-crypto.o"
           "jemacs-qt-ssh-agent-stub.o" "jemacs-qt-ssh-agent-stub.c"
           "jemacs-qt-pty-shim.o" "jemacs-qt-vterm-shim.o"
           "jemacs-qt-jerboa-landlock.o"
diff --git a/lib/jerboa-emacs/async.sls b/lib/jerboa-emacs/async.sls
index 3875e40..46f4863 100644
--- a/lib/jerboa-emacs/async.sls
+++ b/lib/jerboa-emacs/async.sls
@@ -552,8 +552,8 @@
        (let ([v (hashtable-ref
                   cache
                   key
-                  '#{miss m9ldnpzbli8vj712n2xb0ixmh-1})])
-         (if (eq? v '#{miss m9ldnpzbli8vj712n2xb0ixmh-2})
+                  '#{miss ggxha04xz6hyzrhna9yglgxud-0})])
+         (if (eq? v '#{miss ggxha04xz6hyzrhna9yglgxud-1})
              (if (null? default) #f (car default))
              v)))
   (def (weak-cache-set! cache key value)
diff --git a/lib/jerboa-emacs/qt/app.sls b/lib/jerboa-emacs/qt/app.sls
index 46c5d0e..8aa19c1 100644
--- a/lib/jerboa-emacs/qt/app.sls
+++ b/lib/jerboa-emacs/qt/app.sls
@@ -92,6 +92,7 @@
   (def *vterm-render-interval-ms* 33)
   (def *pty-batch-budget* 65536)
   (def *vterm-scrollback-limit* 100000)
+  (def *pty-poll-in-progress?* #f)
   (def *vterm-last-render-time* (make-hash-table-eq))
   (def *vterm-last-rendered* (make-hash-table-eq))
   (def *vterm-initialized* (make-hash-table-eq))
@@ -1761,143 +1762,169 @@
                                        ed))
                                    (loop (cdr wins)))))))))))
                (buffer-list))
-             (for-each
-               (lambda (buf)
-                 (when (shell-buffer? buf)
-                   (let ([ss (hash-get *shell-state* buf)])
-                     (when (and ss (shell-pty-busy? ss))
-                       (let drain ()
-                         (let ([msg (shell-poll-output ss)])
-                           (when msg
-                             (qt-poll-shell-pty-msg! fr buf ss msg)
-                             (when (eq? (car msg) 'data) (drain))))))))
-                 (when (terminal-buffer? buf)
-                   (let ([ts (hash-get *terminal-state* buf)])
-                     (when (and ts (terminal-pty-busy? ts))
-                       (let ([vt (terminal-state-vtscreen ts)])
-                         (when vt
-                           (let ed-loop ([wins (qt-frame-windows fr)])
-                             (when (pair? wins)
-                               (if (eq? (qt-edit-window-buffer (car wins))
-                                        buf)
-                                   (let* ([ed (qt-edit-window-editor
-                                                (car wins))]
-                                          [new-rows (max 2
-                                                         (sci-send
-                                                           ed
-                                                           2370
-                                                           0))]
-                                          [widget-w (qt-widget-width ed)]
-                                          [char-w (max 1
-                                                       (sci-send/string
-                                                         ed
-                                                         2275
-                                                         "0"
-                                                         0))]
-                                          [new-cols (max 20
-                                                         (quotient
-                                                           widget-w
-                                                           char-w))]
-                                          [old-rows (vtscreen-rows vt)]
-                                          [old-cols (vtscreen-cols vt)])
-                                     (when (or (not (= new-rows old-rows))
-                                               (not (= new-cols old-cols)))
-                                       (verbose-log! "PTY-RESIZE: "
-                                         (number->string old-rows) "x"
-                                         (number->string old-cols) " -> "
-                                         (number->string new-rows) "x"
-                                         (number->string new-cols))
-                                       (vtscreen-resize!
-                                         vt
-                                         new-rows
-                                         new-cols)
-                                       (terminal-resize!
-                                         ts
-                                         new-rows
-                                         new-cols)
-                                       (hash-remove! *vterm-row-cache* ts)
-                                       (hash-put!
-                                         *vterm-initialized*
-                                         ts
-                                         #f)))
-                                   (ed-loop (cdr wins))))))))
-                     (when (and ts (terminal-pty-busy? ts))
-                       (let drain ([chunks (list)] [bytes 0] [done-msg #f])
-                         (if (and (> bytes 0)
-                                  (>= bytes *pty-batch-budget*))
-                             (let ([combined (apply
-                                               string-append
-                                               (reverse chunks))])
-                               (verbose-log!
-                                 "PTY-BATCH-CAP: "
-                                 (number->string (string-length combined))
-                                 " bytes (capped)")
-                               (qt-poll-terminal-pty-batch!
-                                 fr
-                                 buf
-                                 ts
-                                 combined))
-                             (let ([msg (terminal-poll-output ts)])
-                               (cond
-                                 [(not msg)
-                                  (if (pair? chunks)
-                                      (let ([combined (apply
-                                                        string-append
-                                                        (reverse chunks))])
-                                        (verbose-log!
-                                          "PTY-BATCH: "
-                                          (number->string
-                                            (string-length combined))
-                                          " bytes")
-                                        (qt-poll-terminal-pty-batch!
-                                          fr
-                                          buf
-                                          ts
-                                          combined))
-                                      (when (and (terminal-state-vtscreen
-                                                   ts)
-                                                 (hash-ref
-                                                   *vterm-initialized*
-                                                   ts
-                                                   #f)
-                                                 (vterm-render-due? ts))
-                                        (qt-poll-terminal-pty-batch!
+             (unless *pty-poll-in-progress?*
+               (dynamic-wind
+                 (lambda () (set! *pty-poll-in-progress?* #t))
+                 (lambda ()
+                   (for-each
+                     (lambda (buf)
+                       (when (shell-buffer? buf)
+                         (let ([ss (hash-get *shell-state* buf)])
+                           (when (and ss (shell-pty-busy? ss))
+                             (let drain ()
+                               (let ([msg (shell-poll-output ss)])
+                                 (when msg
+                                   (qt-poll-shell-pty-msg! fr buf ss msg)
+                                   (when (eq? (car msg) 'data)
+                                     (drain))))))))
+                       (when (terminal-buffer? buf)
+                         (let ([ts (hash-get *terminal-state* buf)])
+                           (when ts
+                             (let ([vt (terminal-state-vtscreen ts)])
+                               (when vt
+                                 (let ed-loop ([wins (qt-frame-windows
+                                                       fr)])
+                                   (when (pair? wins)
+                                     (if (eq? (qt-edit-window-buffer
+                                                (car wins))
+                                              buf)
+                                         (let* ([ed (qt-edit-window-editor
+                                                      (car wins))]
+                                                [new-rows (max 2
+                                                               (sci-send
+                                                                 ed
+                                                                 2370
+                                                                 0))]
+                                                [widget-w (qt-widget-width
+                                                            ed)]
+                                                [char-w (max 1
+                                                             (sci-send/string
+                                                               ed
+                                                               2275
+                                                               "0"
+                                                               0))]
+                                                [new-cols (max 20
+                                                               (quotient
+                                                                 widget-w
+                                                                 char-w))]
+                                                [old-rows (vtscreen-rows
+                                                            vt)]
+                                                [old-cols (vtscreen-cols
+                                                            vt)])
+                                           (when (or (not (= new-rows
+                                                             old-rows))
+                                                     (not (= new-cols
+                                                             old-cols)))
+                                             (verbose-log! "PTY-RESIZE: "
+                                               (number->string old-rows)
+                                               "x"
+                                               (number->string old-cols)
+                                               " -> "
+                                               (number->string new-rows)
+                                               "x"
+                                               (number->string new-cols))
+                                             (vtscreen-resize!
+                                               vt
+                                               new-rows
+                                               new-cols)
+                                             (when (terminal-pty-busy? ts)
+                                               (terminal-resize!
+                                                 ts
+                                                 new-rows
+                                                 new-cols))
+                                             (hash-remove!
+                                               *vterm-row-cache*
+                                               ts)
+                                             (hash-put!
+                                               *vterm-initialized*
+                                               ts
+                                               #f)))
+                                         (ed-loop (cdr wins))))))))
+                           (when (and ts (terminal-pty-busy? ts))
+                             (let drain ([chunks (list)]
+                                         [bytes 0]
+                                         [done-msg #f])
+                               (if (and (> bytes 0)
+                                        (>= bytes *pty-batch-budget*))
+                                   (let ([combined (apply
+                                                     string-append
+                                                     (reverse chunks))])
+                                     (verbose-log!
+                                       "PTY-BATCH-CAP: "
+                                       (number->string
+                                         (string-length combined))
+                                       " bytes (capped)")
+                                     (qt-poll-terminal-pty-batch!
+                                       fr
+                                       buf
+                                       ts
+                                       combined))
+                                   (let ([msg (terminal-poll-output ts)])
+                                     (cond
+                                       [(not msg)
+                                        (if (pair? chunks)
+                                            (let ([combined (apply
+                                                              string-append
+                                                              (reverse
+                                                                chunks))])
+                                              (verbose-log!
+                                                "PTY-BATCH: "
+                                                (number->string
+                                                  (string-length combined))
+                                                " bytes")
+                                              (qt-poll-terminal-pty-batch!
+                                                fr
+                                                buf
+                                                ts
+                                                combined))
+                                            (when (and (terminal-state-vtscreen
+                                                         ts)
+                                                       (hash-ref
+                                                         *vterm-initialized*
+                                                         ts
+                                                         #f)
+                                                       (vterm-render-due?
+                                                         ts))
+                                              (qt-poll-terminal-pty-batch!
+                                                fr
+                                                buf
+                                                ts
+                                                "")))
+                                        (when done-msg
+                                          (qt-poll-terminal-pty-msg!
+                                            fr
+                                            buf
+                                            ts
+                                            done-msg))]
+                                       [(eq? (car msg) 'data)
+                                        (drain
+                                          (cons (cdr msg) chunks)
+                                          (+ bytes
+                                             (string-length (cdr msg)))
+                                          done-msg)]
+                                       [else
+                                        (when (pair? chunks)
+                                          (let ([combined (apply
+                                                            string-append
+                                                            (reverse
+                                                              chunks))])
+                                            (verbose-log!
+                                              "PTY-BATCH+DONE: "
+                                              (number->string
+                                                (string-length combined))
+                                              " bytes")
+                                            (qt-poll-terminal-pty-batch!
+                                              fr
+                                              buf
+                                              ts
+                                              combined)))
+                                        (qt-poll-terminal-pty-msg!
                                           fr
                                           buf
                                           ts
-                                          "")))
-                                  (when done-msg
-                                    (qt-poll-terminal-pty-msg!
-                                      fr
-                                      buf
-                                      ts
-                                      done-msg))]
-                                 [(eq? (car msg) 'data)
-                                  (drain
-                                    (cons (cdr msg) chunks)
-                                    (+ bytes (string-length (cdr msg)))
-                                    done-msg)]
-                                 [else
-                                  (when (pair? chunks)
-                                    (let ([combined (apply
-                                                      string-append
-                                                      (reverse chunks))])
-                                      (verbose-log!
-                                        "PTY-BATCH+DONE: "
-                                        (number->string
-                                          (string-length combined))
-                                        " bytes")
-                                      (qt-poll-terminal-pty-batch!
-                                        fr
-                                        buf
-                                        ts
-                                        combined)))
-                                  (qt-poll-terminal-pty-msg!
-                                    fr
-                                    buf
-                                    ts
-                                    msg)]))))))))
-               (buffer-list))
+                                          msg)]))))))))
+                     (buffer-list)))
+                 (lambda () (set! *pty-poll-in-progress?* #f))))
              (for-each
                (lambda (buf)
                  (when (chat-buffer? buf)
diff --git a/src/jerboa-emacs/editor-core.ss b/src/jerboa-emacs/editor-core.ss
index 7c4a764..bc68d21 100644
--- a/src/jerboa-emacs/editor-core.ss
+++ b/src/jerboa-emacs/editor-core.ss
@@ -31,12 +31,90 @@
         :jerboa-emacs/persist)
 
 ;;;============================================================================
+;;; Missing Scintilla constants (used by scaffold commands)
+;;;============================================================================
+
+(def SCI_GETTEXTRANGE 2162)
+(def SCI_DELETEBACK 2326)
+(def SCI_GETMARGINLEFT 2155)
+(def SCI_SETMARGINLEFT 2155)
+(def SCI_SETMARGINRIGHT 2157)
+;; SCI_INDICSETALPHA already defined below (line ~1599)
+(def SCI_MOVESELECTEDLINESUP 2620)
+(def SCI_MOVESELECTEDLINESDOWN 2621)
+(def SCI_SETELEMENTCOLOUR 2753)
+;; SCI_SETSTYLING and SCI_STARTSTYLING already come from terminal.ss
+
+;;;============================================================================
+;;; Utility: string->alien/nul (identity — Scintilla TUI accepts strings directly)
+;;;============================================================================
+
+(def (string->alien/nul s) s)
+(def (string->alien s) s)
+(def (alien/nul->string s) s)
+(def (bytevector->alien bv) bv)
+(def (cons->alien . args) 0)
+
+(def (tui-rows) 24)
+(def (tui-cols) 80)
+
+;;; Aliases for scaffold commands that use non-existent names
+(def (editor-cursor-position ed) (editor-get-current-pos ed))
+(def (editor-line-start ed line) (editor-position-from-line ed line))
+(def (editor-line-end ed line) (editor-get-line-end-position ed line))
+(def (editor-replace-range ed start end text)
+  (editor-delete-range ed start (- end start))
+  (editor-insert-text ed start text))
+
+;;; Global mode-state table (phantom field app-state-modes doesn't exist)
+(def *jemacs-mode-state* (make-hash-table))
+(def (app-state-modes app) *jemacs-mode-state*)
+
+;;; Indicator helpers (phantom functions in scaffold code)
+(def (editor-indicator-fill ed indicator-id start end)
+  (send-message ed SCI_SETINDICATORCURRENT indicator-id 0)
+  (send-message ed SCI_INDICATORFILLRANGE start (- end start)))
+(def (editor-indicator-clear ed indicator-id start len)
+  (send-message ed SCI_SETINDICATORCURRENT indicator-id 0)
+  (send-message ed SCI_INDICATORCLEARRANGE start len))
+
+;;;============================================================================
 ;;; Shared state (used across editor sub-modules)
 ;;;============================================================================
 (def *auto-pair-mode* #t)
 (def *auto-revert-mode* #f)
 (def *aggressive-indent-mode* #f)
 (def *delete-selection-mode* #t)  ;; default on, like Emacs
+(def *electric-quote-mode* #f)
+
+(def (electric-quote-char ch ed)
+  "Return a curly-quote replacement string for CH at the current position in ED,
+   or #f if no replacement should be made.  Uses the character before point to
+   decide opening vs closing: after whitespace/BOL → opening, otherwise → closing."
+  (let* ((pos (editor-get-current-pos ed))
+         (prev-ch (if (> pos 0)
+                    (send-message ed SCI_GETCHARAT (- pos 1) 0)
+                    0))
+         (at-word-boundary? (or (= pos 0)
+                               (= prev-ch 32)    ; space
+                               (= prev-ch 10)    ; newline
+                               (= prev-ch 13)    ; CR
+                               (= prev-ch 9)     ; tab
+                               (= prev-ch 40)    ; (
+                               (= prev-ch 91)    ; [
+                               (= prev-ch 123)))) ; {
+    (cond
+      ;; Double quote → curly double quotes
+      ((= ch 34)  ; "
+       (if at-word-boundary?
+         "\x201C;"   ; left double quotation mark
+         "\x201D;")) ; right double quotation mark
+      ;; Single quote / apostrophe → curly single quotes
+      ((= ch 39)  ; '
+       (if at-word-boundary?
+         "\x2018;"   ; left single quotation mark
+         "\x2019;")) ; right single quotation mark
+      (else #f))))
 
 ;;;============================================================================
 ;;; Pulse/flash highlight on jump (beacon-like)
@@ -84,6 +162,7 @@
 ;;;============================================================================
 ;;; Volatile highlights — flash changed regions (yank, undo)
 ;;;============================================================================
+(def *volatile-highlights* #f)
 (def *volatile-highlight-indicator* 2)
 (def *volatile-highlight-countdown* 0)
 (def *volatile-highlight-editor* #f)
@@ -580,7 +659,7 @@
           (let ((new-line (string-append target-indent trimmed)))
             (send-message ed SCI_SETTARGETSTART line-start 0)
             (send-message ed SCI_SETTARGETEND line-end 0)
-            (editor-replace-target ed new-line)
+            (send-message/string ed SCI_REPLACETARGET new-line)
             (let ((new-pos (+ line-start (string-length target-indent)
                              (max 0 (- pos line-start
                                        (string-length current-indent))))))
diff --git a/src/jerboa-emacs/editor-extra-editing2.ss b/src/jerboa-emacs/editor-extra-editing2.ss
index b9c5786..044d0a2 100644
--- a/src/jerboa-emacs/editor-extra-editing2.ss
+++ b/src/jerboa-emacs/editor-extra-editing2.ss
@@ -20,6 +20,8 @@
         :jerboa-emacs/echo
         :jerboa-emacs/editor-extra-helpers
         :jerboa-emacs/editor-extra-editing
+        (only-in :jerboa-emacs/editor-core SCI_DELETEBACK)
+        (only-in :jerboa-emacs/terminal SCI_SETSTYLING SCI_STARTSTYLING)
         (only-in :jerboa-emacs/persist
           *enriched-mode* *picture-mode*))
 
diff --git a/src/jerboa-emacs/editor-extra-final.ss b/src/jerboa-emacs/editor-extra-final.ss
index a172df4..a23206c 100644
--- a/src/jerboa-emacs/editor-extra-final.ss
+++ b/src/jerboa-emacs/editor-extra-final.ss
@@ -22,14 +22,22 @@
         :jerboa-emacs/modeline
         :jerboa-emacs/echo
         (only-in :jerboa-emacs/editor-core
-                 search-forward-regexp-impl!)
+                 search-forward-regexp-impl!
+                 string->alien/nul string->alien alien/nul->string
+                 bytevector->alien cons->alien tui-rows tui-cols
+                 SCI_GETTEXTRANGE SCI_SETMARGINLEFT SCI_SETMARGINRIGHT
+                 SCI_INDICSETALPHA
+                 editor-cursor-position editor-line-start editor-line-end
+                 editor-replace-range app-state-modes
+                 editor-indicator-fill editor-indicator-clear)
+        (only-in :jerboa-emacs/org-table editor-current-line)
         (only-in :jerboa-emacs/editor-ui
                  position-cursor-for-replace!)
-        :jerboa-emacs/editor-extra-helpers
-        :jerboa-emacs/editor-extra-web
-        :jerboa-emacs/editor-extra-media
-        :jerboa-emacs/editor-extra-media2
-        :jerboa-emacs/editor-extra-modes)
+        (except-in :jerboa-emacs/editor-extra-helpers
+                   cmd-fido-mode cmd-icomplete-mode)
+        (only-in :jerboa-emacs/editor-extra-web url-encode)
+        (only-in :jerboa-emacs/editor-extra-modes cmd-completion-at-point switch-to-buffer create-buffer cmd-indent-guide-mode *spray-wpm* editor-get-text-range shell-quote cmd-color-theme-select)
+        (only-in :jerboa-emacs/editor-text find-buffer-by-file-path))
 
 ;;;============================================================================
 ;;; Task #51: Additional unique commands to cross 1000 registrations
@@ -1554,7 +1562,6 @@
 ;;; Batch 33: insert-char-by-code, subword, bidi, fill-column indicator, etc.
 ;;; =========================================================================
 
-(def *subword-mode* #f)
 (def *auto-composition-mode* #t)
 (def *bidi-display-reordering* #t)
 (def *fill-column-indicator* #f)
@@ -1853,7 +1860,7 @@
                        *buffer-list*))
          (tabs (map (lambda (b)
                       (let* ((name (buffer-name b))
-                             (mod? (and (buffer-doc b)
+                             (mod? (and (buffer-doc-pointer b)
                                        (let ((ed (create-scintilla-editor 1 1)))
                                          ;; Quick check - can't easily check mod
                                          #f)))
@@ -2233,7 +2240,6 @@
 (def *kmacro-recording* #f)
 (def *kmacro-events* '())
 (def *kmacro-ring* '())
-(def *kmacro-counter* 0)
 
 (def (kmacro-recording?) *kmacro-recording*)
 
@@ -2291,7 +2297,7 @@
         (for-each
           (lambda (ev)
             (when (and (pair? ev) (symbol? (car ev)))
-              (let ((cmd-fn (command-lookup (car ev))))
+              (let ((cmd-fn (find-command (car ev))))
                 (when cmd-fn (cmd-fn app)))))
           macro)
         (echo-message! echo "Macro replayed")))))
@@ -3672,7 +3678,7 @@
          (frame (app-state-frame app))
          (win (current-window frame))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       (let* ((dir (path-directory file))
@@ -3813,7 +3819,7 @@
         (let ((nbuf (make-buffer filepath)))
           (buffer-attach! ed nbuf)
           (set! (edit-window-buffer win) nbuf)
-          (buffer-file-set! nbuf filepath)
+          (buffer-file-path-set! nbuf filepath)
           (editor-set-text ed content)
           (editor-goto-pos ed (string-length content))
           (echo-message! echo (str "Created note: " filename)))))))
@@ -3850,7 +3856,7 @@
                                (nbuf (make-buffer filepath)))
                           (buffer-attach! ed nbuf)
                           (set! (edit-window-buffer win) nbuf)
-                          (buffer-file-set! nbuf filepath)
+                          (buffer-file-path-set! nbuf filepath)
                           (editor-set-text ed content)
                           (editor-goto-pos ed 0)
                           (echo-message! echo (str "Opened: " choice))))))
@@ -3869,17 +3875,17 @@
       (with-catch
         (lambda (e) (echo-message! echo (str "Detach error: " e)))
         (lambda ()
-          (let-values (((p-stdin p-stdout p-stderr pid)
-                        (open-process-ports
-                          (str "nohup " cmd " > /tmp/detached-" (number->string pid)
-                               ".log 2>&1 &")
-                          'block (native-transcoder))))
-            (close-port p-stdin) (close-port p-stdout) (close-port p-stderr)
-            (set! *detached-sessions*
-              (cons (list (cons 'cmd cmd) (cons 'pid pid)
-                          (cons 'time (time-second (current-time))))
-                    *detached-sessions*))
-            (echo-message! echo (str "Detached: " cmd " (pid " pid ")"))))))))
+          (let* ((log-file (str "/tmp/detached-" (number->string (time-second (current-time))) ".log")))
+            (let-values (((p-stdin p-stdout p-stderr pid)
+                          (open-process-ports
+                            (str "nohup " cmd " > " log-file " 2>&1 &")
+                            'block (native-transcoder))))
+              (close-port p-stdin) (close-port p-stdout) (close-port p-stderr)
+              (set! *detached-sessions*
+                (cons (list (cons 'cmd cmd) (cons 'pid pid)
+                            (cons 'time (time-second (current-time))))
+                      *detached-sessions*))
+              (echo-message! echo (str "Detached: " cmd " (pid " pid ")")))))))))
 
 (def (cmd-detached-list app)
   "List all detached sessions."
@@ -4130,7 +4136,7 @@
          (frame (app-state-frame app))
          (win (current-window frame))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       (with-catch
@@ -4242,7 +4248,7 @@
          (win (current-window frame))
          (ed (edit-window-editor win))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       (let* ((line-num (+ 1 (send-message ed SCI_LINEFROMPOSITION
@@ -4880,7 +4886,7 @@
          (frame (app-state-frame app))
          (win (current-window frame))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       (with-catch
@@ -5106,7 +5112,7 @@
          (frame (app-state-frame app))
          (win (current-window frame))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       (let ((ed (edit-window-editor win)))
@@ -5419,7 +5425,7 @@
          (win (current-window frame))
          (ed (edit-window-editor win))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf))
+         (file (buffer-file-path buf))
          (name (if file
                  (let loop ((i (- (string-length file) 1)))
                    (cond ((< i 0) file)
@@ -5506,7 +5512,7 @@
          (frame (app-state-frame app))
          (win (current-window frame))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       (with-catch
@@ -5541,7 +5547,7 @@
          (frame (app-state-frame app))
          (win (current-window frame))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       (let* ((row (tui-rows)) (width (tui-cols))
@@ -5552,7 +5558,7 @@
               (lambda (e) (echo-message! echo (str "Rename error: " e)))
               (lambda ()
                 (rename-file file new-path)
-                (buffer-file-set! buf new-path)
+                (buffer-file-path-set! buf new-path)
                 (echo-message! echo (str "Renamed to " new-path))))))))))
 
 ;; --- Feature 20: Sudo Edit ---
@@ -5564,7 +5570,7 @@
          (win (current-window frame))
          (ed (edit-window-editor win))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       (with-catch
@@ -5788,7 +5794,7 @@
          (win (current-window frame))
          (ed (edit-window-editor win))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       (with-catch
@@ -5848,7 +5854,7 @@
          (frame (app-state-frame app))
          (win (current-window frame))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       (with-catch
@@ -5947,7 +5953,7 @@
       (echo-message! echo "No selection to copy")
       (let* ((text (editor-get-text-range ed start end))
              (buf (edit-window-buffer win))
-             (file (buffer-file buf))
+             (file (buffer-file-path buf))
              (ext (if file (path-extension file) "txt"))
              (row (tui-rows)) (width (tui-cols))
              (fmt (echo-read-string echo "Format [markdown/org/html/slack/jira]: " row width)))
@@ -6055,7 +6061,7 @@
          (frame (app-state-frame app))
          (win (current-window frame))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       (let ((auto-save (str file "~")))
@@ -6352,7 +6358,7 @@
                      (file (car parts))
                      (line-num (string->number (cadr parts))))
                 (if (and file line-num (file-exists? file))
-                  (let ((buf (find-or-create-file-buffer file)))
+                  (let ((buf (or (find-buffer-by-file-path file) (create-buffer file))))
                     (switch-to-buffer frame buf)
                     (let ((new-ed (edit-window-editor (current-window frame))))
                       (let ((target-pos (send-message new-ed SCI_POSITIONFROMLINE (- line-num 1) 0)))
@@ -6484,7 +6490,7 @@
          (win (current-window frame))
          (ed (edit-window-editor win))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf))
+         (file (buffer-file-path buf))
          (text (editor-get-text ed)))
     (if (not file)
       (echo-message! echo "No file — prettier needs a filename for language detection")
@@ -6551,7 +6557,7 @@
          (win (current-window frame))
          (ed (edit-window-editor win))
          (buf (edit-window-buffer win))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "No file associated with buffer")
       ;; Use formatter based on file extension
@@ -6952,13 +6958,13 @@
   "Display an enhanced buffer menu with sizes and modification status."
   (let* ((echo (app-state-echo app))
          (frame (app-state-frame app))
-         (bufs (frame-buffers frame))
+         (bufs (buffer-list))
          (new-buf (create-buffer "*buffer-menu*"))
          (header "  MR Buffer                         Size  File\n  -- ------                         ----  ----\n")
          (lines (map (lambda (buf)
                        (let* ((name (buffer-name buf))
-                              (file (or (buffer-file buf) ""))
-                              (modified (buffer-modified? buf))
+                              (file (or (buffer-file-path buf) ""))
+                              (modified (buffer-modified buf))
                               (flag-m (if modified "*" " "))
                               (pad-name (if (< (string-length name) 30)
                                           (str name (make-string (- 30 (string-length name)) #\space))
@@ -7341,9 +7347,9 @@
            (empty-row "|   |   |   |   |   |   |   |\n")
            (board-text (str "=== Connect Four ===\n\n"
                             header separator
-                            (apply str (let build ((i 0) (acc '()))
-                                         (if (>= i rows) (reverse acc)
-                                           (build (+ i 1) (cons (str empty-row separator) acc)))))
+                            (apply string-append (let build ((i 0) (acc '()))
+                                                   (if (>= i rows) (reverse acc)
+                                                     (build (+ i 1) (cons (str empty-row separator) acc)))))
                             "\nUse M-x c4-drop to drop a piece (column 1-7).\n")))
       (editor-set-text ed board-text)
       (echo-message! echo "Connect Four! Use M-x c4-drop"))))
@@ -7402,8 +7408,8 @@
 
 ;; cmd-paredit-mode: Show paredit-style structural editing cheat sheet
 (def (cmd-paredit-mode app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
          (text (str "=== Paredit Mode Reference ===\n\n"
                     "Paredit provides structural editing for S-expressions.\n\n"
@@ -7429,8 +7435,8 @@
 
 ;; cmd-hi-lock-mode: Highlight all occurrences of a pattern in current buffer
 (def (cmd-hi-lock-mode app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
          (pattern (echo-read-string echo "Hi-lock pattern: ")))
     (if (or (not pattern) (string=? pattern ""))
@@ -7450,11 +7456,11 @@
 
 ;; cmd-syntax-highlight-region: Show syntax info for the selected region
 (def (cmd-syntax-highlight-region app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
-         (sel-start (editor-selection-start ed))
-         (sel-end (editor-selection-end ed)))
+         (sel-start (editor-get-selection-start ed))
+         (sel-end (editor-get-selection-end ed)))
     (if (= sel-start sel-end)
       (echo-message! echo "No region selected")
       (let* ((region-text (editor-get-text-range ed sel-start sel-end))
@@ -7484,8 +7490,8 @@
 ;; cmd-stack-overflow-search: Search Stack Overflow via DuckDuckGo
 (def (cmd-stack-overflow-search app)
   (let* ((echo (app-state-echo app))
-         (buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+         (buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (query (echo-read-string echo "Stack Overflow search: ")))
     (if (or (not query) (string=? query ""))
       (echo-message! echo "No query specified")
@@ -7509,8 +7515,8 @@
 
 ;; cmd-cheat-sheet: Display a cheat sheet for common editor commands
 (def (cmd-cheat-sheet app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
          (text (str "=== jemacs Cheat Sheet ===\n\n"
                     "--- Movement ---\n"
@@ -7556,12 +7562,12 @@
 ;; cmd-apropos-documentation: Search commands by keyword
 (def (cmd-apropos-documentation app)
   (let* ((echo (app-state-echo app))
-         (buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+         (buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (query (echo-read-string echo "Apropos: ")))
     (if (or (not query) (string=? query ""))
       (echo-message! echo "No query specified")
-      (let* ((all-cmds (hash-keys (app-state-commands app)))
+      (let* ((all-cmds (hash-keys *all-commands*))
              (matches (filter (lambda (sym)
                                 (string-contains (symbol->string sym) query))
                               all-cmds))
@@ -7580,8 +7586,8 @@
 
 ;; cmd-scratch-message: Insert the default *scratch* buffer message
 (def (cmd-scratch-message app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
          (text (str ";; This buffer is for text that is not saved, and for Scheme evaluation.\n"
                     ";; To create a file, visit it with C-x C-f and enter text in its buffer.\n"
@@ -7602,8 +7608,8 @@
 
 ;; cmd-geiser-mode: Show Geiser REPL reference for Scheme interaction
 (def (cmd-geiser-mode app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
          (text (str "=== Geiser Mode Reference ===\n\n"
                     "Geiser provides Scheme interaction in Emacs.\n\n"
@@ -7629,8 +7635,8 @@
 
 ;; cmd-sly-mode: Show SLY (Sylvester the Cat's Common Lisp IDE) reference
 (def (cmd-sly-mode app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
          (text (str "=== SLY Mode Reference ===\n\n"
                     "SLY is a Common Lisp IDE for Emacs (fork of SLIME).\n\n"
@@ -7657,8 +7663,8 @@
 
 ;; cmd-slime-mode: Show SLIME (Superior Lisp Interaction Mode) reference
 (def (cmd-slime-mode app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
          (text (str "=== SLIME Mode Reference ===\n\n"
                     "SLIME is the Superior Lisp Interaction Mode for Emacs.\n\n"
@@ -7690,11 +7696,11 @@
 
 ;; cmd-fill-region: Wrap all paragraphs in region to fill column
 (def (cmd-fill-region app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
-         (sel-start (editor-selection-start ed))
-         (sel-end (editor-selection-end ed)))
+         (sel-start (editor-get-selection-start ed))
+         (sel-end (editor-get-selection-end ed)))
     (if (= sel-start sel-end)
       (echo-message! echo "No region selected")
       (let* ((fill-col 80)
@@ -7736,12 +7742,12 @@
 
 ;; cmd-justify-paragraph: Right-justify current paragraph
 (def (cmd-justify-paragraph app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
          (fill-col 80)
          (cur-line (editor-current-line ed))
-         (total-lines (editor-line-count ed))
+         (total-lines (editor-get-line-count ed))
          (para-start (let loop ((ln cur-line))
                        (if (<= ln 0) 0
                          (let ((text (editor-get-line ed ln)))
@@ -7767,8 +7773,8 @@
 
 ;; cmd-center-line: Center the current line within fill column
 (def (cmd-center-line app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
          (fill-col 80)
          (cur-line (editor-current-line ed))
@@ -7802,10 +7808,10 @@
 
 ;; cmd-revert-buffer-quick: Reload buffer from disk without confirmation
 (def (cmd-revert-buffer-quick app)
-  (let* ((buf (app-state-current-buffer app))
-         (ed (buffer-editor buf))
+  (let* ((buf (current-buffer-from-app app))
+         (ed (current-editor app))
          (echo (app-state-echo app))
-         (file (buffer-file buf)))
+         (file (buffer-file-path buf)))
     (if (not file)
       (echo-message! echo "Buffer has no file")
       (if (not (file-exists? file))