projmux 0.4.4 → 0.4.6

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,199 @@
1
+ # Notify queue
2
+
3
+ `projmux` keeps a persistent JSON queue of pending AI notifications.
4
+ `attention` is live tmux pane state; `notify` is the user's pending
5
+ notification source of truth. The queue is derived from live state and user
6
+ pushes, but an entry remains pending until explicit ack. Each entry can route
7
+ the status-bar notify segment or notify sidebar to the originating tmux pane
8
+ via `projmux focus`, and feeds the HUD pill rendered by
9
+ `projmux status notify`.
10
+
11
+ ## File layout
12
+
13
+ ```
14
+ ${XDG_STATE_HOME:-$HOME/.local/state}/projmux/notify.json
15
+ ${XDG_STATE_HOME:-$HOME/.local/state}/projmux/notify.json.lock
16
+ ```
17
+
18
+ The lock file is acquired via `O_CREATE|O_EXCL` with bounded retry
19
+ (default ~200 attempts, 2ms–50ms jittered backoff) and a 30s
20
+ stale-after window so a crashed writer never permanently wedges the
21
+ queue.
22
+
23
+ The queue file is a pretty-printed JSON array of `Notification`
24
+ objects, sorted newest-first on read. `expires_at` is freshness metadata;
25
+ expired entries are not filtered or deleted by `list`.
26
+
27
+ ## Data model
28
+
29
+ `internal/core/notify`:
30
+
31
+ ```go
32
+ type Notification struct {
33
+ ID string // stable key for dedupe / ack
34
+ Text string // capped at 80 runes
35
+ Severity string // info | warn | critical
36
+ Source string // ai | k8s | git | external
37
+ CreatedAt time.Time
38
+ ExpiresAt time.Time
39
+ Target // session, window, pane, socket
40
+ }
41
+
42
+ type Target struct {
43
+ Socket string // tmux -L socket path; empty = default
44
+ Session string
45
+ Window string // optional; "" means session-only
46
+ Pane string // optional; "" means window-level
47
+ }
48
+ ```
49
+
50
+ Defaults: `DefaultTTL = 600s`, `MaxTextLength = 80`. TTL is retained as a
51
+ freshness/display field, not a removal condition. `Severity` and
52
+ `Source` are validated against the constants above; an invalid value
53
+ returns `ErrInvalidSeverity` / `ErrInvalidSource` which the CLI maps to
54
+ exit code 2.
55
+
56
+ ## CLI surface
57
+
58
+ ### push
59
+
60
+ ```
61
+ projmux notify push --text <s> --target <SESSION[:WINDOW[.PANE]]>
62
+ [--socket <s>] [--severity info|warn|critical]
63
+ [--source ai|k8s|git|external]
64
+ [--ttl <seconds>] [--id <s>] [--json]
65
+ ```
66
+
67
+ Appends one entry. With `--id` an existing entry is overwritten in
68
+ place (text + timestamp refresh), enabling idempotent producers like
69
+ the AI reply-ready transition. `--ttl` accepts a positive integer
70
+ number of seconds. `--json` prints `{id, queued}` for scripting.
71
+
72
+ ### list
73
+
74
+ ```
75
+ projmux notify list [--live] [--json] [--limit N] [--ui table|sidebar]
76
+ [--severity ...] [--source ...]
77
+ ```
78
+
79
+ Newest-first pending queue entries. Default output is the tab-aligned
80
+ table `ID AGE SEV SRC TARGET TEXT`. `--severity` / `--source` are
81
+ repeatable filters. Without `--live`, this command reads only the queue and
82
+ preserves the stable JSON array used by scripts.
83
+
84
+ `--ui=sidebar` opens the notify queue as an interactive right-side list when
85
+ run inside the tmux popup surface. Enter focuses the selected target pane and
86
+ acks the row after focus succeeds. `a` acks the selected row. `Ctrl-A` clears
87
+ all rows via `notify ack --all`. Rows are intentionally compact: the visible
88
+ label keeps notification text first, then age, project, window, and pane
89
+ metadata; id/source/severity/target remain searchable.
90
+
91
+ `--live` adds a non-mutating explanation view that reads
92
+ `tmux list-panes -a` and compares the queue with live reply-state panes. It
93
+ does not push, ack, or otherwise repair anything. Human output keeps the
94
+ queue table and adds a `STATE TARGET ID EXPLANATION TEXT` section; JSON
95
+ output becomes `{queue, live, rows, errors}`. Typical states:
96
+
97
+ - `live-manual-reply` — a live reply/green badge exists, but no queue entry
98
+ is expected because the pane has no AI agent metadata.
99
+ - `live-ai-reply-queued` — a live AI reply pane has the matching actionable
100
+ queue entry.
101
+ - `live-ai-reply-missing-queue` — a live AI reply pane lacks the derived
102
+ queue entry; run `projmux notify reconcile` to back-fill it.
103
+ - `queue-stale` — an `ai:` queue entry exists, but the live pane no longer
104
+ matches reply+agent state; it remains pending until explicit ack.
105
+ - `queue-only` — a non-AI/external queue entry is pending and has no live AI
106
+ reply-pane requirement.
107
+
108
+ To inspect live pane attention without queue context, use
109
+ `projmux attention list`.
110
+
111
+ ### ack
112
+
113
+ ```
114
+ projmux notify ack <id>
115
+ projmux notify ack --all
116
+ ```
117
+
118
+ Removes one entry by id, or flushes the queue. `--all` returns the
119
+ removed count.
120
+
121
+ ### reconcile
122
+
123
+ ```
124
+ projmux notify reconcile [--json]
125
+ ```
126
+
127
+ Walks `tmux list-panes -a -F` with the producer-key format (session,
128
+ window id, pane id, attention state, AI state, agent, topic, socket
129
+ path), then:
130
+
131
+ - pushes/refreshes one `ai:<session>:<pane>` entry for every pane
132
+ whose attention state is `reply` AND whose agent option is non-empty;
133
+ - reports every existing queue entry whose id starts with `ai:` and whose
134
+ pane no longer matches that condition as stale, without acking it.
135
+
136
+ Soft-fails when tmux is not running (returns a populated `errors`
137
+ field rather than a non-zero exit) so the post-install hook does not
138
+ break. Run this as the recovery path when the on-disk queue has drifted
139
+ from live pane state.
140
+
141
+ Output: `reconcile: pushed N, acked M, kept K, stale S`.
142
+
143
+ Ack behavior is intentionally queue-local: only `ack` removes explicit ids or
144
+ flushes the queue. TTL is freshness metadata, and `reconcile` only repairs
145
+ derived `ai:` entries. Manual attention badges without agent metadata remain
146
+ live attention only.
147
+
148
+ ## Producer (AI reply-ready)
149
+
150
+ `internal/app/notify_producer.go`. The attention state machine calls
151
+ `PushReplyReady` when a pane flips to `reply`. The producer reads
152
+ `@projmux_ai_pane_agent`, `@projmux_ai_pane_topic`, `#S`,
153
+ `#{window_id}`, `#{pane_id}`, `#{socket_path}` off the pane and writes
154
+ an entry with:
155
+
156
+ - id: `ai:<session>:<pane>`
157
+ - text: `<agent>: <topic>` (or `<agent>: ready` when no topic is set)
158
+ - severity: `info`, source: `ai`, freshness TTL: 10 minutes
159
+
160
+ When the pane leaves the reply state (manual `attention clear`,
161
+ `status set idle`, or a window close), `AckReplyReady` intentionally does not
162
+ remove the entry. The user consumes it through explicit ack. Store errors are
163
+ swallowed so the live tmux UI never blocks on disk IO.
164
+
165
+ Manual `projmux attention toggle` on a pane without an agent option
166
+ does NOT push — the queue is intentionally AI-driven; reconcile honours
167
+ the same contract.
168
+
169
+ ## Consumer (status-bar click)
170
+
171
+ `internal/app/statusbar.go::handleNotify`. A click on the notify range
172
+ or the `prefix s n` chord reads the newest queue entry and dispatches:
173
+
174
+ ```
175
+ projmux focus --target <target> --source status-bar --kind segment-click [--socket <s>]
176
+ ```
177
+
178
+ Outcomes:
179
+
180
+ - **Focus succeeded** — ack the entry. Focus is the single consume path for
181
+ routed notification clicks.
182
+ - **Focus exited 2 (target unresolved)** — keep the entry pending and toast
183
+ `notify target gone; ack to clear`.
184
+ - **Other failure** — keep the entry, toast `focus failed: <reason>`
185
+ so the user can retry without losing the row.
186
+
187
+ The handler never returns a non-zero error to tmux's `run-shell`; every
188
+ failure becomes a `display-message` toast so a transient miss does not
189
+ trigger a tmux error popup.
190
+
191
+ ## Render (status segment)
192
+
193
+ `projmux status notify` is the HUD-style renderer wired to the tmux
194
+ status interval. See [statusbar.md](statusbar.md) for the layout and
195
+ degradation tiers. It shows the newest pending item as a single notification
196
+ block with project, state, optional agent, text, age, and an extra-count
197
+ marker. Window/pane ids remain routable metadata but are not displayed in the
198
+ compact HUD. The renderer is silent on every failure mode (no store, list
199
+ error, empty queue) so the status line never carries a stack trace.
@@ -0,0 +1,86 @@
1
+ # npm Distribution
2
+
3
+ `projmux` remains a Go CLI. npm is a distribution channel that installs a
4
+ small Node.js shim plus one platform-specific Go binary package.
5
+
6
+ The public npm package `projmux` is the root shim package. Release builds use
7
+ this package layout:
8
+
9
+ | package | contents |
10
+ | --- | --- |
11
+ | `projmux` | `npm/projmux.js` shim and optional dependencies |
12
+ | `@projmux/linux-x64` | `linux/amd64` `bin/projmux` |
13
+ | `@projmux/linux-arm64` | `linux/arm64` `bin/projmux` |
14
+ | `@projmux/darwin-x64` | `darwin/amd64` `bin/projmux` |
15
+ | `@projmux/darwin-arm64` | `darwin/arm64` `bin/projmux` |
16
+
17
+ The shim sets `PROJMUX_INSTALLER=npm` before executing the real binary so
18
+ `projmux update status` and the Settings About screen can present
19
+ npm-specific guidance. npm is only an update/install source label here; the
20
+ keybinding flow remains `projmux shell` first, then `projmux setup` and
21
+ `projmux init` only for terminals that swallow shortcuts.
22
+
23
+ ## Local Packaging
24
+
25
+ Build and dry-run pack all npm packages:
26
+
27
+ ```bash
28
+ make npm-pack
29
+ ```
30
+
31
+ or:
32
+
33
+ ```bash
34
+ scripts/package-npm.sh --version 0.4.0 --out /tmp/projmux-npm --pack
35
+ ```
36
+
37
+ The script stages package directories under `dist/npm` by default. It builds
38
+ the Go binary for each supported platform, copies package metadata and docs,
39
+ updates package versions in the staged copies, then runs `npm pack --dry-run`
40
+ when `--pack` is set.
41
+
42
+ ## Publish Order
43
+
44
+ The platform packages must be published before the root package:
45
+
46
+ ```text
47
+ @projmux/linux-x64
48
+ @projmux/linux-arm64
49
+ @projmux/darwin-x64
50
+ @projmux/darwin-arm64
51
+ projmux
52
+ ```
53
+
54
+ Publishing the scoped platform packages requires control of the `@projmux`
55
+ npm scope. Configure npm Trusted Publishing for every package before merging a
56
+ release PR:
57
+
58
+ | npm package | GitHub organization/user | repository | workflow filename |
59
+ | --- | --- | --- | --- |
60
+ | `@projmux/linux-x64` | `crevissepartners` | `projmux` | `release.yml` |
61
+ | `@projmux/linux-arm64` | `crevissepartners` | `projmux` | `release.yml` |
62
+ | `@projmux/darwin-x64` | `crevissepartners` | `projmux` | `release.yml` |
63
+ | `@projmux/darwin-arm64` | `crevissepartners` | `projmux` | `release.yml` |
64
+ | `projmux` | `crevissepartners` | `projmux` | `release.yml` |
65
+
66
+ Leave the npm trusted publisher environment field empty unless the workflow is
67
+ later moved behind a GitHub deployment environment.
68
+
69
+ Tag releases publish npm packages from GitHub Actions after release archives
70
+ are uploaded. The workflow runs:
71
+
72
+ ```bash
73
+ scripts/package-npm.sh --version "${GITHUB_REF_NAME#v}" --out dist/npm
74
+ ```
75
+
76
+ then publishes each staged package with `npm publish --access public`.
77
+ The npm publish job uses GitHub Actions OIDC (`id-token: write`) instead of a
78
+ long-lived `NPM_TOKEN` secret. PR CI runs `make npm-pack` so package staging and
79
+ dry-run packing fail before release.
80
+
81
+ ## Non-Goals
82
+
83
+ The npm installer must not install system dependencies, edit shell startup
84
+ files, or mutate tmux config. Those actions stay behind explicit
85
+ `projmux doctor`, `projmux init`, Settings About update actions, or future
86
+ opt-in install commands.
@@ -0,0 +1,111 @@
1
+ # Picker UI Plan
2
+
3
+ ## Goal
4
+
5
+ The project switcher needs a richer picker surface than a single-line fzf row.
6
+ The target interaction is a card-like list where each item can show a title plus
7
+ small contextual lines such as session state, window/pane summary, branch, or
8
+ path. Search should stay focused on stable identity text, especially the project
9
+ or session title, instead of matching every contextual preview line.
10
+
11
+ ## Current Contract
12
+
13
+ The picker contract is split in two layers:
14
+
15
+ - `internal/ui/picker` owns backend-neutral items, actions, preview metadata,
16
+ backend selection, title-focused filtering, and the opt-in native runner.
17
+ - `internal/ui/fzf` adapts that model into the historical fzf command line.
18
+
19
+ The default fzf backend still sends one logical row per item:
20
+
21
+ ```text
22
+ <visible label>\t<selection value>
23
+ ```
24
+
25
+ fzf is configured with:
26
+
27
+ - `--delimiter "\t"`
28
+ - `--with-nth 1`
29
+ - `--exit-0`
30
+ - optional `--preview` and `--preview-window`
31
+
32
+ The app depends on fzf returning the selected row and then extracts the hidden
33
+ value after the first tab. This contract is simple and stable, but it limits each
34
+ row to one visible line.
35
+
36
+ ## fzf Capability Check
37
+
38
+ The installed fzf version supports multi-line items with `--read0`. That means a
39
+ single item can contain newline characters when input records are NUL-delimited.
40
+ This can render card-like rows.
41
+
42
+ The simple fzf option path is not enough for the desired search behavior:
43
+
44
+ - `--read0` can display multi-line items.
45
+ - `--nth` can restrict search to selected fields.
46
+ - `--with-nth` can transform the displayed fields.
47
+ - In practice, once `--with-nth` is used to show a card field, fzf searches the
48
+ transformed visible text. Context lines become searchable.
49
+
50
+ So fzf can support "multi-line cards", but not "multi-line cards with title-only
51
+ search" through a small option-only extension while preserving the current
52
+ selection contract.
53
+
54
+ ## Viable Paths
55
+
56
+ ### 1. fzf card approximation
57
+
58
+ Use `--read0` and NUL-delimited multi-line entries. This is the smallest change,
59
+ but contextual card text will participate in search unless the visible card is
60
+ kept title-only. This does not meet the intended search model.
61
+
62
+ This path is acceptable only as a temporary visual experiment.
63
+
64
+ ### 2. fzf custom filtering
65
+
66
+ Run fzf in a more controlled mode where query changes reload a filtered list
67
+ from `projmux`, and `projmux` performs title-focused matching. This keeps fzf as
68
+ the renderer but moves filtering into the app.
69
+
70
+ Tradeoffs:
71
+
72
+ - More shell quoting and reload complexity.
73
+ - More edge cases around selection identity and tracking.
74
+ - Still constrained by fzf's list layout and event model.
75
+
76
+ This is viable, but it is a bridge rather than a clean long-term model.
77
+
78
+ ### 3. Native picker TUI
79
+
80
+ Introduce a picker abstraction and implement a native terminal UI for card rows,
81
+ title-focused search, stable selection identity, and app-owned key handling. fzf
82
+ remains the default backend until parity is reached.
83
+
84
+ This best matches the desired product direction:
85
+
86
+ - card rows are first-class data, not encoded fzf strings
87
+ - search fields are explicit
88
+ - preview/context fields can be visible but non-searchable
89
+ - future key behavior can be tested without relying on fzf internals
90
+
91
+ ## Implemented Direction
92
+
93
+ Do not extend the current fzf row format again as the main implementation. The
94
+ previous hidden-field attempt showed that small fzf encoding changes can break
95
+ selection and navigation in subtle ways.
96
+
97
+ Current implementation:
98
+
99
+ - Picker-domain model exists as `picker.Item` with `Title`, `Value`,
100
+ `SearchText`, `MetaLines`, `Badges`, and `PreviewTarget`.
101
+ - `picker.Options` carries backend-neutral actions, preview metadata, prompt,
102
+ footer, initial query, and multiline intent.
103
+ - fzf remains the default backend and renders the same popup/sidebar surfaces.
104
+ - `PROJMUX_PICKER_BACKEND=native` opts into the native runner. It supports
105
+ multiline item rendering, title-focused search via `SearchText`, numeric
106
+ selection, and shared close actions.
107
+ - Switcher popup/sidebar still use the fzf backend for full preview and key
108
+ action parity. Native preview panes, raw-key navigation, and sidebar focus
109
+ tracking remain follow-up work.
110
+
111
+ fzf can stay as the stable fallback while the native picker reaches parity.
@@ -0,0 +1,106 @@
1
+ # PR Guideline
2
+
3
+ Audience: every contributor — humans and agents alike. Agents working in this
4
+ repo (`claude` / `codex` panes, the team-lead session, etc.) MUST follow these
5
+ rules; the conventions here are what `release-please` parses for the next
6
+ release notes, so a sloppy PR title silently breaks the changelog.
7
+
8
+ For the surrounding workflow (worktree, validation gates, post-merge install)
9
+ see [AGENTS.md](../AGENTS.md). This document covers only the PR itself.
10
+
11
+ ## PR title — Conventional Commits
12
+
13
+ Default merge method is **squash**, so the PR title becomes the only commit
14
+ subject that lands on `main`. Format:
15
+
16
+ ```
17
+ <type>(<optional scope>): <imperative summary>
18
+ ```
19
+
20
+ Examples:
21
+
22
+ ```
23
+ feat(ai): add codex split picker keybinding
24
+ fix(ai): prepend agent bin dir to PATH so node-managed CLIs find node
25
+ docs(readme): drop Releases and Configuration sections
26
+ chore: bump release-please manifest to 0.3.0
27
+ refactor(picker): collapse duplicate fzf bootstrap code
28
+ ```
29
+
30
+ Rules:
31
+
32
+ - Subject is in the imperative ("add", "fix", "drop"), no trailing period.
33
+ - Keep the title under ~70 characters when possible. Long detail goes in the
34
+ body.
35
+ - The scope is optional but recommended for non-trivial diffs (`ai`, `picker`,
36
+ `tmux`, `readme`, `ci`, etc.).
37
+ - A `!` after the type or scope marks a breaking change:
38
+ `feat(ai)!: rename PROJMUX_NOTIFY_HOOK to PROJMUX_NOTIFY_BIN`.
39
+ - Or include a `BREAKING CHANGE: <description>` footer in the body. Either form
40
+ bumps the major version on the next release-please run.
41
+
42
+ ### Allowed types
43
+
44
+ | type | use for | release impact |
45
+ | --- | --- | --- |
46
+ | `feat` | user-visible new behavior or capability | minor bump |
47
+ | `fix` | bug fix that ships to users | patch bump |
48
+ | `perf` | measurable runtime/memory improvement | patch bump |
49
+ | `refactor` | code restructure with no user-visible change | none |
50
+ | `docs` | docs-only change | none |
51
+ | `test` | adding or restructuring tests | none |
52
+ | `build` | build system, Makefile, dependencies | none |
53
+ | `ci` | CI workflow / GitHub Actions | none |
54
+ | `chore` | release plumbing, tooling, repo housekeeping | none |
55
+ | `style` | formatting only, no logic change | none |
56
+
57
+ If the change includes both a feat and a fix, split it into two PRs. release-please
58
+ classifies the whole PR by its title type, not by content.
59
+
60
+ ## PR body
61
+
62
+ Use this template:
63
+
64
+ ```markdown
65
+ ## Summary
66
+ - 1–3 bullets describing what changed and why.
67
+
68
+ ## Test plan
69
+ - [ ] make fmt-check
70
+ - [ ] make test
71
+ - [ ] manual verification step (if relevant)
72
+ ```
73
+
74
+ Notes:
75
+
76
+ - **Why** matters more than **what**. Diff already shows the what.
77
+ - Reference issues with `Closes #<n>` so they auto-close on merge.
78
+ - Mention follow-ups explicitly when scope was deliberately deferred.
79
+
80
+ ## Branch protection in effect
81
+
82
+ `main` is governed by ruleset `main-protect`:
83
+
84
+ - Direct push to `main` is blocked. Even repository admin must use a PR.
85
+ - Required status check: the CI `Test` job. The PR cannot merge until it is
86
+ green.
87
+ - Admin bypass is `pull_request` mode — admin can self-merge without
88
+ approvals, but the PR itself is mandatory.
89
+ - Linear history is enforced. The merge methods exposed are
90
+ `merge` / `squash` / `rebase`; **default is squash** and that is what the
91
+ team-lead session uses unless the change explicitly needs preserved history.
92
+ - Force pushes and branch deletions on `main` are blocked.
93
+
94
+ `gh pr merge <num> --squash --delete-branch` is the canonical merge command.
95
+ Use `--auto` if you want the merge queued automatically once CI passes.
96
+
97
+ ## Release-please coupling
98
+
99
+ Every PR title that lands on `main` is parsed by `release-please-action`.
100
+ A `feat:` or `fix:` PR adds an entry to the next release notes; `chore:` /
101
+ `docs:` / `refactor:` etc. do not. To force a release of accumulated non-user
102
+ changes, open a `chore` PR titled `chore: release X.Y.Z` (or wait for any
103
+ real change). The `internal/version/version.go` constant carries the
104
+ `x-release-please-version` marker so release-please bumps it automatically.
105
+
106
+ Do not hand-author CHANGELOG.md or version bumps. release-please owns both.
@@ -0,0 +1,50 @@
1
+ # Repository Layout
2
+
3
+ ## Planned layout
4
+
5
+ ```text
6
+ projmux/
7
+ cmd/
8
+ projmux/
9
+ internal/
10
+ app/
11
+ config/
12
+ core/
13
+ candidates/
14
+ pins/
15
+ preview/
16
+ sessions/
17
+ integrations/
18
+ filesystem/
19
+ git/
20
+ kube/
21
+ tmux/
22
+ state/
23
+ ui/
24
+ fzf/
25
+ render/
26
+ version/
27
+ docs/
28
+ scripts/
29
+ test/
30
+ integration/
31
+ e2e/
32
+ ```
33
+
34
+ ## Notes
35
+
36
+ - `cmd/projmux` contains only CLI wiring.
37
+ - `internal/core` contains product behavior that should be testable without tmux.
38
+ - `internal/integrations/tmux` should be the only place that knows tmux command strings and output formats.
39
+ - `internal/ui/fzf` may depend on shelling out to `fzf`, but should call typed core services.
40
+ - `scripts/` is for development tooling only, not product logic.
41
+
42
+ ## Early implementation order
43
+
44
+ 1. `internal/core/sessions`
45
+ 2. `internal/core/candidates`
46
+ 3. `internal/core/pins`
47
+ 4. `internal/state`
48
+ 5. `internal/integrations/tmux`
49
+ 6. `internal/ui/fzf`
50
+ 7. CLI command wiring
@@ -0,0 +1,95 @@
1
+ # Roadmap
2
+
3
+ ## Done (0.4.x)
4
+
5
+ The 0.4 line filled in the operational surface around the session-management
6
+ core that 0.3 had landed.
7
+
8
+ ### Setup and install
9
+
10
+ - `projmux setup` — TTY raw-mode probe that reports which projmux key
11
+ sequences (`Alt-1..5`, `Ctrl-N`, `Ctrl-Shift-{R,L,M}`, `Ctrl-M`,
12
+ `Alt-Shift-{Left,Right}`) reach the process and which the terminal
13
+ swallows.
14
+ - `projmux init [terminal]` — auto-merges projmux's CSI-u + chord
15
+ bindings into a terminal config. Adapters: Ghostty (with the
16
+ `config` / `config.ghostty` candidate split and symlink guard) and
17
+ Windows Terminal (WSL + native).
18
+
19
+ ### Diagnostics
20
+
21
+ - `projmux doctor` — runtime dependency report. Enforces minimum tmux
22
+ 3.4 and fzf 0.65.0 (`stale` status when present but below the floor).
23
+
24
+ ### Focus
25
+
26
+ - `projmux focus` — unified switch-client dispatch. Resolves a target
27
+ session against the live tmux inventory, redirects an existing client
28
+ if one is attached, otherwise emits a desktop notification. Used by
29
+ the status-bar notify click and by the AI reply-ready handler.
30
+
31
+ ### Notify queue
32
+
33
+ - `projmux notify push|list|ack` — persistent JSON-backed queue at
34
+ `<state>/projmux/notify.json` with TTL, severity, source, and target
35
+ metadata.
36
+ - `projmux notify reconcile` — back-fills the queue from live pane
37
+ state by walking `tmux list-panes -a`.
38
+ - Producer wired to the attention state machine: a pane transitioning
39
+ to `reply` with an AI agent option set pushes an `ai:<session>:<pane>`
40
+ entry; the matching `clear` acks it.
41
+
42
+ ### Usage tracking
43
+
44
+ - `projmux usage` (and `status usage`) — authoritative 5h + weekly
45
+ utilisation for both Claude (OAuth `api/oauth/usage` endpoint with
46
+ 401 token refresh) and Codex (latest rollout `rate_limits` JSONL).
47
+ - Per-adapter throttle (Claude `5m`, default `30s`), 429 backoff
48
+ (`30m`–`60m` exponential), `--force` to bypass both. Snapshots
49
+ preserved on failure so a 429 does not erase prior rows.
50
+
51
+ ### Statusbar and HUD
52
+
53
+ - Two-line clickable status bar: row 0 is the existing
54
+ session/window/path/git/kube row, row 1 splits notify (left) and
55
+ usage (right).
56
+ - `projmux statusbar click` — single dispatcher for both mouse clicks
57
+ and the `prefix s {u,n,g,k,p,s}` keyboard chord. Window-list clicks
58
+ on tabs short-circuit to native `select-window`.
59
+ - `pwd` status click copies the current pane path into the tmux paste
60
+ buffer and shows a compact path popup instead of a transient
61
+ warning-coloured toast.
62
+ - HUD-style notify segment with severity+agent badge, midpoint dot
63
+ separators, and an age field.
64
+ - HUD-style usage segment with bars, last-sync age indicator (Claude),
65
+ and graceful degradation through six tiers as `--max-width` shrinks.
66
+
67
+ ## Next (0.5+)
68
+
69
+ Carried forward from earlier milestones — items still outstanding when
70
+ v0.4 shipped.
71
+
72
+ ### Picker UI
73
+
74
+ - Picker-domain model separate from fzf row encoding (kept fzf as the
75
+ stable fallback backend). Done in the 0.5 picker contract slice.
76
+ - Opt-in native picker backend for multi-line card rows and
77
+ title-focused search. Done in the 0.5 picker contract slice via
78
+ `PROJMUX_PICKER_BACKEND=native`.
79
+ - Port switcher popup/sidebar surfaces after parity tests cover
80
+ selection, preview, and key actions.
81
+
82
+ ### Picker dismissal
83
+
84
+ - Picker-agnostic popup close/toggle handling so AI picker dismissal
85
+ does not depend on fzf-specific key bindings. Done in the 0.5 picker
86
+ contract slice; fzf maps close actions to `abort`, and the native runner
87
+ consumes the same close action keys.
88
+
89
+ ### Docker install and E2E harness
90
+
91
+ - Initial Docker-backed Linux smoke suites are available through
92
+ `make test-integration`, `make test-install-smoke`, and `make test-e2e`.
93
+ They cover install/runtime substrate checks against real `tmux`; host-only
94
+ terminal, WSL, macOS, and GUI checks remain separate in
95
+ [docs/testing.md](testing.md).
@@ -0,0 +1,33 @@
1
+ # Shell Auto-Start
2
+
3
+ If you want every new interactive bash or zsh shell to drop you straight into
4
+ the projmux app, add a guarded hook to `~/.bashrc`, `~/.zshrc`, or the
5
+ equivalent interactive rc file for your shell:
6
+
7
+ ```sh
8
+ if [[ $- == *i* && -z "${TMUX:-}" ]] && command -v projmux >/dev/null 2>&1; then
9
+ exec projmux shell
10
+ fi
11
+ ```
12
+
13
+ The three guards each prevent a common breakage:
14
+
15
+ - `$- == *i*` — only fire for interactive shells. Without this you would
16
+ break `scp`, `ssh host cmd`, `git` over SSH, and any `bash -c '...'` or
17
+ `zsh -c '...'` invocation.
18
+ - `-z "${TMUX:-}"` — skip when already inside tmux. Without this the hook
19
+ recurses every time projmux opens a new pane.
20
+ - `command -v projmux >/dev/null 2>&1` — skip on machines where projmux is not
21
+ installed yet. Without this a fresh login on a new box hangs at a missing
22
+ binary.
23
+
24
+ To bypass the hook for one shell, set `TMUX` before launching:
25
+
26
+ ```sh
27
+ TMUX=1 bash
28
+ TMUX=1 zsh
29
+ ```
30
+
31
+ This is **opt-in** behavior. projmux does not assume you want every shell to
32
+ auto-start the app; the snippet above is here only as a known-safe starting
33
+ point for users who do.