Add 20 Emacs features round 12: docker-ps, docker-logs, git-log-graph, git-bisect, git-reflog, git-tag, randomize-lines, titlecase-region, goto-percent, copy-filename, copy-filepath, hex-to-dec, dec-to-hex, binary-to-dec, string-to-hex, rot47, sha256-hash, md5-hash, word-frequency, text-statistics

ober

86a1e31ec6254af9a176d73e24bdf456f59ecb1b

diff --git a/docs/jemacs-vs-emacs.md b/docs/jemacs-vs-emacs.md
index 8642dd6..554e0d4 100644
--- a/docs/jemacs-vs-emacs.md
+++ b/docs/jemacs-vs-emacs.md
@@ -1346,6 +1346,26 @@ No remaining Tier 1 gaps. All core editing, completion, and navigation features 
 | Sort lines by field | :orange_circle: | Sort buffer lines by whitespace-delimited field |
 | Vagrant | :orange_circle: | Run vagrant commands with completion |
 | Pip (Python) | :orange_circle: | Run pip commands with package completion |
+| Docker PS | :orange_circle: | List running Docker containers with formatted output |
+| Docker logs | :orange_circle: | View container logs by name/ID |
+| Git log graph | :orange_circle: | Graphical git log with branch visualization |
+| Git bisect | :orange_circle: | Interactive git bisect (start/good/bad/reset) |
+| Git reflog | :orange_circle: | View git reflog with timestamps |
+| Git tag | :orange_circle: | List/create/delete git tags |
+| Randomize lines | :orange_circle: | Fisher-Yates shuffle of buffer lines |
+| Titlecase region | :orange_circle: | Convert selection to Title Case |
+| Goto percent | :orange_circle: | Jump to percentage position in buffer |
+| Copy filename | :orange_circle: | Copy current buffer's filename to kill ring |
+| Copy filepath | :orange_circle: | Copy current buffer's full path to kill ring |
+| Hex to decimal | :orange_circle: | Convert hex values to decimal |
+| Decimal to hex | :orange_circle: | Convert decimal values to hex |
+| Binary to decimal | :orange_circle: | Convert binary values to decimal/hex |
+| String to hex | :orange_circle: | Show hex representation of text |
+| ROT47 | :orange_circle: | ROT47 encoding/decoding of text |
+| SHA256 hash | :orange_circle: | Compute SHA256 hash of text via sha256sum |
+| MD5 hash | :orange_circle: | Compute MD5 hash of text via md5sum |
+| Word frequency | :orange_circle: | Word frequency analysis with top-50 display |
+| Text statistics | :orange_circle: | Characters, words, lines, sentences, reading time |
 
 ---
 
diff --git a/src/jerboa-emacs/editor-extra-final.ss b/src/jerboa-emacs/editor-extra-final.ss
index 96d9145..338ba7e 100644
--- a/src/jerboa-emacs/editor-extra-final.ss
+++ b/src/jerboa-emacs/editor-extra-final.ss
@@ -5095,3 +5095,256 @@
                         (editor-goto-pos ed 0)
                         (echo-message! echo (str "pip " cmd " complete"))))
                     (loop (cons line lines))))))))))))
+
+;; ===== Round 12 Batch 2 =====
+
+;; --- Feature 11: Copy Filepath ---
+
+(def (cmd-copy-filepath app)
+  "Copy the current buffer's full file path to kill ring."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (buf (edit-window-buffer win))
+         (file (buffer-file buf)))
+    (if (not file)
+      (echo-message! echo "No file associated with buffer")
+      (let ((ed (edit-window-editor win)))
+        (send-message ed SCI_COPYTEXT (string-length file) file)
+        (echo-message! echo (str "Copied: " file))))))
+
+;; --- Feature 12: Hex to Decimal ---
+
+(def (cmd-hex-to-dec app)
+  "Convert a hexadecimal number to decimal."
+  (let* ((echo (app-state-echo app))
+         (row (tui-rows)) (width (tui-cols))
+         (input (echo-read-string echo "Hex value: " row width)))
+    (when (and input (not (string-empty? input)))
+      (let* ((hex (string-trim input))
+             (clean (if (string-prefix? "0x" hex) (substring hex 2 (string-length hex)) hex))
+             (val (string->number clean 16)))
+        (if val
+          (echo-message! echo (str "0x" clean " = " val))
+          (echo-message! echo "Invalid hex value"))))))
+
+;; --- Feature 13: Decimal to Hex ---
+
+(def (cmd-dec-to-hex app)
+  "Convert a decimal number to hexadecimal."
+  (let* ((echo (app-state-echo app))
+         (row (tui-rows)) (width (tui-cols))
+         (input (echo-read-string echo "Decimal value: " row width)))
+    (when (and input (not (string-empty? input)))
+      (let ((val (string->number (string-trim input))))
+        (if val
+          (echo-message! echo (str val " = 0x" (number->string val 16)))
+          (echo-message! echo "Invalid decimal value"))))))
+
+;; --- Feature 14: Binary to Decimal ---
+
+(def (cmd-binary-to-dec app)
+  "Convert a binary number to decimal."
+  (let* ((echo (app-state-echo app))
+         (row (tui-rows)) (width (tui-cols))
+         (input (echo-read-string echo "Binary value: " row width)))
+    (when (and input (not (string-empty? input)))
+      (let* ((bin (string-trim input))
+             (clean (if (string-prefix? "0b" bin) (substring bin 2 (string-length bin)) bin))
+             (val (string->number clean 2)))
+        (if val
+          (echo-message! echo (str "0b" clean " = " val " (0x" (number->string val 16) ")"))
+          (echo-message! echo "Invalid binary value"))))))
+
+;; --- Feature 15: String to Hex ---
+
+(def (cmd-string-to-hex app)
+  "Convert selected text to hexadecimal representation."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win))
+         (sel-start (send-message ed SCI_GETSELECTIONSTART 0 0))
+         (sel-end (send-message ed SCI_GETSELECTIONEND 0 0)))
+    (if (= sel-start sel-end)
+      ;; No selection, prompt for input
+      (let* ((row (tui-rows)) (width (tui-cols))
+             (input (echo-read-string echo "String: " row width)))
+        (when (and input (not (string-empty? input)))
+          (let ((hex-str (apply string-append
+                           (map (lambda (c) (format "~2,'0x " (char->integer c)))
+                                (string->list input)))))
+            (echo-message! echo (str "Hex: " hex-str)))))
+      (let* ((text (editor-get-text-range ed sel-start (- sel-end sel-start)))
+             (hex-str (apply string-append
+                        (map (lambda (c) (format "~2,'0x " (char->integer c)))
+                             (string->list text)))))
+        (echo-message! echo (str "Hex: " hex-str))))))
+
+;; --- Feature 16: ROT47 ---
+
+(def (cmd-rot47 app)
+  "Apply ROT47 encoding to selected text or buffer."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win))
+         (sel-start (send-message ed SCI_GETSELECTIONSTART 0 0))
+         (sel-end (send-message ed SCI_GETSELECTIONEND 0 0))
+         (has-selection (not (= sel-start sel-end)))
+         (text (if has-selection
+                 (editor-get-text-range ed sel-start (- sel-end sel-start))
+                 (let ((len (send-message ed SCI_GETLENGTH 0 0)))
+                   (editor-get-text ed len))))
+         (rot47 (list->string
+                  (map (lambda (c)
+                         (let ((n (char->integer c)))
+                           (if (and (>= n 33) (<= n 126))
+                             (integer->char (+ 33 (modulo (+ (- n 33) 47) 94)))
+                             c)))
+                       (string->list text)))))
+    (if has-selection
+      (editor-replace-selection ed rot47)
+      (begin (editor-set-text ed rot47) (editor-goto-pos ed 0)))
+    (echo-message! echo "ROT47 applied")))
+
+;; --- Feature 17: SHA256 Hash ---
+
+(def (cmd-sha256-hash app)
+  "Compute SHA256 hash of selected text or buffer content."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win))
+         (sel-start (send-message ed SCI_GETSELECTIONSTART 0 0))
+         (sel-end (send-message ed SCI_GETSELECTIONEND 0 0))
+         (text (if (= sel-start sel-end)
+                 (let ((len (send-message ed SCI_GETLENGTH 0 0)))
+                   (editor-get-text ed len))
+                 (editor-get-text-range ed sel-start (- sel-end sel-start)))))
+    (when text
+      (with-catch
+        (lambda (e) (echo-message! echo (str "hash error: " e)))
+        (lambda ()
+          (let-values (((p-stdin p-stdout p-stderr pid)
+                        (open-process-ports "sha256sum" 'block (native-transcoder))))
+            (display text p-stdin)
+            (close-port p-stdin)
+            (let ((hash (get-line p-stdout)))
+              (close-port p-stdout) (close-port p-stderr)
+              (if (eof-object? hash)
+                (echo-message! echo "Hash failed")
+                (let ((h (car (string-split hash #\space))))
+                  (echo-message! echo (str "SHA256: " h)))))))))))
+
+;; --- Feature 18: MD5 Hash ---
+
+(def (cmd-md5-hash app)
+  "Compute MD5 hash of selected text or buffer content."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win))
+         (sel-start (send-message ed SCI_GETSELECTIONSTART 0 0))
+         (sel-end (send-message ed SCI_GETSELECTIONEND 0 0))
+         (text (if (= sel-start sel-end)
+                 (let ((len (send-message ed SCI_GETLENGTH 0 0)))
+                   (editor-get-text ed len))
+                 (editor-get-text-range ed sel-start (- sel-end sel-start)))))
+    (when text
+      (with-catch
+        (lambda (e) (echo-message! echo (str "hash error: " e)))
+        (lambda ()
+          (let-values (((p-stdin p-stdout p-stderr pid)
+                        (open-process-ports "md5sum" 'block (native-transcoder))))
+            (display text p-stdin)
+            (close-port p-stdin)
+            (let ((hash (get-line p-stdout)))
+              (close-port p-stdout) (close-port p-stderr)
+              (if (eof-object? hash)
+                (echo-message! echo "Hash failed")
+                (let ((h (car (string-split hash #\space))))
+                  (echo-message! echo (str "MD5: " h)))))))))))
+
+;; --- Feature 19: Word Frequency ---
+
+(def (cmd-word-frequency app)
+  "Show word frequency analysis of the current buffer."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win))
+         (len (send-message ed SCI_GETLENGTH 0 0))
+         (text (editor-get-text ed len)))
+    (when (and text (> (string-length text) 0))
+      (let* ((words (map string-downcase
+                         (filter (lambda (w) (> (string-length w) 0))
+                                 (string-split text #\space))))
+             (freq (make-hash-table))
+             (dummy (for-each (lambda (w)
+                                (hash-put! freq w (+ 1 (hash-ref freq w 0))))
+                              words))
+             (pairs (hash->list freq))
+             (sorted (sort (lambda (a b) (> (cdr a) (cdr b))) pairs))
+             (top (if (> (length sorted) 50) (take sorted 50) sorted))
+             (content (string-append "Word Frequency Analysis\n"
+                        (make-string 50 #\=) "\n"
+                        (str "Total words: " (length words) "\n")
+                        (str "Unique words: " (length pairs) "\n\n")
+                        (string-join
+                          (map (lambda (p) (format "~5d  ~a" (cdr p) (car p)))
+                               top)
+                          "\n")))
+             (fbuf (make-buffer "*word-frequency*")))
+        (buffer-attach! ed fbuf)
+        (set! (edit-window-buffer win) fbuf)
+        (editor-set-text ed content)
+        (editor-goto-pos ed 0)
+        (echo-message! echo (str (length words) " words, " (length pairs) " unique"))))))
+
+;; --- Feature 20: Text Statistics ---
+
+(def (cmd-text-statistics app)
+  "Show detailed text statistics for the current buffer."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win))
+         (len (send-message ed SCI_GETLENGTH 0 0))
+         (text (editor-get-text ed len)))
+    (when (and text (> (string-length text) 0))
+      (let* ((chars (string-length text))
+             (chars-no-space (length (filter (lambda (c) (not (char-whitespace? c)))
+                                            (string->list text))))
+             (lines (+ 1 (length (filter (lambda (c) (char=? c #\newline))
+                                         (string->list text)))))
+             (words (length (filter (lambda (w) (> (string-length w) 0))
+                                   (string-split text #\space))))
+             (sentences (length (filter (lambda (c) (or (char=? c #\.) (char=? c #\!) (char=? c #\?)))
+                                       (string->list text))))
+             (paragraphs (let loop ((chars (string->list text)) (count 1) (prev-nl #f))
+                           (cond
+                             ((null? chars) count)
+                             ((char=? (car chars) #\newline)
+                              (if prev-nl (loop (cdr chars) (+ count 1) #t)
+                                (loop (cdr chars) count #t)))
+                             (else (loop (cdr chars) count #f)))))
+             (avg-word-len (if (> words 0)
+                             (exact (round (/ chars-no-space words)))
+                             0))
+             (content (string-append "Text Statistics\n"
+                        (make-string 50 #\=) "\n\n"
+                        (str "Characters: " chars "\n")
+                        (str "Characters (no spaces): " chars-no-space "\n")
+                        (str "Words: " words "\n")
+                        (str "Lines: " lines "\n")
+                        (str "Sentences: " sentences "\n")
+                        (str "Paragraphs: " paragraphs "\n")
+                        (str "Avg word length: " avg-word-len "\n")
+                        (str "\nReading time: ~" (max 1 (quotient words 200)) " min")))
+             (sbuf (make-buffer "*text-stats*")))
+        (buffer-attach! ed sbuf)
+        (set! (edit-window-buffer win) sbuf)
+        (editor-set-text ed content)
+        (editor-goto-pos ed 0)
+        (echo-message! echo (str words " words, " lines " lines"))))))
diff --git a/src/jerboa-emacs/editor-extra-modes.ss b/src/jerboa-emacs/editor-extra-modes.ss
index 90afaa3..8f5fc7a 100644
--- a/src/jerboa-emacs/editor-extra-modes.ss
+++ b/src/jerboa-emacs/editor-extra-modes.ss
@@ -5348,3 +5348,304 @@
                     (editor-goto-pos ed 0)
                     (echo-message! echo (str (length lines) " connections"))))
                 (loop (cons line lines))))))))))
+
+;; ===== Round 12 Batch 1 =====
+
+;; --- Feature 1: Docker PS ---
+
+(def (cmd-docker-ps app)
+  "List running Docker containers."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win)))
+    (with-catch
+      (lambda (e) (echo-message! echo (str "docker error: " e)))
+      (lambda ()
+        (let-values (((si so se pid)
+                      (open-process-ports "docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}' 2>&1"
+                        'block (native-transcoder))))
+          (close-port si)
+          (let loop ((lines '()))
+            (let ((line (get-line so)))
+              (if (eof-object? line)
+                (begin
+                  (close-port so) (close-port se)
+                  (let* ((content (string-append "Docker Containers\n"
+                                    (make-string 60 #\=) "\n\n"
+                                    (string-join (reverse lines) "\n")))
+                         (dbuf (make-buffer "*docker-ps*")))
+                    (buffer-attach! ed dbuf)
+                    (set! (edit-window-buffer win) dbuf)
+                    (editor-set-text ed content)
+                    (editor-goto-pos ed 0)
+                    (echo-message! echo (str (max 0 (- (length lines) 1)) " containers"))))
+                (loop (cons line lines))))))))))
+
+;; --- Feature 2: Docker Logs ---
+
+(def (cmd-docker-logs app)
+  "View logs for a Docker container."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win))
+         (row (tui-rows)) (width (tui-cols))
+         (container (echo-read-string echo "Container name/ID: " row width)))
+    (when (and container (not (string-empty? container)))
+      (with-catch
+        (lambda (e) (echo-message! echo (str "docker error: " e)))
+        (lambda ()
+          (let-values (((si so se pid)
+                        (open-process-ports
+                          (str "docker logs --tail 100 " (shell-quote (string-trim container)) " 2>&1")
+                          'block (native-transcoder))))
+            (close-port si)
+            (let loop ((lines '()))
+              (let ((line (get-line so)))
+                (if (eof-object? line)
+                  (begin
+                    (close-port so) (close-port se)
+                    (let* ((content (string-join (reverse lines) "\n"))
+                           (lbuf (make-buffer (str "*docker-" container "*"))))
+                      (buffer-attach! ed lbuf)
+                      (set! (edit-window-buffer win) lbuf)
+                      (editor-set-text ed content)
+                      (editor-goto-pos ed 0)
+                      (echo-message! echo (str "Logs for " container))))
+                  (loop (cons line lines)))))))))))
+
+;; --- Feature 3: Git Log Graph ---
+
+(def (cmd-git-log-graph app)
+  "Show a graphical git log."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win)))
+    (with-catch
+      (lambda (e) (echo-message! echo (str "git error: " e)))
+      (lambda ()
+        (let-values (((si so se pid)
+                      (open-process-ports
+                        "git log --graph --oneline --decorate --all -50"
+                        'block (native-transcoder))))
+          (close-port si)
+          (let loop ((lines '()))
+            (let ((line (get-line so)))
+              (if (eof-object? line)
+                (begin
+                  (close-port so) (close-port se)
+                  (let* ((content (string-join (reverse lines) "\n"))
+                         (gbuf (make-buffer "*git-graph*")))
+                    (buffer-attach! ed gbuf)
+                    (set! (edit-window-buffer win) gbuf)
+                    (editor-set-text ed content)
+                    (editor-goto-pos ed 0)
+                    (echo-message! echo "Git log graph")))
+                (loop (cons line lines))))))))))
+
+;; --- Feature 4: Git Bisect ---
+
+(def (cmd-git-bisect app)
+  "Start or control a git bisect session."
+  (let* ((echo (app-state-echo app))
+         (row (tui-rows)) (width (tui-cols))
+         (actions '("start" "good" "bad" "reset" "log"))
+         (action (echo-read-string-with-completion echo "git bisect: " actions row width)))
+    (when (and action (not (string-empty? action)))
+      (let ((extra (if (string=? action "start")
+                     (let ((bad (echo-read-string echo "Bad commit (HEAD): " row width))
+                           (good (echo-read-string echo "Good commit: " row width)))
+                       (str (if (or (not bad) (string-empty? bad)) "HEAD" bad) " "
+                            (if (or (not good) (string-empty? good)) "" good)))
+                     "")))
+        (with-catch
+          (lambda (e) (echo-message! echo (str "bisect error: " e)))
+          (lambda ()
+            (let-values (((si so se pid)
+                          (open-process-ports
+                            (str "git bisect " action " " extra " 2>&1")
+                            'block (native-transcoder))))
+              (close-port si)
+              (let loop ((lines '()))
+                (let ((line (get-line so)))
+                  (if (eof-object? line)
+                    (begin
+                      (close-port so) (close-port se)
+                      (echo-message! echo (string-join (reverse lines) " ")))
+                    (loop (cons line lines))))))))))))
+
+;; --- Feature 5: Git Reflog ---
+
+(def (cmd-git-reflog app)
+  "Show git reflog."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win)))
+    (with-catch
+      (lambda (e) (echo-message! echo (str "git error: " e)))
+      (lambda ()
+        (let-values (((si so se pid)
+                      (open-process-ports "git reflog -50 --format='%h %gd: %gs (%cr)'"
+                        'block (native-transcoder))))
+          (close-port si)
+          (let loop ((lines '()))
+            (let ((line (get-line so)))
+              (if (eof-object? line)
+                (begin
+                  (close-port so) (close-port se)
+                  (let* ((content (string-append "Git Reflog\n"
+                                    (make-string 50 #\=) "\n\n"
+                                    (string-join (reverse lines) "\n")))
+                         (rbuf (make-buffer "*git-reflog*")))
+                    (buffer-attach! ed rbuf)
+                    (set! (edit-window-buffer win) rbuf)
+                    (editor-set-text ed content)
+                    (editor-goto-pos ed 0)
+                    (echo-message! echo (str (length lines) " reflog entries"))))
+                (loop (cons line lines))))))))))
+
+;; --- Feature 6: Git Tag ---
+
+(def (cmd-git-tag app)
+  "List or create git tags."
+  (let* ((echo (app-state-echo app))
+         (row (tui-rows)) (width (tui-cols))
+         (actions '("list" "create" "delete"))
+         (action (echo-read-string-with-completion echo "Tag action: " actions row width)))
+    (when (and action (not (string-empty? action)))
+      (cond
+        ((string=? action "list")
+         (let* ((frame (app-state-frame app))
+                (win (current-window frame))
+                (ed (edit-window-editor win)))
+           (with-catch
+             (lambda (e) (echo-message! echo (str "git error: " e)))
+             (lambda ()
+               (let-values (((si so se pid)
+                             (open-process-ports "git tag -l --sort=-creatordate" 'block (native-transcoder))))
+                 (close-port si)
+                 (let loop ((lines '()))
+                   (let ((line (get-line so)))
+                     (if (eof-object? line)
+                       (begin
+                         (close-port so) (close-port se)
+                         (let* ((content (string-join (reverse lines) "\n"))
+                                (tbuf (make-buffer "*git-tags*")))
+                           (buffer-attach! ed tbuf)
+                           (set! (edit-window-buffer win) tbuf)
+                           (editor-set-text ed content)
+                           (editor-goto-pos ed 0)))
+                       (loop (cons line lines))))))))))
+        ((string=? action "create")
+         (let ((name (echo-read-string echo "Tag name: " row width)))
+           (when (and name (not (string-empty? name)))
+             (let ((msg (echo-read-string echo "Message (empty for lightweight): " row width)))
+               (with-catch
+                 (lambda (e) (echo-message! echo (str "tag error: " e)))
+                 (lambda ()
+                   (let* ((cmd (if (and msg (not (string-empty? msg)))
+                                 (str "git tag -a " (shell-quote name) " -m " (shell-quote msg))
+                                 (str "git tag " (shell-quote name)))))
+                     (let-values (((si so se pid)
+                                   (open-process-ports (str cmd " 2>&1") 'block (native-transcoder))))
+                       (close-port si) (close-port so) (close-port se)
+                       (echo-message! echo (str "Created tag: " name))))))))))
+        ((string=? action "delete")
+         (let ((name (echo-read-string echo "Delete tag: " row width)))
+           (when (and name (not (string-empty? name)))
+             (with-catch
+               (lambda (e) (echo-message! echo (str "tag error: " e)))
+               (lambda ()
+                 (let-values (((si so se pid)
+                               (open-process-ports (str "git tag -d " (shell-quote name) " 2>&1") 'block (native-transcoder))))
+                   (close-port si) (close-port so) (close-port se)
+                   (echo-message! echo (str "Deleted tag: " name))))))))))))
+
+;; --- Feature 7: Randomize Lines ---
+
+(def (cmd-randomize-lines app)
+  "Shuffle the lines in the current buffer randomly."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win))
+         (len (send-message ed SCI_GETLENGTH 0 0))
+         (text (editor-get-text ed len))
+         (lines (string-split text #\newline))
+         (n (length lines))
+         (vec (list->vector lines)))
+    ;; Fisher-Yates shuffle
+    (do ((i (- n 1) (- i 1))) ((< i 1))
+      (let* ((j (random (+ i 1)))
+             (tmp (vector-ref vec i)))
+        (vector-set! vec i (vector-ref vec j))
+        (vector-set! vec j tmp)))
+    (editor-set-text ed (string-join (vector->list vec) "\n"))
+    (editor-goto-pos ed 0)
+    (echo-message! echo (str "Shuffled " n " lines"))))
+
+;; --- Feature 8: Titlecase Region ---
+
+(def (cmd-titlecase-region app)
+  "Convert selected text to Title Case."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win))
+         (sel-start (send-message ed SCI_GETSELECTIONSTART 0 0))
+         (sel-end (send-message ed SCI_GETSELECTIONEND 0 0)))
+    (if (= sel-start sel-end)
+      (echo-message! echo "No selection")
+      (let* ((text (editor-get-text-range ed sel-start (- sel-end sel-start)))
+             (words (string-split text #\space))
+             (titled (map (lambda (w)
+                            (if (> (string-length w) 0)
+                              (string-append
+                                (string (char-upcase (string-ref w 0)))
+                                (string-downcase (substring w 1 (string-length w))))
+                              w))
+                          words))
+             (result (string-join titled " ")))
+        (editor-replace-selection ed result)
+        (echo-message! echo "Title case applied")))))
+
+;; --- Feature 9: Goto Percent ---
+
+(def (cmd-goto-percent app)
+  "Go to a percentage position in the buffer."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (ed (edit-window-editor win))
+         (row (tui-rows)) (width (tui-cols))
+         (input (echo-read-string echo "Goto %: " row width)))
+    (when (and input (not (string-empty? input)))
+      (let ((pct (string->number (string-trim input))))
+        (when (and pct (>= pct 0) (<= pct 100))
+          (let* ((len (send-message ed SCI_GETLENGTH 0 0))
+                 (pos (exact (round (* len (/ pct 100.0))))))
+            (send-message ed SCI_GOTOPOS pos 0)
+            (echo-message! echo (str "At " pct "%"))))))))
+
+;; --- Feature 10: Copy Filename ---
+
+(def (cmd-copy-filename app)
+  "Copy the current buffer's filename (without path) to kill ring."
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (win (current-window frame))
+         (buf (edit-window-buffer win))
+         (file (buffer-file buf)))
+    (if (not file)
+      (echo-message! echo "No file associated with buffer")
+      (let* ((name (let loop ((i (- (string-length file) 1)))
+                     (cond
+                       ((< i 0) file)
+                       ((char=? (string-ref file i) #\/) (substring file (+ i 1) (string-length file)))
+                       (else (loop (- i 1))))))
+             (ed (edit-window-editor win)))
+        (send-message ed SCI_COPYTEXT (string-length name) name)
+        (echo-message! echo (str "Copied: " name))))))
diff --git a/src/jerboa-emacs/editor-extra-regs2.ss b/src/jerboa-emacs/editor-extra-regs2.ss
index 4a9ea10..28ff72c 100644
--- a/src/jerboa-emacs/editor-extra-regs2.ss
+++ b/src/jerboa-emacs/editor-extra-regs2.ss
@@ -1681,4 +1681,26 @@
   (register-command! 'sort-lines-by-field cmd-sort-lines-by-field)
   (register-command! 'vagrant cmd-vagrant)
   (register-command! 'pip cmd-pip)
+  ;; Round 12 batch 1: docker-ps, docker-logs, git-log-graph, git-bisect, git-reflog, git-tag, randomize-lines, titlecase-region, goto-percent, copy-filename
+  (register-command! 'docker-ps cmd-docker-ps)
+  (register-command! 'docker-logs cmd-docker-logs)
+  (register-command! 'git-log-graph cmd-git-log-graph)
+  (register-command! 'git-bisect cmd-git-bisect)
+  (register-command! 'git-reflog cmd-git-reflog)
+  (register-command! 'git-tag cmd-git-tag)
+  (register-command! 'randomize-lines cmd-randomize-lines)
+  (register-command! 'titlecase-region cmd-titlecase-region)
+  (register-command! 'goto-percent cmd-goto-percent)
+  (register-command! 'copy-filename cmd-copy-filename)
+  ;; Round 12 batch 2: copy-filepath, hex-to-dec, dec-to-hex, binary-to-dec, string-to-hex, rot47, sha256-hash, md5-hash, word-frequency, text-statistics
+  (register-command! 'copy-filepath cmd-copy-filepath)
+  (register-command! 'hex-to-dec cmd-hex-to-dec)
+  (register-command! 'dec-to-hex cmd-dec-to-hex)
+  (register-command! 'binary-to-dec cmd-binary-to-dec)
+  (register-command! 'string-to-hex cmd-string-to-hex)
+  (register-command! 'rot47 cmd-rot47)
+  (register-command! 'sha256-hash cmd-sha256-hash)
+  (register-command! 'md5-hash cmd-md5-hash)
+  (register-command! 'word-frequency cmd-word-frequency)
+  (register-command! 'text-statistics cmd-text-statistics)
 )