Add TUI modules and termbox2 shim to static binary build

ober

e8f153b695e0f975d4e331c218ccacaee5492312

diff --git a/build-binary.ss b/build-binary.ss
index df21a51..a942233 100644
--- a/build-binary.ss
+++ b/build-binary.ss
@@ -116,6 +116,17 @@
     "lib/jcode/tool/git"
     "lib/jcode/tool/lsp"
     "lib/jcode/mcp/client"
+    "lib/jcode/ui/tui-ffi"
+    "lib/jcode/ui/tui-theme"
+    "lib/jcode/ui/tui-keys"
+    "lib/jcode/ui/tui-markdown"
+    "lib/jcode/ui/tui-diff"
+    "lib/jcode/ui/tui-message"
+    "lib/jcode/ui/tui-status"
+    "lib/jcode/ui/tui-input"
+    "lib/jcode/ui/tui-sidebar"
+    "lib/jcode/ui/tui-dialog"
+    "lib/jcode/ui/tui"
     "lib/jcode/ui/cli"))
 
 ;; --- Step 0: Clean stale .so files so WPO can recompile everything ---
@@ -167,6 +178,31 @@
 (when (file-exists? "vendor/chez-sqlite/src/chez-sqlite.so")
   (delete-file "vendor/chez-sqlite/src/chez-sqlite.so"))
 
+;; Patch tui-ffi: in static binary, termbox symbols are linked in via Sforeign_symbol.
+;; Replace the load-shared-object block with #t so define-tb uses foreign-procedure directly.
+(printf "      Patching tui-ffi for static linking...~n")
+(define tui-ffi-sls "lib/jcode/ui/tui-ffi.sls")
+(define tui-ffi-backup "lib/jcode/ui/tui-ffi.sls.bak")
+(system (format "cp ~a ~a" tui-ffi-sls tui-ffi-backup))
+(let* ((src (call-with-input-file tui-ffi-sls get-string-all))
+       (src (string-replace-first src
+              (string-append
+                "(def _shim-loaded\n"
+                "       (or (guard (e [list #t #f])\n"
+                "             (load-shared-object \"jcode_tui_shim.dylib\"))\n"
+                "           (guard (e [list #t #f])\n"
+                "             (load-shared-object \"jcode_tui_shim.so\"))\n"
+                "           (guard (e [list #t #f])\n"
+                "             (load-shared-object\n"
+                "               (path-join\n"
+                "                 (path-directory (car (command-line)))\n"
+                "                 \"jcode_tui_shim.dylib\")))\n"
+                "           #f))")
+              "(def _shim-loaded #t)")))
+  (call-with-output-file tui-ffi-sls (lambda (p) (put-string p src)) 'replace))
+(when (file-exists? "lib/jcode/ui/tui-ffi.so")
+  (delete-file "lib/jcode/ui/tui-ffi.so"))
+
 ;; --- Step 1: Compile all modules + entry point ---
 (printf "[1/6] Compiling all modules (optimize-level 3, WPO)...~n")
 (parameterize ((compile-imported-libraries #t)
@@ -261,6 +297,19 @@
     "chez_sqlite_bind_null" "chez_sqlite_last_insert_rowid"
     "chez_sqlite_changes" "chez_sqlite_errmsg"
     "chez_SQLITE_ROW" "chez_SQLITE_DONE" "chez_SQLITE_OK"
+    ;; termbox2 TUI shim
+    "jcode_tb_init" "jcode_tb_shutdown"
+    "jcode_tb_width" "jcode_tb_height"
+    "jcode_tb_clear" "jcode_tb_present"
+    "jcode_tb_set_cursor" "jcode_tb_hide_cursor"
+    "jcode_tb_change_cell" "jcode_tb_set_clear_attrs"
+    "jcode_tb_print" "jcode_tb_printf"
+    "jcode_tb_set_input_mode" "jcode_tb_set_output_mode"
+    "jcode_tb_poll_event" "jcode_tb_peek_event"
+    "jcode_tb_event_type" "jcode_tb_event_mod"
+    "jcode_tb_event_key" "jcode_tb_event_ch"
+    "jcode_tb_event_w" "jcode_tb_event_h"
+    "jcode_tb_event_x" "jcode_tb_event_y"
     ;; jerboa-native (TLS/rustls)
     "jerboa_tls_server_new" "jerboa_tls_server_new_mtls"
     "jerboa_tls_server_free" "jerboa_tls_accept"
@@ -362,6 +411,13 @@
     (printf "Error: SQLite shim compilation failed~n")
     (exit 1)))
 
+;; Compile termbox2 TUI shim (header-only library, compiled as single unit)
+(let ((rc (system (format "~a -c -fPIC -O2 -DTB_OPT_ATTR_W=32 -Ivendor/termbox2 -o jcode-tui-shim.o src/jcode/ui/jcode_tui_shim.c"
+                          cc))))
+  (unless (= rc 0)
+    (printf "Error: TUI shim compilation failed~n")
+    (exit 1)))
+
 ;; Compile main
 (let ((rc (system (format "~a -c -I~a -o jcode-main.o jcode-main.c" cc chez-dir))))
   (unless (= rc 0)
@@ -375,18 +431,21 @@
   (printf "Warning: ~a not found — TLS will be unavailable~n" native-lib))
 
 ;; Link — Sforeign_symbol registers FFI symbols
-(let ((rc (system (format "~a -o jcode jcode-main.o chez-sqlite-shim.o ~a -L~a ~a -framework Security -framework CoreFoundation"
+(let ((rc (system (format "~a -o jcode jcode-main.o chez-sqlite-shim.o jcode-tui-shim.o ~a -L~a ~a~a"
                           cc
                           (if (file-exists? native-lib) native-lib "")
-                          chez-dir link-libs))))
+                          chez-dir link-libs
+                          (if macos? " -framework Security -framework CoreFoundation" "")))))
   (unless (= rc 0)
     (printf "Error: linking failed~n")
     (exit 1)))
 
-;; --- Step 5: Restore chez-sqlite ---
-(printf "[5/6] Restoring chez-sqlite...~n")
+;; --- Step 5: Restore patched files ---
+(printf "[5/6] Restoring chez-sqlite and tui-ffi...~n")
 (when (file-exists? chez-sqlite-backup)
   (system (format "mv ~a ~a" chez-sqlite-backup chez-sqlite-sls)))
+(when (file-exists? tui-ffi-backup)
+  (system (format "mv ~a ~a" tui-ffi-backup tui-ffi-sls)))
 
 ;; --- Step 6: Cleanup ---
 (printf "[6/6] Cleaning up intermediate files...~n")
@@ -395,6 +454,7 @@
   '("jcode-main.c"
     "jcode-main.o"
     "chez-sqlite-shim.o"
+    "jcode-tui-shim.o"
     "jcode_program.h"
     "jcode_petite_boot.h"
     "jcode_scheme_boot.h"