projmux 0.6.7 → 0.7.1

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.
@@ -4,7 +4,7 @@ projmux is keyboard-driven, but the guaranteed launch contract is small:
4
4
  fresh installs bind `Alt-1` through `Alt-5` as plain Meta sequences
5
5
  (`M-1`..`M-5`, bytes `\x1b1`..`\x1b5`). Other actions remain discoverable in
6
6
  Settings > Keybindings. Transport-dependent actions keep their built-in
7
- transport default key, and Settings can add separate safe tmux plain aliases to
7
+ transport default key, and Settings can add separate safe tmux plain keys to
8
8
  the same action. They are not installed as terminal-specific User-key
9
9
  fallbacks. `UserN` and `CSI-u` are legacy/removal/unsupported targets, not
10
10
  supported fallback guidance.
@@ -15,14 +15,26 @@ The recommended path when a key does not fire:
15
15
  2. Run `projmux setup` outside tmux to see which bytes reach the process.
16
16
  3. For supported terminals, preview `projmux init [terminal]`; add `--apply`
17
17
  only after reviewing the merge.
18
- 4. For unsupported terminals, configure plain Meta bytes or add a tmux alias in
18
+ 4. For unsupported terminals, configure plain Meta bytes or add a custom key in
19
19
  Settings > Keybindings.
20
20
 
21
- Settings writes safe tmux plain chords to
22
- `~/.config/projmux/keymap.toml`, regenerates
23
- `~/.config/projmux/tmux.conf`, and hot-reloads the live tmux config when it is
24
- running inside tmux. Raw escape payloads, Windows Terminal `sendInput` strings,
25
- and tmux User keys are rejected as aliases.
21
+ Settings saves safe tmux plain chords for actions and automatically applies the
22
+ change to the app config and the running tmux session when Settings is opened
23
+ from inside tmux. After each save/reset it shows the three outcomes together:
24
+ saved, prepared, and running session. Successful in-tmux saves keep those labels
25
+ user-facing; failure and skipped states include diagnostic terms such as
26
+ `keymap.toml`, generated tmux config, or live tmux reload so the broken stage is
27
+ clear. If Settings is run outside tmux, the running-session stage is skipped and
28
+ the recovery/sync action is `projmux tmux apply`. Raw escape payloads, Windows
29
+ Terminal `sendInput` strings, and tmux User keys are rejected as action keys.
30
+ Settings capture diagnostics split the observed result into logical key, raw
31
+ bytes, and the tmux key name that can be saved. Diagnostic states distinguish
32
+ keys that did not arrive, ambiguous bytes such as Enter/Ctrl-M, and keys that
33
+ need a supported terminal adapter. Safe direct keys are logical tmux names such
34
+ as `M-a`, `M-1`, `C-r`, `C-Space`, function/navigation names, or printable
35
+ keys. Raw escape bytes, CSI-u, xterm modified-key payloads, and tmux
36
+ UserKey/UserSequence names stay diagnostic-only and are not promoted into
37
+ `keymap.toml`.
26
38
 
27
39
  ## Quick Start
28
40
 
@@ -32,10 +44,14 @@ These shortcuts are the guaranteed launch defaults. They need no tmux prefix.
32
44
  | --- | --- |
33
45
  | `Alt-1` | Project sidebar |
34
46
  | `Alt-2` | Notify sidebar |
35
- | `Alt-3` | Existing session popup |
47
+ | `Alt-3` | Recent Windows |
36
48
  | `Alt-4` | AI split popup picker |
37
49
  | `Alt-5` | Settings |
38
50
 
51
+ `RecentWindows:Open` opens the cross-project recent windows queue. It switches
52
+ to the selected live tmux window using that window's current active pane; it is
53
+ separate from `last-pane` and from the existing-session popup.
54
+
39
55
  The tmux prefix remains the upstream default `Ctrl-b`. Inside a running
40
56
  session, `Ctrl-b ?` lists the live tmux bindings.
41
57
 
@@ -43,19 +59,29 @@ session, `Ctrl-b ?` lists the live tmux bindings.
43
59
 
44
60
  Settings > Keybindings lists the full action catalogue. In particular, sidebar
45
61
  keymap actions, pane switching, window switching, and rename actions remain
46
- visible. Transport-dependent rows show the default transport key separately
47
- from editable plain aliases.
48
-
49
- The Settings flow is intentionally simple: the root is one action list with
50
- current key summaries, and each action detail shows the action, current
51
- keybinding/aliases, `Add alias`, and reset. Diagnostic/probe/init workflows are
52
- not first-class Settings tabs; use `projmux setup` and `projmux init` from the
53
- terminal when key delivery needs remediation.
54
-
55
- Optional direct aliases can be added for actions such as:
62
+ visible.
63
+
64
+ The Settings flow is intentionally simple: the root is one action list with a
65
+ key summary and state. The key summary uses the first key plus `+N`, or
66
+ `Not bound` when no key is active. State vocabulary is limited to Default,
67
+ Custom, Available, and Unbound. Each action detail shows the action label,
68
+ state, a flat Keys list with `+ Add key`, Options, and a collapsed
69
+ Troubleshooting row with Test key delivery and Advanced... entry points. Key
70
+ rows open key detail for Remove key and Test key. Add key opens Press a key by
71
+ default; Enter key name, the safe direct key pool, risky/reserved key copy, and
72
+ raw diagnostics live under Advanced. Advanced delivery is still owned by the
73
+ selected Projmux action: supported Ghostty and Windows Terminal mappings are
74
+ previewed/applied through `projmux init`, not by storing raw sequences in the
75
+ primary keymap. Options covers unbinding the action and reset/use-default
76
+ flows. Diagnostic/probe/init workflows are not first-class Settings tabs; use
77
+ `projmux setup` and `projmux init` from the terminal when key delivery needs
78
+ remediation.
79
+
80
+ Optional direct keys can be added for actions such as:
56
81
 
57
82
  | Canonical action | Meaning |
58
83
  | --- | --- |
84
+ | `RecentWindows:Open` | Recent windows queue across projects |
59
85
  | `ProjectSwitcherToggle` | Project switcher popup |
60
86
  | `AISplitPickerToggle` | AI split popup picker; pressing again closes the picker popup |
61
87
  | `ai-split-right` | Open a new direct AI split to the right |
@@ -77,20 +103,20 @@ movement. Previous/next window remain transport-dependent and the generated app
77
103
  tmux config binds `M-S-Left` / `M-S-Right` to the tmux window navigation
78
104
  commands. These default transport keys are always rendered by projmux, because
79
105
  delivery still depends on the terminal forwarding the modifier-arrow sequence.
80
- Settings can add extra safe plain aliases, such as `M-[` for
81
- `previous-window`; those aliases are saved to `keymap.toml` as `keys = [...]`
106
+ Settings can add extra safe plain keys, such as `M-[` for
107
+ `previous-window`; those keys are saved to `keymap.toml` as `keys = [...]`
82
108
  without storing or replacing the transport default. Rename actions no longer
83
109
  have a built-in terminal fallback; use tmux's prefix rename flow or configure
84
- an explicit safe alias where the action is editable.
110
+ an explicit safe key where the action is editable.
85
111
 
86
112
  ## Product Requirements
87
113
 
88
114
  Settings > Keybindings stays a discovery surface. It must continue to expose
89
115
  launch toggles, sidebar keymap actions, picker-local actions, pane switching,
90
- window switching, and rename actions. The primary Settings flow is not the
91
- terminal remediation surface: replace-primary, disable-default, typed fallback,
92
- terminal mapping preview/apply, and init execution rows stay out of the action
93
- detail.
116
+ window switching, and rename actions. The basic Settings flow is not the
117
+ terminal remediation surface: key-role replacement, disable-default, typed
118
+ fallback, terminal mapping preview/apply, and init execution rows stay out of
119
+ the action detail.
94
120
 
95
121
  The product model does not support `UserN` or `CSI-u` as fallback guidance.
96
122
  Windows Terminal and Ghostty adapters use built-in plain Meta/control bytes or
@@ -110,10 +136,31 @@ by different picker surfaces, while conflicts inside one surface are rejected.
110
136
  | `SessionPopup:KillSession` | Kill the focused session |
111
137
  | `SessionPopup:CyclePreviewWindowPrev` / `SessionPopup:CyclePreviewWindowNext` | Preview windows |
112
138
  | `SessionPopup:CyclePreviewPanePrev` / `SessionPopup:CyclePreviewPaneNext` | Preview panes |
113
- | `NotifySidebar:Ack` / `NotifySidebar:ClearNonCritical` / `NotifySidebar:ClearAll` | Manage notifications |
114
-
115
- Runtime picker footers render key guides from the merged keymap, preferring the
116
- default alias when it is present and otherwise using the first configured alias.
139
+ | `NotifySidebar:Ack` / `NotifySidebar:AckGroup` / `NotifySidebar:ClearNonCritical` / `NotifySidebar:ClearAll` | Manage notifications |
140
+
141
+ Notify sidebar Right/Left child-row show/hide behavior is picker-local and is
142
+ not part of the Settings action catalog. `NotifySidebar:AckGroup` defaults to
143
+ uppercase `A`, distinct from `NotifySidebar:Ack` on lowercase `a`.
144
+
145
+ Runtime picker footers render key guides from the merged keymap, using the
146
+ first active key as the representative key.
147
+
148
+ In Settings > Keybindings, **Add key** appends a key to the selected action's
149
+ `keys = [...]` list. For catalog popup toggle actions, those keys are used both
150
+ by generated tmux open bindings and by popup-internal close bindings, so the
151
+ same key can close the corresponding already-open popup. This same-key close
152
+ behavior is only for actions cataloged as popup toggles, such as
153
+ `ProjectSidebarToggle`, `NotifySidebarToggle`, `RecentWindows:Open`,
154
+ `AISplitPickerToggle`, `SettingsToggle`, `ProjectSwitcherToggle`, and
155
+ `SessionPopupToggle`. Direct command actions such as `new-window`, pane/window
156
+ navigation, and direct AI split actions remain command bindings and are not
157
+ treated as popup close keys.
158
+
159
+ Settings is the default apply path for key edits: it writes the key list,
160
+ refreshes the generated config, and reloads the running tmux session when
161
+ possible. Use `projmux tmux apply` as a CLI recovery/sync command after editing
162
+ the keymap file by hand, after an outside-tmux Settings save, or after resolving
163
+ a reported generated-config or live-reload failure.
117
164
 
118
165
  ## Keymap File
119
166
 
@@ -150,7 +197,7 @@ so Settings can distinguish them from the `Alt-4` popup picker toggle.
150
197
  ## Diagnose: `projmux setup`
151
198
 
152
199
  Run `projmux setup` outside tmux to find out which projmux keys reach the raw
153
- terminal. Settings > Keybindings remains the action/alias editor; setup is the
200
+ terminal. Settings > Keybindings remains the action-key editor; setup is the
154
201
  terminal delivery diagnostic.
155
202
 
156
203
  | Status | Meaning |
@@ -159,6 +206,16 @@ terminal delivery diagnostic.
159
206
  | `MISS timeout` | No bytes arrived because the terminal swallowed the key. |
160
207
  | `MISS unknown` | Bytes arrived, but they do not match the expected plain sequence. |
161
208
 
209
+ Settings capture uses the same underlying probe but reports a read model with
210
+ separate fields:
211
+
212
+ | Field | Meaning |
213
+ | --- | --- |
214
+ | Logical key | The key the user intended to press, such as `Alt-1`. |
215
+ | Raw bytes | The bytes captured from the terminal, shown escaped. |
216
+ | tmux received key | The logical tmux key name that can be saved, or a diagnostic placeholder when none is safe. |
217
+ | Delivery status | `delivered`, `key-did-not-arrive`, `ambiguous-key`, or `adapter-needed`. |
218
+
162
219
  Useful flags:
163
220
 
164
221
  ```sh
@@ -184,7 +241,7 @@ projmux init --allow-symlink
184
241
 
185
242
  The merge is idempotent: matching bindings are no-ops, missing bindings are
186
243
  added, and keys already mapped to a different user action are skipped with a
187
- warning. `projmux init` does not read `keymap.toml`; direct tmux aliases still
244
+ warning. `projmux init` does not read `keymap.toml`; direct tmux keys still
188
245
  belong in Settings > Keybindings or the keymap file.
189
246
 
190
247
  ### Ghostty
@@ -219,7 +276,7 @@ previous/next window:
219
276
  "actions": [
220
277
  { "command": { "action": "sendInput", "input": "\u001b1" }, "id": "User.projmuxSidebar" },
221
278
  { "command": { "action": "sendInput", "input": "\u001b2" }, "id": "User.projmuxNotifySidebar" },
222
- { "command": { "action": "sendInput", "input": "\u001b3" }, "id": "User.projmuxSessions" },
279
+ { "command": { "action": "sendInput", "input": "\u001b3" }, "id": "User.projmuxRecentWindows" },
223
280
  { "command": { "action": "sendInput", "input": "\u001b4" }, "id": "User.projmuxAIPicker" },
224
281
  { "command": { "action": "sendInput", "input": "\u001b5" }, "id": "User.projmuxSettings" },
225
282
  { "command": { "action": "sendInput", "input": "\u0002r" }, "id": "User.projmuxAISplitRight" },
@@ -112,7 +112,7 @@ The fzf compatibility surface for the native engine is tracked in
112
112
  the top border off screen.
113
113
  - Native preview panes normalize tabs and control bytes before horizontal
114
114
  clipping, preventing long preview rows from wrapping and consuming extra
115
- vertical viewport rows in Alt-3/session popups.
115
+ vertical viewport rows in session popups.
116
116
  - Native sidebar list scrollbars use the fixed list viewport as their track and
117
117
  measure multi-line cards in rendered rows, so the thumb does not shrink or
118
118
  jump when card heights vary.
@@ -179,8 +179,9 @@ Manual UX checks for the Docker sandbox:
179
179
  - Alt-1 opens with the top border/title visible, not clipped.
180
180
  - Vertical borders stay continuous while moving Up/Down.
181
181
  - Alt-1 closes the sidebar immediately when pressed again.
182
- - Alt-2, Alt-3, Alt-4, and Alt-5 open their matching native popups and close
182
+ - Alt-2, Alt-4, and Alt-5 open their matching native popups and close
183
183
  on the same Alt key immediately.
184
+ - Alt-3 opens Recent Windows.
184
185
  - Arrow keys move selection without leaking `^[[` text into the query.
185
186
 
186
187
  `fzf` is intentionally not installed in the image.
@@ -47,9 +47,9 @@ native picker engine and is not a public dependency-policy change.
47
47
  | 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` |
48
48
  | 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` |
49
49
  | frame content width | fzf border inner width | Covered | `ContentLayout` uses the frame inner width so separators and rows reach the right border; `TestRendererContentLayoutUsesFrameInnerWidth` |
50
- | picker-owned app background | native picker frame interior | Covered for renderer-owned cells | `ThemeFromEffective` applies built-in fallback and explicit effective background/foreground SGR to the native frame, and frame rows resume the app style after embedded resets so content padding, empty no-footer rows, footer rows, scrollbars, and preview gaps do not leak terminal default background; `TestThemeFromEffectiveFallbackPaintsFrameBackground`; `TestRendererFrameBackgroundResumesAfterContentResetBeforePadding`; `TestNativeInteractiveNoFooterBlankRowsUseThemeBackground`; `TestNativeInteractiveSplitPreviewGapsUseThemeBackground`; `TestNativeInteractiveSettingsAIBadgeStyleLongPreviewClampsFrameRows` |
51
- | 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; tmux 3.4 per-command `display-popup -s` is used for the popup body style, setting `bg`/`fg` from the same effective theme tmux background/foreground tokens so tmux's blank/pre-draw popup body matches the native picker surface before the app renderer paints; no global `popup-style`, `popup-border-style`, shell pane background, `default-style`, `window-style`, OSC background, or status/window palette options are changed; 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`; `TestAppRunTmuxPopupToggleUsesNativePopupBodyStyleFromEffectiveTheme`; `TestBuildPopupToggleWithPickerBackendStylesNativeOnly`; `TestBuildDisplayPopupArgsAddsBodyStyle`; `TestSessionizerSidebarWidthKeepsFZFMinimum`; `TestSidebarPopupHeightLeavesStatusbarRows`; `TestNotifySidebarWidthMatchesFZFBaseline`; `TestAppRunTmuxPopupToggleKeepsNotifySidebarFZFSizingForNative` |
52
- | optional native titlebar | native picker popup frame | Covered for empty-title compatibility and opt-in titles | `picker.Options.Title`; `RenderFrameWithTitle`; `ContentLayoutWithTitle`; empty titles keep the prior frame unchanged, non-empty titles render in a picker-owned section below the top border, titlebar text/divider/chip gaps inherit the frame background/foreground instead of a separate titlebar overlay ANSI layer, and the native Alt-1 project sidebar opts into `Projects`; `TestRendererRenderFrameWithTitleKeepsDefaultWhenTitleEmpty`; `TestRendererRenderFrameWithTitleUsesTitlebarRow`; `TestRendererContentLayoutWithTitleReservesTitlebarRow`; `TestNativeInteractiveRendersOptionalTitlebar`; `TestSwitchCommandNativeSidebarSetsTitle` |
50
+ | picker-owned app background | native picker frame interior | Covered for renderer-owned cells | `ThemeFromEffective` applies built-in fallback and explicit effective `surface` / `chrome_foreground` SGR to the native frame, and frame rows resume the app style after embedded resets so content padding, empty no-footer rows, footer rows, scrollbars, and preview gaps do not leak terminal default background; `TestThemeFromEffectiveFallbackPaintsFrameBackground`; `TestRendererFrameBackgroundResumesAfterContentResetBeforePadding`; `TestNativeInteractiveNoFooterBlankRowsUseThemeBackground`; `TestNativeInteractiveSplitPreviewGapsUseThemeBackground`; `TestNativeInteractiveSettingsAIBadgeStyleLongPreviewClampsFrameRows` |
51
+ | 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; tmux 3.4 per-command `display-popup -s` is used for the popup body style, setting `bg` from `surface` and `fg` from `chrome_foreground` so tmux's blank/pre-draw popup body matches the native picker surface before the app renderer paints; no global `popup-style`, `popup-border-style`, shell pane background, `default-style`, `window-style`, OSC background, or status/window palette options are changed; 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`; `TestAppRunTmuxPopupToggleUsesNativePopupBodyStyleFromEffectiveTheme`; `TestBuildPopupToggleWithPickerBackendStylesNativeOnly`; `TestBuildDisplayPopupArgsAddsBodyStyle`; `TestSessionizerSidebarWidthKeepsFZFMinimum`; `TestSidebarPopupHeightLeavesStatusbarRows`; `TestNotifySidebarWidthMatchesFZFBaseline`; `TestAppRunTmuxPopupToggleKeepsNotifySidebarFZFSizingForNative` |
52
+ | optional native titlebar | native picker popup frame | Covered for empty-title compatibility and opt-in titles | `picker.Options.Title`; `RenderFrameWithTitle`; `ContentLayoutWithTitle`; empty titles keep the prior frame unchanged, non-empty titles render in a picker-owned section below the top border, titlebar text/divider/chip gaps inherit the frame `surface` / `chrome_foreground` instead of a separate titlebar overlay ANSI layer, and the native Alt-1 project sidebar opts into `Projects`; `TestRendererRenderFrameWithTitleKeepsDefaultWhenTitleEmpty`; `TestRendererRenderFrameWithTitleUsesTitlebarRow`; `TestRendererContentLayoutWithTitleReservesTitlebarRow`; `TestNativeInteractiveRendersOptionalTitlebar`; `TestSwitchCommandNativeSidebarSetsTitle` |
53
53
  | redraw flicker/top clipping | keyboard navigation in exact-height tmux popup | Partially covered | native redraws use synchronized updates plus coalesced row diffs after the first frame, skip unchanged frames, render frame diffs before sidebar focus commands, frame rendering avoids trailing bottom-border CRLF, and screen exit clears the alternate buffer before restore; `TestNativeInteractiveWrapsRedrawsInSynchronizedUpdates`; `TestFrameUpdateRendererSkipsUnchangedFrame`; `TestFrameUpdateRendererCoalescesEachFrameUpdate`; `TestRendererRenderFrameUsesCRLFRowsForRawTTY`; `TestNativeInteractiveUsesAlternateScreen` |
54
54
 
55
55
  ## Native Surface Architecture
@@ -75,7 +75,7 @@ native picker engine and is not a public dependency-policy change.
75
75
 
76
76
  Native picker frame chrome is normalized in `internal/ui/projmuxpicker`.
77
77
  Titlebar text, title dividers, and chip-strip gaps inherit the frame
78
- background/foreground instead of applying a second titlebar overlay ANSI layer;
78
+ `surface` / `chrome_foreground` instead of applying a second titlebar overlay ANSI layer;
79
79
  chip bodies can still carry active/inactive/disabled tones. Search prompt and
80
80
  footer separators fill the available frame width, and header, row, footer, and
81
81
  preview lines close any active SGR style before padding or frame borders can
@@ -106,21 +106,48 @@ table `ID AGE SEV SRC TARGET TEXT`. `--severity` / `--source` are
106
106
  repeatable filters. Without `--live`, this command reads only the queue and
107
107
  preserves the stable JSON array used by scripts.
108
108
 
109
- `--ui=sidebar` opens the notify queue as an interactive right-side list when
110
- run inside the tmux popup surface. Selecting a row focuses the selected target
111
- pane and acks the row after focus succeeds. The surface actions
112
- `NotifySidebar:Ack`, `NotifySidebar:ClearNonCritical`, and
113
- `NotifySidebar:ClearAll` are internal picker actions; direct launch aliases
114
- are edited in Settings, while internal picker aliases are adjusted in
115
- `keymap.toml` when needed. Runtime footer key guides read the merged keymap
116
- and show the default alias when present, otherwise the first configured alias,
117
- so custom aliases do not make the UI stale. `NotifySidebar:Ack` and
109
+ `--ui=sidebar` opens the notify queue as an interactive right-side pane/session
110
+ inbox when run inside the tmux popup surface. The queue source of truth remains
111
+ flat; the sidebar builds a read-only grouped view for display. The first screen
112
+ shows collapsed group rows keyed by pane when available, then window, then
113
+ session/external fallback. Each group row is a fixed three-line card: line 1
114
+ keeps project/session plus agent/provider with newest age, line 2 keeps
115
+ topic/pane-title/task context plus severity/live-state aggregate
116
+ metadata, and line 3 keeps the latest notification preview. Collapsed group
117
+ cards do not promote window/pane ids as primary information. A `+N` badge is
118
+ shown only when the group can unfold, and `N` is the number of child
119
+ notification rows that will appear; one-notification group headers omit both
120
+ the count badge and strong fold marker. Right/Left show and hide child rows for
121
+ foldable groups inside the native sidebar only; this fold state is
122
+ session-local and is not persisted. Right on a childless group refreshes
123
+ without adding rows. Enter on a group row, whether folded or expanded, focuses
124
+ the group's representative pane and acknowledges every visible notification in
125
+ that group only after focus succeeds. Inactive means an `ai:` queue entry points
126
+ to a pane that no longer matches live reply+agent state; it is not a time-age
127
+ TTL state, and Enter still focuses the target when it is routable. If the
128
+ representative target is gone/unroutable, Enter treats the selected pane inbox
129
+ as explicit cleanup and acknowledges/prunes the visible group without focusing,
130
+ including critical notifications. If a live- or inactive-looking representative target
131
+ disappears during focus, Enter uses the same gone-group cleanup policy. Other
132
+ focus failures keep the group pending, show a clear message, and refresh/prune
133
+ the list. Expanded child notification rows are compact event rows with age,
134
+ message preview, and severity/state while keeping the existing focus/ack-one
135
+ behavior. The surface actions
136
+ `NotifySidebar:Ack`, `NotifySidebar:AckGroup`,
137
+ `NotifySidebar:ClearNonCritical`, and `NotifySidebar:ClearAll` are internal
138
+ picker actions; direct launch aliases are edited in Settings, while internal
139
+ picker aliases are adjusted in `keymap.toml` when needed.
140
+ `NotifySidebar:AckGroup` defaults to uppercase `A` and explicitly
141
+ acknowledges every visible notification in the selected group, including
142
+ critical notifications. Runtime footer key guides read the merged keymap and
143
+ show the default alias when present, otherwise the first configured alias, so
144
+ custom aliases do not make the UI stale.
145
+ `NotifySidebar:Ack`, `NotifySidebar:AckGroup`, and
118
146
  `NotifySidebar:ClearNonCritical` refresh rows, live state, and selection inside
119
147
  the same native picker session; `NotifySidebar:ClearAll` still closes the
120
- popup and prints a summary. Rows are intentionally compact: the visible label keeps
121
- notification text first, then age, project, window, and pane metadata; hidden
122
- queue ids remain action values but the sidebar has no search input and
123
- intentionally does not expose a separate metadata detail view.
148
+ popup and prints a summary. Rows are intentionally compact: hidden queue ids
149
+ remain action values but the sidebar has no search input and intentionally does
150
+ not expose a separate metadata detail view.
124
151
 
125
152
  When a new pending notification is successfully pushed by any app producer
126
153
  (`notify push`, reply-ready, reconcile backfill, or bell fallback), open native
@@ -142,11 +169,20 @@ output becomes `{queue, live, rows, errors}`. Typical states:
142
169
  queue entry.
143
170
  - `live-ai-reply-missing-queue` — a live AI reply pane lacks the derived
144
171
  queue entry; run `projmux notify reconcile` to back-fill it.
145
- - `queue-stale` — an `ai:` queue entry exists, but the live pane no longer
146
- matches reply+agent state; it remains pending until explicit ack. Surfaced
147
- in the sidebar/statusbar as `STALE` / `STL`.
148
- - `queue-gone` — a queue entry has no routable target (empty session); it
149
- can only be ack'd. Surfaced as `GONE` / `GON`.
172
+ - `queue-stale` — preserved machine-readable state for an inactive target:
173
+ an `ai:` queue entry whose pane still EXISTS in the live tmux pane inventory,
174
+ but no longer matches reply+agent state. It is not TTL/time age. Surfaced in
175
+ the sidebar/statusbar as `INACTIVE` / `INA`; Enter still focuses and acks if
176
+ the target is routable.
177
+ - `queue-gone` — the queue entry's target is gone. This is now determined two
178
+ ways: (a) the entry has no routable target (empty session), or (b) the entry
179
+ carries a pane target whose pane id is absent from the real tmux live pane
180
+ inventory (`tmux list-panes -a`). Surfaced as `GONE` / `GON`, and Enter/ack
181
+ cleans it up without focusing. The inventory check is best-effort: when the
182
+ pane inventory cannot be read (tmux error, or an empty/unrecognized reply),
183
+ membership-based GONE is skipped so a missing tmux server never falsely
184
+ dims/gones every row, and only pane-target rows are eligible (window/session-
185
+ only rows keep the empty-session check only).
150
186
  - `queue-only` — a non-AI/external queue entry is pending and has no live AI
151
187
  reply-pane requirement.
152
188
 
@@ -176,7 +212,8 @@ path), then:
176
212
  - pushes/refreshes one `ai:<session>:<pane>` entry for every pane
177
213
  whose attention state is `reply` AND whose agent option is non-empty;
178
214
  - reports every existing queue entry whose id starts with `ai:` and whose
179
- pane no longer matches that condition as stale, without acking it.
215
+ pane no longer matches that condition as inactive/`queue-stale`, without
216
+ acking it.
180
217
 
181
218
  Successful backfill pushes publish the same best-effort open-sidebar refresh
182
219
  event as other pending queue additions.
@@ -24,16 +24,22 @@ view-first layout:
24
24
  commands, `Pane navigation`, `Window navigation`, and `Rename` groups or
25
25
  equivalent searchable rows.
26
26
  - `Settings > Keybindings > Action` keeps the user-facing edit path small:
27
- action, current keybinding/aliases, `Add alias`, and reset. It does not offer
28
- replace-primary, disable-default, typed-fallback, terminal mapping preview, or
29
- terminal mapping apply rows.
27
+ action label, state, a flat Keys list, Options, and a collapsed
28
+ Troubleshooting row. Keys shows only currently active/effective keys plus
29
+ `+ Add key`. Pressing a key row opens key detail, where Remove key and Test
30
+ key live. Options offers Unbind and Reset to default/Use default when
31
+ state-appropriate. Add key opens the default Press a key flow with Cancel and
32
+ Advanced...; typed key-name entry and raw diagnostics live under Advanced. It
33
+ does not expose Default key, Apply State, Delivery, Advanced Delivery,
34
+ key-role replacement, terminal mapping preview, or terminal mapping apply
35
+ rows as always-visible sections.
30
36
  - Terminal delivery remediation lives outside Settings primary flow. The
31
37
  supported order is `projmux shell` first, then `projmux setup`, then
32
38
  `projmux init` for supported terminal adapters.
33
39
  - Rows that cannot safely be edited still stay visible. Mark diagnostic-only
34
40
  rows with the delivery path and reason instead of hiding them or turning them
35
- into unsupported editable aliases. Transport-dependent rows stay visible with
36
- a separate default transport key and additive plain-alias entry; replacing or
41
+ into unsupported editable keys. Transport-dependent rows stay visible with
42
+ their default transport key and additive custom-key entry; replacing or
37
43
  disabling the transport default is not exposed.
38
44
  - `Alt-1..5` are the only guaranteed zero-config launch defaults. `UserN` and
39
45
  `CSI-u` are legacy/removal/unsupported targets, not supported fallback
@@ -42,6 +48,13 @@ view-first layout:
42
48
  first-class row, action rows use human-readable labels, internal IDs appear
43
49
  only in detail/source/keymap contexts, and runtime footers are status hints,
44
50
  not key discovery.
51
+ - `Settings > Theme` (Global) is the single theme view. There is no separate
52
+ `Effective theme` item: the Global theme view shows each color token's value
53
+ inline. A token set globally (explicit value or via a global preset) shows its
54
+ set/override/preset summary; an UNSET token shows the resolved fallback value
55
+ with a dim swatch, a `(fallback)` label, and a `fallback` source. Resolver
56
+ warnings render as dim info rows after the token rows. Project `.projmux`
57
+ `[theme]` is never resolved or shown here.
45
58
  - `Settings > Notifications` owns notification delivery IA. Desktop notification
46
59
  mode, AI desktop notification dedupe duration, delivery source diagnostics,
47
60
  AI hook quiet policy, in-app queue status, and
@@ -64,8 +77,7 @@ view-first layout:
64
77
  edit catalog `install` values or run agent install/remove commands.
65
78
  - `Settings > Session State > Sidebar startup picker` controls the Alt-1
66
79
  project-open startup selector. The saved file remains
67
- `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/sidebar-startup-picker`, and the
68
- stale `labs:sidebar-startup-picker` action opens this Session State detail.
80
+ `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/sidebar-startup-picker`.
69
81
  - `Settings > Labs` keeps experimental toggles, but keybindings no longer have a
70
82
  visible Labs row. The hidden compatibility action redirects to the
71
83
  `Settings > Keybindings` action list, not to a diagnostic default.
@@ -83,10 +95,6 @@ view-first layout:
83
95
  and `Notify icon` details. Each detail shows the current mode plus
84
96
  immediately selectable off/symbol/emoji preview rows. There is no separate
85
97
  `Change` page for icon decoration.
86
- - `Settings > Appearance` also shows a read-only `Theme font` status row.
87
- `font_family` and `font_size` are desired values from the effective
88
- project/global theme, and unsupported terminal paths must say `not applied`
89
- instead of implying tmux changed the font.
90
98
  - `Settings > Appearance > Language / Locale` is the global/user language
91
99
  detail. The root row shows the saved `[ui].locale` value and the currently
92
100
  effective locale. The detail shows `Current`, `[ui].locale`, optional
package/docs/statusbar.md CHANGED
@@ -141,13 +141,35 @@ last collect timestamp when present, falls back to the cache file mtime when
141
141
  needed, and keeps stale sync metadata muted instead of escalating it to a
142
142
  warning color.
143
143
  The notification HUD detail surface opens the right-side notification popup
144
- through the notify sidebar action, with newest-first rows and an
145
- attention-tinted title. When notification icon decoration is `symbol` or
146
- `emoji`, the bell appears before the title text.
147
- Selecting a row still focuses and acknowledges that notification. Internal
148
- notify commands use `NotifySidebar:*` IDs in `keymap.toml`; runtime footers
149
- render key guides from the merged keymap and prefer the default alias when it
150
- is still configured.
144
+ through the notify sidebar action, showing the grouped pane/session inbox with
145
+ collapsed group rows and the same attention-tinted title. When notification
146
+ icon decoration is `symbol` or `emoji`, the bell appears before the title text.
147
+ Foldable group rows show `+N`, where `N` is the number of child notification
148
+ rows shown after Right. One-notification group headers omit the count and do
149
+ not render as strongly foldable. Right/Left show and hide child rows locally
150
+ inside the native sidebar; Right on a childless group refreshes without adding
151
+ rows. Enter on a group row, whether folded or expanded, focuses the group's
152
+ representative pane and acknowledges every visible notification in that group
153
+ only after focus succeeds. Inactive means an `ai:` queue entry no longer
154
+ matches live reply+agent state (its pane still EXISTS in tmux), not that the
155
+ row is old; if the target remains routable, Enter and statusbar clicks still
156
+ focus and then ack. Gone means the target is unroutable (empty session) or the
157
+ row's pane id is absent from the real tmux live pane inventory
158
+ (`tmux list-panes -a`). The inventory check is best-effort: an unreadable or
159
+ empty/unrecognized tmux reply is treated as "unavailable", so a missing tmux
160
+ server never falsely gones routable rows. If the
161
+ representative target is gone/unroutable, Enter cleans up the selected group
162
+ without focusing and acknowledges every visible notification in that group,
163
+ including critical notifications. A
164
+ target-gone focus race follows the same cleanup policy; other focus failures
165
+ keep the group pending and show a clear message before refresh/prune. Enter on
166
+ a child notification preserves the existing focus and ack-one behavior.
167
+ `NotifySidebar:AckGroup` remains the explicit group ack action and acknowledges
168
+ every visible notification in the selected group, including critical
169
+ notifications.
170
+ Internal notify commands use `NotifySidebar:*` IDs in `keymap.toml`; runtime
171
+ footers render key guides from the merged keymap and prefer the default alias
172
+ when it is still configured.
151
173
 
152
174
  Empty `#{mouse_status_range}` (a click on whitespace) falls through to
153
175
  `select-window -t @<mouse_window>` when `--mouse-window` is non-empty,
@@ -180,6 +202,8 @@ them as `display-message` toasts:
180
202
 
181
203
  - `notify` click whose focus dispatch exits 2 (target unresolved):
182
204
  ack the entry, toast `notify target gone; cleared`.
205
+ - `notify` click whose AI target is inactive because it no longer matches live
206
+ reply+agent state: focus and ack when the target is still routable.
183
207
  - Any other focus failure: keep the entry, toast `focus failed:
184
208
  <reason>`.
185
209
  - `session`, `kube`, or `git` popup launch failure: toast
package/docs/testing.md CHANGED
@@ -21,6 +21,10 @@ and humans run the same entrypoints.
21
21
  `test/e2e/linux-smoke.sh`. It validates a minimal real-tmux workflow:
22
22
  sessions, panes, config sourcing, reply-state notify reconciliation, focus
23
23
  notify fallback, and status notify rendering.
24
+ - `make deadcode` runs `go tool deadcode` (pinned via the go.mod tool
25
+ directive) over the module and reports unreachable functions, filtering out
26
+ the intentional/MUST-KEEP baseline in `.deadcode-allowlist.txt`; it fails
27
+ only on NEW dead code, and `make fix` runs it after `go fix`.
24
28
 
25
29
  ## Docker-Covered Checks
26
30
 
@@ -90,7 +94,7 @@ Observe:
90
94
 
91
95
  - `Alt-1` opens the project sidebar.
92
96
  - `Alt-2` opens the notification sidebar.
93
- - `Alt-3` opens the existing-session picker.
97
+ - `Alt-3` opens Recent Windows.
94
98
  - `Alt-4` opens the AI split picker.
95
99
  - `Alt-5` opens Settings.
96
100
  - Pressing the same launch key again closes the popup instead of typing escape