new AGENTS.md

ober

f7087d543604a6799655f6df2ae217f7ba278856

diff --git a/AGENTS.md b/AGENTS.md
index aba05ec..0f5b749 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,3 +1,34 @@
+## STOP: Editing `.ss`/`.sls` Files — Mandatory Rules
+
+These rules exist because local-model sessions have lost **hours** fighting
+parenthesis imbalances that these rules would have prevented in seconds.
+They override every habit from other editors and languages.
+
+1. **NEVER use `edit`, `write`, `sed`, `python`, `perl`, or `awk` to modify
+   `*.ss` or `*.sls` files.** Use the jerboa-mcp tools instead:
+   - Add a top-level form → `jerboa_balanced_insert` (anchor = one unique
+     complete form, e.g. the `def` above the insertion point).
+   - Replace exact text → `jerboa_balanced_replace`. It is **dry-run by
+     default** — pass `dry_run: false` to actually write.
+   - Create a whole new file → `jerboa_write_file` (use `verify: true` to
+     reject unbalanced content before it lands).
+2. **After EVERY `.ss` change, run `jerboa_check_balance` before building.**
+   The balanced tools check automatically; if you bypassed them, check now.
+3. **If a file ever becomes unbalanced, STOP.** Do NOT count parens by hand,
+   do NOT write paren-counting scripts, do NOT poke one character at a time.
+   Recovery is exactly one of:
+   - `git checkout -- <file>` and redo the edit with `jerboa_balanced_insert`
+     (preferred — one command, seconds), or
+   - `jerboa_repair_balance` (dry-run shows the plan; `apply: true` writes).
+4. **Keep closer-runs short.** Never hand-write code that ends in more than
+   ~4 consecutive closers (`))))]` runs). Flatten deep nesting with helper
+   `def`s, `let*`, or cond `=>` clauses so no edit ever depends on counting
+   a long `)` run.
+5. **`(def ...)` after an expression in a body is invalid.** Internal defines
+   must come first in a body, or use `let`/`let*`. The build error
+   "invalid context for definition" means you violated this — or a missing
+   paren above glued two top-level forms together (check balance first).
+
 ## The Jerboa Language — Quick Reference
 
 Jerboa is a Scheme dialect built on Chez Scheme. It is Gerbil-inspired but its own language. **All user-facing code is `.ss` files. Never write `.sls` files for the user** — those are internal implementation files.
@@ -429,6 +460,9 @@ Jerboa is a niche Scheme dialect with limited training data. **Never guess — a
 | FFI work | `jerboa_ffi_scaffold` / `jerboa_ffi_type_check` / `jerboa_ffi_null_safety` |
 | Port Gerbil code | `jerboa_migration_check` + `jerboa_translate_scheme` |
 | Detect paren imbalance | `jerboa_check_balance` — use BEFORE `make build` after deep edits |
+| Edit a `.ss` file | `jerboa_balanced_insert` / `jerboa_balanced_replace` — NEVER raw `edit`/`sed`/`python` (see top of file) |
+| Create a `.ss` file | `jerboa_write_file` — whole-file atomic write, `verify: true` rejects unbalanced content |
+| File already unbalanced | `jerboa_repair_balance` — dry-run repair plan; or `git checkout -- <file>` and redo with `balanced_insert` |
 | Full project audit | `jerboa_project_health_check` — balance, exports, cycles, duplicates |
 | Security audit | `jerboa_security_audit` + `jerboa_import_policy_check` |
 | Static build audit | `jerboa_static_symbol_audit` + `jerboa_boot_library_audit` |
@@ -448,7 +482,7 @@ Jerboa is a niche Scheme dialect with limited training data. **Never guess — a
 
 ### Code Generation & Refactoring
 
-`jerboa_rename_symbol`, `jerboa_balanced_replace`, `jerboa_wrap_form`, `jerboa_splice_form`, `jerboa_scaffold_test`, `jerboa_generate_module`, `jerboa_translate_scheme`, `jerboa_project_template`, `jerboa_httpd_handler_scaffold`, `jerboa_db_pattern_scaffold`, `jerboa_actor_ensemble_scaffold`
+`jerboa_rename_symbol`, `jerboa_balanced_replace`, `jerboa_balanced_insert`, `jerboa_write_file`, `jerboa_repair_balance`, `jerboa_wrap_form`, `jerboa_splice_form`, `jerboa_scaffold_test`, `jerboa_generate_module`, `jerboa_translate_scheme`, `jerboa_project_template`, `jerboa_httpd_handler_scaffold`, `jerboa_db_pattern_scaffold`, `jerboa_actor_ensemble_scaffold`
 
 ### Feature Suggestions
 
diff --git a/no-blinky.md b/no-blinky.md
index 8e707f4..423c009 100644
--- a/no-blinky.md
+++ b/no-blinky.md
@@ -125,3 +125,23 @@ The fix is successful when:
 **Implementation time:** ~30 minutes  
 **Testing time:** 10 minutes  
 **Risk level:** Medium (adds state tracking, but tests pass and full redraw still used for mode/resize/first draw)
+
+## Update: real root cause of persisting flicker (found after this doc)
+
+The dirty-region logic above was correct but was being defeated by an
+unrelated units bug in the periodic self-healing invalidate (added earlier
+for `handoff-corruption.md`, unaffiliated with this doc's author).
+
+`signal/tui/main.ss:398`: `*invalidate-interval-s* = 10`, compared directly
+against deltas of `(real-time)`. `real-time` is Chez's builtin and returns
+**milliseconds**, not seconds (confirmed by every other use in this file,
+e.g. `(cons 'ms (- (real-time) started))`). So the "10 second" interval was
+actually 10 *milliseconds* — `tb-invalidate!` (which marks termbox's entire
+back buffer dirty) fired on nearly every draw cycle, including every
+keystroke. `tb-present!` then re-emitted escape codes for every cell on
+screen regardless of how surgical `draw!`'s partial repaint was — full
+flicker on every key.
+
+Fix: renamed to `*invalidate-interval-ms* = 10000`, matching the file's
+existing ms-based constant convention (`*idle-poll-ms*`). No other logic
+changed. `make binary` and `make test` both pass.
diff --git a/signal/tui/main.ss b/signal/tui/main.ss
index ff32195..309db26 100644
--- a/signal/tui/main.ss
+++ b/signal/tui/main.ss
@@ -395,7 +395,7 @@
 
   (def *idle-poll-ms* 250)
   (def *max-actor-events-per-tick* 128)
-  (def *invalidate-interval-s* 10)
+  (def *invalidate-interval-ms* 10000)
 
   (def (event-loop state actor)
     (let loop ([dirty? #t] [last-invalidate (real-time)])
@@ -403,7 +403,7 @@
              [send-dirty? (handle-send-events! state)]
              [timer-dirty? (expire-typing-indicators! state)]
              [now (real-time)]
-             [invalidate? (>= (- now last-invalidate) *invalidate-interval-s*)]
+             [invalidate? (>= (- now last-invalidate) *invalidate-interval-ms*)]
              [need-draw? (or dirty? actor-dirty? send-dirty? timer-dirty? invalidate?)])
         (when need-draw?
           (when invalidate? (tb-invalidate!))