projmux 0.9.0 → 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.
@@ -87,7 +87,7 @@ Files:
87
87
 
88
88
  - `internal/ui/projmuxpicker/*`
89
89
  - `internal/ui/render/*`
90
- - `docs/native-picker-no-fzf-poc.md`
90
+ - `docs/native-picker.md`
91
91
 
92
92
  Classification:
93
93
 
@@ -194,7 +194,7 @@ Settings > Keybindings stays a discovery surface. It must continue to expose
194
194
  launch toggles, sidebar keymap actions, picker-local actions, pane switching,
195
195
  window switching, and rename actions. The basic Settings flow is not the
196
196
  terminal remediation surface: key-role replacement, disable-default, typed
197
- fallback, terminal mapping preview/apply, and init execution rows stay out of
197
+ fallback and terminal mapping preview/apply rows stay out of
198
198
  the action detail.
199
199
 
200
200
  The product model does not support `UserN` or `CSI-u` as fallback guidance.
@@ -242,7 +242,10 @@ Settings is the default apply path for key edits: it writes the key list,
242
242
  refreshes the generated config, and reloads the running tmux session when
243
243
  possible. Use `projmux tmux apply` as a CLI recovery/sync command after editing
244
244
  the keymap file by hand, after an outside-tmux Settings save, or after resolving
245
- a reported generated-config or live-reload failure.
245
+ a reported generated-config or live-reload failure. Generated config first
246
+ unbinds the known retired `C-t` pane-label chord, then installs the current
247
+ keymap; an explicit current `C-t` assignment therefore wins without retaining
248
+ the retired command body. Apply does not rewrite `keymap.toml`.
246
249
 
247
250
  ## Keymap File
248
251
 
@@ -0,0 +1,103 @@
1
+ # Native Picker
2
+
3
+ The native picker is the product picker for every interactive selection flow.
4
+ There is no runtime backend selection, saved picker selector, or external
5
+ picker process. `internal/ui/picker` owns the interaction contract and
6
+ `internal/ui/projmuxpicker` owns its visual composition.
7
+
8
+ ## Product Contract
9
+
10
+ | Area | Behavior | Primary evidence |
11
+ | --- | --- | --- |
12
+ | Items and results | Structured items keep display labels, stable values, optional search text, multi-line metadata, and action results separate. `internal/ui/pickercompat` maps older internal option/result shapes into this contract; it is not a runtime backend. | `TestPickerOptionsFromCompatPickerMapsCandidatesWhenEntriesAreEmpty`; `TestPickerOptionsFromCompatPickerPreservesTheme` |
13
+ | Search | Lower-case queries are case-insensitive, uppercase queries are case-sensitive, hidden values remain searchable when no explicit search key exists, explicit search-key lists preserve caller order, and simple rows receive fuzzy ranking and match highlighting. | `TestFilterItemsUsesSmartCase`; `TestFilterItemsSearchesHiddenValueWhenNoSearchKey`; `TestFilterItemsPreservesSearchKeyOrder`; `TestFilterItemsRanksBetterMatchesFirst` |
14
+ | Navigation | Up/Down, Ctrl-J/Ctrl-N, and Ctrl-K/Ctrl-P wrap selection; PageUp/PageDown and Home/End clamp or jump; empty lists remain safe. Ctrl-N and Ctrl-P remain real navigation keys unless a caller claims the key as a custom action. | `TestNativeInteractiveSupportsControlNavigationKeys`; `TestNativeInteractiveWrapsPreviousNavigationKeys`; `TestNativeInteractiveWrapsNextNavigationKeys`; `TestNativeInteractiveNavigationKeysAreNoopForEmptyList`; `TestNativeInteractiveJumpNavigationRemainsClamped` |
15
+ | Editing | Left/Right, Ctrl-A/E, Delete, Backspace, Ctrl-U/W, printable input, accept-query mode, and a visible query cursor are supported. | `TestNativeInteractiveEditsTypedQueryAtCursor`; `TestNativeInteractiveSupportsQueryLineEditingKeys`; `TestNativeInteractiveCtrlUDeletesBeforeCursor`; `TestNativeRunnerAcceptsTypedQuery` |
16
+ | Actions | Enter accepts, shared close actions abort, printable/control expect keys return stable action keys, command actions can refresh previews, and mutable actions can update items without restarting the picker. | `TestNativeRunnerUsesSharedCloseActions`; `TestNativeInteractiveSupportsPrintableExpectKeys`; `TestNativeInteractiveRunsCustomActionCommandAndRefreshes`; `TestNativeInteractiveCustomActionMutatesItemsAndRefreshes` |
17
+ | Deferred state | Deferred and event-triggered updates preserve query and selection by value, can repeat, and may explicitly choose a new focus value. | `TestNativeInteractiveDeferredUpdateTriggerRefreshesRepeatedly`; switch and notify sidebar mutable-refresh app tests |
18
+ | Preview | Popup previews use a right split, sidebar previews use a bottom split, control bytes and tabs are normalized before clipping, and preview scrolling/cycling rerenders in place. | `TestNativeInteractiveRendersWidePreviewBesideList`; `TestNativeInteractiveRendersDownPreviewBelowList`; `TestRenderSplitPreviewRowsNormalizesPreviewTabsBeforeTruncating`; `TestNativeInteractiveRendersPreviewOffset` |
19
+ | Mouse | SGR mouse input focuses on primary down, follows drag, accepts on matching release, and scrolls with the wheel. | `TestNativeInteractiveSelectsOnMouseRelease`; `TestNativeInteractiveMouseDragFollowsSelection`; `TestNativeInteractiveSupportsMouseWheelSelection` |
20
+ | Lifecycle | Interactive runs use the alternate screen, synchronized/coalesced frame updates, controlling-TTY fallback, and deterministic reader cleanup. | `TestNativeInteractiveUsesAlternateScreen`; `TestNativeInteractiveWrapsRedrawsInSynchronizedUpdates`; picker/setup lifecycle tests in `docs/agent-workflow.md` |
21
+
22
+ ## Rendering And Popup Chrome
23
+
24
+ The renderer owns rounded frames, optional titlebars and chips, search/header
25
+ separators, footer placement, selected-row styling, proportional scrollbars,
26
+ multi-line gaps, preview geometry, ANSI restoration, and terminal-cell width for
27
+ CJK text, emoji, and combining marks. Frame-owned cells inherit the effective
28
+ theme's `surface` and `chrome_foreground` values, including padding after
29
+ embedded resets.
30
+
31
+ Picker popups are always borderless tmux popups (`display-popup -B`) so the
32
+ native renderer owns the visible frame. The popup body receives a per-command
33
+ style derived from the effective theme; no global popup, pane, window, shell,
34
+ or status style is mutated. Project sidebars use a `20%` width with a `40`
35
+ column minimum, notification sidebars use `24%` with a `64` column minimum,
36
+ and both reserve the two statusbar rows when client height is known.
37
+
38
+ Primary evidence includes `TestAppRunTmuxPopupToggleUsesBorderlessNativePopup`,
39
+ `TestAppRunTmuxPopupToggleUsesNativePopupBodyStyleFromGlobalTheme`,
40
+ `TestBuildPopupToggleAppliesNativeBodyStyle`,
41
+ `TestSessionizerSidebarWidthUsesCompactMinimum`,
42
+ `TestNotifySidebarWidthUsesProductContract`, and
43
+ `TestSidebarPopupHeightLeavesStatusbarRows`.
44
+
45
+ ## Fuzzy Scoring Provenance
46
+
47
+ The native fuzzy scorer intentionally follows the fzf V2 dynamic-scoring
48
+ algorithm for the maintained reference cases in `internal/ui/picker/backend_test.go`.
49
+ `TestFuzzyScoreMatchesFZFV2ReferenceScores` and
50
+ `TestFuzzyScoreRejectsFZFV2ReferenceNonMatches` are provenance fixtures; keep
51
+ their expected scores stable when changing filtering internals.
52
+
53
+ ## Retired Artifact Contract
54
+
55
+ The retired picker selector environment variable and saved selector filename
56
+ are not runtime inputs. Their presence must not cause lookup, file reads,
57
+ warnings, deletion, rewriting, propagation to child popups, or any change to
58
+ the native path. There is deliberately no alias, migration, or stale-value
59
+ cleanup behavior.
60
+
61
+ Coverage is split by boundary:
62
+
63
+ - `TestRunNativePickerOptionDoesNotObserveRetiredBackendArtifacts` guards the
64
+ direct picker path, fails if the retired env name is queried, and verifies a
65
+ stale file is neither deleted nor replaced.
66
+ - `TestAppRunSwitchUsesNativePickerWithoutBackendLookup` guards the ordinary
67
+ switch flow.
68
+ - `TestAppRunTmuxPopupToggleIgnoresRetiredBackendArtifacts` and
69
+ `TestBuildPopupTogglePropagatesNativePickerEnvironmentWithoutRetiredBackend`
70
+ guard popup chrome and child-environment construction.
71
+ - `test/e2e/linux-smoke.sh` launches the ordinary attached-client Settings
72
+ popup with stale env/file values, completes the recorder flow, verifies the
73
+ file inode/content are unchanged, and rejects visible compatibility output.
74
+
75
+ ## Removed PoC Coverage Mapping
76
+
77
+ The retired standalone dependency sandbox and focused smoke duplicated product
78
+ coverage or tested a dependency policy that no longer exists. Its valid
79
+ behavior assertions remain covered as follows:
80
+
81
+ | Removed focused assertion | Maintained coverage |
82
+ | --- | --- |
83
+ | Settings and Labs render through the native picker | `TestSettingsUsesNativePicker`; `TestSettingsHubKeepsLabsSectionWithoutRetiredPickerChoices`; ordinary attached-client Settings recorder in `test/e2e/linux-smoke.sh` |
84
+ | Search chrome, smart-case filtering, native frame ownership, and full-height rendering | `TestNativeInteractiveSeparatesSearchHeaderFromList`; `TestFilterItemsUsesSmartCase`; `TestNativeInteractiveRendersBorderFrame`; `TestNativeInteractiveUsesAvailableHeightForSimpleLists` |
85
+ | AI Settings maps a typed selection to the saved mode | `TestAISettingsPickerSetsSelectedMode`; native picker option/result mapping tests |
86
+ | Switch popup/sidebar filtering, selection, preview cycling, and initial focus | `TestAppRunSwitchDefaultsToPopupAndOpensSelectedSession`; `TestSwitchCommandSupportsSidebarUI`; preview cycle and initial-position tests in `internal/app/switch_test.go` |
87
+ | Sessions popup filtering, selection, and preview cycling | `TestAppRunSessionsDefaultsToPopupAndOpensSelectedSession`; session popup preview/cycle tests in `internal/app/session_popup_test.go` |
88
+ | Notification sidebar navigation and mutable actions | `TestNotifySidebarUsesNativePicker`; notify sidebar ack/clear/deferred-refresh app tests |
89
+ | Launch-key close behavior and Ctrl-N/Ctrl-P navigation | `TestNativeInteractiveClosesOnMatchingLaunchCloseKey`; `TestNativeInteractiveSupportsControlNavigationKeys`; empty-list and wrap tests |
90
+ | Mouse selection and alternate-screen restoration | native mouse and lifecycle tests listed above |
91
+ | Generated shell config and the Alt-1 project-sidebar popup path | `TestShellWritesAppConfigAndRunsIsolatedTmux`; `TestAppRunTmuxPopupToggleOpensStandaloneSidebar`; popup frame/body-style tests listed above |
92
+ | Stale selector values do not affect the product | retained and strengthened in the unit/app/e2e negative coverage listed in the previous section |
93
+
94
+ The dependency-absence assertion is now a source-residue property rather than
95
+ a separate container scenario: production code contains no selector, resolver,
96
+ saved-selector access, propagation, or external picker launch path.
97
+
98
+ ## Maintenance
99
+
100
+ Update this document and the maintained list in
101
+ [`docs/agent-workflow.md`](agent-workflow.md) whenever picker behavior changes
102
+ coverage level, gains a new product flow, or changes input/render/action
103
+ semantics.
@@ -2,15 +2,19 @@
2
2
 
3
3
  Projmux records a small local-only operational journal so command failures and
4
4
  state changes can be inspected after the originating process exits. It does
5
- not upload the journal, create support archives, contact an issue tracker, or
6
- provide a background telemetry service.
5
+ not upload the journal, contact an issue tracker, or provide a background
6
+ telemetry service. A support archive is created only by an explicit
7
+ `projmux diagnostics report` invocation and is never transmitted.
7
8
 
8
9
  ## Safe event contract
9
10
 
10
11
  Each JSONL record has a closed schema: `at`, `level`, `component`, `event`,
11
12
  `result`, `duration_ms`, `run_id`, `version`, `mux_backend`, and optional
12
13
  allowlisted `command`, `subcommand`, `kind`, and sanitized `message`. There is
13
- no generic metadata map.
14
+ no generic metadata map. Runtime lifecycle records add only closed
15
+ `operation` and `code` enums. The allowed operations are session create,
16
+ attach, switch, kill, and tmux apply; codes are stable failure/health
17
+ classifications and never carry routing identity or subprocess details.
14
18
 
15
19
  Command and subcommand names come from static allowlists. Unknown argv values,
16
20
  paths, flags, and arguments are dropped. Messages have control/format
@@ -25,6 +29,28 @@ pane captures/output/title/topic/content, transcripts, raw hook payloads,
25
29
  configuration secrets, or arbitrary environment values. Phase 0 also does not
26
30
  add session/window/pane or other routing identifiers.
27
31
 
32
+ One explicit state-changing command owns at most one lifecycle pair. Its
33
+ `lifecycle.start` and `lifecycle.outcome` share the process `run_id`, and a
34
+ composite create-then-attach/switch flow keeps the first real mutation as its
35
+ operation instead of recording nested outcomes. Lifecycle ownership replaces
36
+ the generic top-level `command.outcome`; it never duplicates it. Start/outcome
37
+ append failures are ignored and do not change the command result.
38
+
39
+ The diagnostics package exposes a typed `ReadRuntimeHealth` projection for
40
+ read-only Doctor consumers. It reports the fixed `tmux` backend, latest
41
+ socket/apply state, and a bounded tail/count of safe failures using only
42
+ `Store.ReadOnly`; it does not create, chmod, lock, truncate, apply, restart, or
43
+ repair anything. Doctor schema 2 consumes that seam for its `logs` findings
44
+ and adds one fixed-argv, one-second `tmux -L projmux show-options` probe for
45
+ actual socket/config health. The probe neither generates nor applies config.
46
+ Its captured output is capped at 4 KiB. Doctor reads only a pre-existing
47
+ regular generated config (at most 1 MiB) without following symlinks, and the
48
+ shared read-only journal seam rejects non-regular inputs and files above 5 MiB.
49
+ These conditions degrade to typed findings rather than blocking or repairing
50
+ the source. Windows ACL privacy is reported as unverified because `os.FileMode`
51
+ cannot prove it; a separate finding preserves the metadata-only writability
52
+ result, and Doctor does not modify ACLs.
53
+
28
54
  ## Storage and retention
29
55
 
30
56
  The path is
@@ -54,9 +80,13 @@ arm`, `attention clear`, `attention window`, `tmux autosave-session-state`, and
54
80
  to the journal; an error from any of them still records exactly one safe error
55
81
  outcome. Explicit user mutations such as `attention toggle` retain their
56
82
  state-changing success record. Direct top-level help and explicit preview-only intents (`upgrade
57
- --dry-run`, `update apply --dry-run`, `doctor --install-missing --dry-run`, AI
58
- integration dry-runs, and the currently preview-only session restore) are also
59
- read-only. Multi-mode commands such as AI status/topic, doctor install,
83
+ --dry-run`, `update apply --dry-run`, AI integration dry-runs, and the
84
+ currently preview-only session restore) are also read-only. Doctor is a stricter
85
+ boundary: successes and errors never append to this journal, so diagnostics do
86
+ not make its filesystem contract self-defeating. Support report success and
87
+ errors likewise never append; its strict reader shares the viewer's tolerant
88
+ decoder but never creates/locks/chmods/repairs/truncates the source journal.
89
+ Multi-mode commands such as AI status/topic,
60
90
  terminal apply, snapshot delete, update check, and welcome popup inspect only
61
91
  allowlisted mode/flag names; boolean `=false` values retain mutation-capable
62
92
  classification, and no flag values are ever recorded. Help-looking tokens
@@ -78,3 +108,14 @@ recursion loop.
78
108
  The older bounded `ai-ingest.log` and subsystem-specific `PROJMUX_*_DEBUG`
79
109
  surfaces retain their current paths, formats, and behavior. They are not
80
110
  migrated by this foundation.
111
+
112
+ ## Explicit support report
113
+
114
+ `projmux diagnostics report [--output <path>]` previews and then atomically
115
+ publishes a private local `tar.gz`; see [cli.md](cli.md#diagnostics). The
116
+ manifest records report schema version 2, `default-hash-v1` redaction, every
117
+ included entry, and stable missing/corrupt/permission omission reasons. Doctor
118
+ JSON schema version 2 and the bounded operations decoder are reused rather than
119
+ duplicated. AI ingest contributes count-only allowlisted source/result rows,
120
+ never raw legacy lines. Existing output files survive collisions and partial
121
+ temporary archives are removed.
@@ -24,7 +24,7 @@ feat(ai): add codex split picker keybinding
24
24
  fix(ai): prepend agent bin dir to PATH so node-managed CLIs find node
25
25
  docs(readme): drop Releases and Configuration sections
26
26
  chore: bump release-please manifest to 0.3.0
27
- refactor(picker): collapse duplicate fzf bootstrap code
27
+ refactor(picker): simplify native picker bootstrap code
28
28
  ```
29
29
 
30
30
  Rules:
@@ -90,11 +90,18 @@ The opt-in read-only test below observes an existing socket and emits counts
90
90
  only. It neither captures pane content nor reads process command lines.
91
91
 
92
92
  ```text
93
- PROJMUX_RESOURCE_TMUX_SOCKET=projmux go test \
93
+ PROJMUX_RESOURCE_TMUX_SOCKET=projmux \
94
+ PROJMUX_RESOURCE_EXPECT_PROJECT_ROOT=/path/to/project go test \
94
95
  -run TestResourceAttributionRealTmuxReadOnlySmoke -v \
95
96
  ./internal/integrations/tmux
96
97
  ```
97
98
 
99
+ When `PROJMUX_RESOURCE_EXPECT_PROJECT_ROOT` is set, the smoke additionally
100
+ requires at least one blank explicit anchor to resolve from its pane current
101
+ path and requires the resulting project bucket to contain a pane. Output stays
102
+ bounded to the expected project path and aggregate counts; it does not emit
103
+ session names, pane content, prompts, transcripts, or process command lines.
104
+
98
105
  2026-08-12 result: `panes=8`, `pane_pid_eq_sid=8`, `missing_pids=0`,
99
106
  `attributed_processes=13`, `escaped_boundary=0`, `sampled=474`, `skipped=0`,
100
107
  `race=0`, `permission=0`, `status=ready`. A separate tmux format-only count
@@ -119,6 +126,18 @@ PROJMUX_RESOURCE_TRANSIENT_SMOKE=1 go test \
119
126
  The pane shell remained attributed while its real `setsid` child was counted
120
127
  at the escaped/Other boundary, without reading the child command line.
121
128
 
129
+ The current-path fallback itself has a separate isolated real-tmux smoke. It
130
+ starts with inherited `TMUX`/`TMUX_PANE` removed, uses a dedicated
131
+ `TMUX_TMPDIR` plus `-L` socket, verifies the actual socket path is below that
132
+ temporary root before exact cleanup, and confirms the blank tmux project
133
+ option remains blank after in-memory attribution:
134
+
135
+ ```text
136
+ PROJMUX_RESOURCE_PROJECT_FALLBACK_SMOKE=1 go test \
137
+ -run TestResourceProjectFallbackTransientSmoke -v \
138
+ ./internal/integrations/tmux
139
+ ```
140
+
122
141
  ## Phase 1 inspector
123
142
 
124
143
  The popup retains warming/partial/unavailable and overage states, renders RSS
@@ -127,6 +146,44 @@ samples when it closes. Its non-overlapping default cadence is two seconds;
127
146
  Ctrl-R shares the same scan gate. Selection and query survive refresh by stable
128
147
  row identity, while a vanished row clamps to the nearest valid neighbor.
129
148
  Display labels use label → agent topic → known interactive shell → raw title,
130
- but those values never become ownership keys. Unsupported platforms show an
131
- unavailable reason, not zero metrics. PSS, non-Linux collectors, process-list
132
- drill-down, history, and resource mutation remain outside this contract.
149
+ but those values never become ownership keys. Pane rows and detail reuse that
150
+ identity plus the tmux current command, PID/SID, pane id, and TTY; pane rows
151
+ show attributed process counts while project/window rows retain pane counts.
152
+ Right/Enter move forward, Left moves back (and is a root no-op), and Esc closes
153
+ at every depth. Unsupported platforms show an unavailable reason, not zero
154
+ metrics. PSS, non-Linux collectors, process-list drill-down, history, and
155
+ resource mutation remain outside this contract.
156
+
157
+ Host and attributed CPU/memory use the same semantic classifier as the live
158
+ statusbar: CPU is normal below 70%, warning at 70–89.9%, and critical at 90%
159
+ or above; memory is normal below 75%, warning at 75–89.9%, and critical at 90%
160
+ or above. Values retain the resolved semantic role but omit visible severity
161
+ words. Unknown is rendered as `--`, never as zero; Sample lifecycle and
162
+ freshness remain explicit text.
163
+
164
+ The first paint is a non-actionable warming surface. Completed samples report
165
+ age and fresh/stale state; partial and overage callouts stay bounded to counts
166
+ and aggregate values. Empty and gone scopes are read-only and explain what the
167
+ latest complete sample can no longer open. Automatic refresh runs every two
168
+ seconds; Ctrl-R reports in-progress state while retaining the last complete
169
+ sample. Both paths preserve scope, breadcrumb, query, selection, and the row
170
+ order last computed by Tab. The default order is Name; Tab computes CPU,
171
+ Memory, or Name once from the current sample, while later refreshes update row
172
+ values without silently moving focus. Native synchronized frame diffs repaint
173
+ only changed rows and update state/footer chrome together.
174
+
175
+ The live summary is a fixed five-row bottom dock below the search/list surface:
176
+ one renderer-owned theme-aware divider, then Host, Attributed, Coverage, and
177
+ Sample. It does not scroll or filter with rows. Coverage owns the non-drillable
178
+ Other or current-scope empty/gone explanation; bounded partial/overage details
179
+ stay on Sample. The action footer remains below the dock with its own chrome
180
+ boundary, so diagnostic values and key hints never share a role. The 80x24
181
+ layout retains a navigable list viewport without clipping, border bleed, or a
182
+ second dock divider.
183
+
184
+ Project rows label project paths explicitly. The two attribution buckets keep
185
+ their stable core keys but display `No project match` and `Multiple project
186
+ matches` with bounded explanations. Pane primary identity follows the shared
187
+ label → agent-only AI topic → interactive shell → raw title resolver; pane id,
188
+ process id, and TTY remain labeled secondary details and stable keys are
189
+ unchanged.
@@ -85,8 +85,8 @@ view-first layout:
85
85
  `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/sidebar-startup-picker`.
86
86
  - `Settings > Labs` contains only Live system resources and Project Hooks.
87
87
  Keybindings live at `Settings > Keybindings`; Labs has no visible or hidden
88
- keybindings redirect. Native is the only picker backend, so Labs does not
89
- render picker source/backend information.
88
+ keybindings redirect. The native picker is the product picker, so Labs does
89
+ not render picker source information.
90
90
  - `Settings > Labs > Live system resources` is a direct global on/off toggle
91
91
  for the macOS/Linux/WSL lower-status-row `CPU N% MEM N%` segment. It defaults
92
92
  off, updates live tmux state when toggled, and renders unavailable on
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
@@ -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
@@ -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
@@ -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.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.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.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
  }