projmux 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,63 +1,33 @@
1
1
  # Terminal Keybindings
2
2
 
3
- projmux is keyboard-driven. `projmux shell` writes a tmux config that owns
4
- every binding listed here, so the keys are live the moment the app starts —
5
- no extra setup in most terminals.
3
+ projmux is keyboard-driven, but the guaranteed launch contract is small:
4
+ fresh installs bind `Alt-1` through `Alt-5` as plain Meta sequences
5
+ (`M-1`..`M-5`, bytes `\x1b1`..`\x1b5`). Other actions remain discoverable in
6
+ Settings > Keybindings. Transport-dependent actions keep their built-in
7
+ transport default key, and Settings can add separate safe tmux plain aliases to
8
+ the same action. They are not installed as terminal-specific User-key
9
+ fallbacks. `UserN` and `CSI-u` are legacy/removal/unsupported targets, not
10
+ supported fallback guidance.
6
11
 
7
12
  The recommended path when a key does not fire:
8
13
 
9
- 1. Press the key inside `projmux shell` and see what works on your terminal
10
- out of the box ([Quick start](#quick-start-no-setup)).
11
- 2. If something is swallowed, open Settings > Keybindings and select the
12
- action to capture the key, or run [`projmux setup`](#diagnose-projmux-setup)
13
- outside tmux. Both paths tell you exactly which sequences reach the process
14
- and which the terminal is eating.
15
- 3. For terminals projmux knows how to configure, run
16
- [`projmux init [terminal]`](#auto-config-projmux-init) as the fallback:
17
- preview first, then add `--apply` to merge the right bindings into your
18
- terminal config with a timestamped backup.
19
- 4. If your terminal is not in the init list (or you prefer to edit configs by
20
- hand), use the [Manual fallback / advanced (CSI-u)](#manual-fallback--advanced-csi-u)
21
- section.
22
-
23
- To change a user-facing key, open Settings > Keybindings, select an action,
24
- then choose `Press new key`. Settings captures one keypress through the same
25
- TTY probe path used by `projmux setup`, writes safe tmux plain chords to
26
- `~/.config/projmux/keymap.toml`, regenerates `~/.config/projmux/tmux.conf`,
27
- and hot-reloads the live tmux config when Settings is running inside tmux.
28
- CSI-u/User-key captures are reported as terminal fallback delivery and do not
29
- need a keymap write. Raw sequences that cannot be represented safely as a tmux
30
- plain chord are not persisted; configure terminal fallback with `projmux init`
31
- instead.
32
-
33
- The Settings surface no longer exposes plain/prefix tmux chord fields. Legacy
34
- `prefix = ...` entries in an existing `keymap.toml` still parse during
35
- migration, but Settings does not write new prefix entries and generated tmux
36
- config no longer binds the old action prefix chords.
37
-
38
- > 한국어 요약: 대부분의 터미널은 `projmux shell` 만으로 아래 키가 바로 동작합니다.
39
- > 동작하지 않으면 `projmux setup` 으로 어떤 키가 막혔는지 진단하고,
40
- > `projmux init [terminal]` 으로 자동 설정하세요. 자동 설정이 없는 터미널은
41
- > 마지막 [Manual fallback / advanced (CSI-u)](#manual-fallback--advanced-csi-u)
42
- > 섹션의 매핑표를 참고해 직접 설정하면 됩니다.
43
-
44
- The tmux **prefix** is the upstream default `Ctrl-b`. Inside the running
45
- session, `Ctrl-b ?` lists every binding live.
46
-
47
- ## Quick start (no setup)
48
-
49
- These shortcuts are bound by projmux's generated tmux config. On terminals
50
- that pass `Alt-*` and `Ctrl-*` chords through to the running process
51
- unchanged (e.g. Linux gnome-terminal, foot, most stock Linux terminals),
52
- they work the moment you launch `projmux shell` — no terminal config
53
- required.
54
-
55
- If a key does nothing, jump to
56
- [Diagnose: `projmux setup`](#diagnose-projmux-setup) instead of guessing.
57
-
58
- ### Pickers
59
-
60
- These open the projmux popups and the sidebar. No prefix needed.
14
+ 1. Try the key inside `projmux shell`.
15
+ 2. Open Settings > Keybindings > Diagnostic, or run `projmux setup` outside
16
+ tmux, to see which bytes reach the process.
17
+ 3. For supported terminals, preview `projmux init [terminal]`; add `--apply`
18
+ only after reviewing the merge.
19
+ 4. For unsupported terminals, configure plain Meta bytes or add a tmux alias in
20
+ Settings > Keybindings.
21
+
22
+ Settings writes safe tmux plain chords to
23
+ `~/.config/projmux/keymap.toml`, regenerates
24
+ `~/.config/projmux/tmux.conf`, and hot-reloads the live tmux config when it is
25
+ running inside tmux. Raw escape payloads, Windows Terminal `sendInput` strings,
26
+ and tmux User keys are rejected as aliases.
27
+
28
+ ## Quick Start
29
+
30
+ These shortcuts are the guaranteed launch defaults. They need no tmux prefix.
61
31
 
62
32
  | Shortcut | Action |
63
33
  | --- | --- |
@@ -66,263 +36,164 @@ These open the projmux popups and the sidebar. No prefix needed.
66
36
  | `Alt-3` | Existing session popup |
67
37
  | `Alt-4` | AI split picker |
68
38
  | `Alt-5` | Settings |
69
- | `Alt-6` | Project switcher popup |
70
39
 
71
- ### Windows, panes, AI splits
40
+ The tmux prefix remains the upstream default `Ctrl-b`. Inside a running
41
+ session, `Ctrl-b ?` lists the live tmux bindings.
72
42
 
73
- | Shortcut | Action |
74
- | --- | --- |
75
- | `Ctrl-n` | New tmux window in the current pane's directory |
76
- | `Alt-Shift-Left` / `Alt-Shift-Right` | Previous / next window |
77
- | `Alt-Left` / `Right` / `Up` / `Down` | Move focus between panes |
78
- | `Alt-r` | Rename the current window |
79
-
80
- When a pane closes, projmux re-spreads remaining panes so the surviving split
81
- does not stretch lopsided.
82
-
83
- The AI split bindings use the configured default mode. For one-shot launches,
84
- run `projmux ai split --agent claude|codex|shell|selective right|down`. Extra
85
- args after `--` are appended to the resolved `claude` or `codex` executable for
86
- a managed pane. To register user-level skills, slash commands, editor actions,
87
- or launcher shortcuts that call those one-shot launches, see
88
- [AI Agent Shortcuts](ai-agent-shortcuts.md).
89
-
90
- ### Inside the pickers
91
-
92
- | Surface | Shortcut | Action |
93
- | --- | --- | --- |
94
- | Existing session popup | `Ctrl-X` | Kill the focused session and reopen the popup |
95
- | Existing session popup | `Left/Right` | Preview previous/next window |
96
- | Existing session popup | `Alt-Up/Alt-Down` | Preview previous/next pane |
97
- | Project switcher | `Ctrl-X` | Kill the focused existing session and reopen the picker |
98
- | Project switcher | `Alt-P` | Pin or unpin the focused directory |
99
-
100
- ### Conditional rename keys
101
-
102
- These two only fire if your terminal forwards CSI-u sequences for `Ctrl-M`
103
- and `Ctrl-Shift-M`. Without that wiring, plain `Ctrl-M` is just `Enter` and
104
- `Ctrl-Shift-M` typically does nothing. `projmux init` configures this for
105
- the terminals it supports; otherwise see
106
- [Manual fallback / advanced (CSI-u)](#manual-fallback--advanced-csi-u).
43
+ ## Discoverable Actions
107
44
 
108
- | Shortcut | Action |
45
+ Settings > Keybindings lists the full action catalogue. In particular, sidebar
46
+ keymap actions, pane switching, window switching, and rename actions remain
47
+ visible. Transport-dependent rows show the default transport key separately
48
+ from editable plain aliases.
49
+
50
+ Optional direct aliases can be added for actions such as:
51
+
52
+ | Canonical action | Meaning |
53
+ | --- | --- |
54
+ | `ProjectSwitcherToggle` | Project switcher popup |
55
+ | `new-window` | New tmux window in the current pane directory |
56
+ | `rename-window` | Rename the current tmux window |
57
+
58
+ Pane switching is catalogued as transport-dependent and the generated app tmux
59
+ config binds `M-Left`, `M-Right`, `M-Up`, and `M-Down` to `select-pane`
60
+ movement. Previous/next window remain transport-dependent and the generated app
61
+ tmux config binds `M-S-Left` / `M-S-Right` to the tmux window navigation
62
+ commands. These default transport keys are always rendered by projmux, because
63
+ delivery still depends on the terminal forwarding the modifier-arrow sequence.
64
+ Settings can add extra safe plain aliases, such as `M-[` for
65
+ `previous-window`; those aliases are saved to `keymap.toml` as `keys = [...]`
66
+ without storing or replacing the transport default. Rename actions no longer
67
+ have a built-in terminal fallback; use tmux's prefix rename flow or configure
68
+ an explicit safe alias where the action is editable.
69
+
70
+ ## Roadmap Requirements
71
+
72
+ Follow-up Phase 2 keeps Settings > Keybindings as a discovery surface. It must
73
+ continue to expose launch toggles, sidebar keymap actions, picker-local actions,
74
+ pane switching, window switching, and rename actions. Transport-dependent rows
75
+ should explain the default transport key and offer only additive safe plain
76
+ aliases; diagnostic-only rows should explain why they are not editable.
77
+
78
+ Follow-up Phase 3 removes the `UserN` / `CSI-u` route from the product model.
79
+ Windows Terminal and Ghostty-centered replacements should use plain
80
+ Meta/control chords or xterm modifier sequences where possible. If a key cannot
81
+ be represented that way, leave it as a non-editable unsupported or diagnostic
82
+ row instead of preserving a User-key or CSI-u fallback.
83
+
84
+ ## Picker Actions
85
+
86
+ Picker-internal commands are surface-scoped. The same physical key can be used
87
+ by different picker surfaces, while conflicts inside one surface are rejected.
88
+
89
+ | Surface action | Meaning |
109
90
  | --- | --- |
110
- | `Ctrl-M` | Rename the current window (terminal must send `User10`) |
111
- | `Ctrl-Shift-M` | Rename the current AI pane label / topic (terminal must send `User11`) |
91
+ | `Sidebar:KillSession` | Kill the focused existing session |
92
+ | `Sidebar:PinProject` | Pin or unpin the focused directory |
93
+ | `SessionPopup:KillSession` | Kill the focused session |
94
+ | `SessionPopup:CyclePreviewWindowPrev` / `SessionPopup:CyclePreviewWindowNext` | Preview windows |
95
+ | `SessionPopup:CyclePreviewPanePrev` / `SessionPopup:CyclePreviewPaneNext` | Preview panes |
96
+ | `NotifySidebar:Ack` / `NotifySidebar:ClearNonCritical` / `NotifySidebar:ClearAll` | Manage notifications |
112
97
 
113
- ## Diagnose: `projmux setup`
98
+ Runtime picker footers render key guides from the merged keymap, preferring the
99
+ default alias when it is present and otherwise using the first configured alias.
100
+
101
+ ## Keymap File
102
+
103
+ Settings writes the action-centered multi-alias schema:
104
+
105
+ ```toml
106
+ [bindings.ProjectSidebarToggle]
107
+ keys = ["M-1", "M-a"]
114
108
 
115
- Run `projmux setup` *outside* tmux (in your raw terminal window) to find out
116
- which projmux keys actually reach the process. The command auto-detects your
117
- terminal, then asks you to press each shortcut in turn and classifies the
118
- result:
109
+ [bindings.new-window]
110
+ keys = ["C-t"]
119
111
 
120
- The same diagnostic is available in-app at Settings > Keybindings > Diagnostic.
121
- The flow reads the controlling TTY directly, so it can probe a
122
- key while Settings itself is running inside tmux.
112
+ [bindings.previous-window]
113
+ keys = ["M-["] # additive alias; default M-S-Left is not stored here
114
+
115
+ [bindings."Sidebar:PinProject"]
116
+ keys = ["M-p", "p"]
117
+ ```
118
+
119
+ Legacy files using `plain = "M-a"` still read as a single-primary replacement
120
+ override. New writes use `keys = [...]`. `keys = []` disables the direct plain
121
+ aliases for that action. Legacy `prefix = ...` entries still parse during
122
+ migration and are preserved when Settings rewrites the file, but Settings does
123
+ not create new prefix entries.
124
+
125
+ Legacy popup IDs such as `sessionizer-sidebar`, `notify-sidebar`,
126
+ `session-popup`, `ai-split-picker-right`, `ai-split-settings`, and
127
+ `sessionizer` still read. Settings and new docs show the canonical toggle
128
+ names: `ProjectSidebarToggle`, `NotifySidebarToggle`, `SessionPopupToggle`,
129
+ `AISplitPickerToggle`, `SettingsToggle`, and `ProjectSwitcherToggle`.
130
+
131
+ ## Diagnose: `projmux setup`
132
+
133
+ Run `projmux setup` outside tmux to find out which projmux keys reach the raw
134
+ terminal. The same diagnostic is available in-app at Settings > Keybindings >
135
+ Diagnostic.
123
136
 
124
137
  | Status | Meaning |
125
138
  | --- | --- |
126
- | `OK plain` | The terminal forwarded the bare escape (e.g. `\x1b1` for `Alt-1`); tmux's plain bind handles it. |
127
- | `OK csi-u` | The terminal already forwards a `ESC [ NNNNu` sequence routed to a tmux `User*` key. You are fully wired. |
128
- | `MISS timeout` | No bytes arrived the terminal swallowed the key for its own action. |
129
- | `MISS unknown` | Something arrived, but it does not match either expected sequence — the terminal bound this combo to a different action. |
130
-
131
- The summary at the end lists the failing keys and a remediation hint
132
- tailored to the detected terminal (Ghostty, WezTerm, kitty, iTerm2,
133
- Alacritty, Windows Terminal, foot, VS Code, …). When projmux ships an init
134
- adapter for the terminal, the summary gives both the dry-run preview and the
135
- exact apply command, e.g. `projmux init ghostty --apply`. Settings >
136
- Keybindings shows the same delivery categories after a capture.
139
+ | `OK plain` | The terminal forwarded the expected bytes, such as `\x1b1` for `Alt-1`; tmux can bind this directly. |
140
+ | `MISS timeout` | No bytes arrived because the terminal swallowed the key. |
141
+ | `MISS unknown` | Bytes arrived, but they do not match the expected plain sequence. |
137
142
 
138
143
  Useful flags:
139
144
 
140
145
  ```sh
141
- projmux setup # interactive probe (default)
142
- projmux setup --timeout 10s # wait longer per key
143
- projmux setup --non-interactive # just print the expected key map and exit
146
+ projmux setup
147
+ projmux setup --timeout 10s
148
+ projmux setup --non-interactive
144
149
  ```
145
150
 
146
- `--non-interactive` is handy when you only want to see, on this machine and
147
- shell, which `plain` and `csi-u` byte sequences projmux is listening for —
148
- useful for hand-rolling a config in a terminal projmux does not yet know
149
- about.
151
+ ## Auto-Config: `projmux init`
150
152
 
151
- > 한국어 요약: `projmux setup` tmux *밖에서* 실행하면, 진단 대상 키를 차례로
152
- > 누르며 어떤 키가 plain/CSI-u 도달하는지, 어떤 키가 swallow 되는지 표로
153
- > 알려줍니다. 끝에서 터미널별 다음 단계를 제안합니다.
154
-
155
- ## Auto-config: `projmux init`
156
-
157
- `projmux init` is the supported fallback when `projmux setup` shows that a
158
- terminal swallows shortcuts projmux already binds in tmux. It merges the
159
- projmux CSI-u keybindings into a terminal emulator's config file. Default
160
- mode is **dry-run** — nothing is written until you pass `--apply`. Applying
161
- the merge always creates a timestamped backup of the previous file.
153
+ `projmux init` previews and optionally applies supported terminal mappings.
154
+ Default mode is dry-run; pass `--apply` to write changes with a timestamped
155
+ backup.
162
156
 
163
157
  ```sh
164
- projmux init # auto-detect + dry-run preview
165
- projmux init ghostty # explicit terminal, dry-run preview
166
- projmux init ghostty --apply # write changes (with .bak.<ts> backup)
167
- projmux init --config /path/to/file # bypass auto-detected paths
168
- projmux init --allow-symlink # opt in to merging through a symlink
169
- projmux init --dry-run # force dry-run even with no other flag
158
+ projmux init
159
+ projmux init ghostty
160
+ projmux init ghostty --apply
161
+ projmux init windows-terminal --apply
162
+ projmux init --config /path/to/file
163
+ projmux init --allow-symlink
170
164
  ```
171
165
 
172
- The merge is idempotent: bindings already pointing at the desired action are
173
- no-ops, missing bindings are appended in a managed block, and triggers
174
- already mapped to a *different* action are left untouched and reported as
175
- `skip-conflict` warnings projmux never silently overwrites your custom
176
- mappings. Re-running `projmux init --apply` after editing the file refreshes
177
- just the projmux-owned region.
178
-
179
- `projmux init` always uses the built-in terminal fallback map. To override the
180
- tmux chords rendered by `projmux tmux print-config`, `projmux tmux install`,
181
- or `projmux shell`, use Settings > Keybindings or edit
182
- `~/.config/projmux/keymap.toml`; see [Configuration](configuration.md#keymap-file).
183
-
184
- ### Supported terminals
185
-
186
- #### Ghostty
187
-
188
- - Detection: `TERM_PROGRAM=ghostty` or `GHOSTTY_RESOURCES_DIR` set.
189
- - Config candidates (resolved in this order):
190
- 1. `<config-dir>/ghostty/config` — canonical default
191
- 2. `<config-dir>/ghostty/config.ghostty` — common dotfiles convention
192
-
193
- `<config-dir>` honours `$XDG_CONFIG_HOME` first, then `$HOME/.config`.
194
- When both candidates exist, init refuses to guess and asks for
195
- `--config <path>` to disambiguate.
196
- - Idempotency marker: bindings live inside a managed block delimited by
197
-
198
- ```text
199
- # >>> projmux managed keybindings (do not edit between markers)
200
- ...
201
- # <<< projmux managed keybindings
202
- ```
203
-
204
- Edit anything *outside* those markers freely; init re-renders the inside
205
- on every `--apply`.
206
- - Symlink guard: if the resolved config path is a symlink (typical when
207
- dotfiles users symlink the terminal config into a tracked repo), init
208
- refuses by default to avoid silently mutating the symlink target. Pass
209
- `--allow-symlink` to opt in, or `--config <path>` to point at a
210
- non-symlinked file.
211
-
212
- #### Windows Terminal (including WSL)
213
-
214
- - Detection: `WT_SESSION` set (native Windows), or `WSL_DISTRO_NAME` /
215
- `WSL_INTEROP` set (running from inside WSL where the host terminal is
216
- Windows Terminal).
217
- - Config resolution:
218
- - Native Windows: `%LOCALAPPDATA%\Packages\Microsoft.WindowsTerminal_*\LocalState\settings.json`.
219
- - WSL: resolves `%LOCALAPPDATA%` through `cmd.exe /c echo %LOCALAPPDATA%`
220
- interop, then translates `C:\Users\...` into `/mnt/c/Users/...` so the
221
- Linux side can read/write the file directly.
222
- - Merge model: settings.json is JSONC (allows `//` and `/* */` comments,
223
- trailing commas). Init parses, locates `actions[]` and `keybindings[]`,
224
- and merges entries identified by the `User.projmux*` ID prefix. Anything
225
- outside that prefix is preserved verbatim, including comments and
226
- formatting where reasonable.
227
- - Conflicts: a key already mapped to a non-projmux action is skipped with a
228
- warning, same as Ghostty.
229
-
230
- ### Reading dry-run output
231
-
232
- Each line of the dry-run plan prefixes the action with one of:
233
-
234
- | Prefix | Meaning |
235
- | --- | --- |
236
- | `+` | New binding will be added. |
237
- | `=` | Binding already matches; no change. |
238
- | `!` | Trigger is already mapped to a *different* action; will be skipped. Resolve manually before applying. |
239
-
240
- The trailing `note:` lines flag whether the config file does not yet exist
241
- (it will be created), and the final line tells you whether you still need
242
- `--apply` or whether the file is already up to date.
243
-
244
- > 한국어 요약: `projmux init` 은 dry-run 이 기본이고 `--apply` 시 timestamped
245
- > 백업 후 머지합니다. Ghostty 는 managed 블록 마커로 idempotent, 두 표준
246
- > path 모두 인식하며 symlink 는 default 거절. Windows Terminal 은 WSL interop
247
- > 으로 `/mnt/c/...` 경로까지 알아서 풀고, JSONC 형식의 `settings.json` 에
248
- > `User.projmux*` prefix 로 머지합니다. 사용자가 같은 키를 다른 액션에 매핑한
249
- > 경우 skip + warning.
250
-
251
- ## Manual fallback / advanced (CSI-u)
252
-
253
- Use this section when:
254
-
255
- - your terminal is not yet supported by `projmux init`, or
256
- - you prefer to manage the config by hand, or
257
- - `projmux setup` shows specific keys still being swallowed and you want to
258
- redirect just those.
259
-
260
- The fix is to map the swallowed keystroke to a CSI-u sequence the terminal
261
- forwards to tmux unchanged. projmux binds CSI-u escapes to tmux's
262
- `User0`–`User11` keys, so once the terminal forwards the sequence the
263
- action fires.
264
-
265
- ### CSI-u Map
266
-
267
- | CSI-u sequence | tmux key | Action |
268
- | --- | --- | --- |
269
- | `ESC [ 9001 u` | `User0` | Open AI split to the right |
270
- | `ESC [ 9002 u` | `User1` | Open AI split below |
271
- | `ESC [ 9003 u` | `User2` | Notify sidebar |
272
- | `ESC [ 9004 u` | `User3` | Existing session popup |
273
- | `ESC [ 9005 u` | `User4` | Project sidebar |
274
- | `ESC [ 9006 u` | `User5` | AI split picker |
275
- | `ESC [ 9007 u` | `User6` | Settings |
276
- | `ESC [ 9008 u` | `User7` | New tmux window in the current pane directory |
277
- | `ESC [ 9011 u` | `User10` | Rename the current tmux window |
278
- | `ESC [ 9012 u` | `User11` | Rename the current tmux pane label |
279
- | `ESC [ 9013 u` | `User12` | Project switcher popup |
280
-
281
- > Previous/Next window (Alt-Shift-Left/Right) intentionally **do not** use a
282
- > CSI-u detour — both projmux and modern terminals already agree on the
283
- > xterm-standard modifier sequence (`\x1b[1;4D` / `\x1b[1;4C`), so tmux binds
284
- > directly to `M-S-Left` / `M-S-Right`. This keeps the popup-side chord
285
- > handler and the tmux root binding consuming the same sequence.
286
-
287
- ### Ghostty (manual)
288
-
289
- `projmux init ghostty` is the maintained path. The block below is what the
290
- managed region in `~/.config/ghostty/config` ends up looking like — useful
291
- if you want to author it by hand or vet what init produces. Ghostty
292
- keybinds use `keybind = trigger=action`; the `csi:` action sends a CSI
293
- sequence without the leading `ESC [` bytes.
166
+ The merge is idempotent: matching bindings are no-ops, missing bindings are
167
+ added, and keys already mapped to a different user action are skipped with a
168
+ warning. `projmux init` does not read `keymap.toml`; direct tmux aliases still
169
+ belong in Settings > Keybindings or the keymap file.
170
+
171
+ ### Ghostty
172
+
173
+ `projmux init ghostty` emits plain Meta bytes for `Alt-1` through `Alt-5` in
174
+ the managed block:
294
175
 
295
176
  ```text
296
- keybind = alt+1=csi:9005u
297
- keybind = alt+2=csi:9003u
298
- keybind = alt+3=csi:9004u
299
- keybind = alt+4=csi:9006u
300
- keybind = alt+5=csi:9007u
301
- keybind = alt+6=csi:9013u
302
-
303
- keybind = ctrl+shift+r=csi:9001u
304
- keybind = ctrl+shift+l=csi:9002u
305
-
306
- keybind = ctrl+shift+n=csi:9008u
307
- keybind = ctrl+m=csi:9011u
308
- keybind = ctrl+shift+m=csi:9012u
177
+ # >>> projmux managed keybindings (do not edit between markers)
178
+ keybind = alt+1=text:\x1b1
179
+ keybind = alt+2=text:\x1b2
180
+ keybind = alt+3=text:\x1b3
181
+ keybind = alt+4=text:\x1b4
182
+ keybind = alt+5=text:\x1b5
183
+ # <<< projmux managed keybindings
309
184
  ```
310
185
 
311
- `Alt-Shift-Left` and `Alt-Shift-Right` are intentionally absent: Ghostty
312
- already emits the xterm-standard `\x1b[1;4D` / `\x1b[1;4C` sequences for
313
- those chords, which tmux now binds directly. Adding a `csi:9009u` /
314
- `csi:9010u` override would re-introduce the detour that hid the chord from
315
- projmux popup handlers.
316
-
317
- Reload Ghostty or restart the terminal after changing the config.
186
+ Config candidates are `<config-dir>/ghostty/config` and
187
+ `<config-dir>/ghostty/config.ghostty`, with `$XDG_CONFIG_HOME` preferred over
188
+ `$HOME/.config`. If both exist, pass `--config <path>`. Symlinked configs are
189
+ refused by default; pass `--allow-symlink` to write through the link.
318
190
 
319
- ### Windows Terminal (manual)
191
+ ### Windows Terminal
320
192
 
321
- `projmux init windows-terminal` (which also covers WSL) is the maintained
322
- path. Use the snippet below to author `settings.json` by hand. Add
323
- `sendInput` actions and bind them from `keybindings`. Windows Terminal
324
- works well with plain tmux escape sequences for the default `Alt`
325
- shortcuts, while the split actions can send tmux prefix sequences directly. Escape bytes should be written as `\u001b`.
193
+ `projmux init windows-terminal` merges `sendInput` actions identified by the
194
+ `User.projmux*` ID prefix. The generated inputs use plain Meta bytes, tmux
195
+ prefix sequences for split actions, and xterm modifier-arrow sequences for
196
+ previous/next window:
326
197
 
327
198
  ```json
328
199
  {
@@ -332,35 +203,14 @@ shortcuts, while the split actions can send tmux prefix sequences directly. Esca
332
203
  { "command": { "action": "sendInput", "input": "\u001b3" }, "id": "User.projmuxSessions" },
333
204
  { "command": { "action": "sendInput", "input": "\u001b4" }, "id": "User.projmuxAIPicker" },
334
205
  { "command": { "action": "sendInput", "input": "\u001b5" }, "id": "User.projmuxSettings" },
335
- { "command": { "action": "sendInput", "input": "\u001b6" }, "id": "User.projmuxSwitch" },
336
206
  { "command": { "action": "sendInput", "input": "\u0002r" }, "id": "User.projmuxAISplitRight" },
337
207
  { "command": { "action": "sendInput", "input": "\u0002l" }, "id": "User.projmuxAISplitDown" },
338
208
  { "command": { "action": "sendInput", "input": "\u000e" }, "id": "User.projmuxNewWindow" },
339
209
  { "command": { "action": "sendInput", "input": "\u001b[1;4D" }, "id": "User.projmuxPrevWindow" },
340
- { "command": { "action": "sendInput", "input": "\u001b[1;4C" }, "id": "User.projmuxNextWindow" },
341
- { "command": { "action": "sendInput", "input": "\u001b[9011u" }, "id": "User.projmuxRenameWindow" },
342
- { "command": { "action": "sendInput", "input": "\u001b[9012u" }, "id": "User.projmuxRenamePane" }
343
- ],
344
- "keybindings": [
345
- { "id": "User.projmuxSidebar", "keys": "alt+1" },
346
- { "id": "User.projmuxNotifySidebar", "keys": "alt+2" },
347
- { "id": "User.projmuxSessions", "keys": "alt+3" },
348
- { "id": "User.projmuxAIPicker", "keys": "alt+4" },
349
- { "id": "User.projmuxSettings", "keys": "alt+5" },
350
- { "id": "User.projmuxSwitch", "keys": "alt+6" },
351
- { "id": "User.projmuxAISplitRight", "keys": "ctrl+shift+r" },
352
- { "id": "User.projmuxAISplitDown", "keys": "ctrl+shift+l" },
353
- { "id": "User.projmuxNewWindow", "keys": "ctrl+n" },
354
- { "id": "User.projmuxPrevWindow", "keys": "alt+shift+left" },
355
- { "id": "User.projmuxNextWindow", "keys": "alt+shift+right" },
356
- { "id": "User.projmuxRenameWindow", "keys": "ctrl+m" },
357
- { "id": "User.projmuxRenamePane", "keys": "ctrl+shift+m" }
210
+ { "command": { "action": "sendInput", "input": "\u001b[1;4C" }, "id": "User.projmuxNextWindow" }
358
211
  ]
359
212
  }
360
213
  ```
361
214
 
362
- This example matches the default projmux app shortcuts without depending on
363
- CSI-u support from Windows Terminal for the `Alt` shortcuts. `Ctrl-M` and
364
- `Ctrl-Shift-M` still use CSI-u so tmux can distinguish rename commands from
365
- plain Enter. If a key is already bound by Windows Terminal, remove or change
366
- the conflicting `keybindings` entry before adding the projmux binding.
215
+ Windows Terminal config is JSONC; comments and unrelated user entries are
216
+ preserved where possible. Conflicting user-owned keys are skipped.
@@ -16,6 +16,9 @@ The fzf compatibility surface for the native engine is tracked in
16
16
  execution, and result contracts, while moving visual composition into
17
17
  `projmuxpicker` so projmux can evolve a native picker design without coupling
18
18
  every visual tweak to the compatibility option/result shape.
19
+ Built-in fallback colors are centralized as semantic tokens in
20
+ `internal/theme/palette.go`; see [theme-palette.md](theme-palette.md) for the
21
+ current inventory and truecolor to tmux 256-color mapping policy.
19
22
  - `internal/ui/pickercompat` remains an internal compatibility mapper between
20
23
  the old option/result shape and the backend-neutral `picker.Options`
21
24
  contract. It is not a runtime backend. App code should
@@ -29,7 +32,7 @@ The fzf compatibility surface for the native engine is tracked in
29
32
  - The native picker supports ranked fuzzy search/filter, arrow-key selection in
30
33
  normal CSI and tmux application-cursor modes, Enter, Esc, Ctrl-C, Backspace,
31
34
  Ctrl-U, Ctrl-W, PageUp/PageDown, Home/End, modified CSI keys, custom expect
32
- keys such as Ctrl-X/Alt-P, printable expect keys such as notify `x`, control
35
+ keys such as Ctrl-X/Alt-P, printable expect keys such as notify `a`/`x`, control
33
36
  expect keys such as notify `Ctrl-X`, `start:pos(N)` initial focus, preview
34
37
  command output, preview cycle command bindings, and sidebar focus command
35
38
  bindings.
@@ -40,9 +43,10 @@ The fzf compatibility surface for the native engine is tracked in
40
43
  - Typed-query prompts support cursor-aware insertion/deletion with a visible
41
44
  prompt cursor, Left/Right, Ctrl-A/E, Delete, Backspace, Ctrl-U, and Ctrl-W
42
45
  for settings path entry.
43
- - The native key parser recognizes the app's CSI-u keybind-probe sequences
44
- such as `ESC [ 9005 u` for `Alt-1`, plus generic modified CSI-u forms such as
45
- `ESC [ 115 ; 7 u` for `Ctrl-Alt-S`.
46
+ - The native key parser keeps legacy parser-fixture coverage for app-specific
47
+ modified-key escapes, plus generic modified forms such as `ESC [ 115 ; 7 u`
48
+ for `Ctrl-Alt-S`. This is backend parser parity, not a product fallback
49
+ route.
46
50
  - Native interactive picker screens use an alternate screen lifecycle to better
47
51
  match fzf fullscreen behavior and restore the tmux pane after exit. Frame
48
52
  updates and screen exit both return to column 0 before emitting terminal
@@ -27,10 +27,10 @@ native picker engine and is not a public dependency-policy change.
27
27
  | selected multi-line marker | selected switch/session/notify cards | Covered for app multiline rows | native uses the same compact pointer-width red `▌` gutter as the first selected project line, and metadata lines align to the project-name column without the old deeper indent; `nativeContinuation`; `TestNativeInteractiveRendersSelectedMultilineContinuationMarker`; `TestInteractiveRowLinesUsesCompactSelectedMetaIndent`; `TestInteractiveRowLinesAlignsUnselectedMetaWithProjectName` |
28
28
  | fzf current row colors | simple and multi-line rows | Covered for app rows | `nativeCurrentStart`, `nativePointer`; pointer/continuation gutter tokens carry the current-row background; `TestNativeSelectedContentKeepsCurrentStyleAfterReset`; `TestNativeInteractiveUsesCurrentStyleForSimpleSelection` |
29
29
  | `--expect` keys | Enter/Ctrl-X/Alt-P/notify keys | Covered | `pickercompat.PickerOptions`; `TestNativeInteractiveSupportsCustomExpectKeys` |
30
- | printable expect keys | notify sidebar `x` ack | Covered | `TestNativeInteractiveSupportsPrintableExpectKeys`; Docker no-fzf e2e |
30
+ | printable expect keys | notify sidebar `a` ack and `x` non-critical clear | Covered | `TestNativeInteractiveSupportsPrintableExpectKeys`; Docker no-fzf e2e |
31
31
  | control expect keys | notify sidebar `Ctrl-X`, settings `Ctrl-Alt-S` close | Covered | `TestNativeInteractiveSupportsControlExpectKeys`; `TestNativeInteractiveSupportsControlAltCloseKeys` |
32
32
  | close `--bind key:abort` | Esc, Ctrl-C, Alt-N, Ctrl-Alt-S variants | Covered | `CloseActions`; `TestNativeRunnerUsesSharedCloseActions` |
33
- | terminal CSI-u key encoding | app keybind probe sequences, Ghostty/kitty-style modified keys | Covered | native handles app-specific Alt keys, generic modified letters/digits, and non-text keys such as Enter/Esc/Backspace/Tab; `TestNativeInteractiveSupportsCSIuAppKeyBindings` |
33
+ | terminal modified-key encoding | legacy parser fixtures, Ghostty/kitty-style modified keys | Covered | native handles app-specific parser fixtures, generic modified letters/digits, and non-text keys such as Enter/Esc/Backspace/Tab; this is backend parity, not product fallback guidance; `TestNativeInteractiveSupportsCSIuAppKeyBindings` |
34
34
  | `execute-silent(...)+refresh-preview` | switch/session preview cycling | Covered for command execution and rerender loop | `pickercompat.PickerOptions`/`pickercompat.OptionsFromPicker`; `TestNativeInteractiveRunsCustomActionCommandAndRefreshes`; `TestPickerOptionsMapsCompatBindingsToContractActions`; Docker no-fzf e2e sends `Right` and `Alt-Down` before selection |
35
35
  | `focus:execute-silent(...)` | switch sidebar focus | Covered | native renders the selection frame diff before running sidebar focus commands so movement stays visible before tmux focus side effects; `runNativeFocusAction`; `TestNativeInteractiveRunsFocusActionOnSelectionChange` |
36
36
  | `start:pos(N)` | switch sidebar initial row | Covered | `pickercompat.PickerOptions`/`pickercompat.OptionsFromPicker`; `TestPickerOptionsFromCompatPickerMapsStartPosToInitialIndex`; `TestPickerOptionsMapsCompatBindingsToContractActions` |
@@ -43,7 +43,7 @@ native picker engine and is not a public dependency-policy change.
43
43
  | query cursor editing | typed settings path prompts | Covered | Left/Right, Ctrl-A/E, Delete, Backspace, Ctrl-U/W query editing plus visible prompt cursor; `TestNativeInteractiveEditsTypedQueryAtCursor`; `TestNativeInteractiveSupportsQueryLineEditingKeys`; `TestNativeInteractiveCtrlUDeletesBeforeCursor`; `TestNativePromptLineRendersQueryCursor` |
44
44
  | terminal arrow key variants | interactive selection in tmux/docker | Covered | CSI, SS3/application cursor, modified CSI tests; up/down movement wraps at list boundaries while PageUp/PageDown and Home/End remain clamped or explicit jumps; app TTY `/dev/tty` fallback; raw TTY EOF polling keeps split ESC sequences from leaking into the query; `TestNativeInteractiveWrapsPreviousNavigationKeys`; `TestNativeInteractiveWrapsNextNavigationKeys`; `TestNativeInteractiveJumpNavigationRemainsClamped` |
45
45
  | mouse support | optional fzf mouse picker interaction | Partially covered | native enables SGR mouse reporting in interactive alternate-screen mode, primary mouse down focuses the clicked row, primary mouse up applies it, and wheel input moves selection; drag gestures remain outside this POC; `TestNativeInteractiveSelectsOnMouseRelease`; `TestNativeInteractiveMouseDownOnlyFocuses`; `TestNativeInteractiveIgnoresMouseReleaseBeforeDown`; `TestNativeInteractiveSupportsMouseWheelSelection`; Docker no-fzf e2e clicks the AI settings row under a PTY |
46
- | fzf navigation keys | interactive selection in searchable lists | Covered | native maps CSI-u `Ctrl-J` plus `Ctrl-N` to down and `Ctrl-P`/`Ctrl-K` to up when not claimed by a custom action; up/down-family movement is safe on empty lists; raw LF remains Enter for PTY compatibility; `TestNativeInteractiveSupportsFZFNavigationKeys`; `TestNativeInteractiveNavigationKeysAreNoopForEmptyList` |
46
+ | fzf navigation keys | interactive selection in searchable lists | Covered | native maps modified-key `Ctrl-J` plus `Ctrl-N` to down and `Ctrl-P`/`Ctrl-K` to up when not claimed by a custom action; up/down-family movement is safe on empty lists; raw LF remains Enter for PTY compatibility; `TestNativeInteractiveSupportsFZFNavigationKeys`; `TestNativeInteractiveNavigationKeysAreNoopForEmptyList` |
47
47
  | alternate-screen lifecycle | fzf fullscreen picker screen restore | Covered | native frame updates and screen exit return to column 0 before terminal control sequences, screen exit resets styles plus clears the alternate buffer from the home cursor before restore, and real TTY restores get a short settle window before caller handoff; `nativeScreenEnter`; `TestNativeInteractiveUsesAlternateScreen`; `TestRenderFullFrameUpdateAlwaysHomesAndWritesFrame` |
48
48
  | frame content width | fzf border inner width | Covered | `ContentLayout` uses the frame inner width so separators and rows reach the right border; `TestRendererContentLayoutUsesFrameInnerWidth` |
49
49
  | tmux popup frame interaction | native picker popups launched through `popup-toggle` | Covered for native backend popups | `popup-toggle` passes tmux `display-popup -B` when `PROJMUX_PICKER_BACKEND=native`, so the native picker owns the visible frame instead of double-drawing with the tmux popup border; native Alt-1 uses a compact native-only minimum while fzf keeps the previous project sidebar minimum; Alt-1/Alt-2 sidebar heights reserve two bottom statusbar rows; Alt-2 notify sidebar keeps the fzf-like `24%` / min `64` baseline; `TestAppRunTmuxPopupToggleUsesBorderlessPopupForNativeBackend`; `TestSessionizerSidebarWidthKeepsFZFMinimum`; `TestSidebarPopupHeightLeavesStatusbarRows`; `TestNotifySidebarWidthMatchesFZFBaseline`; `TestAppRunTmuxPopupToggleKeepsNotifySidebarFZFSizingForNative` |
@@ -108,7 +108,7 @@ contract; native popups still rely on the existing borderless tmux popup path.
108
108
  preview cursor, selects `bravo-web`, and asserts tmux reports the selected
109
109
  session's active target on the expected window with the expected pane path.
110
110
  - `notify sidebar`: native routing is unit-covered; Docker no-fzf e2e pushes a
111
- notification, presses printable expect key `x`, and verifies the row is acked.
111
+ notification, presses printable expect key `a`, and verifies the row is acked.
112
112
 
113
113
  ## Experimental Boundaries
114
114
 
@@ -93,12 +93,14 @@ repeatable filters. Without `--live`, this command reads only the queue and
93
93
  preserves the stable JSON array used by scripts.
94
94
 
95
95
  `--ui=sidebar` opens the notify queue as an interactive right-side list when
96
- run inside the tmux popup surface. Enter focuses the selected target pane and
97
- acks the row after focus succeeds. `x` acks the selected row in place, keeps
98
- the sidebar open, and refreshes the list from the queue while preserving the
99
- selection position where possible; acking the last remaining row renders the
100
- empty state until the popup is closed. `Ctrl-X` clears all rows via `notify ack
101
- --all` and exits. Rows are intentionally compact: the visible label keeps
96
+ run inside the tmux popup surface. Selecting a row focuses the selected target
97
+ pane and acks the row after focus succeeds. The surface actions
98
+ `NotifySidebar:Ack`, `NotifySidebar:ClearNonCritical`, and
99
+ `NotifySidebar:ClearAll` are internal picker actions; direct launch aliases
100
+ are edited in Settings, while internal picker aliases are adjusted in
101
+ `keymap.toml` when needed. Runtime footer key guides read the merged keymap
102
+ and show the default alias when present, otherwise the first configured alias,
103
+ so custom aliases do not make the UI stale. Rows are intentionally compact: the visible label keeps
102
104
  notification text first, then age, project, window, and pane metadata; hidden
103
105
  queue ids remain action values but the sidebar has no search input and
104
106
  intentionally does not expose a separate metadata detail view.