tix-tracker 0.12.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
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
# TUI key bindings

TUI key bindings are configured in the optional top-level
`tix_keybindings.toml` beside `tix.toml`. Each mode is independent. A missing or
empty file means every built-in default is active. Omitted modes and actions keep
their defaults, so the file contains only overrides.

```toml
# tix_keybindings.toml
[board]
new_issue = ["ctrl+n"]
help = ["f1", "?"]

[form]
submit = ["ctrl+s"]
cancel = ["esc"]
```

A binding is one terminal key chord, not a key sequence. Every action accepts one or more strings. A user override replaces that action's complete default alias list in that mode.

## Chord grammar

Names are case-insensitive. Supported named keys are `esc`, `enter`, `tab`, `backspace`, `delete`, `insert`, `home`, `end`, `left`, `right`, `up`, `down`, `pgup`, `pgdn`, `f1` through `f12`, and `space`. Use `shift+tab` for BackTab. Supported modifiers are `ctrl` (or `control`), `alt` (or `option`), `shift`, and `super` (or `cmd`). Printable single characters are also accepted, such as `n`, `?`, and `A`. Character bindings do not support modifier combinations.

Bindings must be non-empty. A chord can be assigned to only one action within
one mode. The same chord may be reused in different modes. Invalid names, empty
lists, and conflicts are rejected when the TUI loads `tix_keybindings.toml`, with
the mode and action in the error. If an older `tix.toml` still contains a
`[keybindings]` table, loading refuses it and names the new file.

## Text entry and mouse controls

Printable characters typed into forms, comments, assets, filters, planning fields, and inline-create prompts remain text input unless the exact character is configured as an action in that mode. Backspace and other inherent editing behavior are not commands. Mouse controls are not configurable: a plain left click on a card opens the issue **action menu**, a second click on the same card within the double-click window opens the detail pane, right-click opens the same menu plus a `Refresh board` footer, and drag-and-drop still moves or reorders. Scrolling and the menu's own click targets continue to invoke the same logical actions as the keyboard.

The filter modal's criterion rows are the exception: a row holds no typed value. `Enter`, `l`, `Right` or a click opens that criterion's value list (the calendar on a start/due date row), and characters typed while a list is open filter the list rather than the row. The filter applies as it is edited — closing the modal with `Esc` (or `a`) keeps the criteria, and `r` clears every one of them.

## Mode shell

The TUI's top-level modes are New Issue, Triage, Board, Cycles, Milestones, Repos,
Stats, Schedule, Portfolio, Library, and Config — listed here in the order the
vertical navigation draws them, which is **ascending key order**. Their switch keys
and the navigation-display toggle live in the `[shell]` table of
`tix_keybindings.toml`.

| Action | Default | TOML field |
| --- | --- | --- |
| switch to new issue | `F1` | `switch_to_new_issue` |
| switch to triage | `F2` | `switch_to_triage` |
| switch to board | `F3` | `switch_to_board` |
| switch to cycles | `F4` | `switch_to_cycle` |
| switch to milestones | `F5` | `switch_to_milestones` |
| switch to repos | `F6` | `switch_to_repos` |
| switch to stats | `F7` | `switch_to_stats` |
| switch to schedule | `F8` | `switch_to_schedule` |
| switch to portfolio | `F10` | `switch_to_portfolio` |
| switch to library | `F11` | `switch_to_library` |
| switch to config | `F12` | `switch_to_config` |
| toggle navigation | `Ctrl+n` | `toggle_navigation` |

> **Breaking (UI Overhaul V2):** the chords moved. New Issue was `F4` and is now
> `F1`; Board was `F1` and is now `F3`; Cycle was `F3` and is now `F4`. Triage
> (`F2`) and Config (`F12`) are unchanged, and Milestones (`F5`) is new. The
> action *names* are unchanged, so a `[shell]` override keyed by name
> keeps working.

A shell chord switches modes only when the active mode does not bind that key
itself. This keeps `F2` as "open picker" inside the form and the date pickers, for
example: the local binding always wins. `F6` opens the Repos mode, `F7` the Stats
page, `F8` the Schedule page, `F10` the Portfolio page and `F11` the Library page.

**`F9` is the one key still reserved**, for tix #1593's **Data** page
(import/export, archive, duplicates): it is bound in no context and does nothing.
The pages after it keep their own keys, so the vertical navigation jumps from `F8`
to `F10` rather than renumbering anything.

The vertical navigation is a **tree**: one trunk row per mode, with clickable
limbs indented beneath — one per configured board (Board; the active one bold),
per configured cycle (Cycles), per milestone (Milestones), and per
`[templates.<name>]` (New Issue). The remaining modes own no limbs. An empty group
draws nothing. In the compacted `Small` display the trunk shows its chord and each
limb its first two characters (`Default` → `De`); `Default` shows the full name.

```toml
[shell]
switch_to_milestones = ["f5", "M"]
toggle_navigation = ["ctrl+b"]
```

Overriding `switch_to_milestones` to `["f5", "M"]` gives the mode two chords; the
navigation entry shows both. Clearing a list leaves the mode reachable by click
only, with no chord on its row. An unknown action name or an unparsable chord is
a load error naming the key. The display toggle cycles the vertical navigation
`Off` → `Small` → `Default` (`Default` on launch).

## Refusals

**Every advertised key always produces a visible outcome.** If the action cannot
run, the active mode shows two lines: the reason in that mode's own vocabulary
(`no issue is selected`, `the Series tab has no rows`, `this is the last board`)
and the remedy that makes it possible (`move the cursor onto an issue`, `press n
to define a series`, `press n to add another board first`). The notice is drawn
inside the content area, so it appears on the page that raised it rather than on
the board underneath.

A refusal belongs to the keypress that produced it. Pressing another key consumes
it, and switching modes clears it, so a stale refusal never reappears over an
unrelated surface. Service and validation diagnostics may use their fuller
domain wording, but they follow the same rule: the reader is told what failed and
what to do next.

The headless action sweep drives every bound top-level action against an empty
fixture. An action must either change the frame or request an external action;
an ineligible action must additionally render a non-empty reason and remedy.

## Movement keys

Every browsing mode moves with `j`/`k` and `↑`/`↓`. Modes with a horizontal axis
also move with `h`/`l` and `←`/`→`:

| Mode | Vertical | Horizontal |
| --- | --- | --- |
| Board | `j`/`k`, `↑`/`↓` | `h`/`l`, `←`/`→` (columns) |
| Triage | `j`/`k`, `↑`/`↓` | `h`/`l`, `←`/`→` (control bar) |
| Cycles | `j`/`k`, `↑`/`↓` | `h`/`l`, `←`/`→` (group sections) |
| Milestones | `j`/`k`, `↑`/`↓` | — (registry list) |
| New Issue | `Tab`/`Shift+Tab`, `↑`/`↓` (fields) | — (a form: `h`/`l` are text) |
| Config | `j`/`k`, `↑`/`↓` (focused pane) | `h`/`l`, `←`/`→` (panes) |

Every browsing mode's `h`/`l` and `←`/`→` walk that mode's own horizontal
axis, clamped at the ends rather than wrapping:

- the Board's columns;
- the Triage inbox's control bar — queue, status, assignee, new — where `Enter`
  activates the focused control and `j`/`k` keep moving the queue cursor
  whichever control is focused;
- the Cycle listing's rendered group sections, landing on the first entry of
  the previous / next section;
- the Config mode's two panes — the section rail and the body — where `j`/`k`
  then move inside the focused pane: the selected section in the rail, the row
  cursor in the body.

New Issue is a form with no horizontal axis: `h`/`l` stay typeable text there,
and `Tab`/`Shift+Tab` (or `↑`/`↓`) change fields. The planning surface reached
with `m` uses `h`/`l` and `←`/`→` to switch its Cycles/Milestones segments; the
Config mode reaches those same sections from its rail, with `j`/`k`.

Triage and Cycle reach these keys differently. The Triage inbox's `h`/`l` are
**raw** keys, like its `j`/`k`: the mode accepts typed text (a decline reason, a
duplicate target), so a binding would rewrite the letters a user is typing. The
Cycle mode binds `move_left`/`move_right` to `←`/`h` and `→`/`l` (see the
`cycle` rows below), so its letters canonicalize to the arrows.

The **Milestones** mode (`F5`) is the Config surface pinned to the milestone
registry: `j`/`k` (or `↑`/`↓`) walk the milestones, `Enter`/`e` edit the selected
one, `n` creates, `x` deletes (with a confirmation naming referencing issues),
`m` opens the planning-management surface, `?` opens help, and `q`/`Esc` closes
back to the board. Its rows are the same rows the Config mode's Milestones
section draws, with the same per-milestone progress.

The **Config** mode's rail has five sections — Dashboards, Cycles, Milestones,
Types, and Custom attributes. The last two edit `custom_types` and
`[custom_attributes].keys`: `n` prompts for a new entry, `x` removes the selected
one, and both persist to `tix.toml`.

## Mouse hover

Hovering highlights without changing any selection:

- the vertical mode navigation — trunk rows and their limbs — in every mode;
- the Config mode's section rail;
- on the board, the hovered card **and** its whole column (the column border takes a distinct accent). The column is marked for any position inside it, so an **empty** column lights up too — hovering below the last card, or over a column with no cards at all, outlines that column.

## Board

| Action | Default | TOML field |
| --- | --- | --- |
| move up | `Up / k` | `move_up` |
| move down | `Down / j` | `move_down` |
| previous column | `Left / h` | `move_left` |
| next column | `Right / l` | `move_right` |
| next board | `Tab` | `next_board` |
| previous board | `Shift+Tab` | `previous_board` |
| open action menu | `Enter / click` | `open` |
| edit issue | `e` | `edit` |
| view issue | `Shift+V` | `view` |
| open agent | `Shift+A` | `agent` |
| toggle column | `c` | `toggle_column` |
| filter criteria | `f` | `filter_modal` |
| batch select | `Shift+B` | `bulk_modal` |
| triage inbox | `t` | `triage` |
| mentions | `i` | `open_mentions` |
| saved views | `v` | `views` |
| mark for batch | `Space` | `mark` |
| unmark for batch | `x` | `unmark` |
| clear marks | `Shift+X` | `clear_marks` |
| move to previous column | `<` | `move_previous` |
| move to next column | `>` | `move_next` |
| scroll up one page | `PgUp` | `scroll_up` |
| scroll down one page | `PgDn` | `scroll_down` |
| new issue | `n` | `new_issue` |
| clone issue | `Shift+C` | `clone_issue` |
| show archived issues | `a` | `toggle_archived` |
| toggle sub-issues | `u` | `toggle_children` |
| delete issue | `d` | `delete` |
| refresh | `r` | `refresh` |
| settings | `s` | `settings` |
| planning | `m / Shift+M` | `planning` |
| new board | `b` | `new_board` |
| help | `?` | `help` |
| quit | `q / Shift+Q / Esc` | `quit` |
| search | `/` | `text_filter` |
| reorder up | `Ctrl+Up / Shift+Up / Shift+K` | `reorder_up` |
| reorder down | `Ctrl+Down / Shift+Down / Shift+J` | `reorder_down` |

`Enter` (and a plain single click) opens the **action menu**, not the detail
pane directly. The menu lists, for the card under the cursor:

- `Move to <column>` for every configured column except the current one —
  hidden columns included, marked `(hidden)`;
- `Open detail` (preselected, so `Enter` `Enter` is the old `Enter`), `Edit in
  editor`, `View issue`, `Open agent`, `Clone into new issue`, `Open mentions…`,
  `Related issues…` / `Follow related`;
- `Comment`, `Attach asset`, `New relationship`, `Relationships…`, `Set / change
  / clear parent`, `Record health`, `Add time entry`;
- `Delete`, `Archive issue` (or `Restore issue` on an archived card);
- the batch entries while a selection exists, and the triage decisions while the
  card sits in the `[triage]` entry column.

Typing filters the list (literal substring first, then fuzzy with a `(fuzzy)`
marker); `Up`/`Down` move, `Enter` runs, `Esc` closes. In a submenu `Left` also
returns to the parent list. A second click on the
same card opens the detail pane. Destructive entries always confirm, and every
move still asks its own questions — a reason when leaving a terminal category,
a blocked-work acknowledgement, a WIP override reason, and the optional commit
for a completion column.

`view issue` is `Shift+V` on the board because `v` there opens the saved-views
modal; the plain `v` remains the read-only viewer in the **Detail** context.

### Batch selection

`Space` toggles the card under the cursor into the batch selection; it does not
open the batch modal. Marked cards get a dark-gray background with cyan text,
the bottom bar reads
`<n> marked · <key> clear · …` as soon as one card is marked, and `Enter` on a
marked card applies the chosen action to **every** marked issue for the actions
that support a batch: `Move to <column>`, `Comment`, `Record health`, `Set /
change / clear parent`, and `Archive`/`Restore`. `x` unmarks the card under the
cursor and `Shift+X` empties the selection. A batch asks each decision once (the
terminal-exit reason, the blocked-move acknowledgement, the WIP override, the
completion commit) and reports per-item outcomes; a refused item never rolls
back the items already applied. The selection is keyed by issue number, so it
survives a move and the column change that follows it. `Shift+B` still opens the
batch modal for field edits and relationships.

`mentions` opens the view of every `@mention` pending for the local user (see
[mentions.md](mentions.md)). Whenever there is at least one, the board draws an
extra line above the hint bar — `@ N unread mentions — press i to open` — and the
chord it names is read out of the effective bindings, so rebinding `open_mentions`
changes what the line advertises. The line is absent when nothing is pending.

Inside the mentions view the keys are **raw**, not bindings: `j`/`k` move, `Enter`
opens that issue at the mention, `r` marks it read, `i` (or `Esc`/`q`) leaves it
pending, and `Esc` closes. The same raw-key arrangement applies to the per-issue
notice shown when you open an issue that has unread mentions for you — `Enter`
jumps to the mention, `i` leaves it for later, `r` marks it read.

## Detail

| Action | Default | TOML field |
| --- | --- | --- |
| close | `q / Shift+Q / Esc` | `close` |
| edit issue | `e` | `edit` |
| view issue | `v` | `view` |
| scroll up one page | `PgUp` | `scroll_up` |
| scroll down one page | `PgDn` | `scroll_down` |
| scroll down one line | `j / Down` | `scroll_line_down` |
| scroll up one line | `k / Up` | `scroll_line_up` |
| scroll to top | `g` | `scroll_top` |
| scroll to bottom | `G` | `scroll_bottom` |
| help | `?` | `help` |
| comment | `c` | `comment` |
| attach asset | `a` | `asset` |
| set/change/clear parent | `Shift+P` | `edit_parent` |
| open related-issues picker | `r` | `related_picker` |
| follow selected related entry | `o` | `follow_related` |
| back to previous issue | `Backspace` | `nav_back` |

## Repos

The F6 Repos mode lists the child repositories this project coordinates (the
`[repos]` table in `tix.toml`) and the parent when one is known (`parent_repo`, or
the `TIX_PARENT_REPO` inherited from a parent that launched this session). `Enter`
runs `tix` inside the selected repository; exiting that session returns here.

| Action | Default | TOML field |
| --- | --- | --- |
| close | `q / Shift+Q / Esc` | `close` |
| move up | `Up / k` | `move_up` |
| move down | `Down / j` | `move_down` |
| run tix in the selected repository | `Enter` | `submit` |
| help | `?` | `help` |

## Schedule

The F8 Schedule page (tix #1593) is where recurring **series** live: their stable
id, display name, rule, lifecycle state, generation cursor and skipped dates, with
the keys that act on the selected one. Reading is always safe — detection runs on
start and on `r`, and it only *shows* what is due; the board's due warning is the
same detector's answer, and neither writes anything.

`Tab`/`Shift+Tab` switch between the page's three tabs: recurring series
(`[Series]`), repeating cycle schedules, and capacity, described below.

The occurrence keys act on the series' **due candidate**, the first date the shared
detector reports for it. A series with nothing due says so instead of inventing a
date, and `tix recurring` reaches the same services from the command line.

`n` opens the page's create form — `title`, `rule` and `anchor`, the three fields
`tix recurring create` requires — and its save runs that same service, so a series
defined here is the same write the command line makes. A blank title, an unknown
rule or a malformed anchor is refused by the service's own validation, with its
wording, on the form itself; nothing is written when the form opens, on a refusal,
or on `Esc`.

| Action | Default | TOML field |
| --- | --- | --- |
| close | `q / Shift+Q / Esc` | `close` |
| move up | `Up / k` | `move_up` |
| move down | `Down / j` | `move_down` |
| create a series | `n` | `series_new` |
| generate the due occurrence | `g` | `series_generate` |
| pause the series | `p` | `series_pause` |
| resume the series | `r` | `series_resume` |
| end the series | `x` | `series_end` |
| skip the due date | `s` | `series_skip` |
| defer the due date | `d` | `series_defer` |
| ignore the due date | `i` | `series_ignore` |
| help | `?` | `help` |

The due warning itself is an overlay over the board and takes `g` (generate),
`d` (defer), `i` (ignore), and `c`/`Esc` to close. Closing answers nothing: the
next refresh asks again, because a dismissal is not a decision.

### The Cycle schedules tab

The page's second tab (tix #181) lists the repeating cycle schedules
(`[cycle_schedules.<id>]`, `tix cycle schedule …`) and acts on the selected one,
rendering exactly the values `tix cycle schedule list`/`show` print — no TUI-only
numbers. Every mutation here is confirmed the same way the CLI requires `--confirm`:
`close`/`extend`/`skip` open a reason prompt that `Enter` confirms, and `Esc` cancels
and writes nothing. A key with no TOML field is read by the page's own raw layer,
so it is not rebindable.

| Action | Default | TOML field |
| --- | --- | --- |
| create a schedule | `n` | — (raw key) |
| edit the selected schedule | `E` | — (raw key) |
| generate the look-ahead | `g` | `series_generate` |
| close the current instance | `c` | — (raw key) |
| extend the current instance | `e` | — (raw key) |
| adjust future instances | `a` | — (raw key) |
| pause the schedule | `p` | `series_pause` |
| resume the schedule | `r` | `series_resume` |
| skip the current instance | `s` | `series_skip` |
| ignore a warning | `i` | `series_ignore` |
| move up / move down | `Up / k`, `Down / j` | `move_up` / `move_down` |
| help | `?` | `help` |

### The Capacity tab

The page's third tab (tix #182) renders the configured capacity scopes and the
shared calculation `tix capacity calculate` prints. It is read-only: capacity
configuration is changed through `tix capacity set`, and overflow is reported
rather than acting on issues. See [capacity.md](capacity.md) for the calculation,
units and immutable close evidence.

| Action | Default | TOML field |
| --- | --- | --- |
| close | `q / Shift+Q / Esc` | `close` |
| move up | `Up / k` | `move_up` |
| move down | `Down / j` | `move_down` |
| next tab | `Tab` | — (raw key) |
| previous tab | `Shift+Tab` | — (raw key) |
| help | `?` | `help` |

## Library

The F11 Library page (tix #1593) is the **reader** over the two registries a user
creates once and reuses: the saved views (`[views.<id>]`, `v` on the board) and the
stable-ID issue/form templates (`[issue_templates.<id>]`, `tix template`). `Tab`
switches between the two tabs; each row shows the record and the pane beside it
shows what that record actually holds (a view's id, scope, plan and whether it is
live on the board; a template's id, scope, defaults, prompts and references). An
empty tab says how to fill it instead of drawing a blank pane.

The page is read-only apart from `Enter` on a view row, which applies that view to
the board — the same operation as `tix view apply`, and equally read-only on
issues and configuration. Templates are listed and inspected here; creating from
one stays on `tix create --template <ID>`, the MCP `create_issue` tool, and the F1
form's template row.

| Action | Default | TOML field |
| --- | --- | --- |
| close | `q / Shift+Q / Esc` | `close` |
| next tab | `Tab / l / Right` | `next` |
| previous tab | `Shift+Tab / h / Left` | `previous` |
| move up | `Up / k` | `move_up` |
| move down | `Down / j` | `move_down` |
| apply the selected view | `Enter` | `submit` |
| help | `?` | `help` |

## Forms and prompts

| Mode | Action | Default | TOML field |
| --- | --- | --- | --- |
| `form` | cancel | `Esc` | `cancel` |
| `form` | submit | `Enter` | `submit` |
| `form` | next field/row | `Tab / Down` | `next` |
| `form` | previous field/row | `Shift+Tab / Up` | `previous` |
| `form` | open date/picker | `F2` | `open_picker` |
| `form` | open status/priority dropdown | `Enter` / `F2` (Status, Priority rows) | — row-local |
| `form` | toggle target | `Enter` (Target row) | — row-local |
| `form` | select target side | `Left` / `Right` (Target row) | — row-local |
| `form` | move insertion point | `Left` / `Right` (text rows) | — row-local |
| `form` | start / end of field | `Home` / `End`, `Ctrl-A` / `Ctrl-E` | — row-local |
| `form` | previous / next word | `Ctrl-Left` / `Ctrl-Right`, `Alt-Left` / `Alt-Right` | — row-local |
| `form` | delete before / at caret | `Backspace` / `Delete`, `Ctrl-H` / `Ctrl-D` | — row-local |
| `form` | delete word before | `Ctrl-W`, `Ctrl-Backspace`, `Alt-Backspace` | — row-local |
| `form` | delete to start / end | `Ctrl-U` / `Ctrl-K` | — row-local |
| `form` | open template picker | `Enter` / `F2` (Template row) | — row-local |
| `planning_form` | cancel | `Esc` | `cancel` |
| `planning_form` | submit | `Enter` | `submit` |
| `planning_form` | next field | `Tab / Down` | `next` |
| `planning_form` | previous field | `Shift+Tab / Up` | `previous` |
| `planning_form` | open date picker | `F2` | `open_picker` |
| `filter` | cancel | `Esc` | `cancel` |
| `related_picker` | next | `Down` | `next` |
| `related_picker` | previous | `Up` | `previous` |
| `related_picker` | select | `Enter` | `submit` |
| `related_picker` | cancel | `Esc` | `cancel` |
| `detail` | new relationship | `n` | `new_relationship` |
| `detail` | next relationship | `Tab` | `next_relationship` |
| `detail` | previous relationship | `Shift+Tab` | `previous_relationship` |
| `detail` | scroll down one line | `j / Down` | `scroll_line_down` |
| `detail` | scroll up one line | `k / Up` | `scroll_line_up` |
| `detail` | scroll to top | `g` | `scroll_top` |
| `detail` | scroll to bottom | `G` | `scroll_bottom` |
| `detail` | edit relationship | `Shift+E` | `edit_relationship` |
| `detail` | remove relationship | `d` | `remove_relationship` |
| `detail` | set/change/clear parent | `Shift+P` | `edit_parent` |
| `link_prompt` | submit | `Enter` | `submit` |
| `link_prompt` | cancel | `Esc` | `cancel` |
| `filter` | submit | `Enter` | `submit` |
| `move_into_done` | cancel | `Esc` | `cancel` |
| `move_into_done` | submit | `Enter` | `submit` |
| `comment` | cancel | `Esc` | `cancel` |
| `comment` | submit | `Enter` | `submit` |
| `asset` | cancel | `Esc` | `cancel` |
| `asset` | submit | `Enter` | `submit` |
| `settings_prompt` | cancel | `Esc / q / Shift+Q` | `cancel` |
| `settings_prompt` | submit | `Enter / y / Shift+Y` | `submit` |
| `planning_confirm` | confirm | `y / Shift+Y / Enter` | `confirm` |
| `planning_confirm` | cancel | `Esc` | `cancel` |

## Embedded controls

| Mode | Action | Default | TOML field |
| --- | --- | --- | --- |
| `rollover` | submit | `Enter` | `submit` |
| `rollover` | toggle choice | `Left / Right / Tab` | `previous` |
| `rollover` | cancel | `Esc` | `cancel` |
| `confirm_delete` | confirm | `y / Shift+Y` | `confirm` |
| `confirm_delete` | cancel | `Esc` | `cancel` |
| `help_search` | cancel (clears the query) | `Esc` | `cancel` |
| `help_search` | submit (keeps the query) | `Enter` | `submit` |
| `help` | search the keybindings | `/` | `text_filter` |
| `help` | cancel | `Esc / q / ?` | `cancel` |
| `help` | next row | `Down / j` | `next` |
| `help` | previous row | `Up / k` | `previous` |
| `help` | scroll down one page | `PgDn` | `scroll_down` |
| `help` | scroll up one page | `PgUp` | `scroll_up` |
| `help` | submit | `Enter` | `submit` |
| `context_menu` | cancel | `Esc / q / ?` | `cancel` |
| `context_menu` | next | `Down` | `next` |
| `context_menu` | previous | `Up` | `previous` |
| `context_menu` | submit | `Enter` | `submit` |
| `form_picker` | cancel | `Esc / F2` | `cancel` |
| `form_picker` | next | `Up` | `next` |
| `form_picker` | previous | `Down` | `previous` |
| `form_picker` | submit | `Enter` | `submit` |
| `form_picker` | inline create | `+ / Insert` | `create` |
| `date_picker` | move up | `Up / k` | `move_up` |
| `date_picker` | move down | `Down / j` | `move_down` |
| `date_picker` | previous day | `Left / h` | `move_left` |
| `date_picker` | next day | `Right / l` | `move_right` |
| `date_picker` | previous month | `PgUp` | `previous_month` |
| `date_picker` | next month | `PgDn` | `next_month` |
| `date_picker` | previous year | `[` | `previous_year` |
| `date_picker` | next year | `]` | `next_year` |
| `date_picker` | submit | `Enter` | `submit` |
| `date_picker` | clear | `Delete / c / Shift+C` | `reset` |
| `date_picker` | cancel | `Esc / q` | `cancel` |

## Modal management modes

| Mode | Action | Default | TOML field |
| --- | --- | --- | --- |
| `context_menu` | previous | `Up` | `previous` |
| `context_menu` | next | `Down` | `next` |
| `context_menu` | page up | `PgUp` | `scroll_up` |
| `context_menu` | page down | `PgDn` | `scroll_down` |
| `context_menu` | run the highlighted action | `Enter` | `submit` |
| `context_menu` | close (or leave a submenu) | `Esc` | `cancel` |
| `filter_modal` | next | `Tab / Down` | `next` |
| `filter_modal` | previous | `Shift+Tab / Up` | `previous` |
| `filter_modal` | reset | `r / Shift+R` | `reset` |
| `filter_modal` | apply and close | `a` | `submit` |
| `filter_modal` | apply and close | `Esc` | `cancel` |
| `filter_modal` | open row picker / date picker | `F2 / Enter / l / Right` | `open_picker` |
| `settings` | previous | `Up / k` | `move_up` |
| `settings` | next | `Down / j` | `move_down` |
| `settings` | back out one level | `Left / h` | `move_left` |
| `settings` | drill one level deeper | `Right / l` | `move_right` |
| `settings` | next board | `Tab / ]` | `next_board` |
| `settings` | previous board | `Shift+Tab / [` | `previous_board` |
| `settings` | step deeper / activate row | `Enter` | `submit` |
| `settings` | toggle | `Space` | `toggle` |
| `settings` | add column | `a` | `add_column` |
| `settings` | move selected column earlier | `Shift+K` | `move_column_up` |
| `settings` | move selected column later | `Shift+J` | `move_column_down` |
| `settings` | new board / new cycle / new milestone | `n` | `new_board` |
| `settings` | rename board / edit cycle / edit milestone | `e` | `edit_planning_item` |
| `settings` | delete board / delete cycle / delete milestone | `x / Shift+X` | `delete` |
| `settings` | close | `q / Shift+Q / Esc` | `close` |
| `cycle` | previous cycle | `Up / k` | `move_up` |
| `cycle` | next cycle | `Down / j` | `move_down` |
| `cycle` | previous group | `Left / h` | `move_left` |
| `cycle` | next group | `Right / l` | `move_right` |
| `cycle` | planning management | `m` | `planning` |
| `cycle` | help | `?` | `help` |
| `cycle` | close | `q / Shift+Q / Esc` | `close` |
| `planning_management` | previous | `Up / k` | `move_up` |
| `planning_management` | next | `Down / j` | `move_down` |
| `planning_management` | switch segment | `Tab` | `next` |
| `planning_management` | new | `n` | `new_planning_item` |
| `planning_management` | edit | `e` | `edit_planning_item` |
| `planning_management` | delete | `x / Shift+X` | `delete_planning_item` |
| `planning_management` | close | `q / Shift+Q / Esc` | `close` |
| `edit_repair` | apply the proposed fix | `a / y` | `confirm` |
| `edit_repair` | declare the key in tix.toml | `d / Shift+D` | `declare_key` |
| `edit_repair` | reopen the editor on the retained copy | `r / Shift+R` | `reedit` |
| `edit_repair` | acknowledge (keeps the copy) | `i / Shift+I / Enter / Esc` | `ignore` |
| `edit_repair` | discard the edit (asks again) | `x / Shift+X` | `discard` |
| `edit_conflict` | overwrite the file with this edit | `o / Shift+O / y / Shift+Y` | `confirm` |
| `edit_conflict` | keep the on-disk version | `a / Shift+A / Esc` | `ignore` |
| `edit_conflict` | compare in the editor | `c / Shift+C / r / Shift+R` | `reedit` |

The `edit_repair` modal is what an editor round trip raises when the saved file
has a problem — it will not parse, `tix lint` is unhappy about it, or the edit
lost the title. It lists every problem with its diagnostic and a proposal, and
names the retained copy (`tix-edit-<pid>-<number>.md` in the temp directory) so a
problem with no automatic fix can be corrected by hand.

- **`a`** (or `y`, because it is `confirm` everywhere else) applies the first
  proposed fix. For an undeclared top-level frontmatter key that fix is the
  declaration itself: the key goes into `[custom_attributes]` in `tix.toml` — see
  [custom frontmatter keys](for-users/storage.md#custom-frontmatter-keys). A problem with
  nothing tix can apply safely says so and closes.
- **`d`** declares the undeclared key explicitly, or says there is none.
- **`r`** reopens the editor on the retained copy; a file that still will not
  parse re-enters the prompt with fresh problems.
- **`i`/`Enter`/`Esc`** acknowledges. This **keeps** the retained copy — the
  status line names where — so a stray key never destroys an edit.
- **`x`** discards: the first press arms, the second removes the retained copy.
  This is the **only** thing that removes it.

Nothing is ever discarded automatically. Applying a fix operates on the retained
copy and re-parses it; a fix that does not make the file valid leaves the file
untouched and re-reports. Before any write that replaces the issue, the previous
content is saved beside it as `<name>.tixbak` and named in the status line, so
the version a write replaced is always recoverable.

The `edit_conflict` modal is what an editor round trip raises when the issue file
**changed while the editor had it open** — an agent writing through MCP, another
session, a repair. Nothing has been written at that point: the newer file is
untouched and the edit is still the `tix-edit-<pid>-<number>.md` copy in the temp
directory (`/tmp`, or `$TMPDIR`) that the prompt names. `o` (or `y`) overwrites
the newer file with the edit, `a`/`Esc` keeps the newer file and discards the
edit, and `c` (or `r`) reopens the editor on the temp copy so the two can be
compared; the config's `editor` command is resolved again for that second look.
**Esc is the safe default**: only the overwrite chord writes, so a concurrent
write is never clobbered by a keypress that was not a decision.

`settings` is the one mode where `Enter` is not a second `toggle` chord. `toggle`
is `Space` alone and `submit` is `Enter`, because the two do different things:
`Enter` steps from the section rail into the body and then activates the row,
while `Space` toggles the row under the cursor. A `[settings]`
override that lists `enter` under `toggle` therefore collides with `submit` and
is rejected at load — bind it to `submit` instead.

`filter`, `move_into_done`, `comment`, `asset`, `link_prompt` and `help_search` are
**free-text** modes: they read
arbitrary text into a buffer, including a filesystem path. `cancel` is bound to `Esc`
alone in those six, and not to `q`, so that every printable character can be typed.
A `q` binding there does not merely fail to type — it resolves to the cancel action's
canonical event, which leaves the mode, so everything typed afterwards is applied as
a command in the mode underneath. `q` still closes `help`, `context_menu`,
`settings`, `date_picker`, and the yes/no `blocked_move` confirmation, none of which
accept text. Override the table in `tix.toml` if you want a different trade.

## Startup tips

When a session reaches the TUI (and `tips_on_startup` is not `false` in
`tix.toml`), a "did you know?" modal opens on a random tip from the list compiled
into the binary. Its keys are **fixed** — the modal reads them raw, so a board
binding cannot rewrite them:

| Key | Action |
| --- | --- |
| `Right / l / n` | next tip |
| `Left / h / p` | previous tip |
| `r` | another random tip (never the one already shown) |
| `Enter / Esc / q` | close |
| `d` | turn the startup tips off (writes `tips_on_startup = false` to `tix.toml`) |

Press `d` and the modal stays open one last time with the way back: set
`tips_on_startup = true` (or delete the key) in `tix.toml`. The tips themselves
live in `src/templates/tips/` and are compiled into the binary with
`include_str!`.