Sprint 1 Tier 1: Add Qt infrastructure and sci-shim module

ober

61c6ab7e813e7351bb563c24f2be1b304cdcd607

diff --git a/Makefile b/Makefile
index ef2975e..a6f06c6 100644
--- a/Makefile
+++ b/Makefile
@@ -2,9 +2,9 @@ SCHEME = scheme
 JERBOA    = $(HOME)/mine/jerboa
 JSH       = $(HOME)/mine/jerboa-shell/src
 GHERKIN   = $(HOME)/mine/gherkin/src
-LIBDIRS   = --libdirs lib:$(JERBOA)/lib:$(JSH):$(GHERKIN):$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla/src
+LIBDIRS   = --libdirs lib:$(JERBOA)/lib:$(JSH):$(GHERKIN):$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla/src:$(HOME)/mine/chez-qt
 JERBUILD  = $(SCHEME) --libdirs $(JERBOA)/lib --script $(JERBOA)/jerbuild.ss
-export LD_LIBRARY_PATH := $(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla:$(HOME)/mine/jerboa-shell:$(LD_LIBRARY_PATH)
+export LD_LIBRARY_PATH := $(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla:$(HOME)/mine/chez-qt:$(HOME)/mine/jerboa-shell:$(LD_LIBRARY_PATH)
 export CHEZ_SCINTILLA_LIB := $(HOME)/mine/chez-scintilla
 
 .PHONY: all build rebuild run test-tier0 test-tier2 test-tier3 test-tier4 test-tier5 test-org test-extra test clean clean-generated \
diff --git a/lib/jerboa-emacs/qt/sci-shim.sls b/lib/jerboa-emacs/qt/sci-shim.sls
new file mode 100644
index 0000000..9090990
--- /dev/null
+++ b/lib/jerboa-emacs/qt/sci-shim.sls
@@ -0,0 +1,296 @@
+#!chezscheme
+;;; Generated by jerbuild — DO NOT EDIT
+;;; Source: src/jerboa-emacs/qt/sci-shim.ss
+
+(library (jerboa-emacs qt sci-shim)
+  (export sci-send sci-send/string sci-recv-string rgb->sci
+   doc-editor-register! doc-buffer-register! SCI_REPLACESEL
+   qt-plain-text-edit-create qt-plain-text-edit-set-text!
+   qt-plain-text-edit-text qt-plain-text-edit-append!
+   qt-plain-text-edit-clear! qt-plain-text-edit-set-read-only!
+   qt-plain-text-edit-read-only?
+   qt-plain-text-edit-set-placeholder!
+   qt-plain-text-edit-line-count
+   qt-plain-text-edit-set-max-block-count!
+   qt-plain-text-edit-cursor-line
+   qt-plain-text-edit-cursor-column
+   qt-plain-text-edit-set-line-wrap!
+   qt-on-plain-text-edit-text-changed!
+   qt-plain-text-edit-cursor-position
+   qt-plain-text-edit-set-cursor-position!
+   qt-plain-text-edit-move-cursor!
+   qt-plain-text-edit-select-all!
+   qt-plain-text-edit-selected-text
+   qt-plain-text-edit-selection-start
+   qt-plain-text-edit-selection-end
+   qt-plain-text-edit-has-selection?
+   qt-plain-text-edit-set-selection!
+   qt-plain-text-edit-insert-text!
+   qt-plain-text-edit-remove-selected-text!
+   qt-plain-text-edit-undo! qt-plain-text-edit-redo!
+   qt-plain-text-edit-can-undo? qt-plain-text-edit-cut!
+   qt-plain-text-edit-copy! qt-plain-text-edit-paste!
+   qt-plain-text-edit-text-length qt-plain-text-edit-text-range
+   qt-plain-text-edit-line-from-position
+   qt-plain-text-edit-line-end-position
+   qt-plain-text-edit-find-text
+   qt-plain-text-edit-ensure-cursor-visible!
+   qt-plain-text-edit-center-cursor!
+   qt-plain-text-edit-set-document! qt-plain-text-edit-document
+   qt-plain-text-document-create qt-text-document-create
+   qt-text-document-destroy! qt-text-document-modified?
+   qt-text-document-set-modified! qt-line-number-area-create
+   qt-line-number-area-destroy!
+   qt-line-number-area-set-visible!
+   qt-line-number-area-set-bg-color!
+   qt-line-number-area-set-fg-color!
+   qt-syntax-highlighter-create qt-syntax-highlighter-destroy!
+   qt-syntax-highlighter-add-rule!
+   qt-syntax-highlighter-add-keywords!
+   qt-syntax-highlighter-add-multiline-rule!
+   qt-syntax-highlighter-rehighlight!
+   qt-syntax-highlighter-clear-rules!
+   qt-extra-selections-clear! qt-extra-selection-add-line!
+   qt-extra-selection-add-range! qt-extra-selections-apply!)
+  (import
+    (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
+      getenv path-extension path-absolute? thread? make-mutex
+      mutex? mutex-name)
+    (jerboa-emacs core) (chez-scintilla constants)
+    (chez-scintilla scintilla) (chez-qt qt) (std sugar)
+    (jerboa core) (jerboa runtime))
+  (def SCI_REPLACESEL 2170)
+  (def (sci-send sci msg (wparam 0) (lparam 0))
+       (qt-scintilla-send-message sci msg wparam lparam))
+  (def (sci-send/string sci msg str (wparam 0))
+       (qt-scintilla-send-message/string sci msg str wparam))
+  (def (sci-recv-string sci msg (wparam 0))
+       (qt-scintilla-receive-string sci msg wparam))
+  (def (rgb->sci r g b) (+ r (* 256 g) (* 65536 b)))
+  (def *doc-editor-map* (make-hash-table))
+  (def *doc-buffer-map* (make-hash-table))
+  (def (doc-editor-register! doc editor)
+       (hash-put! *doc-editor-map* doc editor))
+  (def (doc-buffer-register! doc buf)
+       (hash-put! *doc-buffer-map* doc buf))
+  (def (qt-plain-text-edit-create parent: (parent #f))
+       (qt-scintilla-create 'parent: parent))
+  (def (qt-plain-text-edit-text sci) (qt-scintilla-text sci))
+  (def (qt-plain-text-edit-set-text! sci text)
+       (verbose-log!
+         "qt-plain-text-edit-set-text! len="
+         (number->string (string-length text)))
+       (qt-scintilla-set-text! sci text)
+       (verbose-log! "qt-plain-text-edit-set-text! done")
+       (let ([new-len (sci-send sci SCI_GETLENGTH)])
+         (when (> (sci-send sci SCI_GETCURRENTPOS) new-len)
+           (sci-send sci SCI_GOTOPOS new-len))
+         (when (> (sci-send sci SCI_GETANCHOR) new-len)
+           (sci-send sci SCI_SETANCHOR new-len))))
+  (def (qt-plain-text-edit-append! sci text)
+       (let ([to-append (string-append "\n" text)])
+         (sci-send/string
+           sci
+           SCI_APPENDTEXT
+           to-append
+           (string-length to-append))))
+  (def (qt-plain-text-edit-clear! sci)
+       (sci-send sci SCI_CLEARALL))
+  (def (qt-plain-text-edit-insert-text! sci text)
+       (sci-send/string sci SCI_REPLACESEL text))
+  (def (qt-plain-text-edit-remove-selected-text! sci)
+       (sci-send/string sci SCI_REPLACESEL ""))
+  (def (qt-plain-text-edit-text-length sci)
+       (qt-scintilla-text-length sci))
+  (def (qt-plain-text-edit-text-range sci start end)
+       (let ([text (qt-scintilla-text sci)])
+         (if (and (>= start 0)
+                  (<= end (string-length text))
+                  (<= start end))
+             (substring text start end)
+             "")))
+  (def (qt-plain-text-edit-cursor-position sci)
+       (sci-send sci SCI_GETCURRENTPOS))
+  (def (qt-plain-text-edit-set-cursor-position! sci pos)
+       (sci-send
+         sci
+         SCI_GOTOPOS
+         (min pos (sci-send sci SCI_GETLENGTH))))
+  (def (qt-plain-text-edit-cursor-line sci)
+       (sci-send
+         sci
+         SCI_LINEFROMPOSITION
+         (sci-send sci SCI_GETCURRENTPOS)))
+  (def (qt-plain-text-edit-cursor-column sci)
+       (sci-send
+         sci
+         SCI_GETCOLUMN
+         (sci-send sci SCI_GETCURRENTPOS)))
+  (def (qt-plain-text-edit-line-count sci)
+       (sci-send sci SCI_GETLINECOUNT))
+  (def (qt-plain-text-edit-line-from-position sci pos)
+       (sci-send sci SCI_LINEFROMPOSITION pos))
+  (def (qt-plain-text-edit-line-end-position sci line)
+       (sci-send sci SCI_GETLINEENDPOSITION line))
+  (def (qt-plain-text-edit-move-cursor!
+         sci
+         op
+         mode:
+         (mode #f))
+       (let* ([keep-anchor? (and mode (= mode QT_KEEP_ANCHOR))]
+              [cur-pos (sci-send sci SCI_GETCURRENTPOS)]
+              [new-pos cur-pos])
+         (let ([safe-pos (min new-pos (sci-send sci SCI_GETLENGTH))])
+           (if keep-anchor?
+               (sci-send sci SCI_SETCURRENTPOS safe-pos)
+               (sci-send sci SCI_GOTOPOS safe-pos)))))
+  (def (qt-plain-text-edit-ensure-cursor-visible! sci)
+       (sci-send sci SCI_SCROLLCARET))
+  (def (qt-plain-text-edit-center-cursor! sci)
+       (sci-send sci SCI_SCROLLCARET))
+  (def (qt-plain-text-edit-select-all! sci)
+       (sci-send sci SCI_SELECTALL))
+  (def (qt-plain-text-edit-selected-text sci)
+       (sci-recv-string sci SCI_GETSELTEXT))
+  (def (qt-plain-text-edit-selection-start sci)
+       (sci-send sci SCI_GETSELECTIONSTART))
+  (def (qt-plain-text-edit-selection-end sci)
+       (sci-send sci SCI_GETSELECTIONEND))
+  (def (qt-plain-text-edit-has-selection? sci)
+       (not (= (sci-send sci SCI_GETSELECTIONSTART)
+               (sci-send sci SCI_GETSELECTIONEND))))
+  (def (qt-plain-text-edit-set-selection! sci start end)
+       (let ([doc-len (sci-send sci SCI_GETLENGTH)])
+         (sci-send
+           sci
+           SCI_SETSEL
+           (min start doc-len)
+           (min end doc-len))))
+  (def (qt-plain-text-edit-undo! sci) (sci-send sci SCI_UNDO))
+  (def (qt-plain-text-edit-redo! sci) (sci-send sci SCI_REDO))
+  (def (qt-plain-text-edit-can-undo? sci)
+       (not (= 0 (sci-send sci SCI_CANUNDO))))
+  (def (qt-plain-text-edit-cut! sci) (sci-send sci SCI_CUT))
+  (def (qt-plain-text-edit-copy! sci) (sci-send sci SCI_COPY))
+  (def (qt-plain-text-edit-paste! sci)
+       (sci-send sci SCI_PASTE))
+  (def (qt-plain-text-edit-set-read-only! sci ro?)
+       (sci-send sci SCI_SETREADONLY (if ro? 1 0)))
+  (def (qt-plain-text-edit-read-only? sci)
+       (not (zero? (sci-send sci SCI_GETREADONLY))))
+  (def (qt-plain-text-edit-set-line-wrap! sci wrap?)
+       (sci-send
+         sci
+         SCI_SETWRAPMODE
+         (if wrap? SC_WRAP_WORD SC_WRAP_NONE)))
+  (def (qt-plain-text-edit-set-placeholder! sci text) (void))
+  (def (qt-plain-text-edit-set-max-block-count! sci count)
+       (void))
+  (def (qt-on-plain-text-edit-text-changed! sci handler)
+       (qt-on-scintilla-text-changed! sci handler))
+  (def (qt-plain-text-edit-find-text sci text . flags)
+       (let* ([pos (sci-send sci SCI_GETCURRENTPOS)]
+              [len (sci-send sci SCI_GETTEXTLENGTH)])
+         (sci-send sci SCI_SETTARGETSTART pos)
+         (sci-send sci SCI_SETTARGETEND len)
+         (sci-send sci SCI_SETSEARCHFLAGS 0)
+         (let ([found (sci-send/string
+                        sci
+                        SCI_SEARCHINTARGET
+                        text
+                        (string-length text))])
+           (if (>= found 0) found -1))))
+  (def (qt-plain-text-edit-set-document! sci doc)
+       (sci-send sci SCI_SETDOCPOINTER 0 doc))
+  (def (qt-plain-text-edit-document sci)
+       (sci-send sci SCI_GETDOCPOINTER))
+  (def (qt-plain-text-document-create) #f)
+  (def (qt-text-document-create) #f)
+  (def (qt-text-document-destroy! doc) (void))
+  (def (qt-text-document-modified? doc)
+       (let ([buf (hash-get *doc-buffer-map* doc)])
+         (if buf (buffer-modified buf) #f)))
+  (def (qt-text-document-set-modified! doc modified?)
+       (let ([buf (hash-get *doc-buffer-map* doc)])
+         (when buf
+           (buffer-modified-set! buf modified?)
+           (unless modified?
+             (let ([ed (hash-get *doc-editor-map* doc)])
+               (when ed (sci-send ed SCI_SETSAVEPOINT)))))))
+  (def (qt-line-number-area-create editor) #f)
+  (def (qt-line-number-area-destroy! lna) (void))
+  (def (qt-line-number-area-set-visible! lna visible?) (void))
+  (def (qt-line-number-area-set-bg-color! lna r g b) (void))
+  (def (qt-line-number-area-set-fg-color! lna r g b) (void))
+  (def (qt-syntax-highlighter-create doc) #f)
+  (def (qt-syntax-highlighter-destroy! h) (void))
+  (def (qt-syntax-highlighter-add-rule! h pattern r g b bold?
+         italic?)
+       (void))
+  (def (qt-syntax-highlighter-add-keywords! h keywords r g b
+         bold? italic?)
+       (void))
+  (def (qt-syntax-highlighter-add-multiline-rule! h start end
+         r g b bold? italic?)
+       (void))
+  (def (qt-syntax-highlighter-rehighlight! h) (void))
+  (def (qt-syntax-highlighter-clear-rules! h) (void))
+  (def *indic-current-line* 8)
+  (def *indic-brace-match* 9)
+  (def *indic-brace-bad* 10)
+  (def *indic-search* 11)
+  (def *pending-decorations* (list))
+  (def (qt-extra-selections-clear! sci)
+       (set! *pending-decorations* (list))
+       (let ([len (sci-send sci SCI_GETTEXTLENGTH)])
+         (for-each
+           (lambda (indic)
+             (sci-send sci SCI_SETINDICATORCURRENT indic)
+             (sci-send sci SCI_INDICATORCLEARRANGE 0 len))
+           (list
+             *indic-current-line*
+             *indic-brace-match*
+             *indic-brace-bad*
+             *indic-search*))))
+  (def (qt-extra-selection-add-line! sci line bg-r bg-g bg-b)
+       (sci-send sci SCI_SETCARETLINEVISIBLE 1)
+       (sci-send
+         sci
+         SCI_SETCARETLINEBACK
+         (rgb->sci bg-r bg-g bg-b)))
+  (def (qt-extra-selection-add-range! sci pos len fg-r fg-g
+         fg-b bg-r bg-g bg-b bold: (bold? #f))
+       (set! *pending-decorations*
+         (cons
+           (list pos len fg-r fg-g fg-b bg-r bg-g bg-b bold?)
+           *pending-decorations*)))
+  (def (qt-extra-selections-apply! sci)
+       (for-each
+         (lambda (dec)
+           (let ([pos (car dec)]
+                 [len (cadr dec)]
+                 [bg-r (list-ref dec 5)]
+                 [bg-g (list-ref dec 6)]
+                 [bg-b (list-ref dec 7)])
+             (sci-send sci SCI_SETINDICATORCURRENT *indic-brace-match*)
+             (sci-send
+               sci
+               SCI_INDICSETSTYLE
+               *indic-brace-match*
+               INDIC_ROUNDBOX)
+             (sci-send
+               sci
+               SCI_INDICSETFORE
+               *indic-brace-match*
+               (rgb->sci bg-r bg-g bg-b))
+             (let* ([doc-len (sci-send sci SCI_GETLENGTH)]
+                    [safe-pos (min pos doc-len)]
+                    [safe-len (min len (- doc-len safe-pos))])
+               (when (> safe-len 0)
+                 (sci-send
+                   sci
+                   SCI_INDICATORFILLRANGE
+                   safe-pos
+                   safe-len)))))
+         *pending-decorations*)
+       (set! *pending-decorations* (list))))
diff --git a/plan.md b/plan.md
new file mode 100644
index 0000000..e959402
--- /dev/null
+++ b/plan.md
@@ -0,0 +1,352 @@
+# Jerboa-Emacs Feature Parity Plan
+
+**Goal:** Bring jerboa-emacs (Chez Scheme port) to full feature parity with gerbil-emacs (Gerbil Scheme original).
+
+**Date:** 2026-03-17
+
+---
+
+## Executive Summary
+
+| Category | gerbil-emacs | jerboa-emacs | Gap |
+|----------|-------------|--------------|-----|
+| Core modules | 65 files | 65 files | ✅ Complete |
+| Qt backend | 46 files (~50k lines) | 0 files | ❌ Missing |
+| Test files | 30 files | 27 files | ⚠️ Partial |
+| Total lines | ~88k | ~65k | ~23k lines |
+
+The TUI (terminal) backend is complete. The primary gap is the **Qt graphical frontend** (~50k lines across 46 modules).
+
+---
+
+## Phase 1: Qt Backend Infrastructure (Priority: High)
+
+### 1.1 Create Qt Directory Structure
+
+```bash
+mkdir -p src/jerboa-emacs/qt
+```
+
+### 1.2 Port Core Qt Modules (in order)
+
+These modules must be ported first as others depend on them:
+
+| Module | Lines | Description | Dependencies |
+|--------|-------|-------------|--------------|
+| `qt/sci-shim.ss` | 536 | QScintilla compatibility layer | chez-qt, chez-scintilla |
+| `qt/keymap.ss` | ~200 | Qt-specific key handling | sci-shim |
+| `qt/buffer.ss` | 65 | Qt buffer wrapper | sci-shim |
+| `qt/window.ss` | 567 | Qt window management | buffer, keymap |
+| `qt/modeline.ss` | ~150 | Qt modeline widget | window |
+| `qt/echo.ss` | ~200 | Qt echo area/minibuffer | window |
+| `qt/highlight.ss` | ~300 | Qt syntax highlighting | sci-shim |
+
+**Porting Instructions:**
+
+1. Copy the Gerbil source file from `~/mine/gerbil-emacs/qt/`
+2. Convert Gerbil syntax to Chez Scheme:
+   - `def` → `define` (or keep with `(std sugar)`)
+   - `defstruct` → `define-record-type` or use jerboa macros
+   - `:module/path` imports → `(module path)` library imports
+   - `begin-ffi` blocks → `foreign-procedure` with C shim .so files
+   - `displayln` → `(display ...) (newline)`
+   - `with-catch` → `guard` or `with-exception-handler`
+3. Update library declaration: `(library (jerboa-emacs qt module-name) ...)`
+4. Test compilation: `scheme --libdirs lib --script test-file.ss`
+
+### 1.3 Port Command Modules (~35 files)
+
+These implement editor commands for the Qt backend:
+
+```
+commands-core.ss, commands-core2.ss     # Basic editing
+commands-edit.ss, commands-edit2.ss     # Text manipulation
+commands-search.ss, commands-search2.ss # Search/replace
+commands-file.ss, commands-file2.ss     # File operations
+commands-sexp.ss, commands-sexp2.ss     # S-expression editing
+commands-shell.ss, commands-shell2.ss   # Shell integration
+commands-vcs.ss, commands-vcs2.ss       # Git/VCS integration
+commands-ide.ss, commands-ide2.ss       # IDE features
+commands-modes.ss, commands-modes2.ss   # Major/minor modes
+commands-config.ss, commands-config2.ss # Configuration
+commands-aliases.ss, commands-aliases2.ss
+commands-parity.ss through commands-parity5.ss
+commands-lsp.ss                         # LSP protocol
+commands.ss                             # Facade module
+```
+
+### 1.4 Port Specialized Qt Modules
+
+| Module | Lines | Description |
+|--------|-------|-------------|
+| `qt/lsp-client.ss` | 595 | Language Server Protocol client |
+| `qt/magit.ss` | ~800 | Git interface (magit-like) |
+| `qt/image.ss` | ~300 | Image display support |
+| `qt/helm-qt.ss` | ~400 | Helm framework for Qt |
+| `qt/helm-commands.ss` | ~300 | Helm command definitions |
+| `qt/snippets.ss` | ~200 | Code snippets |
+| `qt/menubar.ss` | ~400 | Qt menubar integration |
+| `qt/app.ss` | 1289 | Qt application lifecycle |
+| `qt/main.ss` | ~100 | Qt executable entry point |
+
+---
+
+## Phase 2: Dependencies (Priority: High)
+
+### 2.1 Required Chez Scheme Packages
+
+The Qt backend requires Chez Scheme ports of these Gerbil packages:
+
+| Gerbil Package | Chez Port Needed | Status |
+|----------------|------------------|--------|
+| `gerbil-qt` | `chez-qt` | Check if exists |
+| `gerbil-scintilla` | `chez-scintilla` | ✅ Exists (referenced in Makefile) |
+| `gerbil-litehtml` | `chez-litehtml` | Needed for HTML rendering |
+
+**Action:** Verify `chez-qt` exists at `~/mine/chez-qt` or port it.
+
+### 2.2 FFI Shim Libraries
+
+Create C shim libraries for Qt FFI (similar to `support/pty_shim.c`):
+
+```bash
+# In support/ directory:
+qt_shim.c      # Qt widget bindings
+sci_shim.c     # QScintilla bindings (may exist in chez-scintilla)
+litehtml_shim.c # litehtml bindings
+```
+
+---
+
+## Phase 3: Build System Updates (Priority: Medium)
+
+### 3.1 Update Makefile
+
+Add Qt build targets:
+
+```makefile
+# Add to Makefile
+QT_MODULES = qt/sci-shim qt/keymap qt/buffer qt/window qt/modeline \
+             qt/echo qt/highlight qt/app qt/main ...
+
+build-qt: build
+	$(JERBUILD) src/jerboa-emacs/qt/ lib/jerboa-emacs/qt/
+
+run-qt: build-qt
+	$(SCHEME) $(LIBDIRS) --script qt-main.ss
+
+test-qt-functional:
+	$(SCHEME) $(LIBDIRS) --script tests/test-qt-functional.ss
+```
+
+### 3.2 Add Qt Library Dependencies
+
+Update `LIBDIRS` in Makefile:
+
+```makefile
+CHEZ_QT = $(HOME)/mine/chez-qt/src
+LIBDIRS = --libdirs lib:...:$(CHEZ_QT)
+export LD_LIBRARY_PATH := ...:$(HOME)/mine/chez-qt:$(LD_LIBRARY_PATH)
+```
+
+### 3.3 Create manifest.ss
+
+```scheme
+;; manifest.ss
+(define version-manifest
+  '(("" . "0.1.0")
+    ("Jerboa" . "master")
+    ("Chez Scheme" . "10.x")))
+```
+
+---
+
+## Phase 4: Test Porting (Priority: Medium)
+
+### 4.1 Missing Test Files
+
+Port these test files from gerbil-emacs:
+
+| Test File | Status in jerboa-emacs |
+|-----------|------------------------|
+| `emacs-test.ss` | Missing - port to `tests/test-emacs.ss` |
+| `functional-test.ss` | Missing - port to `tests/test-functional.ss` |
+| `lsp-functional-test.ss` | Missing - needs Qt backend first |
+| `lsp-protocol-test.ss` | Missing - needs LSP client first |
+| `qt-functional-test.ss` | Missing - needs Qt backend first |
+| `qt-highlight-test.ss` | Missing - needs Qt backend first |
+| `term-hang-test.ss` | Missing - port for terminal testing |
+
+### 4.2 Test Naming Convention
+
+jerboa-emacs uses `test-*.ss` prefix; gerbil-emacs uses `*-test.ss` suffix.
+Maintain jerboa-emacs convention when porting.
+
+---
+
+## Phase 5: Documentation (Priority: Low)
+
+### 5.1 Update README.md
+
+Add:
+- Qt backend installation instructions
+- Dependencies list
+- Build instructions for both TUI and Qt modes
+
+### 5.2 Create CLAUDE.md
+
+Add development guidance file (like gerbil-emacs has).
+
+---
+
+## Implementation Order
+
+### Sprint 1: Foundation (Week 1-2)
+1. [ ] Verify/create chez-qt package
+2. [ ] Port `qt/sci-shim.ss` 
+3. [ ] Port `qt/keymap.ss`
+4. [ ] Port `qt/buffer.ss`
+5. [ ] Update Makefile with Qt targets
+
+### Sprint 2: Window System (Week 3-4)
+1. [ ] Port `qt/window.ss`
+2. [ ] Port `qt/modeline.ss`
+3. [ ] Port `qt/echo.ss`
+4. [ ] Port `qt/highlight.ss`
+5. [ ] Basic Qt window test
+
+### Sprint 3: Commands Part 1 (Week 5-6)
+1. [ ] Port `qt/commands-core.ss` and `qt/commands-core2.ss`
+2. [ ] Port `qt/commands-edit.ss` and `qt/commands-edit2.ss`
+3. [ ] Port `qt/commands-file.ss` and `qt/commands-file2.ss`
+4. [ ] Port `qt/commands-search.ss` and `qt/commands-search2.ss`
+
+### Sprint 4: Commands Part 2 (Week 7-8)
+1. [ ] Port remaining command modules
+2. [ ] Port `qt/commands.ss` (facade)
+3. [ ] Port `qt/helm-qt.ss` and `qt/helm-commands.ss`
+
+### Sprint 5: Advanced Features (Week 9-10)
+1. [ ] Port `qt/lsp-client.ss`
+2. [ ] Port `qt/commands-lsp.ss`
+3. [ ] Port `qt/magit.ss`
+4. [ ] Port `qt/image.ss`
+
+### Sprint 6: Application (Week 11-12)
+1. [ ] Port `qt/menubar.ss`
+2. [ ] Port `qt/app.ss`
+3. [ ] Port `qt/main.ss`
+4. [ ] Integration testing
+5. [ ] Port Qt test files
+
+---
+
+## Syntax Conversion Reference
+
+### Import Statements
+
+```scheme
+;; Gerbil
+(import :gemacs/core
+        :gerbil-qt/qt
+        (only-in :gemacs/buffer buffer-name))
+
+;; Chez Scheme (jerboa-emacs)
+(import (jerboa-emacs core)
+        (chez-qt qt)
+        (only (jerboa-emacs buffer) buffer-name))
+```
+
+### Function Definitions
+
+```scheme
+;; Gerbil
+(def (my-function arg1 arg2)
+  (body ...))
+
+;; Chez Scheme - keep def with (std sugar)
+(def (my-function arg1 arg2)
+  (body ...))
+```
+
+### Struct Definitions
+
+```scheme
+;; Gerbil
+(defstruct point (x y))
+
+;; Chez Scheme - use jerboa macros or define-record-type
+(defstruct point (x y))  ; if (std sugar) provides it
+;; OR
+(define-record-type point
+  (fields x y))
+```
+
+### Error Handling
+
+```scheme
+;; Gerbil
+(with-catch
+  (lambda (e) (handle-error e))
+  (lambda () (risky-operation)))
+
+;; Chez Scheme
+(guard (e [else (handle-error e)])
+  (risky-operation))
+;; OR with jerboa compatibility
+(with-catch (lambda (e) (handle-error e))
+  (lambda () (risky-operation)))
+```
+
+### FFI Bindings
+
+```scheme
+;; Gerbil (begin-ffi block)
+(begin-ffi (func-name)
+  (c-declare "...")
+  (define-c-lambda func-name (int string) int "c_func"))
+
+;; Chez Scheme (separate .c file + foreign-procedure)
+;; In support/module_shim.c:
+;;   int c_func(int arg1, const char* arg2) { ... }
+;; In .sls file:
+(load-shared-object "support/module_shim.so")
+(define func-name
+  (foreign-procedure "c_func" (int string) int))
+```
+
+---
+
+## Verification Checklist
+
+For each ported module:
+
+- [ ] File compiles without errors
+- [ ] All exports are defined
+- [ ] Imports resolve correctly
+- [ ] FFI bindings work (if applicable)
+- [ ] Unit tests pass (if tests exist)
+- [ ] Integration with dependent modules works
+
+---
+
+## Notes for Implementers
+
+1. **Start with sci-shim.ss** - It's the foundation for all Qt modules
+2. **Test incrementally** - Compile and test each module before moving on
+3. **Preserve API compatibility** - Function signatures should match gerbil-emacs
+4. **Check chez-scintilla** - Many FFI bindings may already exist there
+5. **Use existing patterns** - Look at `lib/jerboa-emacs/pty.sls` for FFI patterns
+6. **Keep lib structure** - Generated .sls files go in `lib/jerboa-emacs/qt/`
+
+---
+
+## File Counts Summary
+
+| Component | Files | Lines | Priority |
+|-----------|-------|-------|----------|
+| Qt core (sci-shim, window, buffer, etc.) | 10 | ~3,000 | P0 |
+| Qt commands | 30 | ~35,000 | P1 |
+| Qt advanced (LSP, magit, image) | 6 | ~12,000 | P2 |
+| Tests | 8 | ~2,000 | P3 |
+| **Total** | **54** | **~52,000** | |
diff --git a/src/jerboa-emacs/qt/sci-shim.ss b/src/jerboa-emacs/qt/sci-shim.ss
new file mode 100644
index 0000000..f476d95
--- /dev/null
+++ b/src/jerboa-emacs/qt/sci-shim.ss
@@ -0,0 +1,292 @@
+;;; -*- Gerbil -*-
+;;; QScintilla compatibility shim for jerboa-emacs
+;;;
+;;; Ported from gerbil-emacs/qt/sci-shim.ss
+;;; Provides qt-plain-text-edit-* compatible functions backed by QScintilla.
+
+(export
+  ;; Local helpers
+  sci-send sci-send/string sci-recv-string rgb->sci
+  doc-editor-register! doc-buffer-register!
+  SCI_REPLACESEL
+  ;; Qt/Scintilla functions
+  qt-plain-text-edit-create qt-plain-text-edit-set-text! qt-plain-text-edit-text
+  qt-plain-text-edit-append! qt-plain-text-edit-clear!
+  qt-plain-text-edit-set-read-only! qt-plain-text-edit-read-only?
+  qt-plain-text-edit-set-placeholder! qt-plain-text-edit-line-count
+  qt-plain-text-edit-set-max-block-count!
+  qt-plain-text-edit-cursor-line qt-plain-text-edit-cursor-column
+  qt-plain-text-edit-set-line-wrap! qt-on-plain-text-edit-text-changed!
+  qt-plain-text-edit-cursor-position qt-plain-text-edit-set-cursor-position!
+  qt-plain-text-edit-move-cursor! qt-plain-text-edit-select-all!
+  qt-plain-text-edit-selected-text qt-plain-text-edit-selection-start
+  qt-plain-text-edit-selection-end qt-plain-text-edit-has-selection?
+  qt-plain-text-edit-set-selection! qt-plain-text-edit-insert-text!
+  qt-plain-text-edit-remove-selected-text! qt-plain-text-edit-undo!
+  qt-plain-text-edit-redo! qt-plain-text-edit-can-undo?
+  qt-plain-text-edit-cut! qt-plain-text-edit-copy! qt-plain-text-edit-paste!
+  qt-plain-text-edit-text-length qt-plain-text-edit-text-range
+  qt-plain-text-edit-line-from-position qt-plain-text-edit-line-end-position
+  qt-plain-text-edit-find-text qt-plain-text-edit-ensure-cursor-visible!
+  qt-plain-text-edit-center-cursor!
+  qt-plain-text-edit-set-document! qt-plain-text-edit-document
+  qt-plain-text-document-create qt-text-document-create qt-text-document-destroy!
+  qt-text-document-modified? qt-text-document-set-modified!
+  qt-line-number-area-create qt-line-number-area-destroy!
+  qt-line-number-area-set-visible! qt-line-number-area-set-bg-color!
+  qt-line-number-area-set-fg-color!
+  qt-syntax-highlighter-create qt-syntax-highlighter-destroy!
+  qt-syntax-highlighter-add-rule! qt-syntax-highlighter-add-keywords!
+  qt-syntax-highlighter-add-multiline-rule! qt-syntax-highlighter-rehighlight!
+  qt-syntax-highlighter-clear-rules!
+  qt-extra-selections-clear! qt-extra-selection-add-line!
+  qt-extra-selection-add-range! qt-extra-selections-apply!)
+
+(import
+  :jerboa-emacs/core
+  :chez-scintilla/constants
+  :chez-scintilla/scintilla
+  :chez-qt/qt
+  :std/sugar)
+
+;; SCI_REPLACESEL constant
+(def SCI_REPLACESEL 2170)
+
+;; Helpers
+(def (sci-send sci msg (wparam 0) (lparam 0))
+  (qt-scintilla-send-message sci msg wparam lparam))
+
+(def (sci-send/string sci msg str (wparam 0))
+  (qt-scintilla-send-message/string sci msg str wparam))
+
+(def (sci-recv-string sci msg (wparam 0))
+  (qt-scintilla-receive-string sci msg wparam))
+
+(def (rgb->sci r g b)
+  (+ r (* 256 g) (* 65536 b)))
+
+;; Document tracking
+(def *doc-editor-map* (make-hash-table))
+(def *doc-buffer-map* (make-hash-table))
+
+(def (doc-editor-register! doc editor)
+  (hash-put! *doc-editor-map* doc editor))
+
+(def (doc-buffer-register! doc buf)
+  (hash-put! *doc-buffer-map* doc buf))
+
+;; Widget creation
+(def (qt-plain-text-edit-create parent: (parent #f))
+  (qt-scintilla-create parent: parent))
+
+;; Text operations
+(def (qt-plain-text-edit-text sci)
+  (qt-scintilla-text sci))
+
+(def (qt-plain-text-edit-set-text! sci text)
+  (verbose-log! "qt-plain-text-edit-set-text! len=" (number->string (string-length text)))
+  (qt-scintilla-set-text! sci text)
+  (verbose-log! "qt-plain-text-edit-set-text! done")
+  (let ((new-len (sci-send sci SCI_GETLENGTH)))
+    (when (> (sci-send sci SCI_GETCURRENTPOS) new-len)
+      (sci-send sci SCI_GOTOPOS new-len))
+    (when (> (sci-send sci SCI_GETANCHOR) new-len)
+      (sci-send sci SCI_SETANCHOR new-len))))
+
+(def (qt-plain-text-edit-append! sci text)
+  (let ((to-append (string-append "\n" text)))
+    (sci-send/string sci SCI_APPENDTEXT to-append (string-length to-append))))
+
+(def (qt-plain-text-edit-clear! sci)
+  (sci-send sci SCI_CLEARALL))
+
+(def (qt-plain-text-edit-insert-text! sci text)
+  (sci-send/string sci SCI_REPLACESEL text))
+
+(def (qt-plain-text-edit-remove-selected-text! sci)
+  (sci-send/string sci SCI_REPLACESEL ""))
+
+(def (qt-plain-text-edit-text-length sci)
+  (qt-scintilla-text-length sci))
+
+(def (qt-plain-text-edit-text-range sci start end)
+  (let ((text (qt-scintilla-text sci)))
+    (if (and (>= start 0) (<= end (string-length text)) (<= start end))
+      (substring text start end)
+      "")))
+
+;; Cursor operations
+(def (qt-plain-text-edit-cursor-position sci)
+  (sci-send sci SCI_GETCURRENTPOS))
+
+(def (qt-plain-text-edit-set-cursor-position! sci pos)
+  (sci-send sci SCI_GOTOPOS (min pos (sci-send sci SCI_GETLENGTH))))
+
+(def (qt-plain-text-edit-cursor-line sci)
+  (sci-send sci SCI_LINEFROMPOSITION (sci-send sci SCI_GETCURRENTPOS)))
+
+(def (qt-plain-text-edit-cursor-column sci)
+  (sci-send sci SCI_GETCOLUMN (sci-send sci SCI_GETCURRENTPOS)))
+
+(def (qt-plain-text-edit-line-count sci)
+  (sci-send sci SCI_GETLINECOUNT))
+
+(def (qt-plain-text-edit-line-from-position sci pos)
+  (sci-send sci SCI_LINEFROMPOSITION pos))
+
+(def (qt-plain-text-edit-line-end-position sci line)
+  (sci-send sci SCI_GETLINEENDPOSITION line))
+
+;; Cursor movement - simplified for now
+(def (qt-plain-text-edit-move-cursor! sci op mode: (mode #f))
+  (let* ((keep-anchor? (and mode (= mode QT_KEEP_ANCHOR)))
+         (cur-pos (sci-send sci SCI_GETCURRENTPOS))
+         (new-pos cur-pos))  ;; Stub - needs full implementation
+    (let ((safe-pos (min new-pos (sci-send sci SCI_GETLENGTH))))
+      (if keep-anchor?
+        (sci-send sci SCI_SETCURRENTPOS safe-pos)
+        (sci-send sci SCI_GOTOPOS safe-pos)))))
+
+(def (qt-plain-text-edit-ensure-cursor-visible! sci)
+  (sci-send sci SCI_SCROLLCARET))
+
+(def (qt-plain-text-edit-center-cursor! sci)
+  (sci-send sci SCI_SCROLLCARET))
+
+;; Selection
+(def (qt-plain-text-edit-select-all! sci)
+  (sci-send sci SCI_SELECTALL))
+
+(def (qt-plain-text-edit-selected-text sci)
+  (sci-recv-string sci SCI_GETSELTEXT))
+
+(def (qt-plain-text-edit-selection-start sci)
+  (sci-send sci SCI_GETSELECTIONSTART))
+
+(def (qt-plain-text-edit-selection-end sci)
+  (sci-send sci SCI_GETSELECTIONEND))
+
+(def (qt-plain-text-edit-has-selection? sci)
+  (not (= (sci-send sci SCI_GETSELECTIONSTART)
+          (sci-send sci SCI_GETSELECTIONEND))))
+
+(def (qt-plain-text-edit-set-selection! sci start end)
+  (let ((doc-len (sci-send sci SCI_GETLENGTH)))
+    (sci-send sci SCI_SETSEL (min start doc-len) (min end doc-len))))
+
+;; Undo/Redo/Clipboard
+(def (qt-plain-text-edit-undo! sci) (sci-send sci SCI_UNDO))
+(def (qt-plain-text-edit-redo! sci) (sci-send sci SCI_REDO))
+(def (qt-plain-text-edit-can-undo? sci) (not (= 0 (sci-send sci SCI_CANUNDO))))
+(def (qt-plain-text-edit-cut! sci) (sci-send sci SCI_CUT))
+(def (qt-plain-text-edit-copy! sci) (sci-send sci SCI_COPY))
+(def (qt-plain-text-edit-paste! sci) (sci-send sci SCI_PASTE))
+
+;; Properties
+(def (qt-plain-text-edit-set-read-only! sci ro?)
+  (sci-send sci SCI_SETREADONLY (if ro? 1 0)))
+
+(def (qt-plain-text-edit-read-only? sci)
+  (not (zero? (sci-send sci SCI_GETREADONLY))))
+
+(def (qt-plain-text-edit-set-line-wrap! sci wrap?)
+  (sci-send sci SCI_SETWRAPMODE (if wrap? SC_WRAP_WORD SC_WRAP_NONE)))
+
+(def (qt-plain-text-edit-set-placeholder! sci text) (void))
+(def (qt-plain-text-edit-set-max-block-count! sci count) (void))
+
+;; Signals
+(def (qt-on-plain-text-edit-text-changed! sci handler)
+  (qt-on-scintilla-text-changed! sci handler))
+
+;; Search
+(def (qt-plain-text-edit-find-text sci text . flags)
+  (let* ((pos (sci-send sci SCI_GETCURRENTPOS))
+         (len (sci-send sci SCI_GETTEXTLENGTH)))
+    (sci-send sci SCI_SETTARGETSTART pos)
+    (sci-send sci SCI_SETTARGETEND len)
+    (sci-send sci SCI_SETSEARCHFLAGS 0)
+    (let ((found (sci-send/string sci SCI_SEARCHINTARGET text (string-length text))))
+      (if (>= found 0) found -1))))
+
+;; Document management
+(def (qt-plain-text-edit-set-document! sci doc)
+  (sci-send sci SCI_SETDOCPOINTER 0 doc))
+
+(def (qt-plain-text-edit-document sci)
+  (sci-send sci SCI_GETDOCPOINTER))
+
+(def (qt-plain-text-document-create) #f)
+(def (qt-text-document-create) #f)
+(def (qt-text-document-destroy! doc) (void))
+
+(def (qt-text-document-modified? doc)
+  (let ((buf (hash-get *doc-buffer-map* doc)))
+    (if buf (buffer-modified buf) #f)))
+
+(def (qt-text-document-set-modified! doc modified?)
+  (let ((buf (hash-get *doc-buffer-map* doc)))
+    (when buf
+      (set! (buffer-modified buf) modified?)
+      (unless modified?
+        (let ((ed (hash-get *doc-editor-map* doc)))
+          (when ed (sci-send ed SCI_SETSAVEPOINT)))))))
+
+;; Line number area stubs
+(def (qt-line-number-area-create editor) #f)
+(def (qt-line-number-area-destroy! lna) (void))
+(def (qt-line-number-area-set-visible! lna visible?) (void))
+(def (qt-line-number-area-set-bg-color! lna r g b) (void))
+(def (qt-line-number-area-set-fg-color! lna r g b) (void))
+
+;; Syntax highlighter stubs
+(def (qt-syntax-highlighter-create doc) #f)
+(def (qt-syntax-highlighter-destroy! h) (void))
+(def (qt-syntax-highlighter-add-rule! h pattern r g b bold? italic?) (void))
+(def (qt-syntax-highlighter-add-keywords! h keywords r g b bold? italic?) (void))
+(def (qt-syntax-highlighter-add-multiline-rule! h start end r g b bold? italic?) (void))
+(def (qt-syntax-highlighter-rehighlight! h) (void))
+(def (qt-syntax-highlighter-clear-rules! h) (void))
+
+;; Extra selections - indicators
+(def *indic-current-line* 8)
+(def *indic-brace-match* 9)
+(def *indic-brace-bad* 10)
+(def *indic-search* 11)
+(def *pending-decorations* [])
+
+(def (qt-extra-selections-clear! sci)
+  (set! *pending-decorations* [])
+  (let ((len (sci-send sci SCI_GETTEXTLENGTH)))
+    (for-each
+      (lambda (indic)
+        (sci-send sci SCI_SETINDICATORCURRENT indic)
+        (sci-send sci SCI_INDICATORCLEARRANGE 0 len))
+      (list *indic-current-line* *indic-brace-match* *indic-brace-bad* *indic-search*))))
+
+(def (qt-extra-selection-add-line! sci line bg-r bg-g bg-b)
+  (sci-send sci SCI_SETCARETLINEVISIBLE 1)
+  (sci-send sci SCI_SETCARETLINEBACK (rgb->sci bg-r bg-g bg-b)))
+
+(def (qt-extra-selection-add-range! sci pos len fg-r fg-g fg-b bg-r bg-g bg-b bold: (bold? #f))
+  (set! *pending-decorations*
+    (cons (list pos len fg-r fg-g fg-b bg-r bg-g bg-b bold?) *pending-decorations*)))
+
+(def (qt-extra-selections-apply! sci)
+  (for-each
+    (lambda (dec)
+      (let ((pos (car dec))
+            (len (cadr dec))
+            (bg-r (list-ref dec 5))
+            (bg-g (list-ref dec 6))
+            (bg-b (list-ref dec 7)))
+        (sci-send sci SCI_SETINDICATORCURRENT *indic-brace-match*)
+        (sci-send sci SCI_INDICSETSTYLE *indic-brace-match* INDIC_ROUNDBOX)
+        (sci-send sci SCI_INDICSETFORE *indic-brace-match* (rgb->sci bg-r bg-g bg-b))
+        (let* ((doc-len (sci-send sci SCI_GETLENGTH))
+               (safe-pos (min pos doc-len))
+               (safe-len (min len (- doc-len safe-pos))))
+          (when (> safe-len 0)
+            (sci-send sci SCI_INDICATORFILLRANGE safe-pos safe-len)))))
+    *pending-decorations*)
+  (set! *pending-decorations* []))