projmux 0.8.4 → 0.10.0
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/README-ko.md +2 -2
- package/README.md +6 -5
- package/docs/agent-workflow.md +33 -18
- package/docs/ai-agent-shortcuts.md +25 -0
- package/docs/architecture.md +32 -10
- package/docs/cli.md +342 -78
- package/docs/configuration.md +69 -10
- package/docs/globalization.md +2 -2
- package/docs/hooks.md +68 -25
- package/docs/install.md +2 -2
- package/docs/keybindings.md +27 -17
- package/docs/native-picker.md +103 -0
- package/docs/notify-queue.md +4 -3
- package/docs/npm-distribution.md +2 -2
- package/docs/operational-diagnostics.md +121 -0
- package/docs/pr-guideline.md +1 -1
- package/docs/resource-attribution.md +189 -0
- package/docs/session-restore.md +31 -6
- package/docs/settings-ia.md +52 -12
- package/docs/statusbar.md +31 -4
- package/docs/testing.md +1 -1
- package/docs/tmux-surface-inventory.md +108 -775
- package/docs/upgrading.md +21 -0
- package/docs/usage-tracking.md +91 -28
- package/package.json +5 -5
- package/docs/native-picker-no-fzf-poc.md +0 -227
- package/docs/native-picker-parity.md +0 -153
- package/docs/picker-ui-plan.md +0 -91
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
|
package/docs/usage-tracking.md
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
# Usage tracking
|
|
2
2
|
|
|
3
|
-
`projmux usage` and `projmux status usage` report authoritative
|
|
4
|
-
|
|
3
|
+
`projmux usage` and `projmux status usage` report authoritative fixed-window
|
|
4
|
+
utilisation for Claude/Codex and official named quota buckets for Antigravity.
|
|
5
|
+
`--model all`, the tmux
|
|
5
6
|
HUD, and the statusbar usage popup use Settings > AI Settings > Enabled
|
|
6
|
-
agents as the source of truth, so disabled Claude/Codex providers are
|
|
7
|
+
agents as the source of truth, so disabled Claude/Codex/Antigravity providers are
|
|
7
8
|
not refreshed or rendered on ambient/all surfaces. Explicit read-only
|
|
8
|
-
requests such as `projmux usage --model claude
|
|
9
|
+
requests such as `projmux usage --model claude`, `--model codex`, or
|
|
10
|
+
`--model antigravity`
|
|
9
11
|
still collect and render that provider even when it is disabled.
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
--model antigravity` and ambient all-model table output therefore render an
|
|
18
|
-
explicit unsupported note when Antigravity is enabled. The statusbar usage
|
|
19
|
-
popup shows an `Antigravity ctx ... unsupported` row, while the compact tmux
|
|
20
|
-
status segment stays silent unless Claude/Codex quota rows exist. Projmux does
|
|
21
|
-
not infer quota, reset timestamps, or account limits from screen scraping,
|
|
13
|
+
Claude and Codex adapters read the upstream's own account view. Antigravity
|
|
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,
|
|
22
19
|
tokens, history, OAuth/cache files, or binary strings.
|
|
23
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.
|
|
25
|
+
|
|
24
26
|
## Adapters
|
|
25
27
|
|
|
26
28
|
### Claude (`internal/core/usage/adapters/claude`)
|
|
@@ -48,6 +50,23 @@ floor.
|
|
|
48
50
|
- A clean 200 resets the consecutive counter.
|
|
49
51
|
- `--force` (BackoffResetter) clears the persisted state and attempts
|
|
50
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.
|
|
51
70
|
|
|
52
71
|
### Codex (`internal/core/usage/adapters/codex`)
|
|
53
72
|
|
|
@@ -73,6 +92,28 @@ Codex shares the manager's default `30s` throttle (no
|
|
|
73
92
|
`ThrottleHinter`). It does not implement `BackoffStater` — local-only
|
|
74
93
|
read.
|
|
75
94
|
|
|
95
|
+
### Antigravity (`internal/core/usage/adapters/antigravity`)
|
|
96
|
+
|
|
97
|
+
Local managed-statusline sidecars. No network or credential reads.
|
|
98
|
+
|
|
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.
|
|
102
|
+
- The official `quota` map is sorted by its exact bucket ID. Each valid bucket
|
|
103
|
+
becomes `window=quota`, `bucket=<upstream ID>` and renders as
|
|
104
|
+
`quota/<upstream ID>` on account-inspection surfaces.
|
|
105
|
+
- Used percent is `100 * (1 - remaining_fraction)`. Non-finite or values
|
|
106
|
+
outside `[0,1]`, empty IDs, null/disabled entries, and negative relative
|
|
107
|
+
resets are ignored safely.
|
|
108
|
+
- `reset_time` and optional `reset_in_seconds` are stored independently. An
|
|
109
|
+
absent relative reset differs from explicit zero; no value is derived from
|
|
110
|
+
the other.
|
|
111
|
+
- Context and quota use independent private sidecars. A context-only payload
|
|
112
|
+
does not erase the last quota observation. An explicit empty/null quota map
|
|
113
|
+
records no buckets; the manager's existing rule still preserves prior model
|
|
114
|
+
rows when an adapter returns zero total rows. Context never participates in
|
|
115
|
+
that account-row replacement decision.
|
|
116
|
+
|
|
76
117
|
## Snapshot store
|
|
77
118
|
|
|
78
119
|
```
|
|
@@ -81,7 +122,10 @@ ${PROJMUX_USAGE_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/projmux/usage}/
|
|
|
81
122
|
|
|
82
123
|
JSON document keyed by adapter, recording:
|
|
83
124
|
|
|
84
|
-
- per-window `Snapshot{Model, Window, Pct, Limit, ResetsAt,
|
|
125
|
+
- per-window `Snapshot{Model, Window, Bucket, Pct, Limit, ResetsAt,
|
|
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
|
|
85
129
|
- per-adapter `last_collect` timestamp (drives the throttle)
|
|
86
130
|
- per-adapter `Backoff{Until, Consecutive}` (drives the cooldown)
|
|
87
131
|
|
|
@@ -95,7 +139,7 @@ across machines (Dropbox, iCloud Drive).
|
|
|
95
139
|
### `projmux usage`
|
|
96
140
|
|
|
97
141
|
```
|
|
98
|
-
projmux usage [--model codex|claude|antigravity|all] [--window 5h|weekly|all]
|
|
142
|
+
projmux usage [--model codex|claude|antigravity|all] [--window 5h|weekly|context|quota|all]
|
|
99
143
|
[--json] [--force|-f]
|
|
100
144
|
```
|
|
101
145
|
|
|
@@ -104,11 +148,10 @@ For `--model all`, calls `Manager.Collect` (or `ForceCollect` with
|
|
|
104
148
|
Enabled agents, filters by window, and renders the tab-aligned table:
|
|
105
149
|
|
|
106
150
|
```
|
|
107
|
-
MODEL
|
|
108
|
-
claude
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
codex weekly 4% 2026-05-09T00:00:00+09:00
|
|
151
|
+
MODEL WINDOW PCT RESETS_AT RESET_IN STALE
|
|
152
|
+
claude 5h 80% 2026-05-07T14:00:00+09:00 -
|
|
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 - *
|
|
112
155
|
```
|
|
113
156
|
|
|
114
157
|
`STALE` is `*` when `now - UpdatedAt > 10m`. A `--json` payload returns
|
|
@@ -124,10 +167,12 @@ When no AI agents are enabled, all-model table output contains no
|
|
|
124
167
|
provider rows and prints a short Settings hint. `--json` returns an
|
|
125
168
|
empty array. Explicit `--model claude`, `--model codex` and
|
|
126
169
|
`--model antigravity` bypass the enabled-agent filter for read-only
|
|
127
|
-
inspection and collect/render only the requested adapter. Antigravity
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
170
|
+
inspection and collect/render only the requested adapter. Antigravity reports
|
|
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.
|
|
131
176
|
|
|
132
177
|
### `projmux status usage`
|
|
133
178
|
|
|
@@ -143,12 +188,19 @@ are swallowed unless `PROJMUX_USAGE_DEBUG` is set. Then it loads the
|
|
|
143
188
|
cache, filters to the same enabled-agent scope, and renders. If no AI
|
|
144
189
|
agents are enabled, the status segment emits nothing.
|
|
145
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
|
+
|
|
146
198
|
Output degrades through six tiers as `--max-width` shrinks:
|
|
147
199
|
|
|
148
200
|
1. Long form with last-sync age + bars: `Claude (3m) 5h [████████░░]
|
|
149
|
-
80% · weekly [...]
|
|
201
|
+
80% · weekly [...] Antigravity weekly [...]`
|
|
150
202
|
2. Drop the age indicator (legacy long form).
|
|
151
|
-
3.
|
|
203
|
+
3. Keep one primary bar per provider (`5h`, or `weekly` when 5h is absent).
|
|
152
204
|
4. Drop bars entirely (`Claude 5h:80% weekly:30%`).
|
|
153
205
|
5. Single-letter labels (`C 5h:80% weekly:30%`).
|
|
154
206
|
6. Hard rune-truncate with trailing `…`.
|
|
@@ -167,6 +219,17 @@ sync metadata to the same enabled-agent scope as the ambient HUD. This keeps
|
|
|
167
219
|
tmux click path a structured table with aligned rows, right-aligned numeric
|
|
168
220
|
values, dim unavailable cells, amber usage at 80%, and red usage at 95%.
|
|
169
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
|
+
|
|
170
233
|
The popup sync line uses the maximum authoritative `LastCollect` timestamp from
|
|
171
234
|
the cache. If that field is unavailable, it falls back to the snapshots file
|
|
172
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.
|
|
3
|
+
"version": "0.10.0",
|
|
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.
|
|
32
|
-
"@projmux/linux-arm64": "0.
|
|
33
|
-
"@projmux/darwin-x64": "0.
|
|
34
|
-
"@projmux/darwin-arm64": "0.
|
|
31
|
+
"@projmux/linux-x64": "0.10.0",
|
|
32
|
+
"@projmux/linux-arm64": "0.10.0",
|
|
33
|
+
"@projmux/darwin-x64": "0.10.0",
|
|
34
|
+
"@projmux/darwin-arm64": "0.10.0"
|
|
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 experimental settings, but picker
|
|
28
|
-
backend selection 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.24 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, stores the native backend through Settings > Labs, verifies the saved
|
|
196
|
-
backend works without an env override, 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
|
-
```
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
# Native Picker fzf Parity Map
|
|
2
|
-
|
|
3
|
-
This audit note reverse-engineers the subset of fzf behavior that projmux
|
|
4
|
-
currently uses and maps it to native picker evidence. It supports the default
|
|
5
|
-
native picker engine and is not a public dependency-policy change.
|
|
6
|
-
|
|
7
|
-
## App fzf Surface
|
|
8
|
-
|
|
9
|
-
| fzf surface | projmux usage | Native status | Evidence |
|
|
10
|
-
| --- | --- | --- | --- |
|
|
11
|
-
| `--prompt` | AI, settings, shell update, switch, sessions, notify | Covered | `renderNativeInteractive`, `renderNative`; `TestNativePromptLineIncludesInlineMatchCount` |
|
|
12
|
-
| prompt/list separation | native searchable picker chrome | Covered | native renders an explicit `Search` header label plus a separator under searchable prompt/count lines; `TestNativeInteractiveSeparatesSearchHeaderFromList`; Docker no-fzf e2e asserts the `Search` header in Settings, AI settings, mouse-click, switch sidebar, switch popup, sessions popup, and shell Alt-1 PTY logs |
|
|
13
|
-
| `--height 100%` / `--border` | all interactive picker screens | Covered for fullscreen rounded border frame | `renderNativeFrame`; screen-height list budgeting; conservative 80x24 fallback only when terminal size detection is unavailable; `TestNativeInteractiveRendersBorderFrame`; `TestNativeInteractiveUsesAvailableHeightForSimpleLists` |
|
|
14
|
-
| `--header` | AI, settings, shell update, notify | Covered | `renderNativeInteractive`, `renderNative`; settings native tests |
|
|
15
|
-
| `--footer` / `--footer-border line` | AI, settings, shell update, switch, sessions, notify | Covered for interactive native screens | `renderNativeInteractive` reserves bottom footer space and renders a separator line; `TestNativeInteractiveRendersFooterAtBottom` |
|
|
16
|
-
| `--ansi` | colored row labels from render package | Covered | native writes row labels directly, strips ANSI escapes from default search text, restores selected-row styling after embedded ANSI resets, and measures rendered cell width for Korean/CJK text, emoji, and combining marks; `TestFilterItemsIgnoresANSIEscapeSequences`; `TestNativeInteractiveUsesCurrentStyleForSimpleSelection`; `TestVisibleLenUsesTerminalCellWidth`; Docker e2e shows ANSI rows |
|
|
17
|
-
| hidden value after tab delimiter | all picker selections and default fzf matching | Covered by `picker.Item.Value` and default search text | `pickercompat.PickerOptions`; `TestNativeRunnerFiltersAndSelectsByNumber`; `TestFilterItemsSearchesHiddenValueWhenNoSearchKey` |
|
|
18
|
-
| plain fzf candidates without structured entries | compat option call shape | Covered | `pickercompat.PickerOptions`; `TestPickerOptionsFromCompatPickerMapsCandidatesWhenEntriesAreEmpty` |
|
|
19
|
-
| search key filtering (`--nth`/reload filter file) | switch/sessions/notify entries | Covered by `Item.SearchText` with fzf reload order preservation | `FilterItems`; `TestFilterItemsUsesSearchTextNotMetadata`; `TestFilterItemsPreservesSearchKeyOrder` |
|
|
20
|
-
| default `--smart-case` matching | all searchable picker rows | Covered | native filter keeps lower-case queries case-insensitive and uppercase queries case-sensitive; `TestFilterItemsUsesFZFSmartCase` |
|
|
21
|
-
| fzf match highlighting | searchable simple picker rows | Covered for non-search-key simple rows | native highlights matched visible label runes while preserving embedded ANSI style; search-key reload lists intentionally keep fzf disabled-filter rendering without match highlights; `TestNativeInteractiveHighlightsSimpleQueryMatches`; `TestNativeInteractiveDoesNotHighlightSearchKeyReloadLists` |
|
|
22
|
-
| `--disabled --no-input` | navigation-only notify sidebar | Covered | native suppresses prompt/query editing and ignores printable non-action input; `TestNativeInteractiveDisableSearchIgnoresPrintableInput`; Docker no-fzf e2e asserts notify prompt is hidden |
|
|
23
|
-
| fuzzy result ranking | simple non-search-key picker UX | Covered with fzf V2 dynamic scoring for normal app rows | `fuzzyScore`; `TestFilterItemsRanksBetterMatchesFirst`; `TestFilterItemsPrefersFZFBoundaryAndCamelCaseMatches`; `TestFuzzyScoreMatchesFZFV2ReferenceScores` |
|
|
24
|
-
| `--scrollbar █` | long switch/session/settings lists | Covered for app lists | `nativeListLinesWithScrollbarRows`; proportional multi-row thumb rendering in `projmuxpicker`; split-preview and sidebar list viewports keep a fixed row budget and scrollbar track; multi-line cards use rendered-row scroll units; `TestNativeInteractiveUsesScrollbarForLongLists`; `TestListLinesWithScrollbarUsesProportionalThumb`; `TestListLinesWithScrollbarMovesThumbGradually`; `TestListLinesWithScrollbarRowsKeepsViewportTrack`; `TestRenderSplitPreviewRowsKeepsRequestedViewport`; `TestNativeListScrollbarUnitsUseRenderedRowsForMultiline`; `TestNativeInteractiveKeepsMultilineScrollbarOnViewportTrack` |
|
|
25
|
-
| `--read0` multi-line rows | switch, sessions, notify | Covered | `Options.MultiLine`; `TestNativeInteractiveRendersFZFLikeMultilineSelection` |
|
|
26
|
-
| `--gap --gap-line ─` | switch, sessions, notify multi-line rows | Covered for app multiline rows | `nativeGapLine`, row-budgeted range; `TestNativeInteractiveRendersMultilineGapLine`, `TestNativeVisibleRangeCountsMultilineRenderedRows` |
|
|
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
|
-
| 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
|
-
| `--expect` keys | Enter/Ctrl-X/Alt-P/notify keys | Covered | `pickercompat.PickerOptions`; `TestNativeInteractiveSupportsCustomExpectKeys` |
|
|
30
|
-
| printable expect keys | notify sidebar `a` ack and `x` non-critical clear | Covered | `TestNativeInteractiveSupportsPrintableExpectKeys`; Docker no-fzf e2e |
|
|
31
|
-
| control expect keys | notify sidebar `Ctrl-X`, settings `Ctrl-Alt-S` close | Covered | `TestNativeInteractiveSupportsControlExpectKeys`; `TestNativeInteractiveSupportsControlAltCloseKeys` |
|
|
32
|
-
| close `--bind key:abort` | Esc, Ctrl-C, Alt-N, Ctrl-Alt-S variants | Covered | `CloseActions`; `TestNativeRunnerUsesSharedCloseActions` |
|
|
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
|
-
| `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
|
-
| action-local/event-backed mutable refresh | notify sidebar `a` ack and `x` non-critical clear; notify sidebar queue-write event refresh; switch sidebar `Ctrl-X` kill | Covered for in-session row/live-state refresh without picker restart | `picker.Action.Mutate` returns a `DeferredUpdate`, notify queue-write events trigger the same `DeferredUpdate` path after an event arrives, and both reuse the native frame diff renderer plus value-then-clamp selection preservation; `TestNativeInteractiveCustomActionMutatesItemsAndRefreshes`; `TestNativeInteractiveCustomActionRefreshPreservesSelectedValue`; `TestNativeInteractiveDeferredUpdateTriggerRefreshesRepeatedly`; notify sidebar app tests assert one picker invocation with refreshed rows/live state and event subscription; switch sidebar kill app tests assert one native picker invocation with refreshed rows/preview and previous-live-session guard preservation |
|
|
36
|
-
| `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` |
|
|
37
|
-
| `start:pos(N)` | switch sidebar initial row | Covered | `pickercompat.PickerOptions`/`pickercompat.OptionsFromPicker`; `TestPickerOptionsFromCompatPickerMapsStartPosToInitialIndex`; `TestPickerOptionsMapsCompatBindingsToContractActions` |
|
|
38
|
-
| `--preview` | switch, sessions | Covered by command output | `nativePreviewLines`; `TestNativeInteractiveRendersSelectedPreview` |
|
|
39
|
-
| `--preview-window right,60%,border-left` | switch popup, sessions popup | Covered for projmux option shape | `renderNativeSplitPreview` renders a single-column left border without a synthetic title row, uses fzf-measured percent sizing, normalizes preview tabs/control bytes before clipping, and pads both panes to fixed row widths so long preview rows do not wrap into extra vertical rows; `TestNativeInteractiveRendersWidePreviewBesideList`; `TestNativePreviewWidthUsesPreviewWindowPercent`; `TestRenderSplitPreviewRowsPadsBothPanes`; `TestRenderSplitPreviewRowsNormalizesPreviewTabsBeforeTruncating` |
|
|
40
|
-
| `--preview-window down,25%,border-top` | switch sidebar | Covered for projmux option shape | `renderNativeDownPreview` renders an immediate top border without a synthetic title row, uses fzf-measured percent sizing, and pads preview rows to the surface width; `TestNativeInteractiveRendersDownPreviewBelowList`; `TestNativePreviewHeightUsesPreviewWindowPercent`; `TestRenderDownPreviewPadsPreviewRows` |
|
|
41
|
-
| preview scrolling | long switch/session preview output | Covered for keyboard preview scroll | `previewOffset`; `TestNativeInteractiveRendersPreviewOffset` |
|
|
42
|
-
| `--query` | typed settings path defaults | Covered | `Options.InitialQuery`; settings tests |
|
|
43
|
-
| `--print-query` accept-query mode | typed settings path prompts | Covered | `Options.AcceptQuery`; `TestNativeRunnerAcceptsTypedQuery` |
|
|
44
|
-
| 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` |
|
|
45
|
-
| 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` |
|
|
46
|
-
| 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 |
|
|
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
|
-
| 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
|
-
| 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 `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
|
-
| 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
|
-
|
|
55
|
-
## Native Surface Architecture
|
|
56
|
-
|
|
57
|
-
- `internal/ui/picker` remains the backend-neutral contract and owns native
|
|
58
|
-
routing, keyboard input, fuzzy filtering, action dispatch, preview command
|
|
59
|
-
execution, and result handling.
|
|
60
|
-
- `internal/ui/projmuxpicker` owns projmux-native visual composition: frame,
|
|
61
|
-
redraw updates, ANSI width/truncation, theme tokens, prompt/footer/list
|
|
62
|
-
rendering, selected row styling, scrollbars/gap rows, and preview pane
|
|
63
|
-
geometry/rendering.
|
|
64
|
-
- `internal/ui/pickercompat` remains as the internal compatibility option/result
|
|
65
|
-
mapper from older app option shapes to `picker.Options` for the native
|
|
66
|
-
backend. It is not a runtime backend. This keeps app code closer to a
|
|
67
|
-
DI-style picker contract instead of embedding binding strings at each call
|
|
68
|
-
site.
|
|
69
|
-
- Settings > Labs remains available, but picker backend selection has been
|
|
70
|
-
retired. Deprecated saved/env backend values normalize to native.
|
|
71
|
-
- The split lets projmux grow a first-party picker design independently from
|
|
72
|
-
the compatibility option/result mapper.
|
|
73
|
-
|
|
74
|
-
## Frame Chrome ANSI
|
|
75
|
-
|
|
76
|
-
Native picker frame chrome is normalized in `internal/ui/projmuxpicker`.
|
|
77
|
-
Titlebar text, title dividers, and chip-strip gaps inherit the frame
|
|
78
|
-
`surface` / `chrome_foreground` instead of applying a second titlebar overlay ANSI layer;
|
|
79
|
-
chip bodies can still carry active/inactive/disabled tones. Search prompt and
|
|
80
|
-
footer separators fill the available frame width, and header, row, footer, and
|
|
81
|
-
preview lines close any active SGR style before padding or frame borders can
|
|
82
|
-
inherit it. This phase does not add popup modes or change the `popup-toggle`
|
|
83
|
-
contract; native popups still rely on the existing borderless tmux popup path.
|
|
84
|
-
|
|
85
|
-
## Verified Flows
|
|
86
|
-
|
|
87
|
-
- `ai` picker/settings: native backend routing covered by app tests. Docker
|
|
88
|
-
no-fzf e2e also types `Codex` into `projmux ai settings` and verifies the
|
|
89
|
-
native simple picker writes the `codex` mode without fzf.
|
|
90
|
-
- `shell` update prompt: native backend routing covered by shared compat-to-native
|
|
91
|
-
bridge and settings-style typed prompt tests.
|
|
92
|
-
- `settings`: native backend exercised in unit tests and Docker no-fzf e2e
|
|
93
|
-
using Enter plus arrow-key navigation under a PTY. The Docker e2e also fails
|
|
94
|
-
if the Settings flows write tmux no-server noise to stderr while running
|
|
95
|
-
outside tmux.
|
|
96
|
-
- `settings > Labs`: unit-covered backend toggle writes
|
|
97
|
-
`~/.config/projmux/picker-backend`, updates the tmux global
|
|
98
|
-
`PROJMUX_PICKER_BACKEND`, and lets env override saved config.
|
|
99
|
-
- `switch --ui=sidebar`: Docker no-fzf e2e creates sample projects, types
|
|
100
|
-
`bravo`, selects `bravo-web`, and confirms the opened tmux shell path.
|
|
101
|
-
- `switch --ui=popup`: Docker no-fzf e2e creates existing tmux sessions using
|
|
102
|
-
the app's session naming convention, runs the picker under a 150x30 PTY,
|
|
103
|
-
types `bravo`, sends `Right` and `Alt-Down` to exercise preview window/pane
|
|
104
|
-
cycle, asserts the popup stays on the right-side preview layout instead of
|
|
105
|
-
inline preview, asserts the stored preview cursor, selects `bravo-web`, and
|
|
106
|
-
asserts tmux reports the selected session's active target on the expected
|
|
107
|
-
window with the expected pane path.
|
|
108
|
-
- `sessions --ui=popup`: Docker no-fzf e2e creates existing tmux sessions,
|
|
109
|
-
runs the picker under a 150x30 PTY, types `bravo`, sends `Right` and
|
|
110
|
-
`Alt-Down` to exercise preview window/pane cycle, asserts the popup stays on
|
|
111
|
-
the right-side preview layout instead of inline preview, asserts the stored
|
|
112
|
-
preview cursor, selects `bravo-web`, and asserts tmux reports the selected
|
|
113
|
-
session's active target on the expected window with the expected pane path.
|
|
114
|
-
- `notify sidebar`: native routing is unit-covered; app tests cover
|
|
115
|
-
queue-write event subscription and picker tests cover repeated
|
|
116
|
-
event-triggered deferred refresh. Docker no-fzf e2e pushes a notification,
|
|
117
|
-
presses printable expect key `a`, and verifies the row is acked.
|
|
118
|
-
|
|
119
|
-
## Experimental Boundaries
|
|
120
|
-
|
|
121
|
-
- Preview-window parity is covered for the concrete projmux option shapes
|
|
122
|
-
(`right,60%,border-left` and `down,25%,border-top`) with fzf-measured percent
|
|
123
|
-
sizing. The full fzf preview-window grammar, threshold alternatives, sticky
|
|
124
|
-
headers, and offset expressions are intentionally outside this POC surface.
|
|
125
|
-
- fzf V2 dynamic scoring is covered for normal app-length non-search-key
|
|
126
|
-
rows, including reference scores for boundary, delimiter, camelCase/number,
|
|
127
|
-
gap, and consecutive bonuses. Very large `query * row` matrices intentionally
|
|
128
|
-
fall back to the greedy scorer to avoid pathological memory use. Search-keyed
|
|
129
|
-
app pickers preserve fzf's `--disabled` reload order instead of score-sorting.
|
|
130
|
-
- Mouse support is intentionally narrow in this POC: primary mouse down focuses
|
|
131
|
-
the clicked row, primary mouse up applies it, and wheel input moves selection.
|
|
132
|
-
Drag gestures and the full fzf mouse grammar are follow-up work.
|
|
133
|
-
- The public doctor/docs dependency policy no longer includes an external
|
|
134
|
-
picker binary.
|
|
135
|
-
- Draft PR: https://github.com/crevissepartners/projmux/pull/98.
|
|
136
|
-
|
|
137
|
-
## Commands
|
|
138
|
-
|
|
139
|
-
Automated no-fzf e2e:
|
|
140
|
-
|
|
141
|
-
```sh
|
|
142
|
-
wt run poc/native-picker-no-fzf -- scripts/poc-native-picker-no-fzf-e2e.sh
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Interactive no-fzf sandbox:
|
|
146
|
-
|
|
147
|
-
```sh
|
|
148
|
-
bash "$(wt path poc/native-picker-no-fzf)/scripts/poc-native-picker-no-fzf-sandbox.sh"
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Do not run the interactive sandbox through `wt run`: current `wt run` captures
|
|
152
|
-
child stdio instead of forwarding the caller's TTY, so Docker cannot attach
|
|
153
|
-
`-it` and terminal picker input will not behave like a real session.
|