projmux 0.9.0 → 0.10.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.
package/docs/statusbar.md CHANGED
@@ -68,7 +68,8 @@ row 1 [#S] #{pane_current_path} ⎈ <ctx>/<ns> <git> CPU 12% MEM 41% 
68
68
  without dominating the status row. Window tab indexes stay left of each tab,
69
69
  and tab titles are centered in a fixed-width trim so long active pane names
70
70
  do not resize the status row.
71
- - `Settings > Labs > Live system resources` adds the compact `CPU N% MEM N%`
71
+ - `Settings > Labs > Live system resources` adds the compact
72
+ `CPU N% MEM N%`
72
73
  segment between git and the clock on macOS, Linux, and WSL. It is global,
73
74
  default off, and updates with tmux's existing five-second status interval.
74
75
  CPU and memory are host-scoped telemetry, not pane, window, project, or
@@ -76,8 +77,13 @@ row 1 [#S] #{pane_current_path} ⎈ <ctx>/<ns> <git> CPU 12% MEM 41% 
76
77
  normal below 70%, warning at 70–89%, and critical at 90% or above; memory is
77
78
  normal below 75%, warning at 75–89%, and critical at 90% or above. Normal and
78
79
  unavailable (`--`) values use the secondary status-text role, warnings use
79
- the warning role, and critical values use the bold critical role. Styling one
80
- value never promotes the other value.
80
+ the warning role, and critical values use the bold critical role. Severity
81
+ words are omitted. Each percent value, including `%`, occupies one fixed
82
+ four-column slot (` 9%`, ` 15%`, `100%`, or ` --%`), so styling or changing
83
+ either metric cannot move the following segment. Styling one value never
84
+ promotes the other value. The Resource Inspector uses this same classifier
85
+ and semantic roles for host and attributed CPU/memory while rendering
86
+ unavailable metrics as `--` without severity suffixes.
81
87
  Linux CPU is the aggregate delta from `/proc/stat`; memory is
82
88
  `(MemTotal - MemAvailable) / MemTotal` from `/proc/meminfo`. macOS CPU uses
83
89
  the aggregate Mach host tick delta; memory is total physical memory minus
@@ -116,16 +122,16 @@ bind-key -n MouseDown1Status if-shell -F "#{==:#{mouse_status_range},window}" \
116
122
 
117
123
  ## Range catalogue
118
124
 
119
- | Range id | Row | Click action | Keyboard |
120
- | -------- | --- | ----------------------------------------- | ------------- |
121
- | `session` | 0 | `projmux tmux popup-toggle sessionizer-sidebar` | `prefix s s` |
122
- | `pwd` | 0 | show a native-framed current-path popup; no clipboard or tmux buffer copy | `prefix s p` |
123
- | `kube` | 0 | `projmux tmux popup-toggle sessionizer` | `prefix s k` |
124
- | `git` | 0 | `projmux tmux popup-toggle sessionizer` | `prefix s g` |
125
- | `settings` | 0 | `projmux tmux popup-toggle --client <tty> ai-split-settings` | mouse only; `prefix s s` remains `session` |
126
- | `resources` | 0 | `projmux tmux popup-toggle --client <tty> resource-inspector` | mouse or custom `Resources:Open`; no default key |
127
- | `usage` | 1 | show a native-framed usage HUD popup from cached usage state | `prefix s u` |
128
- | `notify` | 1 | `projmux focus --target <newest> --source status-bar --kind segment-click [--client <tty>]`, then ack on focus success | `prefix s n` |
125
+ | Range id | Generated row | Click action | Keyboard |
126
+ | -------- | ------------- | ----------------------------------------- | ------------- |
127
+ | `notify` | `status-format[0]` | `projmux focus --target <newest> --source status-bar --kind segment-click [--client <tty>]`, then ack on focus success | `prefix s n` |
128
+ | `usage` | `status-format[0]` | show a native-framed usage HUD popup from cached usage state | `prefix s u` |
129
+ | `session` | `status-format[1]` | `projmux tmux popup-toggle sessionizer-sidebar` | `prefix s s` |
130
+ | `pwd` | `status-format[1]` | show a native-framed current-path popup; no clipboard or tmux buffer copy | `prefix s p` |
131
+ | `kube` | `status-format[1]` | `projmux tmux popup-toggle sessionizer` | `prefix s k` |
132
+ | `git` | `status-format[1]` | `projmux tmux popup-toggle sessionizer` | `prefix s g` |
133
+ | `resources` | `status-format[1]` | `projmux tmux popup-toggle --client <tty> resource-inspector` | mouse or custom `Resources:Open`; no default key |
134
+ | `settings` | `status-format[1]` | `projmux tmux popup-toggle --client <tty> ai-split-settings` | mouse only; `prefix s s` remains `session` |
129
135
 
130
136
  `notify` reads the pending queue only. For a live pane-state view that is
131
137
  independent of queued reminders, use `projmux attention list`. To explain why
@@ -155,7 +161,11 @@ Antigravity rows keep conversation-local `context` separate from account
155
161
  `quota/<exact upstream bucket ID>` rows; the popup displays an absolute reset
156
162
  when provided and otherwise the exact optional relative reset seconds. Opaque
157
163
  bucket IDs are escaped for terminal/tmux safety and are never assigned a
158
- `5h`/`weekly` cadence.
164
+ `5h`/`weekly` cadence. Claude retains aggregate `5h`/`weekly` rows alongside
165
+ typed named/model `limits[]` rows in this popup: model-scoped rows display the
166
+ exact upstream group plus model display identity with a bounded terminal-safe
167
+ label, reset, and per-row age. The compact status line excludes every Claude
168
+ named/model row and continues to use only the aggregate official windows.
159
169
  Session State inspection lives under `Projects > Sessions > State`; global
160
170
  Settings > Session State is settings-only and the statusbar no longer exposes a
161
171
  duplicate State button.
@@ -169,7 +179,8 @@ does not leave terminal key state behind. The usage popup uses the same
169
179
  single-payload print and plain Enter-close pattern. It shows the authoritative
170
180
  last collect timestamp when present, falls back to the cache file mtime when
171
181
  needed, and keeps stale sync metadata muted instead of escalating it to a
172
- warning color.
182
+ warning color. Percent-only named rows do not synthesize `USED`, `LIMIT`, or
183
+ `LEFT` counts.
173
184
  The notification HUD detail surface opens the right-side notification popup
174
185
  through the notify sidebar action, showing the grouped pane/session inbox with
175
186
  collapsed group rows and the same attention-tinted title. When notification
@@ -273,9 +284,8 @@ updates the matching live tmux option
273
284
  (`@projmux_statusbar_decoration_cwd`, `_git`, or `_notify`) when run inside
274
285
  tmux. The legacy `~/.config/projmux/statusbar-decoration` and
275
286
  `@projmux_statusbar_decoration` remain fallback defaults for older configs.
276
- Appearance also shows the effective desired theme font. This is a status row,
277
- not a font editor: tmux status strings and ANSI output cannot force terminal
278
- font family or size, so unsupported environments report `not applied`.
287
+ Settings > Theme controls the bottom status bar background through
288
+ `status_background`; `surface` controls popup and native frame backgrounds.
279
289
 
280
290
  Settings > Labs controls the experimental live resource segment. Its saved
281
291
  value is `~/.config/projmux/live-resources`; changing it inside tmux updates
@@ -18,7 +18,6 @@ Primary production sources:
18
18
  Support and fixture sources:
19
19
 
20
20
  - `test/`
21
- - `scripts/poc-native-picker-no-fzf-*.sh`
22
21
  - current docs that describe generated tmux snippets
23
22
 
24
23
  Useful inventory searches:
package/docs/upgrading.md CHANGED
@@ -40,6 +40,27 @@ still requires an explicit `PROJMUX_INSTALLER=github-release`.
40
40
 
41
41
  ## Behavior Changes
42
42
 
43
+ ### Terminal init command removed
44
+
45
+ The deprecated top-level `projmux init` command and its legacy-only
46
+ `--dry-run` flag have been removed. Use the exact replacement
47
+ `projmux setup terminal`; it previews by default, and accepts `--apply`,
48
+ `--config <path>`, and `--allow-symlink` when those behaviors are needed.
49
+
50
+ ### Pane rename keymap action ID removed
51
+
52
+ The deprecated `rename-pane-topic` keybinding action ID has been removed. If
53
+ `~/.config/projmux/keymap.toml` still contains
54
+ `[bindings.rename-pane-topic]`, rename that table to
55
+ `[bindings.rename-pane-label]` before running Settings or
56
+ `projmux tmux apply`. Projmux now rejects the stale table with that exact
57
+ replacement instead of silently applying its keys to the user-label action.
58
+
59
+ This removal does not change `projmux ai topic set/clear`: those advanced CLI
60
+ commands continue to write only AI topic and manual-ownership state. User pane
61
+ rename continues to write only the pane label, and raw pane title remains an
62
+ independent fallback.
63
+
43
64
  ### Theme is now global-only
44
65
 
45
66
  Theme is a global user preference. The effective theme resolves from the global
@@ -95,13 +116,14 @@ through separate terminal preset variants.
95
116
 
96
117
  ### Pane body vs popup backgrounds
97
118
 
98
- The general (pane) background and the popup/chrome background are now driven by
99
- separate public tokens. The pane body follows `background` (unset keeps the
100
- terminal default), while the status bar, native popup bodies, and the
101
- settings/notify/recent/picker frames follow `surface`. Because the `surface`
102
- fallback equals `background`, leaving both unset looks exactly as before; set
103
- them to different values to make popups read as a distinct surface from the pane
104
- body.
119
+ The general pane, bottom status bar, and popup/native frame backgrounds are now
120
+ driven by separate public tokens. The pane body follows `background` (unset
121
+ keeps the terminal default), the status bar follows `status_background`, and
122
+ native popup bodies plus the settings/notify/recent/picker frames follow
123
+ `surface`. Because the `surface` fallback equals `background`, leaving those two
124
+ unset looks exactly as before; set `surface` separately to make popups read as a
125
+ distinct surface from the pane body. Set `status_background` separately to
126
+ repaint only the bottom status bar.
105
127
 
106
128
  ### Theme font keys removed
107
129
 
@@ -11,12 +11,17 @@ requests such as `projmux usage --model claude`, `--model codex`, or
11
11
  still collect and render that provider even when it is disabled.
12
12
 
13
13
  Claude and Codex adapters read the upstream's own account view. Antigravity
14
- reads only the official managed statusline payload: `context_window` remains a
15
- conversation-local fullness gauge, while each `quota` map entry is a separate
16
- account row. Projmux preserves the upstream bucket ID and never guesses that an
17
- undocumented ID means `5h` or `weekly`. It does not infer quota, cadence, reset
18
- timestamps, or account limits from screen scraping, tokens, history,
19
- OAuth/cache files, or binary strings.
14
+ reads only the official managed statusline payload: `context_window` remains
15
+ private conversation-local diagnostic metadata, while each valid `quota` map
16
+ entry is a separate account row. Projmux preserves the upstream bucket ID and
17
+ never guesses that an undocumented ID means `5h` or `weekly`. It does not infer
18
+ quota, cadence, reset timestamps, or account limits from screen scraping,
19
+ tokens, history, OAuth/cache files, or binary strings.
20
+
21
+ Claude keeps the canonical aggregate `five_hour` and `seven_day` rows and also
22
+ preserves structurally valid typed `limits[]` rows as named account quotas for
23
+ inspection surfaces. These named rows never participate in the ambient status
24
+ projection.
20
25
 
21
26
  ## Adapters
22
27
 
@@ -45,6 +50,23 @@ floor.
45
50
  - A clean 200 resets the consecutive counter.
46
51
  - `--force` (BackoffResetter) clears the persisted state and attempts
47
52
  the call regardless of streak.
53
+ - Canonical `five_hour` and `seven_day` blocks remain `5h` and `weekly`.
54
+ Each valid typed `limits[]` row becomes `window=quota` with the exact opaque
55
+ `group` copied to `bucket`. The snapshot also preserves `kind`, `severity`,
56
+ `is_active`, and nullable `scope`, model ID, and surface metadata; percent and
57
+ reset remain the authoritative common snapshot fields.
58
+ - A model-scoped quota renders as `quota/<group> · <model display name>` in
59
+ text and popup inspection. Control characters and tmux format introducers are
60
+ escaped and the display label is bounded, while the stored identity remains
61
+ byte-for-byte unchanged. No model-family inference, aliasing, aggregation, or
62
+ percentage-to-count derivation is performed.
63
+ - `limits[]` is capped at 64 rows and required field presence/types are checked
64
+ explicitly. A malformed container or row fails that adapter collection so the
65
+ manager retains the complete last-known-good Claude slice. A valid
66
+ aggregate-only response succeeds and therefore removes obsolete named rows.
67
+ - Null legacy top-level model hints and unknown experiment keys are ignored.
68
+ Billing/credit blocks such as `extra_usage` and `spend` are not ingested or
69
+ rendered.
48
70
 
49
71
  ### Codex (`internal/core/usage/adapters/codex`)
50
72
 
@@ -74,12 +96,12 @@ read.
74
96
 
75
97
  Local managed-statusline sidecars. No network or credential reads.
76
98
 
77
- - `context_window.used_percentage` becomes the conversation-local `context`
78
- row and retains the conversation ID in its sidecar. The legacy string
79
- percentage remains a compatibility fallback.
99
+ - `context_window.used_percentage` and its conversation ID remain in the
100
+ private context sidecar for hook/notify diagnostics. They do not become
101
+ Usage snapshots. The legacy string percentage remains a writer fallback.
80
102
  - The official `quota` map is sorted by its exact bucket ID. Each valid bucket
81
103
  becomes `window=quota`, `bucket=<upstream ID>` and renders as
82
- `quota/<upstream ID>` beside, never instead of, `ctx`.
104
+ `quota/<upstream ID>` on account-inspection surfaces.
83
105
  - Used percent is `100 * (1 - remaining_fraction)`. Non-finite or values
84
106
  outside `[0,1]`, empty IDs, null/disabled entries, and negative relative
85
107
  resets are ignored safely.
@@ -89,8 +111,8 @@ Local managed-statusline sidecars. No network or credential reads.
89
111
  - Context and quota use independent private sidecars. A context-only payload
90
112
  does not erase the last quota observation. An explicit empty/null quota map
91
113
  records no buckets; the manager's existing rule still preserves prior model
92
- rows when an adapter returns zero total rows, while any returned context row
93
- causes the normal full-model replacement.
114
+ rows when an adapter returns zero total rows. Context never participates in
115
+ that account-row replacement decision.
94
116
 
95
117
  ## Snapshot store
96
118
 
@@ -101,7 +123,9 @@ ${PROJMUX_USAGE_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/projmux/usage}/
101
123
  JSON document keyed by adapter, recording:
102
124
 
103
125
  - per-window `Snapshot{Model, Window, Bucket, Pct, Limit, ResetsAt,
104
- ResetInSeconds, UpdatedAt}`; `Bucket` is populated only for `window=quota`
126
+ ResetInSeconds, UpdatedAt, NamedQuota}`; `Bucket` is populated only for
127
+ `window=quota`, and `NamedQuota` is populated only when an upstream typed
128
+ named-quota contract supplies the metadata
105
129
  - per-adapter `last_collect` timestamp (drives the throttle)
106
130
  - per-adapter `Backoff{Until, Consecutive}` (drives the cooldown)
107
131
 
@@ -126,8 +150,8 @@ Enabled agents, filters by window, and renders the tab-aligned table:
126
150
  ```
127
151
  MODEL WINDOW PCT RESETS_AT RESET_IN STALE
128
152
  claude 5h 80% 2026-05-07T14:00:00+09:00 -
129
- antigravity context 14% - -
130
153
  antigravity quota/gemini-weekly 6% 2026-07-06T16:50:32+09:00 560580s
154
+ claude quota/group-redacted · Model Redacted Alpha 38% 2031-02-03T15:05:06+09:00 - *
131
155
  ```
132
156
 
133
157
  `STALE` is `*` when `now - UpdatedAt > 10m`. A `--json` payload returns
@@ -144,9 +168,11 @@ provider rows and prints a short Settings hint. `--json` returns an
144
168
  empty array. Explicit `--model claude`, `--model codex` and
145
169
  `--model antigravity` bypass the enabled-agent filter for read-only
146
170
  inspection and collect/render only the requested adapter. Antigravity reports
147
- its conversation-local `context` row separately from zero or more account
148
- `quota/<bucket-id>` rows. `--window quota` selects only account buckets;
149
- `--window weekly` never matches an opaque quota bucket named `weekly`.
171
+ zero or more account `quota/<bucket-id>` rows. Legacy cached `window=context`
172
+ rows are suppressed in text and JSON output. `--window quota` selects only
173
+ account buckets; `--window weekly` never matches an opaque quota bucket named
174
+ `weekly`. `--window context` remains an accepted compatibility filter and
175
+ returns no Usage rows.
150
176
 
151
177
  ### `projmux status usage`
152
178
 
@@ -162,12 +188,19 @@ are swallowed unless `PROJMUX_USAGE_DEBUG` is set. Then it loads the
162
188
  cache, filters to the same enabled-agent scope, and renders. If no AI
163
189
  agents are enabled, the status segment emits nothing.
164
190
 
191
+ The HUD first derives an ambient projection separate from lossless account
192
+ snapshots. Canonical `5h`/`weekly` rows are eligible for every provider;
193
+ Antigravity's exact `quota/gemini-weekly` identity is projected as `weekly`
194
+ without rewriting the cache. Context, `3p-weekly`, and unknown quota buckets
195
+ do not participate in status width. Claude `limits[]` named/model rows are also
196
+ excluded; only its aggregate official `5h` and `weekly` rows reach the HUD.
197
+
165
198
  Output degrades through six tiers as `--max-width` shrinks:
166
199
 
167
200
  1. Long form with last-sync age + bars: `Claude (3m) 5h [████████░░]
168
- 80% · weekly [...] Antigravity ctx [...] · quota/gemini-weekly [...]`
201
+ 80% · weekly [...] Antigravity weekly [...]`
169
202
  2. Drop the age indicator (legacy long form).
170
- 3. Drop the weekly bar.
203
+ 3. Keep one primary bar per provider (`5h`, or `weekly` when 5h is absent).
171
204
  4. Drop bars entirely (`Claude 5h:80% weekly:30%`).
172
205
  5. Single-letter labels (`C 5h:80% weekly:30%`).
173
206
  6. Hard rune-truncate with trailing `…`.
@@ -186,6 +219,17 @@ sync metadata to the same enabled-agent scope as the ambient HUD. This keeps
186
219
  tmux click path a structured table with aligned rows, right-aligned numeric
187
220
  values, dim unavailable cells, amber usage at 80%, and red usage at 95%.
188
221
 
222
+ The popup suppresses legacy cached context rows while preserving every valid
223
+ named quota ID, reset, and freshness value. Its columns are data-driven: when
224
+ no displayed row has authoritative absolute token counts, `USED`, `LIMIT`, and
225
+ `LEFT` are omitted together. If any row has real counts, all three columns are
226
+ shown; percent-only rows use unavailable cells. Counts are never derived from
227
+ percentages.
228
+
229
+ Named Claude rows use the same bounded, injection-safe label as text output and
230
+ include their reset plus per-row `AGE`. JSON retains exact opaque group/model
231
+ identity, nullable scope fields, `updated_at`, and the derived `stale` flag.
232
+
189
233
  The popup sync line uses the maximum authoritative `LastCollect` timestamp from
190
234
  the cache. If that field is unavailable, it falls back to the snapshots file
191
235
  mtime. The sync line turns amber when the timestamp is more than 60 seconds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projmux",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "tmux project session manager",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/crevissepartners/projmux#readme",
@@ -28,9 +28,9 @@
28
28
  "package:npm:pack": "scripts/package-npm.sh --pack"
29
29
  },
30
30
  "optionalDependencies": {
31
- "@projmux/linux-x64": "0.9.0",
32
- "@projmux/linux-arm64": "0.9.0",
33
- "@projmux/darwin-x64": "0.9.0",
34
- "@projmux/darwin-arm64": "0.9.0"
31
+ "@projmux/linux-x64": "0.10.1",
32
+ "@projmux/linux-arm64": "0.10.1",
33
+ "@projmux/darwin-x64": "0.10.1",
34
+ "@projmux/darwin-arm64": "0.10.1"
35
35
  }
36
36
  }
@@ -1,227 +0,0 @@
1
- # Native Picker Engine
2
-
3
- This note tracks the native picker engine. Native is the only picker backend.
4
-
5
- The fzf compatibility surface for the native engine is tracked in
6
- [native-picker-parity.md](native-picker-parity.md).
7
-
8
- ## What This Covers
9
-
10
- - `internal/ui/picker` is the backend-neutral contract for native picker rows,
11
- actions, filtering, and typed-query prompts.
12
- - `internal/ui/projmuxpicker` is the projmux-specific native picker surface for
13
- frame, redraw updates, theme tokens, ANSI width/truncation,
14
- prompt/footer/list rendering, and preview pane layout. The POC keeps `picker`
15
- responsible for backend routing, keyboard input, filtering, preview command
16
- execution, and result contracts, while moving visual composition into
17
- `projmuxpicker` so projmux can evolve a native picker design without coupling
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.
22
- - `internal/ui/pickercompat` remains an internal compatibility mapper between
23
- the old option/result shape and the backend-neutral `picker.Options`
24
- contract. It is not a runtime backend. App code should
25
- describe picker intent as rows, actions, preview commands, and initial focus,
26
- then route through the native picker.
27
- - Settings > Labs remains available for Live system resources and Project
28
- Hooks, but picker backend/source information has been retired.
29
- - Picker flows covered by the native path include AI picker/settings, shell
30
- update prompt, settings hub sections, switch settings/add-pin, the main
31
- project switcher list, recent sessions, and notify sidebar.
32
- - The native picker supports ranked fuzzy search/filter, arrow-key selection in
33
- normal CSI and tmux application-cursor modes, Enter, Esc, Ctrl-C, Backspace,
34
- Ctrl-U, Ctrl-W, PageUp/PageDown, Home/End, modified CSI keys, custom expect
35
- keys such as Ctrl-X/Alt-P, printable expect keys such as notify `a`/`x`, control
36
- expect keys such as notify `Ctrl-X`, `start:pos(N)` initial focus, preview
37
- command output, preview cycle command bindings, and sidebar focus command
38
- bindings.
39
- - FZF-style movement keys are supported for native selection: `Ctrl-N` and
40
- `Ctrl-J` move down, while `Ctrl-P` and `Ctrl-K` move up unless the app claims
41
- the key as a custom action. Up/down-family movement wraps at list boundaries;
42
- PageUp/PageDown and Home/End remain clamped or explicit jumps.
43
- - Typed-query prompts support cursor-aware insertion/deletion with a visible
44
- prompt cursor, Left/Right, Ctrl-A/E, Delete, Backspace, Ctrl-U, and Ctrl-W
45
- for settings path entry.
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.
50
- - Native interactive picker screens use an alternate screen lifecycle to better
51
- match fzf fullscreen behavior and restore the tmux pane after exit. Frame
52
- updates and screen exit both return to column 0 before emitting terminal
53
- control sequences, and screen exit resets styles plus clears the alternate
54
- buffer from the home cursor before restore. This keeps restore/update escapes
55
- and selected-session handoff output from visually trailing the bottom border
56
- in tmux/script captures. Native also gives real TTY screen restore a short
57
- settle window before returning to callers that may immediately draw a tmux
58
- session.
59
- - Native interactive picker screens render inside a full-screen border frame to
60
- match the app's fzf `--height 100% --border` surface more closely.
61
- - Popup-toggle commands use tmux `display-popup -B` when the native backend is
62
- active so the native picker owns the visible frame and does not double-draw
63
- with tmux's outer popup border.
64
- - Native Alt-1 sidebar popups use the same responsive width calculation as fzf
65
- with a smaller native-only minimum, so the borderless native frame is not
66
- wider than the existing fzf sidebar surface on normal terminals.
67
- - Native Alt-2 notify sidebar popups keep the fzf baseline width contract
68
- (`24%`, minimum `64`) while still letting the native picker own the frame.
69
- - Native sidebar popups reserve two rows at the bottom for the tmux statusbar
70
- area instead of using the full client height.
71
- - Simple native lists use the available terminal height after header, prompt,
72
- footer, and preview reservations instead of a fixed page-sized viewport.
73
- - Navigation-only native lists mirror fzf `--disabled --no-input`: the input
74
- prompt is hidden, printable non-action keys do not alter the query, and
75
- expect/action keys still work.
76
- - Native frame content now uses the full inner border width so prompt/list/footer
77
- separators reach the right border like fzf.
78
- - Native frames can render an optional picker-owned titlebar row below the top
79
- border when `picker.Options.Title` is set; empty titles keep the default frame
80
- unchanged. Non-empty titles use a distinct neutral titlebar surface with rule
81
- fill and a divider row separating the title section from the search/content
82
- section. The native Alt-1 project sidebar uses this for a
83
- `Projects` titlebar.
84
- - Native width/truncation uses terminal cell width for Korean/CJK text, emoji,
85
- and combining marks instead of raw rune count, so localized project names and
86
- decorated notify headers do not push the right frame border out of alignment.
87
- - Searchable native pickers draw an explicit `Search` label and a muted
88
- separator under the prompt/count line so the query area reads as distinct
89
- chrome rather than the first row of the list. Footer, down-preview, and
90
- multi-line card gaps share the same `projmuxpicker` separator primitive for a
91
- more consistent native surface.
92
- - Native interactive mode enables SGR mouse reporting while the alternate screen
93
- is active. Primary mouse down focuses the row under the cursor, primary mouse
94
- up applies it, mouse wheel moves selection up/down, and reporting is disabled
95
- again during screen restore.
96
- - When terminal size detection is unavailable, native picker falls back to a
97
- conservative 80x24 terminal instead of assuming a wider surface. Interactive
98
- tmux popups still use the detected popup size when `stty size` is available.
99
- - Simple and multi-line native rows share the same projmux current-row style and
100
- pointer marker rather than falling back to terminal inverse video for simple
101
- pickers.
102
- - Selected multi-line rows use the same pointer-width `▌` continuation marker
103
- as the first selected project line, so switch/session/notify cards read as one
104
- focused block.
105
- - Pointer and continuation markers render inside the current-row gutter style so
106
- selected cards do not visually break between the marker and row content.
107
- - Native redraws use terminal synchronized-update wrappers and coalesced
108
- row-diff updates after the first frame. The frame/redraw renderer lives in
109
- `projmuxpicker` rather than the backend loop, skips unchanged frames, and
110
- avoids a trailing newline after the bottom border. This reduces visible
111
- keyboard-navigation flicker and prevents exact-height popups from scrolling
112
- the top border off screen.
113
- - Native preview panes normalize tabs and control bytes before horizontal
114
- clipping, preventing long preview rows from wrapping and consuming extra
115
- vertical viewport rows in session popups.
116
- - Native sidebar list scrollbars use the fixed list viewport as their track and
117
- measure multi-line cards in rendered rows, so the thumb does not shrink or
118
- jump when card heights vary.
119
- - Switch picker git branch badges are capped more tightly for the native card
120
- surface, so inactive branch backgrounds do not dominate narrow Alt-1 sidebar
121
- rows.
122
- - Native selection changes render their frame diff before running sidebar focus
123
- commands, so tmux focus/switch side effects do not delay the visible picker
124
- movement.
125
- - In app TTY contexts, the native picker opens the controlling terminal
126
- (`/dev/tty`) before entering raw mode. This avoids stdin/stdout mismatch and
127
- line-mode escape leakage such as arrow keys appearing as `^[[`.
128
- - Raw TTY reads keep polling briefly across empty reads while decoding
129
- escape-key sequences, so split arrow/Alt key bytes are consumed by the picker
130
- instead of leaking into the query or parent shell.
131
-
132
- ## Experimental Boundaries
133
-
134
- - The `projmuxpicker` package is intended as a foundation that can be carried
135
- forward, along with the compatibility option/result mapping in
136
- `internal/ui/pickercompat`.
137
- Its frame, row, preview, theme, ANSI, and redraw modules are foundation code;
138
- Docker sandbox scripts and dependency-policy notes remain support
139
- scaffolding.
140
- - Switch and sessions preview panes are native previews for the concrete
141
- projmux option shapes. Wide right-side preview windows render beside the
142
- list, and sidebar-style `down,25%,border-top` previews render below the list
143
- without a synthetic preview title row, using fzf-measured percent sizing.
144
- Preview rows are padded by `projmuxpicker` to the full preview surface width
145
- before the outer frame is applied, which keeps split/down preview columns
146
- visually stable during redraws.
147
- The full fzf preview-window grammar remains outside this POC surface.
148
- - Preview cycle state is covered in Docker e2e against real tmux sessions: the
149
- switch and sessions popup flows type a filtered query, send `Right` and
150
- `Alt-Down`, and assert the stored preview window/pane cursor for the selected
151
- session.
152
- - Public doctor dependency policy no longer includes an external picker binary.
153
-
154
- ## Interactive No-fzf Sandbox
155
-
156
- Use this when you want to enter a Docker container and experience this build
157
- directly without `fzf`. It builds the no-fzf dependency image, mounts this
158
- worktree, builds `projmux` inside the container, creates sample projects under
159
- `/workspace/projects`, stores the native picker backend in the sandbox config,
160
- writes a tmux config with the same backend in the tmux server environment, and
161
- launches `projmux shell`. It also forces UTF-8 locale inside the container. This
162
- uses `wt path` instead of `wt run` because `docker run -it` needs the current
163
- terminal TTY:
164
-
165
- ```sh
166
- bash "$(wt path poc/native-picker-no-fzf)/scripts/poc-native-picker-no-fzf-sandbox.sh"
167
- ```
168
-
169
- Inside the tmux shell, try:
170
-
171
- ```sh
172
- projmux switch
173
- projmux settings
174
- projmux doctor --json
175
- ```
176
-
177
- Manual UX checks for the Docker sandbox:
178
-
179
- - Alt-1 opens with the top border/title visible, not clipped.
180
- - Vertical borders stay continuous while moving Up/Down.
181
- - Alt-1 closes the sidebar immediately when pressed again.
182
- - Alt-2, Alt-4, Alt-5, and Alt-7 open their matching native popups and close
183
- on the same Alt key immediately.
184
- - Alt-3 opens Recent Windows.
185
- - Arrow keys move selection without leaking `^[[` text into the query.
186
-
187
- `fzf` is intentionally not installed in the image.
188
-
189
- ## Automated No-fzf Docker E2E Command
190
-
191
- Run this from the repository root. It builds a Go 1.25 Trixie no-fzf
192
- dependency image from `test/docker/no-fzf-poc.Dockerfile`, including Go module
193
- cache, then mounts the repository into an isolated `--network none` container,
194
- builds `projmux`, asserts `fzf` is not on `PATH`, runs the focused native-picker
195
- tests, opens Settings > Labs with legacy `fzf` env/file values, verifies native
196
- operation without a Labs picker-source row or config rewrite, exercises `projmux switch --ui=sidebar`
197
- search/selection under a container PTY,
198
- exercises `projmux switch --ui=popup` and `projmux sessions --ui=popup` against
199
- existing tmux sessions under a wide 150x30 PTY, sends `Right` and `Alt-Down`
200
- once to smoke the preview-cycle bindings, asserts those popup flows stay on the
201
- right-side preview layout instead of falling back to inline preview, asserts the
202
- explicit `Search` chrome across the searchable native picker surfaces, launches
203
- `projmux shell` under a container PTY, verifies
204
- that it creates a tmux session, verifies immediate launch-key close behavior for
205
- Alt-1 through Alt-5 native popup surfaces, exercises `notify list --ui=sidebar`
206
- with the printable `x` expect key, and exercises the settings picker under a PTY
207
- using Enter and arrow-key navigation through the native backend. Settings flows
208
- must keep stderr clean in the no-tmux-server container; tmux no-server noise is
209
- treated as an e2e failure.
210
-
211
- Short tmux-friendly form:
212
-
213
- ```sh
214
- wt run poc/native-picker-no-fzf -- scripts/poc-native-picker-no-fzf-e2e.sh
215
- ```
216
-
217
- The script contains the Docker image build and isolated `docker run` command:
218
-
219
- ```sh
220
- scripts/poc-native-picker-no-fzf-e2e.sh
221
- ```
222
-
223
- To compare another base image without editing the repo, override the build arg:
224
-
225
- ```sh
226
- PROJMUX_POC_NO_FZF_BASE_IMAGE=golang:1.24-bookworm bash "$(wt path poc/native-picker-no-fzf)/scripts/poc-native-picker-no-fzf-sandbox.sh"
227
- ```