Update Jerboa discovery data

ober

60b00bb2abe2cb27b759670a18d7d1b7e6d436d3

diff --git a/data/cookbooks.sexp b/data/cookbooks.sexp
index 656285c..12eb0be 100644
--- a/data/cookbooks.sexp
+++ b/data/cookbooks.sexp
@@ -1786,10 +1786,10 @@
    ("title" . "Write string to file (with overwrite support)"))
  (("code"
     .
-    ";; When a long-running operation blocks the Qt event loop (e.g. terminal\n;; rendering with many FFI calls), the UI becomes unresponsive — M-x,\n;; keyboard shortcuts, etc. stop working.\n;;\n;; Fix: call qt-app-process-events! periodically to pump pending events.\n\n;; Store a global reference to the Qt application:\n(def *qt-app-ref* #f)\n;; Set it during init:\n(set! *qt-app-ref* qt-app)\n\n;; Then in your long-running loop or after heavy processing:\n(when *qt-app-ref*\n  (qt-app-process-events! *qt-app-ref*))\n\n;; Real example: vterm rendering after vtscreen-feed! (3840+ FFI calls\n;; for row-diff + per-cell colors). Without process-events, keystrokes\n;; queue up and M-x never fires during flooding commands like `find ~/ -ls`.\n\n;; Also useful in minibuffer polling loops:\n;; (let loop ()\n;;   (qt-app-process-events! app)\n;;   (when (not done?) (loop)))") ("id" . "qt-process-events-responsiveness") ("imports")
+    ";; When a long-running operation blocks the Qt event loop (e.g. terminal\n;; rendering with many FFI calls), the UI becomes unresponsive — M-x,\n;; keyboard shortcuts, etc. stop working.\n;;\n;; Fix: call qt-pump-events! periodically to pump pending events.\n\n;; Store a global reference to the Qt application:\n(def *qt-app-ref* #f)\n;; Set it during init:\n(set! *qt-app-ref* qt-app)\n\n;; Then in your long-running loop or after heavy processing:\n(when *qt-app-ref*\n  (qt-pump-events! *qt-app-ref*))\n\n;; Real example: vterm rendering after vtscreen-feed! (3840+ FFI calls\n;; for row-diff + per-cell colors). Without process-events, keystrokes\n;; queue up and M-x never fires during flooding commands like `find ~/ -ls`.\n\n;; Also useful in minibuffer polling loops:\n;; (let loop ()\n;;   (qt-pump-events! app)\n;;   (when (not done?) (loop)))") ("id" . "qt-process-events-responsiveness") ("imports")
    ("notes"
      .
-     "qt-app-process-events! dispatches all pending events (key presses, mouse, paint, timers). Call it between chunks of work, not inside tight inner loops (each call has FFI overhead). Combine with a byte/time budget to cap how much work happens per tick before pumping events.")
+     "qt-pump-events! dispatches all pending events (key presses, mouse, paint, timers). Call it between chunks of work, not inside tight inner loops (each call has FFI overhead). Combine with a byte/time budget to cap how much work happens per tick before pumping events.")
    ("tags" "qt" "process-events" "responsive" "event-loop"
      "freeze" "ui" "blocking")
    ("title"
@@ -1800,7 +1800,7 @@
     ";; When a PTY reader thread posts data chunks to a channel, the main-thread\n;; timer tick drains all available chunks. For flooding commands (find ~/ -ls),\n;; this can accumulate megabytes per tick, blocking the event loop.\n;;\n;; Fix: set a byte budget per tick. Process up to the budget, then stop\n;; and let the next tick handle the rest.\n\n(def *pty-batch-budget* 65536)  ;; 64KB per tick\n\n(let drain ((chunks []) (bytes 0) (done-msg #f))\n  (if (and (> bytes 0) (>= bytes *pty-batch-budget*))\n    ;; Budget exhausted — render what we have, leave rest for next tick\n    (let ((combined (apply string-append (reverse chunks))))\n      (process-batch! combined))\n    ;; Try to get more data (non-blocking)\n    (let ((msg (channel-try-get ch)))\n      (cond\n        ((not msg)\n         ;; Channel empty — render accumulated chunks\n         (unless (null? chunks)\n           (let ((combined (apply string-append (reverse chunks))))\n             (process-batch! combined))))\n        ((string? msg)\n         (drain (cons msg chunks)\n                (+ bytes (string-length msg))\n                done-msg))\n        (else\n         ;; Control message (e.g. process-done sentinel)\n         (drain chunks bytes msg))))))") ("id" . "pty-batch-budget-throttle") ("imports")
    ("notes"
      .
-     "The byte budget alone may not be sufficient for UI responsiveness if the rendering step itself is expensive (e.g. vtscreen row-diff with per-cell FFI color calls). Combine with qt-app-process-events! (see qt-process-events-responsiveness recipe) to pump the event queue between render batches.")
+     "The byte budget alone may not be sufficient for UI responsiveness if the rendering step itself is expensive (e.g. vtscreen row-diff with per-cell FFI color calls). Combine with qt-pump-events! (see qt-process-events-responsiveness recipe) to pump the event queue between render batches.")
    ("related" "qt-process-events-responsiveness")
    ("tags" "pty" "terminal" "vterm" "throttle" "budget" "batch"
      "responsiveness")
@@ -1850,7 +1850,7 @@
     ";; PITFALL: qt-plain-text-edit-set-text! writes to whatever Scintilla\n;; document is currently active on the editor widget. In async callbacks,\n;; the user may have switched buffers, so the active document is WRONG.\n;;\n;; WRONG — writes into whatever buffer is currently displayed:\n;; (async-read-file! path\n;;   (lambda (text)\n;;     (let ((ed (qt-current-editor fr)))\n;;       (qt-plain-text-edit-set-text! ed text))))  ;; BUG!\n;;\n;; CORRECT — temporarily switch to the target document:\n(let ((target-doc (buffer-doc-pointer target-buf)))\n  (async-read-file! path\n    (lambda (text)\n      (let* ((ed (qt-current-editor (app-state-frame app)))\n             (current-doc (sci-send ed SCI_GETDOCPOINTER 0)))\n        ;; Switch to target document\n        (sci-send ed SCI_SETDOCPOINTER 0 target-doc)\n        (qt-plain-text-edit-set-text! ed text)\n        ;; Switch back\n        (sci-send ed SCI_SETDOCPOINTER 0 current-doc)))))") ("id" . "scintilla-async-document-safety") ("imports")
    ("notes"
      .
-     "Same pattern applies to ANY async or timer callback that writes text to a Scintilla editor. The vterm render path has a related issue: qt-app-process-events! can process switch-buffer events mid-render, changing the active document. Always re-check window-buffer association after processEvents.")
+     "Same pattern applies to ANY async or timer callback that writes text to a Scintilla editor. The vterm render path has a related issue: qt-pump-events! can process switch-buffer events mid-render, changing the active document. Always re-check window-buffer association after processEvents.")
    ("tags" "scintilla" "async" "document" "SCI_SETDOCPOINTER"
      "set-text" "buffer-switch")
    ("title"
@@ -1858,16 +1858,16 @@
      "Safe async text writes to Scintilla: switch to target document first"))
  (("code"
     .
-    ";; PITFALL: qt-app-process-events! pumps the Qt event queue, which can\n;; fire key event callbacks. If a switch-buffer keystroke is queued, it\n;; fires MID-OPERATION, changing the editor's active document.\n;;\n;; WRONG — assumes state is unchanged after processEvents:\n;; (let ((ed (find-editor-for-terminal-buffer)))\n;;   (vtscreen-feed! vt data)\n;;   (qt-app-process-events! app)   ;; may fire switch-buffer!\n;;   (qt-plain-text-edit-set-text! ed rendered))  ;; writes to WRONG doc!\n;;\n;; CORRECT — re-check after processEvents:\n(let ((ed (qt-edit-window-editor win)))\n  (vtscreen-feed! vt data)\n  (qt-app-process-events! app)\n  ;; Re-check: window may now show a different buffer\n  (when (eq? (qt-edit-window-buffer win) terminal-buf)\n    (qt-plain-text-edit-set-text! ed rendered)))") ("id" . "qt-process-events-recheck-state") ("imports")
+    ";; PITFALL: qt-pump-events! pumps the Qt event queue, which can\n;; fire key event callbacks. If a switch-buffer keystroke is queued, it\n;; fires MID-OPERATION, changing the editor's active document.\n;;\n;; WRONG — assumes state is unchanged after processEvents:\n;; (let ((ed (find-editor-for-terminal-buffer)))\n;;   (vtscreen-feed! vt data)\n;;   (qt-pump-events! app)   ;; may fire switch-buffer!\n;;   (qt-plain-text-edit-set-text! ed rendered))  ;; writes to WRONG doc!\n;;\n;; CORRECT — re-check after processEvents:\n(let ((ed (qt-edit-window-editor win)))\n  (vtscreen-feed! vt data)\n  (qt-pump-events! app)\n  ;; Re-check: window may now show a different buffer\n  (when (eq? (qt-edit-window-buffer win) terminal-buf)\n    (qt-plain-text-edit-set-text! ed rendered)))") ("id" . "qt-process-events-recheck-state") ("imports")
    ("notes"
      .
-     "This applies to ANY long-running operation that calls qt-app-process-events! for responsiveness. The processEvents call is necessary for UI responsiveness (M-x during vterm flood), but it introduces re-entrancy. Always re-validate mutable state after the call.")
+     "This applies to ANY long-running operation that calls qt-pump-events! for responsiveness. The processEvents call is necessary for UI responsiveness (M-x during vterm flood), but it introduces re-entrancy. Always re-validate mutable state after the call.")
    ("related" "qt-process-events-responsiveness")
    ("tags" "qt" "process-events" "state" "reentrant"
      "buffer-switch" "race-condition")
    ("title"
      .
-     "Re-check mutable state after qt-app-process-events! (can trigger buffer switches)"))
+     "Re-check mutable state after qt-pump-events! (can trigger buffer switches)"))
  (("code"
     .
     ";; Key chords should be case-sensitive and use UPPERCASE characters.\n;; This ensures lowercase typing in terminals has zero interference.\n;;\n;; Registration (case-sensitive, both orderings):\n(def (key-chord-define-global two-char-str cmd)\n  (let ((c1 (string-ref two-char-str 0))\n        (c2 (string-ref two-char-str 1)))\n    (hash-put! *chord-map* (string c1 c2) cmd)\n    (when (not (char=? c1 c2))\n      (hash-put! *chord-map* (string c2 c1) cmd))\n    (hash-put! *chord-first-chars* c1 #t)\n    (hash-put! *chord-first-chars* c2 #t)))\n\n;; Example bindings (all uppercase):\n;; chord MT vterm\n;; chord EE eshell\n;; chord GG keyboard-quit\n\n;; IMPORTANT: During chord detection, IGNORE bare modifier key releases\n;; (Shift, Ctrl, Alt, Meta). When typing uppercase TM, the Shift release\n;; arrives between T and M. Without filtering, it cancels the chord.\n;; Qt key codes for modifiers: #x01000020 to #x01000025, #x01000053, #x01000054") ("id" . "key-chord-case-sensitive-uppercase") ("imports")
@@ -5537,7 +5537,7 @@
    ("title" . "Wrap a String to a Fixed Character Width"))
  (("code"
     .
-    "(import (jerboa prelude))\n(import (chezscheme))\n(import (jerboa-qt qt))\n\n(def immediate-exit (foreign-procedure \"_exit\" (int) void))\n\n(def (finish status)\n  (flush-output-port (current-output-port))\n  (flush-output-port (current-error-port))\n  (immediate-exit status))\n\n(def (pump! app n)\n  (when (> n 0)\n    (qt-app-process-events! app)\n    (pump! app (- n 1))))\n\n(def app (qt-app-create))\n(def win (qt-main-window-create))\n(def canvas (qt-paint-widget-create))\n(def paint-count (vector 0))\n(def key-code (vector #f))\n\n(qt-widget-set-minimum-size! canvas 120 160)\n(qt-on-paint! canvas\n  (lambda ()\n    (vector-set! paint-count 0 (+ (vector-ref paint-count 0) 1))\n    (let ((p (qt-paint-widget-painter canvas)))\n      (when p\n        (qt-painter-fill-rect! p 0 0 120 160 24 26 32 255)\n        (qt-painter-fill-rect! p 20 20 30 30 239 83 80 255)))))\n(qt-main-window-set-central-widget! win canvas)\n(qt-on-key-press! win\n  (lambda ()\n    (vector-set! key-code 0 (qt-last-key-code))))\n(qt-widget-show! win)\n(qt-widget-set-focus! win)\n(qt-paint-widget-update! canvas)\n(pump! app 8)\n(unless (> (vector-ref paint-count 0) 0)\n  (error 'smoke \"paint callback did not run\"))\n(unless (qt-widget-screenshot! win \"/tmp/jerboa-qt-smoke.png\")\n  (error 'smoke \"screenshot failed\"))\n(qt-send-key-press! win QT_KEY_LEFT QT_MOD_NONE \"\")\n(pump! app 4)\n(unless (equal? (vector-ref key-code 0) QT_KEY_LEFT)\n  (error 'smoke \"synthetic key press was not observed\"))\n(displayln \"qt smoke passed\")\n(finish 0)\n") ("id" . "jerboa-qt-offscreen-screenshot-smoke")
+    "(import (jerboa prelude))\n(import (chezscheme))\n(import (jerboa-qt qt))\n\n(def immediate-exit (foreign-procedure \"_exit\" (int) void))\n\n(def (finish status)\n  (flush-output-port (current-output-port))\n  (flush-output-port (current-error-port))\n  (immediate-exit status))\n\n(def (pump! app n)\n  (when (> n 0)\n    (qt-pump-events! app)\n    (pump! app (- n 1))))\n\n(def app (qt-app-create))\n(def win (qt-main-window-create))\n(def canvas (qt-paint-widget-create))\n(def paint-count (vector 0))\n(def key-code (vector #f))\n\n(qt-widget-set-minimum-size! canvas 120 160)\n(qt-on-paint! canvas\n  (lambda ()\n    (vector-set! paint-count 0 (+ (vector-ref paint-count 0) 1))\n    (let ((p (qt-paint-widget-painter canvas)))\n      (when p\n        (qt-painter-fill-rect! p 0 0 120 160 24 26 32 255)\n        (qt-painter-fill-rect! p 20 20 30 30 239 83 80 255)))))\n(qt-main-window-set-central-widget! win canvas)\n(qt-on-key-press! win\n  (lambda ()\n    (vector-set! key-code 0 (qt-last-key-code))))\n(qt-widget-show! win)\n(qt-widget-set-focus! win)\n(qt-paint-widget-update! canvas)\n(pump! app 8)\n(unless (> (vector-ref paint-count 0) 0)\n  (error 'smoke \"paint callback did not run\"))\n(unless (qt-widget-screenshot! win \"/tmp/jerboa-qt-smoke.png\")\n  (error 'smoke \"screenshot failed\"))\n(qt-send-key-press! win QT_KEY_LEFT QT_MOD_NONE \"\")\n(pump! app 4)\n(unless (equal? (vector-ref key-code 0) QT_KEY_LEFT)\n  (error 'smoke \"synthetic key press was not observed\"))\n(displayln \"qt smoke passed\")\n(finish 0)\n") ("id" . "jerboa-qt-offscreen-screenshot-smoke")
    ("imports"
      "(jerboa prelude)"
      "(chezscheme)"
@@ -5574,7 +5574,7 @@
      "Run a jerboa-qt example with jerbuild and shim environment"))
  (("code"
     .
-    "(import (jerboa prelude))\n(import (chezscheme))\n(import (jerboa-qt qt))\n\n(def immediate-exit (foreign-procedure \"_exit\" (int) void))\n\n(def (finish status)\n  (flush-output-port (current-output-port))\n  (flush-output-port (current-error-port))\n  (immediate-exit status))\n\n(def (pump! app n)\n  (when (> n 0)\n    (qt-app-process-events! app)\n    (pump! app (- n 1))))\n\n(def (draw-cell! painter col row r g b)\n  (qt-painter-fill-rect! painter\n                         (+ 12 (* col 18))\n                         (+ 12 (* row 18))\n                         16 16 r g b 255))\n\n(def (draw-board! painter)\n  (qt-painter-fill-rect! painter 0 0 220 400 24 26 32 255)\n  (let row-loop ((row 0))\n    (when (< row 20)\n      (let col-loop ((col 0))\n        (when (< col 10)\n          (draw-cell! painter col row 44 48 58)\n          (col-loop (+ col 1))))\n      (row-loop (+ row 1)))))\n\n(def app (qt-app-create))\n(def win (qt-main-window-create))\n(def canvas (qt-paint-widget-create))\n(def pos (vector 4 3))\n(def paint-count (vector 0))\n\n(qt-widget-set-minimum-size! canvas 220 400)\n(qt-on-paint! canvas\n  (lambda ()\n    (vector-set! paint-count 0 (+ (vector-ref paint-count 0) 1))\n    (let ((p (qt-paint-widget-painter canvas)))\n      (when p\n        (draw-board! p)\n        (let ((col (vector-ref pos 0))\n              (row (vector-ref pos 1)))\n          (draw-cell! p col row 132 204 22)\n          (draw-cell! p (+ col 1) row 132 204 22)\n          (draw-cell! p col (+ row 1) 132 204 22)\n          (draw-cell! p (+ col 1) (+ row 1) 132 204 22))))))\n\n(qt-on-key-press! win\n  (lambda ()\n    (let ((key (qt-last-key-code))\n          (col (vector-ref pos 0)))\n      (cond\n       ((= key QT_KEY_LEFT)\n        (vector-set! pos 0 (max 0 (- col 1))))\n       ((= key QT_KEY_RIGHT)\n        (vector-set! pos 0 (min 8 (+ col 1))))))\n    (qt-paint-widget-update! canvas)))\n\n(qt-main-window-set-title! win \"Stateful Board\")\n(qt-main-window-set-central-widget! win canvas)\n(qt-widget-resize! win 240 460)\n(qt-widget-show! win)\n(qt-widget-set-focus! win)\n(qt-paint-widget-update! canvas)\n(pump! app 8)\n\n(qt-send-key-press! win QT_KEY_RIGHT QT_MOD_NONE \"\")\n(pump! app 4)\n(unless (= (vector-ref pos 0) 5)\n  (error 'stateful-board \"right key did not move piece\"))\n\n(qt-send-key-press! win QT_KEY_LEFT QT_MOD_NONE \"\")\n(qt-send-key-press! win QT_KEY_LEFT QT_MOD_NONE \"\")\n(pump! app 4)\n(unless (= (vector-ref pos 0) 3)\n  (error 'stateful-board \"left keys did not move piece\"))\n\n(unless (> (vector-ref paint-count 0) 0)\n  (error 'stateful-board \"paint callback did not run\"))\n(unless (qt-widget-screenshot! win \"/tmp/jerboa-qt-stateful-board.png\")\n  (error 'stateful-board \"screenshot failed\"))\n\n(displayln \"stateful board passed\")\n(finish 0)\n") ("id" . "jerboa-qt-stateful-board-key-redraw")
+    "(import (jerboa prelude))\n(import (chezscheme))\n(import (jerboa-qt qt))\n\n(def immediate-exit (foreign-procedure \"_exit\" (int) void))\n\n(def (finish status)\n  (flush-output-port (current-output-port))\n  (flush-output-port (current-error-port))\n  (immediate-exit status))\n\n(def (pump! app n)\n  (when (> n 0)\n    (qt-pump-events! app)\n    (pump! app (- n 1))))\n\n(def (draw-cell! painter col row r g b)\n  (qt-painter-fill-rect! painter\n                         (+ 12 (* col 18))\n                         (+ 12 (* row 18))\n                         16 16 r g b 255))\n\n(def (draw-board! painter)\n  (qt-painter-fill-rect! painter 0 0 220 400 24 26 32 255)\n  (let row-loop ((row 0))\n    (when (< row 20)\n      (let col-loop ((col 0))\n        (when (< col 10)\n          (draw-cell! painter col row 44 48 58)\n          (col-loop (+ col 1))))\n      (row-loop (+ row 1)))))\n\n(def app (qt-app-create))\n(def win (qt-main-window-create))\n(def canvas (qt-paint-widget-create))\n(def pos (vector 4 3))\n(def paint-count (vector 0))\n\n(qt-widget-set-minimum-size! canvas 220 400)\n(qt-on-paint! canvas\n  (lambda ()\n    (vector-set! paint-count 0 (+ (vector-ref paint-count 0) 1))\n    (let ((p (qt-paint-widget-painter canvas)))\n      (when p\n        (draw-board! p)\n        (let ((col (vector-ref pos 0))\n              (row (vector-ref pos 1)))\n          (draw-cell! p col row 132 204 22)\n          (draw-cell! p (+ col 1) row 132 204 22)\n          (draw-cell! p col (+ row 1) 132 204 22)\n          (draw-cell! p (+ col 1) (+ row 1) 132 204 22))))))\n\n(qt-on-key-press! win\n  (lambda ()\n    (let ((key (qt-last-key-code))\n          (col (vector-ref pos 0)))\n      (cond\n       ((= key QT_KEY_LEFT)\n        (vector-set! pos 0 (max 0 (- col 1))))\n       ((= key QT_KEY_RIGHT)\n        (vector-set! pos 0 (min 8 (+ col 1))))))\n    (qt-paint-widget-update! canvas)))\n\n(qt-main-window-set-title! win \"Stateful Board\")\n(qt-main-window-set-central-widget! win canvas)\n(qt-widget-resize! win 240 460)\n(qt-widget-show! win)\n(qt-widget-set-focus! win)\n(qt-paint-widget-update! canvas)\n(pump! app 8)\n\n(qt-send-key-press! win QT_KEY_RIGHT QT_MOD_NONE \"\")\n(pump! app 4)\n(unless (= (vector-ref pos 0) 5)\n  (error 'stateful-board \"right key did not move piece\"))\n\n(qt-send-key-press! win QT_KEY_LEFT QT_MOD_NONE \"\")\n(qt-send-key-press! win QT_KEY_LEFT QT_MOD_NONE \"\")\n(pump! app 4)\n(unless (= (vector-ref pos 0) 3)\n  (error 'stateful-board \"left keys did not move piece\"))\n\n(unless (> (vector-ref paint-count 0) 0)\n  (error 'stateful-board \"paint callback did not run\"))\n(unless (qt-widget-screenshot! win \"/tmp/jerboa-qt-stateful-board.png\")\n  (error 'stateful-board \"screenshot failed\"))\n\n(displayln \"stateful board passed\")\n(finish 0)\n") ("id" . "jerboa-qt-stateful-board-key-redraw")
    ("imports"
      "(jerboa prelude)"
      "(chezscheme)"
@@ -5589,14 +5589,14 @@
      "Jerboa Qt stateful board redraw from keyboard input"))
  (("code"
     .
-    "(import (jerboa prelude))\n(import (chezscheme))\n(import (jerboa-qt qt))\n\n(def immediate-exit (foreign-procedure \"_exit\" (int) void))\n\n(def (finish status)\n  (flush-output-port (current-output-port))\n  (flush-output-port (current-error-port))\n  (immediate-exit status))\n\n(def (pump! app n)\n  (when (> n 0)\n    (qt-app-process-events! app)\n    (pump! app (- n 1))))\n\n(def (pump-wait! app n delay-ms)\n  (when (> n 0)\n    (sleep-ms delay-ms)\n    (qt-app-process-events! app)\n    (pump-wait! app (- n 1) delay-ms)))\n\n(def app (qt-app-create))\n(def win (qt-main-window-create))\n(def canvas (qt-paint-widget-create))\n(def timer (qt-timer-create))\n(def piece-row (vector 0))\n(def paint-count (vector 0))\n\n(qt-widget-set-minimum-size! canvas 220 400)\n(qt-on-paint! canvas\n  (lambda ()\n    (vector-set! paint-count 0 (+ (vector-ref paint-count 0) 1))\n    (let ((p (qt-paint-widget-painter canvas)))\n      (when p\n        (qt-painter-fill-rect! p 0 0 220 400 24 26 32 255)\n        (qt-painter-fill-rect! p 84 (+ 12 (* (vector-ref piece-row 0) 18))\n                               34 34 132 204 22 255)))))\n\n(qt-on-timeout! timer\n  (lambda ()\n    (vector-set! piece-row 0 (min 18 (+ (vector-ref piece-row 0) 1)))\n    (qt-paint-widget-update! canvas)))\n\n(qt-main-window-set-title! win \"Timer Redraw\")\n(qt-main-window-set-central-widget! win canvas)\n(qt-widget-resize! win 240 460)\n(qt-widget-show! win)\n(qt-paint-widget-update! canvas)\n(pump! app 4)\n\n(qt-timer-start! timer 20)\n(pump-wait! app 10 25)\n(qt-timer-stop! timer)\n\n(unless (> (vector-ref piece-row 0) 0)\n  (error 'timer-redraw \"timer did not advance state\"))\n(unless (> (vector-ref paint-count 0) 0)\n  (error 'timer-redraw \"paint callback did not run\"))\n\n(let ((stopped-row (vector-ref piece-row 0)))\n  (pump-wait! app 4 25)\n  (unless (= (vector-ref piece-row 0) stopped-row)\n    (error 'timer-redraw \"timer kept firing after stop\")))\n\n(unless (qt-widget-screenshot! win \"/tmp/jerboa-qt-timer-redraw.png\")\n  (error 'timer-redraw \"screenshot failed\"))\n\n(qt-timer-destroy! timer)\n(displayln \"timer redraw passed\")\n(finish 0)\n") ("id" . "jerboa-qt-offscreen-timer-redraw")
+    "(import (jerboa prelude))\n(import (chezscheme))\n(import (jerboa-qt qt))\n\n(def immediate-exit (foreign-procedure \"_exit\" (int) void))\n\n(def (finish status)\n  (flush-output-port (current-output-port))\n  (flush-output-port (current-error-port))\n  (immediate-exit status))\n\n(def (pump! app n)\n  (when (> n 0)\n    (qt-pump-events! app)\n    (pump! app (- n 1))))\n\n(def (pump-wait! app n delay-ms)\n  (when (> n 0)\n    (sleep-ms delay-ms)\n    (qt-pump-events! app)\n    (pump-wait! app (- n 1) delay-ms)))\n\n(def app (qt-app-create))\n(def win (qt-main-window-create))\n(def canvas (qt-paint-widget-create))\n(def timer (qt-timer-create))\n(def piece-row (vector 0))\n(def paint-count (vector 0))\n\n(qt-widget-set-minimum-size! canvas 220 400)\n(qt-on-paint! canvas\n  (lambda ()\n    (vector-set! paint-count 0 (+ (vector-ref paint-count 0) 1))\n    (let ((p (qt-paint-widget-painter canvas)))\n      (when p\n        (qt-painter-fill-rect! p 0 0 220 400 24 26 32 255)\n        (qt-painter-fill-rect! p 84 (+ 12 (* (vector-ref piece-row 0) 18))\n                               34 34 132 204 22 255)))))\n\n(qt-on-timeout! timer\n  (lambda ()\n    (vector-set! piece-row 0 (min 18 (+ (vector-ref piece-row 0) 1)))\n    (qt-paint-widget-update! canvas)))\n\n(qt-main-window-set-title! win \"Timer Redraw\")\n(qt-main-window-set-central-widget! win canvas)\n(qt-widget-resize! win 240 460)\n(qt-widget-show! win)\n(qt-paint-widget-update! canvas)\n(pump! app 4)\n\n(qt-timer-start! timer 20)\n(pump-wait! app 10 25)\n(qt-timer-stop! timer)\n\n(unless (> (vector-ref piece-row 0) 0)\n  (error 'timer-redraw \"timer did not advance state\"))\n(unless (> (vector-ref paint-count 0) 0)\n  (error 'timer-redraw \"paint callback did not run\"))\n\n(let ((stopped-row (vector-ref piece-row 0)))\n  (pump-wait! app 4 25)\n  (unless (= (vector-ref piece-row 0) stopped-row)\n    (error 'timer-redraw \"timer kept firing after stop\")))\n\n(unless (qt-widget-screenshot! win \"/tmp/jerboa-qt-timer-redraw.png\")\n  (error 'timer-redraw \"screenshot failed\"))\n\n(qt-timer-destroy! timer)\n(displayln \"timer redraw passed\")\n(finish 0)\n") ("id" . "jerboa-qt-offscreen-timer-redraw")
    ("imports"
      "(jerboa prelude)"
      "(chezscheme)"
      "(jerboa-qt qt)")
    ("notes"
      .
-     "For offscreen timer tests, start the timer with `(qt-timer-start! timer msec)`, then alternate `sleep-ms` with `qt-app-process-events!` so wall-clock timer events can fire. Timeout handlers should mutate authoritative state and then call `qt-paint-widget-update!`. Stop the timer before asserting it no longer moves. `_exit` is harness-only for Qt smoke tests that must avoid C++ teardown crashes; normal apps should use the Qt event loop normally.")
+     "For offscreen timer tests, start the timer with `(qt-timer-start! timer msec)`, then alternate `sleep-ms` with `qt-pump-events!` so wall-clock timer events can fire. Timeout handlers should mutate authoritative state and then call `qt-paint-widget-update!`. Stop the timer before asserting it no longer moves. `_exit` is harness-only for Qt smoke tests that must avoid C++ teardown crashes; normal apps should use the Qt event loop normally.")
    ("tags" "jerboa-qt" "qt" "timer" "offscreen" "paint-widget"
      "sleep-ms")
    ("title"
@@ -5675,7 +5675,7 @@
      "Set PTY child as foreground process group after TIOCSCTTY"))
  (("code"
     .
-    "(import (jerboa prelude))\n(import (jerboa-qt qt))\n\n(def app (qt-app-create))\n(def win (qt-main-window-create))\n(def state (vector 0))\n\n(def (move-left!)\n  (vector-set! state 0 (- (vector-ref state 0) 1))\n  #t)\n\n(def (move-right!)\n  (vector-set! state 0 (+ (vector-ref state 0) 1))\n  #t)\n\n(qt-on-key-press! win\n  (lambda ()\n    (let ((key (qt-last-key-code)))\n      (cond\n        ((= key QT_KEY_LEFT)  (move-left!))\n        ((= key QT_KEY_RIGHT) (move-right!))\n        (else #t)))))\n\n(qt-widget-show! win)\n(qt-app-process-events! app)") ("id" . "jerboa-qt-key-code-cond-dispatch")
+    "(import (jerboa prelude))\n(import (jerboa-qt qt))\n\n(def app (qt-app-create))\n(def win (qt-main-window-create))\n(def state (vector 0))\n\n(def (move-left!)\n  (vector-set! state 0 (- (vector-ref state 0) 1))\n  #t)\n\n(def (move-right!)\n  (vector-set! state 0 (+ (vector-ref state 0) 1))\n  #t)\n\n(qt-on-key-press! win\n  (lambda ()\n    (let ((key (qt-last-key-code)))\n      (cond\n        ((= key QT_KEY_LEFT)  (move-left!))\n        ((= key QT_KEY_RIGHT) (move-right!))\n        (else #t)))))\n\n(qt-widget-show! win)\n(qt-pump-events! app)") ("id" . "jerboa-qt-key-code-cond-dispatch")
    ("imports" "(jerboa prelude)" "(jerboa-qt qt)")
    ("notes"
      .