inkhaven 1.2.21

Inkhaven — TUI literary work editor for Typst books
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
# Inkhaven keybinding reference

Every keystroke the TUI recognizes, organized by which pane or overlay has
focus. Keys flagged **configurable** are read from
`<project>/inkhaven.hjson` under the `keys` block; the values below are the
shipping defaults. Everything else is hard-coded and not user-overridable in
this release.

> **Printable companion**: [`INKHAVEN_CHEAT_SHEET.typ`](INKHAVEN_CHEAT_SHEET.typ)
> — a two-column A4 reference. Compile with `typst compile
> Documentation/INKHAVEN_CHEAT_SHEET.typ` or open the file in the TUI
> (F3 → pick → `Ctrl+B B` to build).

The TUI has five focus states (Tree, Editor, AI, Search bar, AI prompt) plus
three transient overlays (Search results, Prompt picker, and a modal stack of
Add prompt / Delete confirm). Overlays absorb keys; the underlying pane keeps
its visual focus state but does not see input until the overlay closes.

---

## 0. Mouse

Inkhaven captures mouse input on startup. Left-click moves focus to the
clicked pane; in the Tree pane the click positions the row cursor, in the
Editor it positions the character cursor (clicks in the gutter are
ignored).

**Scroll wheel** (1.2.8+):

* **Tree pane** — moves the tree cursor up / down 3 rows per tick.
* **Editor pane** — scrolls the viewport up / down 3 lines per tick.
* **AI pane** — scrolls the chat history (older messages on wheel up).
* **OS Shell pane modal** — scrolls the turn buffer (older turns on wheel up).
* **HJSON editor modal** — moves the textarea cursor up / down 3 lines.
* **Kill-ring picker / Fuzzy paragraph picker** — moves the cursor through entries.
* Other modals ignore the wheel (silent no-op).

Floating overlays (search results, prompt picker) still swallow mouse
input so a stray click can't focus a hidden pane.

Terminal-side text selection still works while the alternate screen is
up — hold **Shift** (or Option, depending on the terminal) while
selecting to bypass mouse capture and copy text through the terminal's
own clipboard integration.

## 1. Global

These chords work from any focus except where noted. Chords marked
**configurable** can be remapped in HJSON.

| Chord                | Action                                                      | Configurable |
| -------------------- | ----------------------------------------------------------- | ------------ |
| `Tab`                | Cycle focus Tree → Editor → AI → Tree.                      | `next_pane`  |
| `Shift+Tab`          | Cycle in reverse.                                           | `prev_pane`  |
| `Ctrl+/`             | Focus the top Search bar.                                   | `search`     |
| `Ctrl+I`             | Focus the bottom AI prompt bar.                             | `ai_prompt`  |
| `Ctrl+S`             | Save current paragraph + re-embed (no-op if nothing open).  | `save`       |
| `Ctrl+Q`             | Hard quit. Auto-saves the open paragraph first if dirty; if the save fails, refuses to quit so the error stays visible. | no |
| `Ctrl+1`             | Focus the **Editor** pane.                                  | no           |
| `Ctrl+2` / `Ctrl+T`  | Focus the **Tree** pane. Use `Ctrl+T` if your terminal re-encodes `Ctrl+2` as NUL or `Ctrl+@`. | no |
| `Ctrl+3`             | Focus the **AI** pane.                                      | no           |
| `Ctrl+4`             | Focus the **Search** bar (top).                             | no           |
| `Ctrl+5`             | Focus the **AI prompt** bar (bottom).                       | no           |
| `Ctrl+B`             | Enter **meta mode**. The next keystroke is the action selector (see §1.1). | `meta_prefix` |
| `Ctrl+B H`           | Open the pane-aware **Quick reference** floating pane. Works from every pane (Tree / Editor / AI). Scroll with arrows / PgUp / PgDn; close with `Esc`. Routed through the meta prefix so it never collides with the editor's `Ctrl+H` split-scroll. | no |
| `Ctrl+B V`           | Open the **Version / author / credits** floating pane. Shows the running Inkhaven version, the author block, the repository / licence, and the curated list of direct dependencies with their licences. Scrollable; `Esc` closes. | no |
| `F1`                 | Open the **Help-manual** query pane (RAG over the Help book). Type a question, Enter to ask. The default LLM provider streams a grounded answer into the AI pane; the model is constrained to use only the Help excerpts (no external knowledge). Same flow as typing `Help! <question>` into the AI prompt bar. Help answers are **one-shot** and do not enter the AI chat history. | no |
| `F7`                 | **Grammar check** the currently-open paragraph. Resolver precedence for the prompt template: (1) a paragraph titled / slugged `grammar-check` in the **Prompts** system book, (2) an entry of the same name in `prompts.hjson`, (3) a built-in fallback that runs a grammar/punctuation review against the configured `language` (HJSON top-level) and explicitly preserves any Typst markup. Result streams into the AI pane and focus moves there so you can watch the review in real time. | no |
| `F9`                 | **Cycle the AI scope** through `None → Selection → Paragraph → Subchapter → Chapter → Book → None`. The next prompt sent from the AI prompt bar prepends the matching context (selection text / open paragraph / enclosing branch contents) then **auto-resets to `None`**. Mode is shown in the AI prompt title (`AI prompt · scope: Paragraph`) and the status bar. Works from every pane. | no |
| `F10`                | **Toggle inference mode** between `Local` and `Full`. `Local` instructs the model to use only the supplied context (and prior chat turns); `Full` lets it augment with general knowledge. Both modes are shown in the AI pane title (`AI — gemini · done · infer=Full · scope=Paragraph`). Help inference (F1 / `Help! …`) is **pinned to Local** regardless of this toggle so the help-manual answer never invents features. Works from every pane. | no |
| `Ctrl+B C`           | Clear the AI chat history + currently displayed inference. (F9's old behaviour; F9 now drives the scope cycle.) | no |
| `Ctrl+B ]`           | (1.2.5) **Tag the open paragraph** — open the floating tag picker scoped to the editor buffer. Inside the picker: `↑↓` select, `Space` multi-selects, `T` applies selected tags (or the cursor tag if none selected), `A` adds a new tag (prompt), `D` deletes a tag project-wide (y/n confirm), `Enter` applies, `Esc` closes. | no |
| `Ctrl+B }`           | (1.2.5) **Search by tag** — open the floating tag picker in read-only mode. `Enter` on a tag lists every paragraph that carries it with a typeable filter input; `Enter` on a paragraph row opens it in the editor. `A` / `D` still work (tag management is project-wide). | no |
| `Ctrl+B 0`           | (1.2.8) **Edit project HJSON** — open `<project>/inkhaven.hjson` in a full-screen modal editor with HJSON syntax highlighting. `Ctrl+S` saves; when the saved bytes differ from the loaded bytes, a *Restart required* overlay pops up (config applies on next launch). **`Ctrl+R` (1.2.12+) fires an LLM review of the current buffer** — same "reviewer LLM, not executor" pattern the prompts-editor TUI uses; response streams into `App.inference` and is visible in the AI pane after closing the modal. `Esc` closes (status-line warning fires if there are unsaved edits). The editor mirrors the main paragraph editor's chord set: arrows / Home / End / PgUp / PgDn / Shift+arrows for selection / Ctrl+Home,End top/bottom / Ctrl+Left,Right word jumps / Ctrl+Backspace delete-word / Ctrl+U undo / Ctrl+Y redo / Ctrl+K cut / Ctrl+C copy / Ctrl+P paste / Ctrl+A select-all / Ctrl+D delete-line / Ctrl+E delete-to-EOL / Ctrl+W delete-to-BOL. | `bund.edit_project_hjson` |
| `Ctrl+B Shift+0`     | (1.2.15) **Project doctor panel** — same scan + repair flow as `inkhaven doctor --scan` + `--autofix`, but as a TUI modal.  Synchronous project scan runs on open; modal table lists every finding with class + severity + path + one-line detail.  Inside: `↑↓` navigate, `r` repair the highlighted finding, `R` repair every finding, `Esc` closes.  Repairs are logged to `<project>/.inkhaven/doctor.log` for audit.  Paired mnemonically with `Ctrl+B 0` (HJSON config editor): the digit-0 row is the "system inspection" cluster. | `view.doctor_panel` |
| `Ctrl+B W`           | **Distraction-free / focus mode** — hides Tree, AI, Search, and AI-prompt panes; the editor occupies the full window. Forces focus to the editor on enter. Re-press to restore the four-pane layout. Mutually exclusive with `Ctrl+B K` AI-fullscreen. Internally called "typewriter mode" in legacy strings + the HJSON serde key (`global.toggle_typewriter`) — the binding key stays for backward compatibility, but the user-facing name is now "focus mode". | `global.toggle_typewriter` |
| `Ctrl+B S` (editor)  | (1.2.9) **Read aloud (TTS)** — speak the open paragraph through the OS text-to-speech engine. Cross-platform via `tts-rs` (AVFoundation on macOS, SAPI / WinRT on Windows, Speech Dispatcher on Linux). Default voice is `Milena` (Russian female; ships free with macOS + Windows after a one-time language download). Gated by `editor.tts.enabled = true` in HJSON — disabled by default. While playing, a `Read aloud` modal shows the elapsed time, the chosen voice, and the first 80 chars of the paragraph; any key (Esc / Space) stops playback. Modal auto-closes when the paragraph finishes. Tree-scope `Ctrl+B S` still adds a subchapter. | `editor.tts_read_paragraph` |
| `Ctrl+B Shift+F`     | (1.2.9) **Toggle style warnings** — flip the inline filter-word overlay on / off without leaving the editor. When on, the editor underlines intensifier crutches and hedges (`just`, `really`, `very`, `просто`, `очень`, …) in amber so the writer can question + rewrite. Built-in word lists ship for English, Russian, French, German, Spanish; the active list is keyed by the project's top-level `language` field. Extra words via `editor.style_warnings.filter_words.extra_words` in HJSON. Master switch is `editor.style_warnings.enabled`; this chord is a session-local override. | `editor.toggle_style_warnings` |
| `Ctrl+B Shift+K` (editor) | (1.2.20) **Toggle echo overlay** — the live, in-editor companion to the `echo-repetition` doctor scan. When on, every occurrence in the *open* paragraph of a word echoing across nearby paragraphs of the chapter is underlined in its own colour (`theme.style_warning_echo_fg`, default a muted purple distinct from the repeated-phrase magenta), so you catch a reused distinctive word as you write rather than at scan time. Cross-paragraph: it reads the chapter's paragraphs (the open one live, including unsaved edits) and uses the shared `editor.echo_window` / `echo_min_repeats` / `echo_max_global` tunables. Multilingual via the project's Snowball stemmer (ё-folded for Russian). Master switch is `editor.echo_overlay`; this chord is a session-local override. | `editor.toggle_echo_overlay` |
| `Ctrl+B Shift+R` (editor) | (1.2.9) **Save paragraph as audio** — write the open paragraph to an AIFF file via macOS `say -o <path>`. Opens a path picker pre-filled with `<project>/audio/<paragraph-slug>.aiff` — edit the path then Enter to write, Esc cancels. Uses the same voice + speed as `Ctrl+B S`. Output format follows the file extension (`.aiff` / `.wav` / `.m4a` all work on macOS 13+). macOS-only. | `editor.tts_save_as_audio` |
| `Ctrl+B Shift+V`     | (1.2.17) **Piper TTS voice picker** — browse the Hugging Face piper-voices catalog + every voice already downloaded into the project's `.inkhaven/voices/` directory.  Catalog entries sorted by language code asc → quality tier desc → key asc.  Type characters to filter (matches voice key, language code, or English language name; case-insensitive).  ↑↓ / PgUp / PgDn / Home / End navigate; Enter on an `⬇ available` row downloads the voice (blocking, ~5–30 s on a fast connection) + sets the runtime `tts.voice`; Enter on a `✓ downloaded` row just sets the runtime voice; `d` removes a downloaded voice + updates the LRU index (skipped while a filter is active so it doesn't collide with type-to-filter); Esc closes.  Mirrored by the headless `inkhaven tts voice list / download / remove` CLI.  Picker is engine-agnostic — works even when the active backend is macOS `say`, so users can stage Piper voices before flipping `tts.engine`. | `tts.voice_picker` |
| `Ctrl+B Shift+E` (editor) | (1.2.18) **Reader-pace preview** — a teleprompter that advances a highlight word-by-word through the open paragraph at `editor.reading_wpm` (default 200), so you experience your prose at a reader's speed instead of editing-glance speed — surfaces pacing problems (a run-on that drags, a too-abrupt beat) invisible when you skim.  Already-read words dim, current word reverse-highlighted, upcoming words normal; footer shows position + time-left.  `Space` pause/resume (elapsed time carries across cycles), `←` / `→` step the highlight back/forward one word, `r` restart from the top, `Esc` close.  Reads clean prose (the same markup-stripping pass the audiobook export uses).  E for Experience. | `view.reader_pace` |
| `Ctrl+B Shift+G`     | (1.2.9) **Writing-streak heatmap** — GitHub-style 13×7 grid of the last 91 days of project-wide word deltas. Each cell colored by daily word count bucket (0 → dim, 1-249 → faint green, 250-499 → medium, 500-999 → bright, 1000+ → max). Footer shows current streak + longest streak in window + 91-day total + active-day average. Today's cell highlighted with a dark background. Any key closes. | `view.open_writing_streak_heatmap` |
| `Ctrl+B <` (editor)  | (1.2.9) **Previous scene break** — jump cursor to the previous scene-break line in the open paragraph. Scene breaks: `* * *`, `***`, `---`, `___`, `###`, `~~~`, or a lone `§`. Originally requested as `Ctrl+B Shift+{`; reassigned to `<` (vim-style) because `Shift+}` collides with the 1.2.5 `Ctrl+B }` = TagSearch chord. | `editor.scene_break_prev` |
| `Ctrl+B >` (editor)  | (1.2.9) **Next scene break** — same detector as `Ctrl+B <`, jumps forward. | `editor.scene_break_next` |
| `Ctrl+B Shift+L`     | (1.2.9) **Concordance view** — project-wide list of every distinct lexical stem with total count + up to 3 KWIC samples. Walks every paragraph under regular books (system books — Prompts / Characters / Places / Lore / Help / Notes / Artefacts — are excluded from the corpus since 1.2.11 as metadata, not prose), tokenises with UAX-#29, drops stop-words / single-char tokens / pure digits, and groups by Snowball stem so `walk`/`walked`/`walking` collapse to one row. Type to filter (substring match against headword + variants); `Ctrl+S` toggles sort (count ↔ alphabetical); ↑↓ / PgUp/PgDn / Home / End navigate; **Enter (1.2.11+) closes the modal and jumps to the first sample's source paragraph at the matching line — the heading offset is computed on the live editor body so the cursor lands on the right textarea row even though the index was built over heading-stripped bodies**; Esc closes. Multilingual via the project's `language` field (English / Russian / French / German / Spanish stop-word lists). | `view.open_concordance` |
| `Ctrl+B Shift+P`     | (1.2.9) **Toggle POV chip** — flip the status-bar POV / character chip on or off (session-local override on top of `editor.pov_chip_enabled` in HJSON). When on, the status bar shows the most-mentioned character in the open paragraph as the heuristic POV character, plus up to three additional named characters present. Driven by the project's existing `characters` lexicon — no separate tagging needed. Ties broken by first-mention order. | `view.toggle_pov_chip` |
| `Ctrl+B Shift+N`     | (1.2.12) **Toggle prompt-language mode** — cycle the prompt-language resolution mode through `None` (defer to `editor.prompt_language_mode` in HJSON) → `book_defined` → `paragraph_detected` → `None`. Session-local; does NOT rewrite HJSON. `book_defined` uses the top-level `language` field for every AI prompt resolution; `paragraph_detected` runs whatlang on the live paragraph (falls back to book language for paragraphs shorter than `editor.prompt_language_detection_min_chars`). The AI pane title bar's `lang=` chip reflects the active mode (`ru (book)` vs `ru (paragraph)`); the status bar echoes the new mode on toggle. Mnemonic: N for Natural language / laNguage picker. | `view.toggle_prompt_language_mode` |
| `Ctrl+B Shift+H` (editor) | (1.2.9) **Sentence-rhythm gauge** — open a modal that splits the open paragraph into sentences (hand-rolled walker with abbreviation suppression: Mr., Mrs., Dr., e.g., i.e., Ph.D., …), computes word-count mean / stdev / coefficient of variation (CV), and maps CV to a verdict: Monotone (`CV < 0.25` — drones), Steady (`0.25-0.45`), Varied (`0.45-0.80` — strong prose rhythm), Choppy (`≥ 0.80`). Shows a per-sentence bar list + the three shortest + three longest outliers. ↑↓ / PgUp/PgDn / Home/End scroll; any other key closes. Mnemonic: H for heartbeat. | `view.open_sentence_rhythm` |
| `Ctrl+B Shift+T` (editor) | (1.2.9) **AI show-don't-tell scan** — send the open paragraph to the configured LLM with a system prompt asking for telling passages plus suggested rewrites. The response streams into the AI pane. Complements the always-on regex overlay (`editor.style_warnings.show_dont_tell`) — the regex catches obvious 2-grams (`was angry`, `realised`); the AI scan catches subtler instances and proposes alternatives. Mnemonic: T for tell. | `ai.analyse_show_dont_tell` |
| `Ctrl+B Shift+X` (editor) | (1.2.21) **AI fact-check** — fact-check the open paragraph against the project's **Facts** book. Locks the AI scope to the local paragraph and grounds the check against every established world fact (climate, geography, seasons, distances, chronology), so the model flags any claim that contradicts the world — snow in a tropical region, a three-day ride that should be three weeks. With an empty Facts book it degrades to a generic local fact-check. Multilingual fact-analysis system prompt (en/ru/de/fr/es); prompt resolution: Prompts-book `fact-check` paragraph → `prompts.hjson` → embedded default. Streams the verdict into the AI pane. Pairs with the F9 **Facts** scope, which loads the same facts as an interactive chat session. Mnemonic: X for fact e**X**amination. | `ai.fact_check` |
| `Ctrl+B Shift+S` (editor) | (1.2.21) **Facts search** — open the Facts semantic-search modal. Two-phase: type a query (multi-word OK) and **Enter** runs a semantic search over the Facts book (the same vector index as `Ctrl+V S`, post-filtered to the Facts subtree); then **↑↓** navigate the ranked matches, **Space** marks several (multi-select), **Enter** sends the marked facts — or the cursor's row — to a **targeted Facts chat** grounded in just those facts (reuses the F9 Facts-scope seed + fact-analysis system prompt). Any printable / Backspace in the results drops back to refine the query; **Esc** closes. The scalable path for a large Facts book — ground in the relevant handful instead of loading the whole book. Mnemonic: S for **S**earch facts. | `ai.search_facts` |
| `Ctrl+B Shift+J` (editor) | (1.2.21) **Next fact finding** — after a fact-check (`Ctrl+B Shift+X`) flags contradictions, cycle through them: each press moves the editor cursor to the next flagged claim in the open paragraph (best-effort — the model's quoted phrase may be paraphrased) and shows the violated fact + explanation on the status bar. Wraps around. Findings are tied to the checked paragraph; switching paragraphs prompts a re-run. Mnemonic: J for **J**ump. | `ai.next_fact_finding` |
| `Ctrl+B Shift+M` (editor) | (1.2.11) **AI sentence-rhythm rewrite** — send the open paragraph to the configured LLM with a prompt asking it to break monotonous rhythm by mixing short and long sentences while preserving voice + meaning. Prompt resolution: Prompts book (slug or title `sentence-rhythm-rewrite`) → `prompts.hjson` → embedded multilingual fallback that respects the project's `language` setting. On stream completion, an AI diff modal pops automatically; `a` accept creates a snapshot annotated `Sentence rhythm rewrite` first then replaces the buffer; `r` reject leaves it untouched. Pairs with the `Ctrl+B Shift+H` rhythm gauge — and the chord also fires from **inside** that gauge modal, so the natural diagnose-then-rewrite path needs no extra keystrokes: open the gauge, see MONOTONE, press `Ctrl+B Shift+M`; the gauge dismisses as the rewrite spawns. Mnemonic: M for Modulate / Mix it up. | `ai.rewrite_sentence_rhythm` |
| `Shift+F4` (editor) | (1.2.12) **Toggle fullscreen split-view** — left pane is the primary buffer; right pane is the `secondary` slot. Tab swaps focus. Tree + AI response panes hidden; AI prompt input stays at the bottom so `Ctrl+I` works from either pane. The right pane is populated via `Shift+Enter` in any paragraph picker — tree pane Enter, fuzzy paragraph picker (`Ctrl+V P`), bookmark picker (`Ctrl+V M`), recent-paragraph picker (`Ctrl+V Shift+P`). The existing `F4` (same-paragraph snapshot split) and `Ctrl+F4` (accept snapshot) are untouched. Both editors are full-featured with independent dirty tracking + idle autosave. | `editor.toggle_split_view` |
| `Shift+Enter` (tree, paragraph pickers) | (1.2.12) **Pin to split-view secondary pane** — works on tree-pane Enter, fuzzy paragraph picker (`Ctrl+V P`), bookmark picker (`Ctrl+V M`), recent-paragraph picker (`Ctrl+V Shift+P`). Loads the focused paragraph into the `secondary` slot instead of replacing the primary. Combined with `Shift+F4`, this is the universal "open this beside that" gesture. Errors surface on the status bar when the chosen paragraph is already primary (showing the same paragraph in both panes is rejected). | — (modifier on Enter) |
| `Ctrl+V Shift+B` | (1.2.12) **Sibling-book lookup for split-view** — given the open paragraph's slug, walks the project hierarchy for paragraphs with the same slug under a *different* top-level book. Zero matches → status message names the slug. Single match → auto-pin to the `secondary` slot. Two-or-more matches → open a fuzzy picker pre-scoped to those entries. Primary translation-workflow chord: from `manuscript-en/03-rain`, finds `manuscript-ru/03-rain` and pins it for side-by-side review via `Shift+F4`. | `view.sibling_book_lookup` |
| `Ctrl+B Q` (editor) | (1.2.13) **Translate paragraph INTO invented language** — composes a prompt envelope from the target language sub-book's `Meta/overview`, `Grammar` (all rules), `Phonology` (all rules), `Dictionary` (RAG-filtered to entries whose translation appears in the source), and `Sample texts` (up to 3 as register anchors); streams the response into the AI pane. With zero Language sub-books the chord errors; with exactly one it translates directly; with two-or-more it pops a picker (↑↓ + Enter, or type the first letter of the language name to jump-and-commit). The translation block is wrapped between `<<<TRANSLATION>>>` / `<<<END>>>` markers; the AI pane's `I` apply chord lifts only that block. Pane title shows italic `translate[on]` chip while extraction is armed. See Tutorial 49. | `ai.translate_to_invented` |
| `Ctrl+B Shift+Q` (editor) | (1.2.13) **Translate paragraph FROM invented language** — reverse direction. Same envelope shape, flipped from/to direction labels. Picker title shows `Translate FROM`. Roundtrip-test workflow: `Ctrl+B Q` an English paragraph → copy translation into the next paragraph → `Ctrl+B Shift+Q` → back-translation in AI pane. When the back-translation drifts beyond register the grammar rules or dictionary entries have an inconsistency the manuscript will eventually trip over. Closes the proposal's §13 roundtrip-test concept for interactive use. | `ai.translate_from_invented` |
| Tree pane `b` on `Language` system book | (1.2.13) **Scaffold a language sub-book** — when the tree cursor is on (or inside) the `Language` system book and the user presses `b` (Add Book), the new Book is parented under Language directly (bypassing the normal "Books are top-level" rule via a `validate_placement` carve-out) and the commit handler runs `scaffold_language_chapters` to populate the 5 standard chapters (`Meta`, `Dictionary`, `Grammar`, `Phonology`, `Sample texts`) + seeds `Meta/overview` with the full commented HJSON template. Status: `added language `<name>` — 5 chapters scaffolded`. Pressing `b` from anywhere else still slots a top-level user book above the system block (unchanged). Matches `inkhaven language init <name>`. See Tutorial 49. | `tree.add_book` (context-sensitive routing) |
| Tree pane `+` under Language `<lang>/Dictionary` | (1.2.13) **Add dictionary entry** — when the tree cursor sits anywhere under `Language/<lang>/Dictionary` and the user presses `+` (Add Paragraph), the commit handler intercepts: walks the parent chain to identify the per-language Book, derives the alphabet bucket from the typed word's first character (consulting `Meta/overview.alphabet` first, first-char uppercase fallback), auto-creates the bucket subchapter if missing, creates the entry under it, and seeds the body with the full commented HJSON dictionary-entry template + `word` pre-filled. Paragraph's `content_type` is set to `"hjson"`. Matches `inkhaven language add-word`. See Tutorial 49. | `tree.add_paragraph` (context-sensitive routing) |
| Tree pane `+` under Language `<lang>/Grammar` or `/Phonology` | (1.2.13) **Add rule paragraph** — under Grammar / Phonology chapters of a Language sub-book the commit handler seeds the body with the schema-aware HJSON rule template (proposal §4 for Grammar, §5 for Phonology). Paragraph `content_type` = `"hjson"`. See Tutorial 49. | `tree.add_paragraph` (context-sensitive routing) |

### 1.1 Meta mode (Ctrl+B prefix)

The meta prefix is a single `Ctrl+B`; the second key selects the action.
**The action table is pane-specific** — `Ctrl+B` then `S` means different
things depending on whether the Tree, Editor, or AI pane has focus. The
status bar shows a yellow **META** chip and a prompt listing the actions
for the current pane while it's pending.

`Esc` cancels meta mode without running anything. Any unrecognized key
cancels with a status hint telling you which pane's table it consulted.

**Tree pane (and Search bar focus)** — hierarchy management:

| Second key | Action                                              |
| ---------- | --------------------------------------------------- |
| `B` / `b`  | Open Add modal — new **book** at the root.          |
| `C` / `c`  | Open Add modal — new **chapter**.                   |
| `S` / `s`  | Open Add modal — new **subchapter**.                |
| `P` / `p`  | Open Add modal — new **paragraph**.                 |
| `D` / `d`  | Open Delete confirm modal for the cursor's node.    |
| `↑`        | Swap the cursor's node with its previous sibling.   |
| `↓`        | Swap the cursor's node with its next sibling.       |
| `H` / `h`  | Open the pane-aware **Quick reference** overlay.    |

**Editor pane** — paragraph operations:

| Second key | Action                                                          |
| ---------- | --------------------------------------------------------------- |
| `S` / `s`  | **Save** the open paragraph (alternative to Ctrl+S).            |
| `N` / `n`  | **New snapshot** of the current buffer (== F5).                 |
| `R` / `r`  | Open the snapshot histo**R**y picker (== F6). Moved off `H` so Help can claim that letter across every pane. |
| `L` / `l`  | Open the **load file** dialog (== F3).                          |
| `F` / `f`  | Toggle **split-edit** mode (== F4). See §3.9.                   |
| `T` / `t`  | **Retitle paragraph** — re-derive the displayed title from the buffer's first sentence (same logic that fires on save for placeholder titles, but runnable on demand). |
| `P` / `p`  | **Place-RAG inference** — treats the editor's current selection (or word under cursor) as a place name, sweeps matching paragraphs in the **Places** system book, and prepends them as RAG context to the next AI prompt. If the AI prompt is non-empty, the inference fires immediately and focus jumps to the AI pane. If empty, the context is stashed and focus jumps to the **AI prompt** so you can type your question. |
| `C` / `c`  | **Character-RAG inference** — same flow as `P` but against the **Characters** system book. |
| `H` / `h`  | Open the pane-aware **Quick reference** overlay.                |

**AI pane (and AI prompt focus)** — inference management:

| Second key | Action                                              |
| ---------- | --------------------------------------------------- |
| `C` / `c`  | **Clear** the current inference (cancel streaming or discard a finished result). |
| `H` / `h`  | Open the pane-aware **Quick reference** overlay.    |

The Tree pane's plain-letter shortcuts (`B`, `C`, `V`, `A`, `S`, `+`, `P`,
`D`, `-`) still work directly without the meta prefix when Tree has focus —
see §2.2. To run a tree action from the Editor, switch focus first
(`Ctrl+2` or `Tab`) and then use either the plain letter or meta.

`Tab` / `Shift+Tab` do **not** cycle focus when the editor pane has an open
paragraph — they cycle anyway in our implementation because we intercept them
before tui-textarea sees them, so they never insert a literal tab.

### 1.2 View mode (Ctrl+V prefix)

The third meta prefix. Routes to in-process exporters, pickers,
the writing-progress modal, paragraph links, and bookmarks. In
1.2.4+ all view-sub chords are rebindable through HJSON
`keys.bindings.view_sub` and `ink.key.bind_view_sub`; the
prefix itself is rebindable via `keys.view_prefix` (default
`"Ctrl+V"`). See tutorials 15 / 16 / 17 / 19 / 21 for the
full workflows.

| Chord (after `Ctrl+V`) | Pane (focus filter)      | Action                                                                 |
| ---------------------- | ------------------------ | ---------------------------------------------------------------------- |
| `1`                    | Editor / AI-prompt       | Write the **open paragraph's buffer** as markdown via the save-as picker (1.2.4 — default path pre-filled; bare Enter writes there). |
| `2`                    | Editor / AI-prompt       | Write the **containing subchapter's subtree** as markdown via the save-as picker. |
| `1`                    | Tree                     | Write the tree-cursor's **node and all descendants** as markdown via the save-as picker. |
| `S` / `s`              | any                      | Toggle **similar-paragraph mode** — saves the buffer, picks via vector search, opens a second editor side-by-side. Re-press to save both and exit. Both editors autosave on idle (1.2.4). |
| `G` / `g`              | any                      | Open the **writing-progress modal** (today / streak / per-book pace / 30-day sparkline / status-ladder counts / per-book bar chart). |
| `T` / `t`              | any (needs open paragraph) | Set / clear the **per-paragraph word-count target** (1.2.4+). Empty / `0` clears. Saves that cross the target auto-promote status one ladder step when `goals.auto_promote_on_target` is true. |
| `A` / `a`              | any (needs open paragraph) | **Add outgoing paragraph link** (1.2.4) — tree pane enters select-paragraph-to-link mode; Enter confirms. Self-link / duplicate / cycle are rejected with a status-bar message. |
| `I` / `i`              | any (needs open paragraph) | **Add incoming paragraph link** (1.2.4) — tree pane enters select-paragraph-that-will-link-to-current mode. Reverse of `A`. |
| `L` / `l`              | any (needs open paragraph) | **List outgoing links** (1.2.4) — floating picker. Enter opens (autosaves prev); D removes the link. |
| `K` / `k`              | any (needs open paragraph) | **List backlinks** (1.2.4) — paragraphs that link TO the open one. D removes the source's outgoing link. |
| `B` / `b`              | any (needs open paragraph) | Toggle **bookmark** on the open paragraph (1.2.4). |
| `M` / `m`              | any                      | Open the **bookmark picker** (1.2.4). Enter opens; D removes the bookmark. |
| `P` / `p`              | any                      | **Fuzzy paragraph picker** (1.2.4) — type-to-filter modal over every user-book paragraph. Three-tier ranking (title-starts > title-contains > slug-contains). |
| `R` / `r`              | any (needs open paragraph) | (1.2.5) **Render paragraph** — save the buffer, compile it in-process via `typst-render`, float a PNG preview on top of the editor. Inside the preview: `←` / `→` navigate pages (multi-page documents), `Home` / `End` jump to first / last; `Esc` closes; `S` opens a save-as picker for the **current page** at full DPI (288 dpi); `A` opens the picker for **all pages** at full DPI (writes `<base>-page-NNN.png` per page). Cancelling the save picker restores the preview with navigation state intact. |
| `N` / `n`              | any (needs open paragraph) | (1.2.5) **Next typst diagnostic** — move the editor cursor to the next parse or semantic diagnostic in the buffer. Wraps at the end. Refreshes the diagnostic cache up-front so navigation reflects the current buffer state, not the last save. Status bar reports `diag N/M  line L:C  — <message>`. |
| `W` / `w`              | any (needs current user book) | (1.2.5) **Story view** — build a twopi-style radial graph of the current book (book at centre, each depth on a concentric ring) with the hierarchy (chapters / subchapters / paragraphs / scripts / images / json) plus paragraph links (dashed purple) and Characters / Places / Artefacts mentions on an outer ring (dashed green). Rasterised via `resvg` and floated on top of the editor. Inside the modal: `Esc` closes, `S` opens a save-as picker (default `<book-slug>-story-YYYYDDMM-HHMM.png`). |
| `Shift+H`              | any                      | (1.2.14) **Threads picker** — fuzzy picker over every thread in the project's `Threads` system book. Enter opens a swim-lane weave view of the selected thread plus its 4 neighbours (by waypoint overlap), with manuscript paragraphs as columns and threads as horizontal lanes; waypoints as `●`, gaps as `·`. Inside the weave: ←/→ scroll the manuscript axis, `Esc` returns to the picker. Mnemonic: H for tHread (lowercase `h` is hidden-character report). |
| `Shift+A`              | any                      | (1.2.14) **AI thread audit** — sends every thread's beats + waypoints to the configured LLM with a prompt asking for blind spots (zero links, payoff unfired, dormant arcs). The response streams into the AI pane. Pairs with `Shift+D` (thread doctor modal) which runs the same checks deterministically without the LLM. |
| `Shift+D`              | any                      | (1.2.14) **Thread doctor modal** — deterministic blind-spot report. Walks every thread, flags `ZERO LINKS` (no waypoints linked to any paragraph), `PAYOFF UNFIRED` (climax beat exists but no waypoint after it), `DORMANT` (last waypoint > 30 days old). The TUI equivalent of `inkhaven thread doctor`. Inside the modal: ↑↓ scroll, `Esc` closes. |
| `c`                    | Editor (needs open paragraph) | (1.2.14) **Add inline comment** — anchors a comment to the current selection's character range (or to the word at the cursor if no selection); pops a multi-line text input for the body; on commit writes a sidecar `<paragraph>.comments.json` alongside the `.typ`. Commented spans render with `theme.comment_span_modifier` (default underline + italic); cursor inside a span surfaces `comment by <author> · <age>: <text>` in the editor footer. Char-offset anchors (not byte) for UTF-8 safety. Comments travel with the prose in git and diff cleanly. |
| `Shift+C`              | any                      | (1.2.14) **Comments panel** — project-wide panel over every `.comments.json` sidecar. Columns: breadcrumb / author / age / snippet / `(N/M in ¶)` dense indicator when a paragraph carries more than one comment. Panel chords: ↑↓ navigate, Enter open the source paragraph (cursor jumps to the comment span via `CursorMove::Jump`), `r` resolve, `R` toggle resolved-filter, `d` delete (no confirm), `/` filter (substring across slug + author + body), `a` AI digest (groups comments by STRUCTURAL / PROSE / FACTUAL / QUESTION categories, streams into the AI pane), `Esc` closes. |
| `d`                    | Editor (needs open paragraph) | (1.2.14) **AI continuation drafting** — asks the configured LLM to continue the open paragraph in the author's voice. Envelope: previous N paragraphs as voice anchors (`editor.continuation_anchor_count`, default 3) + the open paragraph with `[[CURSOR_HERE]]` marker. Response wrapped in `<<<DRAFT>>> / <<<END>>>` markers; the AI pane's `I` apply lifts only the draft block and inserts it at the cursor. Pairs with snippet expansion (e.g. `\tdo` + `Ctrl+V d` for AI-generated TODOs). |
| `f`                    | Editor (needs open paragraph) | (1.2.14) **Insert inline footnote** — pops a multi-line text input for the footnote body; on commit inserts `#footnote[<body>]` at the cursor (Typst, the default) or `[^id]` plus a trailing `[^id]: <body>` line (markdown, when `editor.footnote_style = "markdown"`). For academic / reference writing; the Typst markup is honoured by the assembled-book renderer. |
| `Shift+G`              | any                      | (1.2.14) **Project word-count goal modal** — reads `project.word_count_goal` + `project.target_date` from HJSON, projects the finish date from the last-30-day word delta (`progress_cache.sparkline`), renders a progress bar + verdict (`Ahead` ✓ / `On track` · / `Behind` ✗ / `Complete`). When unconfigured, the modal explains how to add the keys. Pairs with the writing-progress modal (`Ctrl+V g` — current pace) but goal-oriented rather than retrospective. |
| `Shift+J`              | any                      | (1.2.16) **Manuscript intelligence dashboard** — synthesis pane that unifies every metric inkhaven collects since 1.2.5: word count (today / total / streak / active minutes), structure (books / chapters / paragraphs + chapter-word-count CV + pacing verdict), threads (active / dormant), comments (open / resolved this week / lifetime).  `↑↓` scrolls; `e` exports the snapshot to `<project>/journal-<UTC>.md`; `Esc` closes.  J for Journal.  Cycle headline. |
| `y`                    | Editor (needs open paragraph) | (1.2.14) **Style-transfer rewrite** — picker over previously-edited paragraphs (sorted by recency); selection becomes the style reference. Envelope asks the LLM to rewrite the open paragraph in the reference's voice (match sentence length / register / narrative distance / rhythm; preserve meaning + entities + facts). Response wrapped in `<<<REWRITE>>> / <<<END>>>` markers; the AI pane's `I` apply lifts only the rewrite block and replaces the paragraph body. |
| `Esc`                  | any                      | Cancel the chord without acting.                                       |

While in similar-paragraph mode, `Tab` inside the editor toggles
keyboard focus between the left and right editor panes (instead
of cycling to the missing AI pane).

---

## 2. Tree pane

Focused on launch. Shows the project hierarchy with depth indentation, kind
glyphs (`📖` book, `▸` chapter, `▹` subchapter, `¶` paragraph), and a dim
`Nw` word-count suffix for paragraphs.

### 2.1 Navigation

| Key                  | Action                                                      |
| -------------------- | ----------------------------------------------------------- |
| `↑` / `↓`            | Move cursor one row up/down (within scroll).                |
| `→`                  | **Expand** the cursor's branch (book/chapter/subchapter), revealing its children. No-op on a paragraph or an already-expanded branch. |
| `←`                  | **Collapse** the cursor's expanded branch. If already collapsed (or on a paragraph), moves the cursor to the parent node. Same semantics as the F3 file picker. |
| `Home`               | Jump to first row.                                          |
| `End`                | Jump to last row.                                           |
| `PageUp`             | Move cursor 10 rows up (configurable: `page_up`).           |
| `PageDown`           | Move cursor 10 rows down (configurable: `page_down`).       |
| `Enter`              | Open the cursor's node. Paragraphs load into the editor and shift focus there; if a different paragraph was open with unsaved edits, it's autosaved first. Branches print a status hint and stay in Tree. |
| `F2`                 | Open the **Rename** modal pre-filled with the current node's title. Slug + filesystem entry stay; only the displayed title changes (re-embeds for search). |
| `F3`                 | Open the **file picker** dialog. Enter on a file creates a new paragraph (inserted after the current cursor) with that file's content. Enter on a directory **recursively imports** the tree — subdirectories become branches one level deeper (Book→Chapter→Subchapter), files become paragraphs. If the directory tree exceeds the hierarchy depth, the deeper files are flattened into the deepest legal branch (with `unbounded_subchapters: false`). See §12. |
| `q` or `Q`           | Quit (autosaves the open paragraph first if dirty).         |
| `Esc`                | Cycle focus to the **Search bar** (second leg of the Editor → Tree → Search → Editor rotation). |

**Open-paragraph indicator** — the row of the paragraph currently loaded in
the Editor is rendered with a **green bold "►"** marker (instead of the
usual `¶` glyph) regardless of focus. The marker stays visible whether the
Editor or Tree pane has focus, so you can always see which paragraph is
loaded. If your tree cursor happens to land on the open paragraph, the
REVERSED cursor highlight wins visually but the green color underneath
still marks the row.

### 2.2 Tree-pane shortcuts (modifier-free)

These plain-key shortcuts work only when the Tree pane has focus. They exist
alongside the global meta-prefix chords (§1.1) because terminals and
multiplexers commonly intercept those (see §13 for details). All four open
the same modals as their global equivalents — no destructive action without
confirmation.

**Append at end** — `B`, `C`, `A`, `+` open the Add modal and place the new node at the end of its parent's children. The parent is chosen by walking up from the tree cursor to the nearest node that can host the requested kind.

**Insert after current** — `V`, `S`, `P` open the same Add modal but place the new node immediately after the cursor's same-kind ancestor. All subsequent siblings get their `order` bumped by `+1` and their filesystem entries renamed. If no same-kind ancestor exists (e.g. pressing `P` on a book with no paragraphs), falls back to append-at-end so the action still does something.

| Key       | Action                                                                                  |
| --------- | --------------------------------------------------------------------------------------- |
| `B` / `b` | Add a new **book** at the root. User books are inserted **above** the system block (Notes, Research, Prompts, Places, Characters, Help) by shifting it down; the new book takes Notes' old order. Equivalent to `Ctrl+B` then `B`. |
| `C` / `c` | **Append** a chapter at the end of the book's children. Equivalent to `Ctrl+B` then `C`. |
| `V` / `v` | **Insert** a chapter immediately after the cursor's enclosing chapter.                  |
| `A` / `a` | **Append** a subchapter at the end of the chapter's children. Equivalent to `Ctrl+B` then `S`. |
| `S` / `s` | **Insert** a subchapter immediately after the cursor's enclosing subchapter.            |
| `+`       | **Append** a paragraph at the end of the parent's children. Equivalent to `Ctrl+B` then `P`. |
| `P` / `p` | **Insert** a paragraph immediately after the cursor's enclosing paragraph.              |
| `D` / `d` | Delete the cursor's node — only if it's a **branch** (book/chapter/subchapter). On a paragraph, shows a hint to press `-` instead. |
| `-`       | Delete the cursor's node — only if it's a **paragraph**. On a branch, shows a hint to press `D` instead. |
| `U` / `u` | **Move up** — swap the cursor's node with its previous sibling. Plain-letter form of `Ctrl+B ↑`. |
| `J` / `j` | **Move down** — swap the cursor's node with its next sibling. Plain-letter form of `Ctrl+B ↓`. |
| `Z` / `z` | **Collapse subchapter** — folds the cursor's enclosing Subchapter (or the cursor's node itself if it IS a Subchapter). Lands the tree cursor on the folded row. |
| `X` / `x` | **Collapse all** — folds every expanded branch in the tree. Empty branches and paragraphs are untouched. |
| `Space`   | (1.2.4) **Mark / unmark** the cursor row for multi-select. Status bar shows `marked N`. `Esc` clears all marks. |
| `T` / `t` | (1.2.4) **Cycle node type** (`paragraph → json → script`). No marks: cursor row only (folders skipped). With marks: every marked leaf. |
| `O` / `o` | (1.2.4) **Cycle status** one rung up the ladder (`napkin → first → … → ready → napkin`). No marks: cursor row. With marks: every marked paragraph. |
| `G` / `g` | (1.2.5) **Tag the marked set** — open the floating tag picker scoped to every marked paragraph (or just the cursor row when no marks). Same modal as `Ctrl+B ]`; T applies the selected tag set across every target at once. |

Empty paragraph titles are allowed for `+` and `P` — the first sentence of the body becomes the title on next save.

**Multi-select interaction** (1.2.4): with at least one row marked,
`Ctrl+B I` (reindex) and `Del` walk the mark set instead of the
cursor row. `Ctrl+B R` (rename) is intentionally single-row only.

Why kind-specific delete? Safety. `-` won't nuke an entire chapter if your
cursor accidentally landed on it, and `D` won't kill a paragraph you meant
to keep. If you want delete that doesn't care about kind, use the global
`Ctrl+B` then `D`.

Shortcuts ignore the `Shift` modifier (uppercase implies Shift on most
layouts) but reject `Ctrl` / `Alt` / `Super` — so `Ctrl+A` will *not* fire
Add-subchapter.

All global chords also fire from the Tree pane.

---

## 3. Editor pane

Focused automatically when a paragraph is opened. Backing widget is
`tui-textarea` driven by `input_without_shortcuts`, so emacs-style defaults
(Ctrl+A → start of line, Ctrl+P → previous line, etc.) are **off**. We
intercept the modern conventional shortcuts ourselves; everything else falls
through to tui-textarea's typing / cursor handling.

**Border color** carries the dirty state at a glance — but only while the
pane has focus:

- **Green (bold)** — focused, in sync with disk + bdslib (saved).
- **Yellow (bold)** — focused, with unsaved edits.
- **White** — pane is *unfocused*. Dirty signaling moves to the title's
  `[modified]` suffix and the red `●` chip in the status bar (both
  always-on indicators).

**Focus-loss autosave**: whenever focus moves away from the Editor pane —
via `Tab`, `Ctrl+1..5`, `Ctrl+T`, `Ctrl+/`, `Ctrl+I`, `Esc` from another
input, etc. — the open paragraph is automatically saved if dirty. So you
can shift focus mid-edit without worrying about losing work; the next save
trigger (idle/quit/switch) won't catch the same change twice.

### 3.1 Cursor movement

| Key                  | Action                                                      |
| -------------------- | ----------------------------------------------------------- |
| `←` / `→`            | One character left / right.                                 |
| `↑` / `↓`            | One line up / down.                                         |
| `Home`               | Start of current line.                                      |
| `End`                | End of current line.                                        |
| `PageUp` / `PageDown`| One viewport up / down (tui-textarea internal).             |
| `Ctrl+←`             | Previous word boundary.                                     |
| `Ctrl+→`             | Next word boundary.                                         |
| `Ctrl+Home`          | Top of document.                                            |
| `Ctrl+End`           | Bottom of document.                                         |

### 3.2 Editing

| Key                  | Action                                                      |
| -------------------- | ----------------------------------------------------------- |
| any character        | Insert at cursor. Replaces selection if one exists.         |
| `Enter`              | Insert newline.                                             |
| `Backspace`          | Delete character before cursor (or whole selection).        |
| `Delete`             | Delete character at cursor.                                 |
| `Ctrl+Backspace`     | Delete previous word.                                       |
| `Ctrl+S`             | Save current paragraph to disk and re-embed in bdslib. Triggers a tree reload so word counts refresh. |

### 3.3 Selection, clipboard, undo

`tui-textarea` maintains a single linear selection range. Shift+arrows extend
it. **Note:** the editor uses non-standard keys for cut and paste because the
conventional bindings now do other things (`Ctrl+X` is "repeat" for search,
`Ctrl+Z` is delete-to-end-of-line). The mapping below has been chosen so
each operation lives on a distinct key with no overlap.

| Key                  | Action                                                      |
| -------------------- | ----------------------------------------------------------- |
| `Shift+←` / `Shift+→`| Extend selection left / right one character.                |
| `Shift+↑` / `Shift+↓`| Extend selection up / down one line.                        |
| `Ctrl+A`             | Select entire document.                                     |
| `Ctrl+C`             | **Copy** selection to system clipboard (falls back to internal yank if `arboard` failed to init). |
| `Ctrl+K`             | **Cut** selection to clipboard. Marks doc dirty.            |
| `Ctrl+P`             | **Paste** from clipboard at cursor (or replace selection). Marks dirty. |
| `Ctrl+U`             | **Undo.**                                                   |
| `Ctrl+Y`             | **Redo.**                                                   |

The line-targeted delete shortcuts (§3.11) all preserve the yank buffer so
they don't clobber clipboard state.

If `arboard::Clipboard::new()` fails at startup (typical on headless or some
Wayland setups), copy/cut/paste silently fall back to tui-textarea's
internal yank buffer — the chords still work within the editor session, but
don't cross process boundaries.

### 3.4 Vertical block selection (rectangular)

A second, separate selection model independent of tui-textarea's native
range. Always rectangular: anchor + current cursor define inclusive
`(row_min..row_max, col_min..col_max)`. Drawn with REVERSED style on top of
the syntax highlighting.

| Key                          | Action                                                  |
| ---------------------------- | ------------------------------------------------------- |
| `Alt+↑` / `↓` / `←` / `→`    | Enter block-select mode (if not already), then move cursor by one cell without changing tui-textarea's linear selection. Rectangle redraws each frame. |
| `Alt+C`                      | Copy the rectangle to system clipboard as a multi-line string (each row a line). Clears the anchor. |
| `Esc`                        | Cancel block-select; keep the doc open.                 |
| any non-Alt key              | Cancels block-select implicitly (falls through to normal editor handling). |

**Deferred in this release**: rectangular cut and rectangular paste require
bulk character-deletion across multiple lines, which tui-textarea doesn't
expose cleanly. Copy-only covers the common cases (extracting a column of
leading numbers, a list of names, a verse stanza).

### 3.11 Line-targeted delete shortcuts

Four chords that delete chunks of the current line without touching the
clipboard. Each saves and restores the yank buffer around the operation, so
`Ctrl+P` paste still produces the last copy.

| Key       | Action                                                                 |
| --------- | ---------------------------------------------------------------------- |
| `Ctrl+D`  | **Delete current line** — removes the entire line + its trailing newline; cursor lands on the line that takes its place. On the very last line, the content is cleared and an empty line remains (no newline to delete). |
| `Ctrl+E`  | **Delete to end of line** — removes from the cursor to the line end.   |
| `Ctrl+W`  | **Delete to start of line** — removes from the cursor back to column 0. |

*(`Ctrl+Z` is the **Bund-meta prefix** in 1.2+ — runs scripting actions
like `Ctrl+Z R` run-buffer, `Ctrl+Z N` new-script, `Ctrl+Z E` eval,
`Ctrl+Z ?` script-picker. See [`KEYS_REASSIGNMENT.md`](KEYS_REASSIGNMENT.md).
Undo is `Ctrl+U`, delete-to-EOL is `Ctrl+E`.)*

*(`Ctrl+V` is the **view-meta prefix** in 1.2.3+ — markdown export,
similar-paragraph mode, and progress tracking. See section 1.2 below
and tutorials 15 / 16 / 17.)*

**Note on `Ctrl+W`**: bash, tmux, and some terminals interpret `Ctrl+W` as
"delete previous word" before forwarding the keystroke. If your shell layer
eats `Ctrl+W`, use the meta prefix path (`Ctrl+B`, then a future-defined
alias) or rebind the chord in `inkhaven.hjson` once configurable bindings
for it are added.

### 3.9 Split-edit mode

A two-pane "edit with lookback" view. Toggle with `F4`. While split is
active the editor area is divided 50/50 horizontally: the **upper pane** is
your normal read-write editor and the **lower pane** is a read-only
snapshot of the buffer captured at the moment you pressed F4. The lower
pane scrolls independently so you can keep an earlier passage visible
while you rewrite it above.

| Key       | Action                                                                  |
| --------- | ----------------------------------------------------------------------- |
| `F4`      | Toggle split. Capture the buffer on enter; drop the snapshot on exit.   |
| `Ctrl+F4` | **Accept** the snapshot — replace the live buffer with the captured copy, exit split, mark dirty (bold marks the diff; Ctrl+S commits the rollback). |
| `Ctrl+H`  | Scroll the lower (snapshot) pane up by one line. Only active in split.  |
| `Ctrl+J`  | Scroll the lower pane down by one line. Only active in split.           |

The upper pane behaves exactly like the full editor — same shortcuts, same
syntax highlighting, same selection / clipboard / undo, same idle autosave,
same diff bolding. The lower pane is fully passive: no cursor, no
highlighting, dim grey text. Its header shows the current visible line and
the snapshot's total line count, plus a reminder of the available keys.

`Ctrl+H` and `Ctrl+J` are routed to the split pane **only while split is
active**. When split is off they fall through to normal editor handling
(tui-textarea's defaults), so they don't shadow anything in regular use.
The Quick-reference overlay is opened via `Ctrl+B` `H` (meta prefix)
precisely so it never contends with the split-scroll chord.

### 3.10 Find and replace (regex)

In-buffer regex search with optional replacement. Matches are highlighted
in **red** on top of the syntax coloring; the cursor's current match gets a
brighter **LightRed + bold** style so it stands out among siblings.

| Key                | Action                                                                |
| ------------------ | --------------------------------------------------------------------- |
| `Ctrl+F`           | Open the **Find** modal (magenta-bordered). Type a regex, Enter to run. Cursor jumps to the first match; all matches stay highlighted. Status bar reports `match 1 / N`. |
| `Ctrl+X`           | **"Repeat"** (multifunction). In search mode: jump to the next match (wraps). In replace mode: replace the current match and advance to the next. Only active while a search is in progress; otherwise the keystroke falls through. |
| `Ctrl+R`           | **First press**: open the **Find & Replace** modal (search + replace fields, `Tab` switches between them). Enter applies the **first** replacement automatically and stays in replace mode. **Second press while in replace mode**: replace every remaining match and exit replace mode. |
| `Esc` (in editor)  | Clear the active search (drops the highlights, exits replace mode).   |

**Regex flavor:** full Rust [`regex`](https://docs.rs/regex) syntax. Use
flags via `(?i)` (case-insensitive), `(?s)` (dot matches newlines), etc.

**Per-line matching:** v1 searches line-by-line so cross-line patterns
won't match. Most literary search/replace tasks (word substitution, name
changes) are within-line anyway.

**Layer order in the renderer:** syntax color → `[modified]` bold → match
red bg → current-line highlight → selection REVERSED. Selection wins
visually when a char is both selected and matched; matches win over the
subtle current-line highlight.

**Pre-fill:** opening `Ctrl+F` or `Ctrl+R` again after an active search
pre-populates the modal inputs with the previous pattern (and replacement).
Edit them and Enter to re-run.

### 3.5 Snapshots and file loading

| Key  | Action                                                              |
| ---- | ------------------------------------------------------------------- |
| `F3` | Open the **file picker** dialog. Pick a file with Enter to replace the open paragraph's editor buffer (bold marks the change vs the saved version). Directories are rejected in this context. See §12 for navigation. |
| `F4` | Toggle **split-edit** mode — see §3.9. |
| `F5` | Save a versioned **snapshot** of the open paragraph's current body (stored as a bdslib document with `kind:"snapshot"` and a `parent_id` back-reference; doesn't appear in vector search). |
| `F6` | Open the **snapshot picker** overlay listing every snapshot for the open paragraph, newest first. `↑↓` navigates, `Enter` loads the selected snapshot (1.2.4: takes a **pre-restore safety snapshot** of the live buffer first), `V` opens a **side-by-side diff** of the snapshot vs current (1.2.4 — Esc returns to picker), `D` / `Del` removes the snapshot, `Esc` cancels. |

Snapshots are independent documents — they survive paragraph saves and aren't
deleted when their parent is deleted, so they can act as a recovery hatch.

**Pre-restore safety net (1.2.4)**: Enter in the snapshot picker first creates
a snapshot of the live buffer, then replaces. If creating the safety snapshot
fails, the load aborts entirely — the buffer stays untouched. To undo an
unwanted restore: F6 again, the safety snapshot is at the top, Enter.
Currently they're not surfaced from the CLI; that's an easy follow-up if you
need scripted access.

### 3.6 Autosave and background sync

Three save triggers, plus manual `Ctrl+S`:

- **Idle**: when the editor has unsaved edits and the user hasn't pressed a
  key for `editor.autosave_seconds` (default 5; set to 0 to disable).
- **Paragraph switch**: opening another paragraph from the Tree pane
  autosaves the current one first.
- **Quit**: `Ctrl+Q` and the `q` quit chords autosave before exiting.

In addition, a background task calls `Store::sync()` every
`sync_interval_seconds` (default 60). This flushes the HNSW vector index +
DuckDB checkpoint without blocking the UI. Set to 0 to disable.

Every save also resets the bold "added since last save" overlay (§3.7).

### 3.7 Visual change tracking

Characters added to the editor since the last save (Ctrl+S, autosave, or
load) are rendered **bold** on top of the syntax highlighting. The marker
goes away the moment you save. Implemented with a per-line longest-common-
prefix/suffix diff — fast at literary scale, accurate for the common case
of typing within or appending to a line. Cross-line inserts may
misattribute briefly until the next save resets the snapshot.

### 3.8 Pane management while focused

| Key                  | Action                                                      |
| -------------------- | ----------------------------------------------------------- |
| `Esc`                | Defocus to Tree without closing the document. If a block selection is active, Esc clears it first. |
| `Tab` / `Shift+Tab`  | Cycle focus (intercepted globally so they don't insert tab).|

### 3.6 When no paragraph is open

If the editor pane is focused but `opened` is `None`, only one key matters:

| Key       | Action |
| --------- | ------ |
| `q` / `Q` | Quit.  |

Plus all global chords.

---

## 4. AI pane

Focus lands here automatically only via the AI prompt's `Esc` bounce — when
you submit a query the prompt bar **keeps** focus so follow-ups are one
keystroke away. Pane title shows provider, streaming status, and a
`· N turn(s)` chip whenever the chat history has accumulated content.

| Key       | Condition                       | Action                                              |
| --------- | ------------------------------- | --------------------------------------------------- |
| `Esc`     | always                          | Bounce focus back to the **AI prompt** bar (mirror of the AI-prompt → AI Esc). |
| `r` / `R` | inference done, doc open        | Replace editor selection (or entire doc if no selection) with the AI text. Marks dirty, refocuses Editor. |
| `i` / `I` | inference done, doc open        | Insert AI text at cursor. Marks dirty.              |
| `t` / `T` | inference done, doc open        | Prepend AI text to top of paragraph (with blank line separator). |
| `g` / `G` | inference done, doc open        | **Grammar-check apply**: lifts only the corrected paragraph from the response (between `<<<CORRECTED>>>` / `<<<END>>>` markers, or last fenced code, or after a "Corrected …" heading) and overwrites the editor buffer wholesale (constructs a fresh `TextArea` so nothing of the old buffer survives). Skips the markdown→Typst conversion because the grammar prompt preserves Typst markup verbatim. Changed characters render in `theme.grammar_change_fg` (default red) and the highlight **survives saves** — dismiss it explicitly with `Ctrl+B` then `C`, or by switching paragraphs. Refuses with a status message if no extraction pattern matches. |
| `b` / `B` | inference done, doc open        | Append AI text to bottom of paragraph.              |
| `c` / `C` | inference done                  | Copy AI text to system clipboard only (no editor change). |
| `q` / `Q` | always                          | Quit.                                               |

Action keys fire only when `inference.status == Done` and the response is
non-empty. While streaming or on error, single-character keys do nothing
(except `q` to quit and `Esc` to bounce).

**Chat history.** Each non-Help inference appends a `(User, Assistant)` pair
to the in-memory chat history; the next prompt replays the whole history to
the model so the conversation is continuous. The title's `· N turn(s)` chip
shows the current depth. Press `F9` (or `Ctrl+B` then `C`) at any time to
clear both the history and the currently displayed inference.

Help (`F1` / `Help! …`) inferences are deliberately **one-shot** — they use
a strict RAG system prompt and are not added to the chat history, so a
prior set of chat turns won't dilute their grounding.

---

## 5. Search bar (top input)

Activated by `Ctrl+/` from any non-modal focus. Cursor appears as a `│`
character at the buffer's character position.

| Key                  | Behavior                                                    |
| -------------------- | ----------------------------------------------------------- |
| any printable char (no Ctrl/Alt) | Insert at cursor. Closes the results overlay if it was open (query has changed). |
| `Backspace`          | Delete char before cursor; closes results overlay.          |
| `Delete`             | Delete char at cursor; closes results overlay.              |
| `←` / `→`            | Move cursor one char left / right.                          |
| `Home`               | Cursor to start.                                            |
| `End`                | Cursor to end.                                              |
| `↑`                  | (overlay open) Move result cursor up.                       |
| `↓`                  | (overlay open) Move result cursor down.                     |
| `Enter`              | If results overlay is open: open the highlighted result. Otherwise: run `Store::search_text(query, 10)` and show results. |
| `Esc`                | If results overlay is open, close it (one press); else cycle focus to the **Editor** pane (third leg of the Editor → Tree → Search → Editor rotation). |

Opening a result from this overlay positions the tree cursor on the target
node. Paragraphs additionally load into the editor (focus moves to Editor).

---

## 6. AI prompt bar (bottom input)

Activated by `Ctrl+I`. Behaves like the Search bar with a different submit
action and the `/`-triggered Prompt picker overlay.

| Key                  | Behavior                                                    |
| -------------------- | ----------------------------------------------------------- |
| any printable char (no Ctrl/Alt) | Insert at cursor. If the buffer starts with `/`, opens the Prompt picker; otherwise closes it. |
| `Backspace`          | Delete char before cursor. Refreshes the picker if visible. |
| `Delete`             | Delete char at cursor. Refreshes the picker.                |
| `←` / `→`            | Move cursor one char left / right.                          |
| `Home`               | Cursor to start.                                            |
| `End`                | Cursor to end.                                              |
| `↑`                  | (picker open) Move selection up.                            |
| `↓`                  | (picker open) Move selection down.                          |
| `Tab`                | (picker open) Expand selected prompt template into the buffer with `{{selection}}` / `{{context}}` substituted. |
| `Enter`              | If picker open: same as Tab — expand selected template. Otherwise: spawn a streaming inference. Focus **stays** on the AI prompt bar (it does not jump to the AI pane). The buffer is sent verbatim, except: a leading `/name` is resolved against the prompt library, and a leading `Help!` (case-sensitive) routes the rest of the line through the F1 Help-RAG flow. |
| `Esc`                | If picker open, close it; else bounce focus to the **AI pane** so you can read or scroll the answer. Pressing `Esc` again from the AI pane brings you straight back here. |
| `Ctrl+1`             | Focus the **Editor** pane (global shortcut, works from this input too). |
| `Ctrl+T`             | Focus the **Tree** pane (global shortcut, works from this input too). |

Submitting a query when no API key is set in the environment surfaces a
status-line error like `GEMINI_API_KEY not set in environment — `export
GEMINI_API_KEY=...`` and does not spawn a request. **Local providers** like
Ollama omit `api_key_env` in their `llm.providers` block entirely; the
check is skipped and genai routes to `http://localhost:11434/` from the
model name. Provider, model, and API key env var are all driven by the
`llm` block in `inkhaven.hjson`.

**Continuous chat.** Each submitted query plus its assistant response is
appended to the chat history and replayed back on the next prompt. The AI
pane title shows the current `· N turn(s)`. Press `F9` (or `Ctrl+B` then
`C`) to clear it.

---

## 7. Search results overlay

Floating yellow panel rendered over the body when a search has run. Top line
shows `Results for `<query>` (N)`; each result occupies three rows
(score+kind+path, title, snippet).

Keys are routed to this overlay implicitly while it is open and the Search
bar is focused (see §5). The pane's own keys are:

| Key                  | Action                                                      |
| -------------------- | ----------------------------------------------------------- |
| `↑` / `↓`            | Move result cursor.                                         |
| `Enter`              | Open the highlighted result.                                |
| `Esc`                | Close the overlay (Search bar stays focused).               |
| Typing               | Closes the overlay and continues editing the query.         |

---

## 8. Prompt picker overlay

Floating magenta panel anchored just above the AI prompt bar. Two sources
are merged, in this order:

1. **System prompts** from `prompts.hjson` — well-known templates that ship
   with the project. Shown with a cyan `[ system ]` chip.
2. **Book prompts** — every paragraph nested under the **Prompts** system
   book. The paragraph's slug supplies the `/name` identifier and the
   title supplies the description. Body is the template. Shown with a
   green `[ book ]` chip.

A name or description that contains the text after `/` in the bar (case-
insensitive) is included. Filter updates live as you type.

Routed to the AI prompt bar (§6) — the picker has no separate focus.

| Key                  | Action                                                      |
| -------------------- | ----------------------------------------------------------- |
| `↑` / `↓`            | Move selection.                                             |
| `Enter` or `Tab`     | Expand the selected prompt's template into the buffer.      |
| `Esc`                | Close the picker without expanding.                         |
| Backspace / Delete   | Modify the filter; picker re-filters live.                  |

The leading `= Title` Typst heading is stripped from book-prompt bodies on
expansion so it doesn't end up in the LLM prompt — the heading is editor
chrome, not prose. `{{selection}}` / `{{context}}` substitutions in the
expanded body still fire for both sources.

A direct `/name` typed into the AI prompt bar and submitted with `Enter`
(no picker open) is also resolved against both sources, system-first.

---

## 9. Add modal

Triggered by `Ctrl+B` followed by `B`/`C`/`S`/`P` (or by the Tree pane's plain-letter shortcuts, §2.2). Green-bordered floating box.

```
┌── Add chapter ──────────────────────────────────┐
│  Parent: midnight-library                       │
│  Title : My chapter title│                      │
│                                                 │
│  Enter to confirm · Esc to cancel               │
└─────────────────────────────────────────────────┘
```

| Key                          | Action                                              |
| ---------------------------- | --------------------------------------------------- |
| any printable char (no Ctrl/Alt) | Insert into title buffer.                       |
| `Backspace`                  | Delete previous char.                               |
| `Delete`                     | Delete char at cursor.                              |
| `←` / `→` / `Home` / `End`   | Cursor navigation in the title buffer.              |
| `Enter`                      | Commit: derives slug, creates filesystem entry, inserts bdslib record, reloads tree, moves tree cursor to the new node. |
| `Esc`                        | Cancel without creating anything.                   |
| `Ctrl+Q`                     | Hard quit (modal does not absorb this).             |

Empty title shows a status hint and keeps the modal open. Validation errors
(e.g. trying to add a subchapter under a paragraph) close the modal and
display the error in the status line.

---

## 10. Delete confirm modal

Triggered by `Ctrl+B` then `D` (or the Tree pane's `D`/`-` shortcuts). Red-bordered floating box. Shows the kind,
title, and descendant count.

```
┌── Confirm delete ───────────────────────────────┐
│  Delete chapter `Storm` and 4 descendants?      │
│                                                 │
│  Removes files from disk AND records from bdslib│
│  y / Enter to confirm · n / Esc to cancel       │
└─────────────────────────────────────────────────┘
```

| Key                  | Action                                                      |
| -------------------- | ----------------------------------------------------------- |
| `y` / `Y` / `Enter`  | Confirm. fs subtree removed, bdslib records deleted, tree reloads, cursor lands on the deleted node's parent (or stays put if parent vanished too — i.e. you deleted a book). |
| `n` / `N` / `Esc`    | Cancel.                                                     |
| `Ctrl+Q`             | Hard quit.                                                  |

If the open paragraph is inside the deleted subtree, the editor closes too.

---

## 11. Configurable bindings (HJSON)

The `keys` block in `inkhaven.hjson` accepts the chord strings below. Parser
recognizes:

- **modifiers**: `Ctrl` (or `Control`), `Shift`, `Alt` (or `Meta` / `Option`), `Super` (or `Cmd` / `Command`)
- **named keys**: `Tab`, `Enter` / `Return`, `Esc` / `Escape`, `Space`, `Backspace`, `Delete` (or `Del`), `Insert` (or `Ins`), `Home`, `End`, `PageUp` (or `PgUp`), `PageDown` (or `PgDown` / `PgDn`), `Up`, `Down`, `Left`, `Right`, `F1` through `F24`
- **single characters**: any printable ASCII character

Modifiers are case-insensitive; named keys are case-insensitive; single-letter
chars are normalized (Ctrl+s, Ctrl+S, and Ctrl+Shift+S all parse and match
the same way — useful because terminals vary in how they report case with
modifiers).

Defaults shipped in `assets/default_project.hjson`:

```hjson
keys: {
  save:             Ctrl+s
  search:           Ctrl+/
  ai_prompt:        Ctrl+i
  next_pane:        Tab
  prev_pane:        Shift+Tab
  page_up:          PageUp
  page_down:        PageDown
  meta_prefix:      Ctrl+b           // chord prefix for tree / editor / AI actions
  bund_prefix:      Ctrl+z           // chord prefix for Bund scripting (1.2+)
  view_prefix:      Ctrl+v           // chord prefix for view sub-chords (1.2.4+)
  bindings:         []               // user overlay; see KEYS_REASSIGNMENT.md
}

editor: {
  // ...
  autosave_seconds: 5      // idle-trigger save in editor; 0 disables
}

// Background flush interval. 0 disables.
sync_interval_seconds: 600
```

**Rebinding sub-chords** (the letters under `Ctrl+B …`, `Ctrl+Z …`,
and `Ctrl+V …`) went data-driven in 1.2 — list overrides in
`keys.bindings` or, at runtime, via the `ink.key.*` Bund stdlib.
The full action table and both rebinding channels are documented
in [`KEYS_REASSIGNMENT.md`](KEYS_REASSIGNMENT.md).

**F-keys in the binding table (1.2.4)** — F1 through F10 and the
Shift-F variants migrated from hardcoded matches into
`Layer::TopLevel`. HJSON overlays accept single-token chords:

```hjson
keys: {
  bindings: [
    { layer: "view_sub",  key: "P",  action: "view.fuzzy_paragraph_picker" }
    { layer: "top_level", key: "F7", action: "view.add_link" }  // rebind F7
  ]
}
```

Non-configurable bindings (the editor's modern shortcut overrides, the
AI-action `r/i/t/b/c` keys, the modal `y/n` confirmations, and
`Ctrl+Q` hard-quit) remain hard-coded.

---

## 12. File picker dialog (F3)

Tree-style filesystem browser overlay, rooted at the shell's current working
directory. Same navigation in both contexts (Editor F3 and Tree F3); only
the Enter action differs.

```
┌── Pick file — /Users/you/some/dir ────────────────────────────────────────┐
│  ▸ 📁 books                                                               │
│  ▾ 📁 imports                                                             │
│      ▸ 📁 chapter-one                                                     │
│      ▸ 📁 chapter-two                                                     │
│        📄 preface.md                                                      │
│    📄 README.md                                                           │
│    📄 todo.txt                                                            │
│                                                                           │
│ ↑↓ navigate · → expand · ← collapse/parent · Enter pick · Esc cancel      │
└───────────────────────────────────────────────────────────────────────────┘
```

| Key                  | Action                                                      |
| -------------------- | ----------------------------------------------------------- |
| `↑` / `↓`            | Move cursor one entry up / down.                            |
| `PageUp` / `PageDown`| Jump by 10.                                                 |
| `Home` / `End`       | First / last entry.                                         |
| `→`                  | If cursor is on a directory: expand it (children inline immediately below). No-op for files or already-expanded directories. |
| `←`                  | If cursor is on an *expanded* directory: collapse it. Otherwise: move cursor to the parent entry. |
| `Enter`              | Commit (see action table below).                            |
| `Esc`                | Cancel; modal closes, nothing happens.                      |

**Sort order within each level**: directories first, then files, each
alphabetical. Hidden entries (names starting with `.`) are skipped.

**Action on Enter:**

| Context (F3 fired in) | Picked entry | What happens |
| --------------------- | ------------ | ------------ |
| Editor pane           | file         | Replaces the open paragraph's buffer with the file content. Marks the document dirty so the next save commits the change (a save will also re-create the snapshot baseline). |
| Editor pane           | directory    | Rejected — status hint says to pick a file. |
| Tree pane             | file         | Creates a new paragraph inserted **after** the cursor's same-kind ancestor (same as the `P` shortcut), titled from the filename, body = the file's bytes. |
| Tree pane             | directory    | **Recursive import**: the directory itself becomes a subchapter under the cursor's nearest valid host, every subdirectory becomes a nested subchapter, every file becomes a paragraph inside its containing subchapter. Sorted alphabetically with dirs-first. Requires `hierarchy.unbounded_subchapters: true` if the dir tree is deeper than two levels under a chapter. |

## 13. When chords don't reach Inkhaven

Some of the configured chords — especially `Ctrl+S`, `Ctrl+Q`, and the
`Ctrl+B` meta prefix — can be eaten by your terminal emulator, your shell,
or a terminal multiplexer (tmux / screen) before they reach Inkhaven. This
is not a bug in Inkhaven; it's a layer above us deciding the chord means
something else.

Common interceptors:

| Chord                  | Often intercepted by                                                |
| ---------------------- | ------------------------------------------------------------------- |
| `Ctrl+S`               | Terminal flow control (XOFF / freeze output). Run `stty -ixon` in your shell to disable. |
| `Ctrl+Q`               | Terminal flow control (XON). Same `stty -ixon` fix.                |
| `Ctrl+B`               | **tmux default prefix.** If you run inkhaven inside tmux, either rebind tmux's prefix (`set -g prefix C-a`) or remap inkhaven's `meta_prefix` in `inkhaven.hjson` to something tmux doesn't eat (e.g. `Ctrl+g`). |
| `Ctrl+Shift+Up/Down`   | Some terminals don't transmit the Ctrl modifier with arrow keys. Use the plain-letter shortcuts (`B`, `C`, `A`, `+`, `D`, `-`) in the Tree pane instead. |

**Workarounds Inkhaven provides:**

- The Tree pane has modifier-free `A` / `+` / `D` / `-` shortcuts (§2.2) for
  the most common add/delete operations.
- For reorder, both `Ctrl+B ↑/↓` (TUI) and `inkhaven mv ... up`
  /`down` (CLI) exist; use the CLI in a second pane if the TUI chord is
  blocked.
- Save is also reachable via the CLI: open the `.typ` in an external
  editor, save there, then `inkhaven reindex` from a shell.

**If your terminal swallows Ctrl+S**, the simplest fix is to add this to
your shell rc:

```bash
stty -ixon
```

Then `Ctrl+S` reaches applications normally.

## 14. Quick cheat sheet

For when you just want the high-level map:

```
GLOBAL          Ctrl+Q       quit (autosaves if dirty)
                Ctrl+1..5    focus Editor / Tree / AI / Search / AI prompt
                Tab/S-Tab    cycle Tree / Editor / AI panes
                Ctrl+/       focus search
                Ctrl+I       focus AI prompt
                Ctrl+S       save current paragraph
                Ctrl+B       meta prefix (table depends on focused pane):
                  Tree:       B/C/S/P add · D delete · ↑/↓ reorder
                  Editor:     S save · N snapshot · H history · L load · F split
                  AI:         C clear inference
                  Esc         cancel meta

TREE            ↑↓ Home End  navigate
                ←/→          collapse/expand branch (← steps to parent if not expanded)
                PgUp PgDn    by 10
                Enter        open paragraph (autosaves the previous one)
                F2           rename current node
                F3           file picker → insert file or import dir
                B            add book at root
                C            append chapter         (V = insert after current)
                A            append subchapter      (S = insert after current)
                +            append paragraph       (P = insert after current)
                D            delete branch          (or Ctrl+B then D)
                -            delete paragraph       (or Ctrl+B then D)
                Ctrl+B ↑/↓   reorder within siblings
                q            quit (autosaves if dirty)

EDITOR          arrows       move cursor
                Ctrl+arrows  word / top / bottom
                Shift+arrows extend linear selection
                Ctrl+U/Y     undo / redo
                Ctrl+K/C/P   cut / copy / paste (system clipboard)
                Ctrl+A       select all
                Ctrl+D       delete current line
                Ctrl+E       delete cursor → end of line
                Ctrl+W       delete cursor → start of line
                Alt+arrows   extend rectangular block selection
                Alt+C        copy rectangular block
                Ctrl+S       save + re-embed
                Ctrl+F       open find (regex)
                Ctrl+X       "repeat" (next match / replace+next, search active only)
                Ctrl+R       open find&replace · replace all (in replace mode)
                F3           load file → replaces buffer
                F4 / Ctrl+F4 toggle split / accept snapshot
                Ctrl+H/J     (split only) scroll lower pane up/down
                Ctrl+B H     open Quick reference overlay (global)
                F5           create snapshot
                F6           open snapshot picker
                Esc          clear search (if active) · else cycle to Tree
                (idle autosave fires after editor.autosave_seconds)
                (new text since last save is rendered bold)

AI              r            replace selection / doc
                i            insert at cursor
                t            prepend to top
                b            append to bottom
                c            copy to clipboard only

SEARCH BAR      Enter        run search (or open highlighted result)
                ↑↓           navigate results overlay
                Esc          close overlay → defocus

AI PROMPT       /            open prompt picker
                ↑↓           navigate picker
                Tab/Enter    expand template (in picker)
                Enter        send to LLM (outside picker)
                Esc          close picker → defocus

MODALS          Enter        confirm
                Esc          cancel
                y/n          (delete only)
```


---

## 1.2.5 + 1.2.6 — chord additions

Every chord introduced between the original document and the
1.2.6 release, organised by feature. This section is
maintained in delta-style so the canonical chord-by-pane
tables above stay readable; the entries below are the
new ground.

### Tag workflows (1.2.5+)

```
Ctrl+B ]   open the tag picker on the open paragraph
Ctrl+B }   open the project-wide tag-search picker
g (tree)   open the tag picker for the tree-cursor's paragraph
           (or every marked paragraph at once)
```

Inside the tag picker (`Ctrl+B ]` / `Ctrl+B }` / `g`):

```
Space      toggle the cursor tag
A          add a new tag (one-line prompt)
R          rename project-wide (1.2.6+; merges if name exists)
D          delete project-wide (confirm)
T          commit marked tags onto the target
Enter      Search mode: open the per-tag paragraph list
↑↓ Home/End navigate
```

### Story view (1.2.5–1.2.6)

```
Ctrl+V Shift+W   book story view (1.2.5+)
Ctrl+V w         paragraph mini story view (1.2.6+)
```

Inside either view:

```
S       save the rendered PNG to cwd
Esc     close
```

### Diagnostics (1.2.5–1.2.6)

```
F8                 (1.2.6+) typst diagnostics list modal
Ctrl+V N           next diagnostic in the open buffer
Ctrl+V Shift+N     previous
Ctrl+F12           (1.2.6+) AI explain the diagnostic at cursor
                   (was F11 pre-1.2.6 — macOS grabs F11)
```

Inside the F8 modal:

```
↑↓ Home/End    navigate
Enter          jump editor cursor to the diagnostic, close modal
Esc            close
```

### AI critique + diff modal (1.2.6+)

```
F12       AI critique (mode-aware: critique-edit / critique-changes)
```

Inside the AI diff-review modal (`r` / `g` in the AI pane
when `ai.diff_review_on_apply: true`):

```
a / A / Enter   accept — apply and refocus editor
r / R           reject — buffer unchanged
e / E           alias for `a`
↑ ↓ PgUp PgDn   scroll the diff
Home / End      jump top / bottom
Esc             same as reject
```

### Snapshot annotation prompt (1.2.6+)

```
F5    open the annotation prompt over the editor
```

Inside the prompt:

```
Type a line   build up the annotation
Enter         commit (empty = un-annotated)
Esc           cancel — no snapshot
```

### Render-preview zoom (1.2.6+)

Inside `Ctrl+V R`:

```
+ / =     zoom in  (multiply ticks/cell by 0.66)
- / _     zoom out (multiply by 1.5)
0         reset to 1.00×
```

### Story timeline (1.2.6 — opt-in)

```
Ctrl+V e         chronological event picker
Ctrl+V Shift+T   swim-lane timeline view
                 (lowercase Ctrl+V t stays bound to the
                  per-paragraph word-count target modal)
```

Inside Ctrl+V Shift+T:

```
← / →             scroll by ~10 cells
PgUp / PgDn       page by ~60 cells
+ / =             zoom in   (0.66× ticks/cell)
- / _             zoom out  (1.5×)
0                 reset zoom to 1.00×
Home / End        jump to first / last event in the visible set

u / U             up-scope    (subchapter → chapter → book)
d / D             open the inline descent picker
b / B             jump to book scope
p / P             toggle project overlay

Tab               cycle highlighted track
Enter             open the event closest to cursor
n / N             new event at cursor tick (annotation prompt)

y                 AI critique — current scope + current track
Y                 AI critique — current scope + all tracks
Ctrl+Y            AI critique — book scope (widens regardless)
Esc               close
```

Inside the descent picker (`d` from the swim-lane view):

```
↑ ↓ Home/End   navigate
Enter          descend into the selected scope
Esc            return to the same scope
```

Inside Ctrl+V e:

```
↑ ↓ Home/End   navigate
t / T          cycle the track filter (None → t0 → … → None)
Enter          open the event paragraph
Esc            close
```

## 1.2.7 — chord additions

### Paragraph undelete (1.2.7+)

```
Ctrl+B U       restore the most recently deleted paragraph
               (single-slot kill-ring; new uuid; paragraph links to
                old id stay broken).  Cleared by any branch
                delete or another single-¶ delete (the new one
                takes the slot).
```

See [`Tutorials/32-paragraph-undelete.md`](Tutorials/32-paragraph-undelete.md).

### Navigation history (1.2.7+)

```
Alt+←          step backward through visited paragraphs
Alt+→          step forward (after stepping back)
Ctrl+V Shift+P recent-paragraph picker (most-recent-first list,
               up to 32 entries, deduped against the previous)
```

The ring is in-memory only — restart clears it.  Opening a
new paragraph (via Enter / picker / paragraph link / undelete /
similar / timeline-Enter) clears the forward stack.

See [`Tutorials/33-navigation-history.md`](Tutorials/33-navigation-history.md).

### Mouse + external-change behaviour (1.2.7+)

```
Ctrl+Shift+M   toggle mouse capture on / off
               OFF lets the terminal handle drag-select +
                   native clipboard (Cmd/Ctrl+Shift+C).
               ON  restores click-to-focus + wheel-scroll
                   for the active pane.  Session-only;
                   defaults to ON.
```

External-change auto-reload has no chord — it runs passively
on every autosave tick.  Status bar reads:

```
↻ reloaded `<title>` — file changed on disk   (clean buffer)
⚠ `<title>` changed on disk while you have unsaved edits —
  Ctrl+S to overwrite the external change      (dirty buffer)
```

See [`Tutorials/34-mouse-and-external-changes.md`](Tutorials/34-mouse-and-external-changes.md).

### Timeline polish (1.2.7+)

Inside `Ctrl+V Shift+T` (swim-lane view) the navigation model
gained a second focus level mirroring the tree pane:

```
Focus = Track                         (default on open)
  Tab / Shift+Tab    cycle highlighted track
  Space              collapse / expand the focused track
  Enter              expand + drop focus into Event mode

Focus = Event
  Tab / Shift+Tab    cycle events of the expanded track in time
  Enter              open the linked-paragraphs picker for the
                     focused event
  Esc / Backspace    pop back to Track focus

Anywhere in swim lanes:
  ↑ / ↓              select previous / next event by start tick;
                     viewport auto-pans to show whole span
  F12                full-book AI health critique (same payload
                     as Ctrl+Y; alternative chord)
```

Session-restored state (per book, in `.session.json`):
collapsed tracks, expanded track, track highlight, zoom
(`ticks_per_cell`), scroll tick, cursor tick.

See [`Tutorials/31-story-timeline.md`](Tutorials/31-story-timeline.md) "1.2.7 polish".

### F8 from any pane (1.2.7+)

`F8` (typst-diagnostics list modal) now works from any pane,
not just the editor.  Opens against the most-recently-active
paragraph's cached diagnostics.

## 1.2.8 — chord additions

### Kill-ring picker (1.2.8+)

```
Ctrl+V Shift+U   open the kill-ring picker — list of the most
                 recent (up to 10) deleted paragraphs.  Enter
                 restores the cursor selection at its original
                 position.  Esc cancels.

Ctrl+B U         (existing) restores the front of the ring
                 without opening the picker.  Branch deletes
                 (chapter/book) no longer clear the ring —
                 older single-¶ entries remain valid recoveries.
```

### Hidden-character report (1.2.8+)

```
Ctrl+V h         one-shot scan of the open paragraph; status
                 bar reads e.g. "hidden chars: 3 tab(s), 5
                 line(s) with trailing whitespace, 0 CR(s)".
                 Clean buffers report "no tabs, trailing
                 whitespace, or CRs".  No buffer rewrite —
                 visual editor overlay is 1.2.9 work.
```

### Breadcrumb status-line chord (1.2.8+)

```
Ctrl+V Shift+S   print the cursor's hierarchy path on the
                 status bar (`Book ▸ Chapter ▸ Subchapter
                 ▸ Paragraph`).  Pane-aware: tree pane walks
                 from the tree cursor, editor pane walks from
                 the open paragraph.
```

### F1 query history (1.2.8+)

```
Inside the F1 Help-query input:
  Up             previous query (newest first); shell-style.
  Down           next; past the newest entry clears the input.
  Enter          submit; pushes the query onto the ring
                 (dedup against the immediate predecessor).
```

Session-only; F1 history is intentionally not persisted.

### Tag autocomplete (1.2.8+)

Inside the `A` (add-new-tag) prompt opened from `Ctrl+B ]`:

```
Tab              completes to the first existing project
                 tag whose name starts with the typed prefix
                 (case-insensitive).  No-op when no match.
```

### F6 annotation filter (1.2.8+)

Inside the F6 snapshot picker:

```
/                enter filter-focus mode — typed characters
                 narrow the visible list to snapshots whose
                 annotation contains the substring (case-
                 insensitive).
Esc (in filter)  exit filter focus (keeps the query).  Picker
                 returns to chord mode — Up/Down/Enter/D/V
                 again.
Backspace        edit the filter (in focus mode only).
Enter (in filter) commit filter (exits focus) — second Enter
                 loads the snapshot.
```

Filter resets each `F6` open — previous session's filter
doesn't haunt the next picker.

### Active-LLM chip in AI pane (1.2.8+)

The AI pane title always shows `· llm=<provider>` (the bound
`llm.default` from HJSON) so `Ctrl+B L` swap effect is visible
without opening `Ctrl+B I`.  In-flight provider fragment is
suppressed when it matches the bound default; surfaces only
when they diverge (user swapped default mid-stream).

### Shift+letter chord fix (1.2.8+)

Pre-existing bug — `Ctrl+V Shift+P` (recent-¶ picker) collapsed
onto `Ctrl+V p` (fuzzy picker) on terminals without the kitty
disambiguation protocol because the chord matcher required the
SHIFT modifier flag.  Now uppercase letters arriving without
SHIFT are treated as implicit-Shift — `Ctrl+V Shift+P`,
`Ctrl+V Shift+U`, `Ctrl+V Shift+S` all route to their distinct
actions.

### Mouse-capture default knob (1.2.8+)

```hjson
editor: {
  mouse_captured: true    // 1.2.8+ default
}
```

Setting `false` releases mouse capture at startup so the
terminal's native drag-select + system-clipboard copy work
without pressing `Ctrl+Shift+M` first.  The runtime
`Ctrl+Shift+M` toggle still flips state regardless.

### Embedded nushell pane (1.2.8+)

```
Ctrl+Z o         open / close the floating shell pane.
                 Engine state (env vars, defs) + turn
                 buffer + on-disk history all preserved
                 across close+reopen.
Ctrl+Z O         (Shift) drop the cached engine + in-
                 memory turn buffer and open fresh.  Does
                 NOT wipe `.inkhaven/shell_history.db`.
Ctrl+Z h         (inside the pane) toggle history-
                 selection mode.

Inside the pane (normal mode):
  Enter          run the line through the embedded
                 nu_engine; output + stderr land as a new
                 turn in the buffer.  Scroll is reset so
                 the new output is auto-visible.  Typing
                 `exit` (or `quit`) closes the pane
                 instead of forwarding to nu (whose
                 built-in `exit` would kill inkhaven
                 itself).
  Tab            autocomplete the token under the cursor.
                 In command position (start of line or
                 after `|` / `;`) matches against nu's
                 declared command set + executables on
                 $PATH; otherwise filesystem entries
                 under `$env.PWD`.  Single match →
                 splice + trailing space; multiple →
                 splice the longest common prefix and
                 surface the candidates on the status
                 line.

Line editing (readline-style):
  Ctrl+A / Ctrl+E    move cursor to start / end of line
  Ctrl+U             kill from cursor to start
  Ctrl+K             kill from cursor to end
  Ctrl+W             kill the word before the cursor
  Ctrl+Left/Right    move cursor by word
  Alt+B / Alt+F      move cursor by word (readline alias)
  Alt+Backspace      kill word backward
  Ctrl+L             clear scrollback (engine + history kept)
  Ctrl+D             clear input; if input is empty, close pane

Pane help:
  Ctrl+B H           open the OS Shell help overlay.
                     Any key dismisses it; pane state
                     (input, scroll, history) is preserved
                     unchanged underneath.
  ↑ / ↓          walk the per-project command history
                 ring (shell-style; Down past newest
                 clears the input).
  PgUp / PgDn    scroll the turn buffer up / down by 10
                 logical lines.  Title bar shows
                 `↑ scrolled` while above the newest turn.
  Shift+Home     jump to the top of the buffer.
  Shift+End      jump back to the newest output.
  Esc            close the pane (state preserved).

Inside selection mode:
  ↑ / ↓               walk the turn cursor.
  Home / End          jump to first / last turn AND scroll
                      the buffer to match.
  PgUp / PgDn         scroll independently of the cursor.
  c                   copy the highlighted turn's output
                      (stderr appended when failed).
  i                   insert the output into the editor
                      at cursor, wrapped in
                      `shell.insert_template`.  Pane
                      closes + editor refocuses.
  Esc                 exit selection (keep pane open).
  Ctrl+Z h            same — toggle back.
```

Pane gated on `shell.enabled = true` in HJSON (default
true).  See [`Tutorials/35-embedded-shell.md`](Tutorials/35-embedded-shell.md).

## `inkhaven prompts-editor` (1.2.10+)

Standalone four-pane TUI for editing
`<project>/prompts.hjson` — the prompt library
the main TUI's F7 / F12 / `Ctrl+B C / P / Y / G`
flows read from.  Launched outside the main TUI:

```
inkhaven prompts-editor -p <project-dir>
```

Layout: prompts list (left) · prompt editor
(centre, full tui-textarea chord set) · AI
response (right, display-only) · AI prompt input
(3-row bottom strip).

### Global chords

```
Ctrl+S              save library (atomic + .prompts-backups/ snapshot)
Ctrl+R              rollback picker (list, preview, restore, delete)
Ctrl+H / ?          focus-aware help pane
Tab / Shift+Tab     cycle pane focus (3 stops: list → editor → ai prompt → list)
Esc / Ctrl+Q        quit (confirm if unsaved)
```

### Prompts list pane

```
↑↓ / PgUp / PgDn / Home / End   navigate (cursor auto-loads into editor)
Enter                            load focused prompt + jump focus to editor
a                                add new prompt (name prompt → empty body)
d                                delete focused prompt (confirm modal)
                                   second `d` on a staged-deleted entry revokes
```

### Editor pane

Full tui-textarea defaults: arrows, Home/End,
PgUp/PgDn, Shift+arrows selection, Ctrl+A/E
start/end-of-line, Ctrl+B/F cursor left/right,
Ctrl+N/P up/down, Ctrl+K kill-to-end, Ctrl+W
delete-previous-word, Ctrl+U/Y undo/redo.

Plus one prompts-editor-only chord (meta-prefix
because terminals eat plain Ctrl+G as ASCII BEL):

```
Ctrl+B G            "Get" — insert latest AI pane response at the editor
                    cursor and jump focus to the editor.  Works from any
                    pane.  No-op (with status) when the response is
                    missing or still streaming.
```

### AI prompt input pane

```
type / Backspace / Delete         buffer edit
Left / Right / Home / End         cursor movement
Ctrl+A / Ctrl+E                   start / end of line (readline-style)
Up / Down                         in-session history walk (deduped)
Enter                             send for analysis
Ctrl+L                            clear input
Ctrl+K                            clear input + clear history
```

### Send semantics

The LLM acts as a prompt-engineering **reviewer**
— it does NOT execute the template.  Placeholders
like `{{selection}}` are NOT substituted; the
reviewer sees them as literal text and comments
on their use.  Pressing Enter sends:

  * `system` — fixed framing that explains the
    reviewer role + the placeholder conventions.
  * `user` — fenced template body verbatim +
    "Analysis request:" + your typed instruction
    (or an embedded default critique if empty).

Single-shot per send; multi-turn isn't planned
for this surface.

### Save chips

Top bar shows a red `N unsaved` chip when any
prompt is staged for change.  List rows carry
per-prompt markers:

  * `✱` unsaved edit (red bold)
  * `✚` newly-added (green bold)
  * `✗` staged for deletion (red strike-through)

### Rollback

`Ctrl+R` lists every
`.prompts-backups/prompts_YYYYMMDD_HHMMSS.hjson`
newest-first.  Inside the picker:

```
↑↓ / PgUp / PgDn / Home / End   navigate
Enter                            stage the backup as the working library
                                   (Ctrl+S commits)
v                                preview the file's contents
d                                delete with confirm
Esc                              back to the main view
```

The first Ctrl+S after a rollback writes a fresh
backup of the pre-rollback state, so the safety
chain stays intact.

See [`Tutorials/44-prompts-editor.md`](Tutorials/44-prompts-editor.md)
for the full workflow walkthrough.