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.
@@ -0,0 +1,121 @@
1
+ # Operational Diagnostics and Privacy
2
+
3
+ Projmux records a small local-only operational journal so command failures and
4
+ state changes can be inspected after the originating process exits. It does
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.
8
+
9
+ ## Safe event contract
10
+
11
+ Each JSONL record has a closed schema: `at`, `level`, `component`, `event`,
12
+ `result`, `duration_ms`, `run_id`, `version`, `mux_backend`, and optional
13
+ allowlisted `command`, `subcommand`, `kind`, and sanitized `message`. There is
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.
18
+
19
+ Command and subcommand names come from static allowlists. Unknown argv values,
20
+ paths, flags, and arguments are dropped. Messages have control/format
21
+ characters removed, whitespace normalized, the current home path abbreviated
22
+ to `~`, and length capped at 512 Unicode code points. Top-level outcomes never
23
+ copy `error.Error()` into the journal: their message is one of three stable,
24
+ lossy phrases (`command failed`, `invalid command usage`, or a classified
25
+ non-success status). Error `kind` is stored separately from that phrase.
26
+
27
+ The journal must never contain raw argv, stdin, prompts, notification bodies,
28
+ pane captures/output/title/topic/content, transcripts, raw hook payloads,
29
+ configuration secrets, or arbitrary environment values. Phase 0 also does not
30
+ add session/window/pane or other routing identifiers.
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
+
54
+ ## Storage and retention
55
+
56
+ The path is
57
+ `${XDG_STATE_HOME:-$HOME/.local/state}/projmux/logs/operations.jsonl`.
58
+ On POSIX systems the `projmux` state and `logs` directories are private
59
+ (`0700`) and the journal is private (`0600`); accesses make a best-effort
60
+ repair of older permissive modes.
61
+
62
+ Append and trim share an OS-owned advisory inter-process lock. The kernel
63
+ releases ownership when a process exits, so an orphaned lock path needs no
64
+ path deletion or stale-owner reclamation and cannot race a successor owner.
65
+ Lock acquisition has an explicit 200 ms total budget so this side channel
66
+ cannot materially delay the original command result. When the file exceeds
67
+ 5 MiB, a platform-specific atomic replacement retains approximately the
68
+ newest 2 MiB, beginning at a complete valid record; Windows uses replace-
69
+ existing semantics rather than plain rename. A trailing partial record is
70
+ discarded before the next append, and the reader skips malformed or truncated
71
+ records.
72
+
73
+ Classification is intentionally conservative for mutation-capable interactive
74
+ commands: opening session/project/settings/popup flows is treated as changing
75
+ even when a user cancels. Explicit read variants (`status`, `list`, `get`,
76
+ `preview`, config printing, plain welcome, and the diagnostics viewer) remain
77
+ read-only. The successful automatic hook/poll paths `ai ingest`, `attention
78
+ arm`, `attention clear`, `attention window`, `tmux autosave-session-state`, and
79
+ `window record` are also read-only so high-frequency operation does not append
80
+ to the journal; an error from any of them still records exactly one safe error
81
+ outcome. Explicit user mutations such as `attention toggle` retain their
82
+ state-changing success record. Direct top-level help and explicit preview-only intents (`upgrade
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,
90
+ terminal apply, snapshot delete, update check, and welcome popup inspect only
91
+ allowlisted mode/flag names; boolean `=false` values retain mutation-capable
92
+ classification, and no flag values are ever recorded. Help-looking tokens
93
+ after the direct command position stay conservatively mutation-capable because
94
+ they may be values rather than help intent.
95
+
96
+ Failures to resolve the path, create/repair permissions, lock, append, or trim
97
+ are ignored by the top-level command boundary. They do not change the original
98
+ command's stdout, stderr, exit code, or success/failure meaning, and journal
99
+ failures are never recursively journaled.
100
+
101
+ ## Inspecting records
102
+
103
+ Use `projmux diagnostics log`; see [cli.md](cli.md#diagnostics). All text,
104
+ JSONL, tail, and filter views consume the same tolerant reader. A successful
105
+ viewer read is excluded from success logging, so inspection does not create a
106
+ recursion loop.
107
+
108
+ The older bounded `ai-ingest.log` and subsystem-specific `PROJMUX_*_DEBUG`
109
+ surfaces retain their current paths, formats, and behavior. They are not
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:
@@ -0,0 +1,189 @@
1
+ # Linux resource attribution core
2
+
3
+ Phase 0 provides the read-only attribution contract consumed by the Resource
4
+ Inspector shipped in Phase 1. `projmux resources`, the client-scoped
5
+ `resource-inspector` popup, the statusbar range, and `Resources:Open` all keep
6
+ the snapshot in memory only for the interactive process lifetime; it remains
7
+ outside Session State.
8
+
9
+ ## Identity and inventory
10
+
11
+ `tmux.Client.ListResourcePanes` reads a resource-specific inventory containing
12
+ socket path, session id/name, window id, pane id, pane PID/TTY, and the session
13
+ `@projmux_project_path` anchor. This is deliberately separate from the general
14
+ `tmux.Pane` inventory so the resource contract requires PID, TTY, and project
15
+ anchor data without weakening other pane consumers.
16
+
17
+ The ownership key is `(socket, pane_id)`. Process identity is `(PID,
18
+ /proc/<pid>/stat starttime)` and a process is attributed only when its POSIX
19
+ SID maps to exactly one unique pane PID. Pane labels, AI topics, raw titles,
20
+ current commands, and cwd-derived names are not ownership inputs.
21
+
22
+ Linked appearances of one pane are deduplicated. Multiple non-empty project
23
+ anchors become `Shared / ambiguous`; no anchor becomes `Unassigned`. Processes
24
+ that use `setsid` or otherwise leave the pane SID remain host-only and are
25
+ counted at the escaped boundary instead of guessed back onto a pane.
26
+
27
+ ## Sampling and read model
28
+
29
+ The Linux collector enumerates `/proc` once per sample, then reads only
30
+ `/proc/stat`, `/proc/meminfo`, and numeric `/proc/<pid>/stat` files. It never
31
+ reads command lines, environment, prompts, pane content, transcripts, memory
32
+ maps, SQLite, protobuf, or remote telemetry.
33
+
34
+ - CPU needs two samples with the same positive logical CPU count. Primary CPU
35
+ is process tick delta divided by aggregate host tick delta (host capacity
36
+ share, normally comparable on a 0–100% scale); secondary CPU is that share
37
+ multiplied by logical CPUs (core-equivalent). First sample, invalid/reset
38
+ deltas, PID reuse, and logical CPU changes remain unknown/partial, never zero
39
+ or silently clamped.
40
+ - Memory is summed RSS bytes plus `RSS / MemTotal`. RSS is a per-process sum;
41
+ shared pages may therefore be counted more than once.
42
+ - Pane values are aggregated into unique window and project rows. Tests enforce
43
+ that window/project totals equal the same set of unique pane totals.
44
+ - `Attributed` is not host total. `Other / unattributed` is a separate host
45
+ remainder. When process-delta timing or RSS sharing makes attributed values
46
+ exceed the host comparison sample, the model exposes an overage and leaves
47
+ remainder unknown instead of clamping it to zero.
48
+ - Snapshot state is `warming`, `ready`, `partial`, or `unavailable`. Bounded
49
+ diagnostics contain scan duration and sampled/skipped/race/permission counts,
50
+ plus identity/delta quality counts; they contain no user payload.
51
+
52
+ ## Measurements
53
+
54
+ Measured 2026-08-12 on Linux amd64, Intel Core Ultra 5 125U. Collector cases
55
+ used generated procfs directory fixtures and `-benchtime=20x`; aggregation used
56
+ `-benchtime=100x`. Commands:
57
+
58
+ ```text
59
+ go test -run '^$' -bench BenchmarkCollectorScan -benchtime=20x -count=1 ./internal/integrations/procfsresources
60
+ go test -run '^$' -bench BenchmarkBuildSnapshot -benchtime=100x -count=1 ./internal/core/resources
61
+ ```
62
+
63
+ | Panes | Processes | Procfs scan | Scan allocation | Aggregation | Aggregation allocation |
64
+ |---:|---:|---:|---:|---:|---:|
65
+ | 10 | 50 | 0.407 ms | 80.6 KB | 0.026 ms | 32.1 KB |
66
+ | 10 | 200 | 0.927 ms | 313.0 KB | 0.074 ms | 75.0 KB |
67
+ | 10 | 1000 | 6.332 ms | 1.56 MB | 0.230 ms | 481.6 KB |
68
+ | 50 | 50 | 0.407 ms | 80.6 KB | 0.084 ms | 90.4 KB |
69
+ | 50 | 200 | 0.927 ms | 313.0 KB | 0.111 ms | 133.2 KB |
70
+ | 50 | 1000 | 6.332 ms | 1.56 MB | 0.261 ms | 539.9 KB |
71
+
72
+ The scan is process-count-bound and does not multiply by pane count. This
73
+ supports the planned 2-second popup cadence without a daemon or persistent
74
+ history. A separate count-only live cost probe (`PROJMUX_RESOURCE_PSS_MEASURE=1
75
+ go test -run TestPSSReadCostMeasurement -v
76
+ ./internal/integrations/procfsresources`) attempted 200 host processes: 53
77
+ `smaps_rollup` reads succeeded and 147 were skipped because of access
78
+ restrictions. Those reads took 92.865058 ms, versus 7.323994 ms for the matching
79
+ one-pass RSS/stat scan. This is not a universal cost multiplier: accessibility,
80
+ process shape, kernel state, and cache effects differ by host. It does show the
81
+ structural cost of a separate `smaps_rollup` read and kernel page accounting per
82
+ accessible process, whereas RSS comes from the stat file already needed for
83
+ CPU/identity. PSS is therefore **deferred**. It may be reconsidered only as an
84
+ on-demand pane-detail measurement with a concrete consumer; it is not part of
85
+ continuous refresh.
86
+
87
+ ## Sanitized real tmux smoke
88
+
89
+ The opt-in read-only test below observes an existing socket and emits counts
90
+ only. It neither captures pane content nor reads process command lines.
91
+
92
+ ```text
93
+ PROJMUX_RESOURCE_TMUX_SOCKET=projmux \
94
+ PROJMUX_RESOURCE_EXPECT_PROJECT_ROOT=/path/to/project go test \
95
+ -run TestResourceAttributionRealTmuxReadOnlySmoke -v \
96
+ ./internal/integrations/tmux
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
+
105
+ 2026-08-12 result: `panes=8`, `pane_pid_eq_sid=8`, `missing_pids=0`,
106
+ `attributed_processes=13`, `escaped_boundary=0`, `sampled=474`, `skipped=0`,
107
+ `race=0`, `permission=0`, `status=ready`. A separate tmux format-only count
108
+ showed four shell panes, three direct agent panes, and one launcher pane. The
109
+ zero escaped count is a valid observed boundary; the setsid fixture test pins
110
+ the non-attribution behavior deterministically. Race and permission states are
111
+ also deterministic fixtures: the collector test injects `fs.ErrNotExist` and
112
+ `fs.ErrPermission` for numeric proc entries and checks the separate counts.
113
+
114
+ A positive real-kernel setsid boundary is covered by an isolated transient
115
+ smoke. It creates and removes its own tmux socket/server and never touches the
116
+ existing production socket:
117
+
118
+ ```text
119
+ PROJMUX_RESOURCE_TRANSIENT_SMOKE=1 go test \
120
+ -run TestResourceAttributionTransientSetsidSmoke -v \
121
+ ./internal/integrations/tmux
122
+ ```
123
+
124
+ 2026-08-12 result: `panes=1`, `attributed_processes=1`,
125
+ `escaped_boundary=1`, `sampled=480`, `skipped=0`, `race=0`, `permission=0`.
126
+ The pane shell remained attributed while its real `setsid` child was counted
127
+ at the escaped/Other boundary, without reading the child command line.
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
+
141
+ ## Phase 1 inspector
142
+
143
+ The popup retains warming/partial/unavailable and overage states, renders RSS
144
+ explicitly as a sum, keeps `Other / unattributed` non-drillable, and discards
145
+ samples when it closes. Its non-overlapping default cadence is two seconds;
146
+ Ctrl-R shares the same scan gate. Selection and query survive refresh by stable
147
+ row identity, while a vanished row clamps to the nearest valid neighbor.
148
+ Display labels use label → agent topic → known interactive shell → raw title,
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.
@@ -11,9 +11,10 @@ projmux session-state restore --dry-run [--session <name>]
11
11
  projmux session-state delete [--session <name>]
12
12
  ```
13
13
 
14
- Snapshots preserve source metadata, not a final display label. Window records
15
- keep `window_name`; pane records keep `pane_title`, recipe fields, AI topic
16
- metadata (`@projmux_ai_topic`), and resume metadata when available. There is no
14
+ Snapshots preserve source metadata, not a resolved display label. Window records
15
+ keep `window_name`; pane records keep the user label, raw `pane_title`, recipe
16
+ fields, AI topic and manual-ownership metadata (`@projmux_ai_topic` and
17
+ `@projmux_ai_topic_manual`), and resume metadata when available. There is no
17
18
  `display_label` field in the snapshot schema. After restore, pane borders and
18
19
  app window tabs are display-time tmux policy: the app config derives both from
19
20
  the active pane's visible label expression, while raw shell or terminal titles
@@ -48,11 +49,21 @@ unknown sources are low or none. The old statusbar Session State shortcut has
48
49
  been removed; use `Projects > Sessions > State` or the `projmux session-state`
49
50
  CLI for inspection/actions.
50
51
 
52
+ Session snapshots capture each pane's user-owned `label` separately from its
53
+ raw `title` and agent recipe `topic`. Older snapshots decode with an empty
54
+ label and no manual topic ownership; no title/topic equality heuristic is
55
+ applied. Replay explicitly sets or clears the label, startup recipe fields, AI
56
+ agent/topic/ownership/resume fields, and finally the raw title on the pane id
57
+ returned by tmux creation. It does not derive a target or identity from pane
58
+ order, a visible title, or equality between saved fields.
59
+
51
60
  Agent restore direct-starts supported resume commands when creating fresh tmux
52
61
  panes, matching the `projmux ai split` wrapper shape: the wrapper prepends the
53
- agent binary directory to `PATH`, changes to the saved cwd, sets the terminal
54
- and tmux pane title from the saved agent topic, then execs `codex resume <id>`,
55
- `claude --resume <id>`, or `agy --conversation <uuid>`. Antigravity restore
62
+ agent binary directory to `PATH`, changes to the saved cwd, then execs
63
+ `codex resume <id>`, `claude --resume <id>`, or
64
+ `agy --conversation <uuid>`. It does not copy the saved topic into OSC or raw
65
+ tmux title state; replay restores the final raw title only from `Pane.Title`.
66
+ Antigravity restore
56
67
  uses only the stable statusline `conversation_id` or hook `conversationId`
57
68
  metadata captured as the pane resume id; missing or non-UUID Antigravity ids
58
69
  render as `resume unavailable` rather than falling back silently to a shell
@@ -63,6 +74,20 @@ environment, shell functions, aliases, or live process state. Startup recipes
63
74
  continue to use their saved `send-keys` command replay, and shell recipes only
64
75
  restore cwd/layout.
65
76
 
77
+ This live capture lane remains distinct from resume-picker disk discovery and
78
+ has high confidence (`hook`/`session-id`). When an Antigravity picker row starts
79
+ a pane, its source is captured too: a UUID verified by an exact regular
80
+ `conversations/<uuid>.db` through `last_conversations` or workspace-bearing
81
+ summarized metadata has medium confidence, while a legacy `history.jsonl` row
82
+ has low confidence. Preview and doctor report that source/confidence as stored;
83
+ they do not claim that the upstream cache exposes complete history. Disk
84
+ discovery does not replace an existing live hook source, and it never opens a
85
+ conversation database or reads prompt/transcript content.
86
+ Bounded Session State agent-pane previews place resume health before the full
87
+ resume id, topic, and title so status, confidence, and source remain visible;
88
+ the underlying snapshot and unbounded preview model retain those identity and
89
+ context fields unchanged. Non-agent pane preview ordering is unchanged.
90
+
66
91
  Settings > Session State is global settings only: global auto-save, auto-save
67
92
  interval, and storage/retention policy. It does not show the current
68
93
  snapshot tree. Delete for current-session snapshots and destructive restore
@@ -11,6 +11,10 @@ view-first layout:
11
11
  current state, source, and expected rendered result before offering mutation
12
12
  rows. If a detail opens a dedicated `Change` page, that page is mutation-only
13
13
  and does not repeat the same read-only view rows.
14
+ - Every rendered non-empty row value is classified as navigation, actionable,
15
+ or passive information/disabled state and is mapped to a closed owner-loop
16
+ contract before rendering. An unowned value is a Settings error; Enter on a
17
+ passive row is consumed as a no-op.
14
18
  - `Settings > Project Picker > Workdirs` is the list/overview entry. Add/remove
15
19
  actions live inside that view.
16
20
  - `Settings > Project Picker > Project Root` shows effective and saved values
@@ -35,7 +39,7 @@ view-first layout:
35
39
  rows as always-visible sections.
36
40
  - Terminal delivery remediation lives outside Settings primary flow. The
37
41
  supported order is `projmux shell` first, then `projmux setup`, then
38
- `projmux init` for supported terminal adapters.
42
+ `projmux setup terminal` for supported terminal adapters.
39
43
  - Rows that cannot safely be edited still stay visible. Mark diagnostic-only
40
44
  rows with the delivery path and reason instead of hiding them or turning them
41
45
  into unsupported editable keys. Transport-dependent rows stay visible with
@@ -57,9 +61,10 @@ view-first layout:
57
61
  `[theme]` is never resolved or shown here.
58
62
  - `Settings > Notifications` owns notification delivery IA. Desktop notification
59
63
  mode, AI desktop notification dedupe duration, delivery source diagnostics,
60
- AI hook quiet policy, in-app queue status, and
61
- `PROJMUX_NOTIFY_HOOK` visibility live together without mixing mutation
62
- boundaries.
64
+ and AI hook quiet policy live together without mixing mutation boundaries.
65
+ The in-app queue is consumed from the statusbar/sidebar, not from a standalone
66
+ Settings row. `PROJMUX_NOTIFY_HOOK` override presence is folded into Delivery
67
+ sources summary/detail instead of appearing as a separate root row.
63
68
  - `Settings > Notifications > Desktop notifications` owns the desktop
64
69
  notification mode. The detail choices are `none`, `notify`, and `raise`.
65
70
  - `Settings > Notifications > AI notification dedupe` owns the duplicate
@@ -67,10 +72,10 @@ view-first layout:
67
72
  the effective source; `PROJMUX_TMUX_NOTIFY_DEDUPE_SECONDS` remains the top
68
73
  override. The tmux bell fallback keeps its fixed 5 second window.
69
74
  - `Settings > Notifications > Delivery sources` shows Codex hooks, Claude, and
70
- tmux producer diagnostics plus copyable install/remove/dry-run commands.
71
- Settings copies command text only; it does not install or remove external
72
- notify wiring. The legacy Codex notify source is intentionally omitted from
73
- Settings.
75
+ tmux producer diagnostics, the effective desktop sender override state, and
76
+ copyable install/remove/dry-run commands. Settings copies command text only;
77
+ it does not install or remove external notify wiring. The legacy Codex notify
78
+ source is intentionally omitted from Settings.
74
79
  - `Settings > Notifications > Hook quiet policy` shows Codex/Claude hook
75
80
  runtime action values and writes only
76
81
  `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/ai-hook-actions.json`. It does not
@@ -78,13 +83,17 @@ view-first layout:
78
83
  - `Settings > Session State > Sidebar startup picker` controls the Alt-1
79
84
  project-open startup selector. The saved file remains
80
85
  `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/sidebar-startup-picker`.
81
- - `Settings > Labs` keeps experimental toggles, but keybindings no longer have a
82
- visible Labs row. The hidden compatibility action redirects to the
83
- `Settings > Keybindings` action list, not to a diagnostic default.
86
+ - `Settings > Labs` contains only Live system resources and Project Hooks.
87
+ Keybindings live at `Settings > Keybindings`; Labs has no visible or hidden
88
+ keybindings redirect. The native picker is the product picker, so Labs does
89
+ not render picker source information.
84
90
  - `Settings > Labs > Live system resources` is a direct global on/off toggle
85
91
  for the macOS/Linux/WSL lower-status-row `CPU N% MEM N%` segment. It defaults
86
92
  off, updates live tmux state when toggled, and renders unavailable on
87
- unsupported platforms. WSL values describe the Linux guest/VM view.
93
+ unsupported platforms. CPU and memory use fixed independent semantic
94
+ thresholds (CPU warning/critical at 70/90; memory at 75/90); the toggle does
95
+ not expose threshold customization. WSL values describe the Linux guest/VM
96
+ view.
88
97
  - `Settings > Labs > Project Hooks` is overview-first. The Labs root opens the
89
98
  overview, and the on/off mutation rows live one level deeper.
90
99
  - `Settings > AI Settings` is view-first. The root contains `Default split
@@ -106,6 +115,37 @@ view-first layout:
106
115
  `ko-KR`. When `auto` is active it must show the detected source (`LC_ALL`,
107
116
  `LC_MESSAGES`, `LANG`, or fallback). Unsupported locale tags must remain
108
117
  visible as warnings and fall back to `en-US`.
118
+ - Global root descriptions keep ownership explicit: Appearance owns language,
119
+ AI badge style, and status/notification icon decoration; Theme owns presets,
120
+ color tokens, and font hints. About describes only the surface it retains.
121
+ - `Settings > About` is intentionally compact: Version, Source, update
122
+ status/actions (including Latest, Update state, Installer, and Release notes
123
+ when available), Welcome, and Quit. It does not reproduce static key,
124
+ terminal, dependency, terminal-emulator, or documentation guides. Key
125
+ delivery discovery lives in `projmux setup`, supported terminal remediation
126
+ in `projmux setup terminal`, read-only dependency/runtime diagnostics in
127
+ `projmux doctor`, and broader orientation in Welcome and maintained docs.
128
+ - Without an actionable project context, the Project surface renders one
129
+ passive context-guidance row instead of repeating the same disabled reason
130
+ for Trust, Hooks, Project recipe, and Effective merge view. With project
131
+ context, those four rows and Session State retain their existing actions.
132
+
133
+ Settings mutation feedback follows one transient contract. The next picker
134
+ frame inserts one passive `Feedback` row after Back; the row uses the catalogued
135
+ `settingsNoopValue`, so Enter cannot create an unknown action. Selecting another
136
+ navigation/action clears the old row before that operation runs, and a handled
137
+ result replaces it. The inventory includes AI defaults/enabled agents/resume
138
+ limits, notification modes/dedupe/hook policy, Appearance and locale choices,
139
+ Labs toggles, project roots/workdirs/pins, project hooks/recipe/trust, Theme,
140
+ Session State, direct keybinding reset/remove/toggle operations, and About
141
+ update apply/check. Typed validation and staged apply failures stay in the
142
+ popup instead of being visible only on stdout/stderr.
143
+
144
+ The generic feedback inventory deliberately excludes Welcome, Quit,
145
+ read-only hook/effective/notification diagnostics, Session State preview, and
146
+ key capture/probe/diagnostic bodies. Those flows own a viewer, confirmation, or
147
+ multi-step output surface; only an actual Settings write at their boundary is
148
+ eligible for transient mutation feedback.
109
149
 
110
150
  Hooks remain the reference pattern for this IA:
111
151
 
package/docs/statusbar.md CHANGED
@@ -68,9 +68,22 @@ 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.
75
+ CPU and memory are host-scoped telemetry, not pane, window, project, or
76
+ session attribution. Each value has an independent semantic style: CPU is
77
+ normal below 70%, warning at 70–89%, and critical at 90% or above; memory is
78
+ normal below 75%, warning at 75–89%, and critical at 90% or above. Normal and
79
+ unavailable (`--`) values use the secondary status-text role, warnings use
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.
74
87
  Linux CPU is the aggregate delta from `/proc/stat`; memory is
75
88
  `(MemTotal - MemAvailable) / MemTotal` from `/proc/meminfo`. macOS CPU uses
76
89
  the aggregate Mach host tick delta; memory is total physical memory minus
@@ -82,6 +95,10 @@ row 1 [#S] #{pane_current_path} ⎈ <ctx>/<ns> <git> CPU 12% MEM 41% 
82
95
  re-enabling after a pause starts at `CPU --%` instead of showing a long-term
83
96
  average. Missing or malformed procfs data degrades to `--` or an empty segment
84
97
  without producing a tmux error popup.
98
+ The complete live segment is wrapped in `#[range=user|resources]...#[norange]`.
99
+ Clicking it opens the same canonical client-scoped `resource-inspector`
100
+ popup as the `Resources:Open` keybinding action. Disabling the Lab hides only
101
+ the segment; it does not disable a custom action or `projmux resources`.
85
102
  - The settings chip keeps its label padding inside the `settings` range
86
103
  and inside the chip background. The compact app chip renders `` with
87
104
  the extra right-side icon padding painted by the same background, while
@@ -112,6 +129,7 @@ bind-key -n MouseDown1Status if-shell -F "#{==:#{mouse_status_range},window}" \
112
129
  | `kube` | 0 | `projmux tmux popup-toggle sessionizer` | `prefix s k` |
113
130
  | `git` | 0 | `projmux tmux popup-toggle sessionizer` | `prefix s g` |
114
131
  | `settings` | 0 | `projmux tmux popup-toggle --client <tty> ai-split-settings` | mouse only; `prefix s s` remains `session` |
132
+ | `resources` | 0 | `projmux tmux popup-toggle --client <tty> resource-inspector` | mouse or custom `Resources:Open`; no default key |
115
133
  | `usage` | 1 | show a native-framed usage HUD popup from cached usage state | `prefix s u` |
116
134
  | `notify` | 1 | `projmux focus --target <newest> --source status-bar --kind segment-click [--client <tty>]`, then ack on focus success | `prefix s n` |
117
135
 
@@ -136,10 +154,18 @@ hard-truncate path still closes with `#[default]` so later status segments do
136
154
  not inherit notification styling. That dotless narrow fallback applies only to
137
155
  the queued notify segment, not to the separate window-list live attention badge.
138
156
  `usage` opens a native-framed detail HUD for the compact usage bar. It reads
139
- the cached usage state in-process, keeps the existing `projmux usage` CLI
140
- output shape unchanged for external consumers, aligns model/window rows with
157
+ the cached usage state in-process and aligns model/window rows with
141
158
  right-aligned numeric values, dims unavailable values, keeps stale sync/age
142
159
  metadata muted, and colors only threshold values: amber at 80% and red at 95%.
160
+ Antigravity rows keep conversation-local `context` separate from account
161
+ `quota/<exact upstream bucket ID>` rows; the popup displays an absolute reset
162
+ when provided and otherwise the exact optional relative reset seconds. Opaque
163
+ bucket IDs are escaped for terminal/tmux safety and are never assigned a
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.
143
169
  Session State inspection lives under `Projects > Sessions > State`; global
144
170
  Settings > Session State is settings-only and the statusbar no longer exposes a
145
171
  duplicate State button.
@@ -153,7 +179,8 @@ does not leave terminal key state behind. The usage popup uses the same
153
179
  single-payload print and plain Enter-close pattern. It shows the authoritative
154
180
  last collect timestamp when present, falls back to the cache file mtime when
155
181
  needed, and keeps stale sync metadata muted instead of escalating it to a
156
- warning color.
182
+ warning color. Percent-only named rows do not synthesize `USED`, `LIMIT`, or
183
+ `LEFT` counts.
157
184
  The notification HUD detail surface opens the right-side notification popup
158
185
  through the notify sidebar action, showing the grouped pane/session inbox with
159
186
  collapsed group rows and the same attention-tinted title. When notification
package/docs/testing.md CHANGED
@@ -77,7 +77,7 @@ Observe:
77
77
  - `Alt-1` through `Alt-5` report `OK plain`. These are the guaranteed
78
78
  zero-config launch defaults.
79
79
  - If a guaranteed key reports `MISS timeout`, preview a supported terminal
80
- mapping with `projmux init ghostty` or `projmux init windows-terminal`,
80
+ mapping with `projmux setup terminal ghostty` or `projmux setup terminal windows-terminal`,
81
81
  apply it with the same command plus `--apply`, restart that terminal if
82
82
  required, and rerun `projmux setup --timeout 10s`.
83
83
  - Optional direct aliases and transport-dependent chords may be reported by