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.
package/README.md CHANGED
@@ -1,311 +1,109 @@
1
1
  # projmux
2
2
 
3
- Project-aware tmux workspace management for people who live in terminals.
3
+ <p align="center">
4
+ <img src="docs/assets/projmux-icon.png" alt="projmux icon" width="112">
5
+ </p>
4
6
 
5
- `projmux` turns project directories into durable tmux workspaces with previews,
6
- sidebar navigation, generated keybindings, status metadata, and AI-pane
7
- attention signals. It can run as its own tmux app (`projmux shell`) or install
8
- the same behavior into your existing tmux server.
7
+ Project-aware tmux workspaces with fast switching, previews, status context,
8
+ and AI-pane attention built in.
9
9
 
10
10
  [![npm version](https://img.shields.io/npm/v/projmux?logo=npm)](https://www.npmjs.com/package/projmux)
11
11
  [![CI](https://github.com/crevissepartners/projmux/actions/workflows/ci.yml/badge.svg)](https://github.com/crevissepartners/projmux/actions/workflows/ci.yml)
12
12
 
13
- [한국어 README](README-ko.md)
13
+ [Korean README](README-ko.md)
14
14
 
15
- ## Why projmux
15
+ ![projmux shell project sidebar demo](docs/assets/projmux-shell-sidebar.gif)
16
16
 
17
- Most tmux project switchers stop at "pick a directory and attach a session".
18
- `projmux` treats that as the foundation, then adds the app-level pieces needed
19
- for a daily terminal workspace:
17
+ ## What It Is
20
18
 
21
- - **Project identity stays stable.** Directories, pins, live sessions, preview
22
- selection, and lifecycle commands all use the same normalized session model.
23
- - **The UI shows context before you switch.** Popup and sidebar pickers preview
24
- sessions, windows, panes, git branch, Kubernetes context, and pane metadata.
25
- - **The tmux layer is generated, not hand-spliced.** `projmux` writes the tmux
26
- config it needs for popup launchers, window/pane rename flows, status
27
- segments, pane borders, attention badges, and app mode.
28
- - **AI panes are first-class.** Codex and Claude panes can be launched,
29
- labeled, tracked as thinking or waiting, surfaced in pane/window/session
30
- badges, and announced through desktop notifications.
31
- - **You can choose isolation or integration.** Use `projmux shell` as a
32
- self-contained tmux app, or install the generated snippet into your normal
33
- tmux server.
19
+ `projmux` turns project directories into durable tmux sessions. It gives you a
20
+ keyboard-first workspace app for switching projects, previewing sessions,
21
+ opening AI splits, and keeping useful context visible in tmux.
34
22
 
35
- ## What It Does
36
-
37
- - Creates or switches to tmux sessions from project directories.
38
- - Shows existing sessions with window and pane previews.
39
- - Provides popup and sidebar navigation surfaces backed by `fzf`.
40
- - Pins important projects and scans common source roots for new ones.
41
- - Persists preview selection for fast window and pane cycling.
42
- - Generates tmux bindings for launchers, rename prompts, pane borders, status
43
- segments, and attention hooks.
44
- - Displays git branch and Kubernetes context/namespace in the status area.
45
- - Renders a two-line clickable status bar with click-to-switch tabs on
46
- row 0 and HUD-style notify (left) and AI usage (right) segments on
47
- row 1.
48
- - Launches AI splits and keeps their agent name, topic, status, and
49
- notification state visible in tmux.
50
-
51
- ## Typical Workflow
52
-
53
- ```sh
54
- projmux shell
55
- ```
56
-
57
- Open the app once, then use its generated tmux bindings to:
58
-
59
- - jump between projects from a sidebar or popup,
60
- - inspect sessions before attaching,
61
- - split Codex, Claude, or a plain shell into the current workspace,
62
- - rename windows and AI pane topics without losing metadata,
63
- - see which panes need review from badges and desktop notifications.
23
+ Use it when you want one command to open your terminal workspace and one set of
24
+ keys to move between projects, windows, panes, notifications, and settings.
64
25
 
65
26
  ## Requirements
66
27
 
67
- - [Node.js](https://nodejs.org/) and npm — required for the recommended npm
68
- install path.
69
- - [Go 1.24+](https://go.dev/dl/) required only when installing with
70
- `go install` or building from source.
71
- - [tmux](https://github.com/tmux/tmux/wiki/Installing) **≥ 3.4** — the workspace runtime. Earlier versions miss `display-popup -T` and other features projmux depends on.
72
- - [fzf](https://github.com/junegunn/fzf#installation) **≥ 0.55** — interactive popup/sidebar pickers. The multiline picker uses `--marker-multi-line`, `--gap-line`, and `--highlight-line`, which landed by 0.55.
73
- - A Unix shell such as `bash`, `zsh`, or `sh` — `projmux shell` uses your
74
- absolute `$SHELL` for the generated app config, falling back to `/bin/sh`.
75
- - [git](https://git-scm.com/downloads) — branch/status metadata.
76
- - `stty` — POSIX terminal control, used by `projmux setup`. Already shipped by every macOS / Linux base system; not applicable on Windows hosts.
77
- - [kubectl](https://kubernetes.io/docs/tasks/tools/) — optional, only for the Kubernetes status segment.
78
-
79
- Desktop notifications: Linux uses `notify-send`; WSL routes Windows toasts via
80
- `powershell.exe`. Override either with `PROJMUX_NOTIFY_HOOK`.
28
+ - [Node.js](https://nodejs.org/) and npm, for the main install path.
29
+ - [tmux](https://github.com/tmux/tmux/wiki/Installing) **3.4 or newer**.
30
+ - [fzf](https://github.com/junegunn/fzf#installation) **0.65.0 or newer**.
81
31
 
82
- Run `projmux doctor` any time to verify runtime dependencies are on `PATH`
83
- and that tmux/fzf meet the minimum supported versions. Terminal key delivery
84
- is diagnosed separately with `projmux setup`.
32
+ Run `projmux doctor` after installing to check the local runtime. The `fzf`
33
+ requirement is the junegunn/fzf CLI binary; `npm i fzf` is a different
34
+ JavaScript library.
85
35
 
86
36
  ## Install
87
37
 
88
38
  ```sh
89
39
  npm install -g projmux
90
- ```
91
-
92
- npm installs a small Node.js shim plus the matching platform binary package
93
- for Linux and macOS on x64 or arm64. The shim marks the install as npm-managed
94
- so `projmux update` and the Settings About screen can use the right upgrade
95
- path.
96
-
97
- Verify:
98
-
99
- ```sh
100
40
  projmux version
101
41
  ```
102
42
 
103
- If npm is not a fit for your machine, install with Go:
104
-
105
- ```sh
106
- go install github.com/crevissepartners/projmux/cmd/projmux@latest
107
- ```
108
-
109
- This drops the binary in `$(go env GOBIN)` (when set) or `$(go env GOPATH)/bin`
110
- (default `~/go/bin`). Make sure that directory is on your `PATH`.
111
-
112
- ### Optional: `PROJMUX_PROJDIR`
113
-
114
- `PROJMUX_PROJDIR` is the primary project root projmux uses for picker and
115
- discovery when you explicitly configure it. It is optional; when unset,
116
- projmux does not assume a canonical repo root. Discovery still uses pins, live
117
- sessions, saved workdirs, and weak common-folder probes (`~/source`, `~/work`,
118
- `~/projects`, `~/src`, `~/code`) when they exist.
119
-
120
- ```sh
121
- export PROJMUX_PROJDIR="/your/path"
122
- ```
123
-
124
- Add the line to `~/.bashrc`, `~/.zshrc`, or your shell's rc file. The resolved value is
125
- memoized to `~/.config/projmux/projdir` after first use, so later shells keep
126
- the same root even without the env var.
127
-
128
- `PROJMUX_PROJDIR` accepts an OS-native PATH-style multi-value (`:` on
129
- Linux/macOS, `;` on Windows). The first non-empty entry is the primary
130
- project root; any additional entries are prepended to the managed-roots
131
- search list, so they participate in discovery just like
132
- `PROJMUX_MANAGED_ROOTS`. Only the primary path is memoized to
133
- `~/.config/projmux/projdir`.
134
-
135
- ```sh
136
- # Linux/macOS — primary repo + secondary search root
137
- export PROJMUX_PROJDIR="/main/repos:/srv/work/repos"
138
- ```
139
-
140
- #### Set the project root during setup
141
-
142
- ```sh
143
- PROJMUX_PROJDIR=/your/path projmux shell
144
- ```
145
-
146
- The first invocation that sees the env var writes
147
- `~/.config/projmux/projdir`, so later shells without the env var still
148
- resolve the same root.
149
-
150
- ### From source
151
-
152
- ```sh
153
- git clone https://github.com/crevissepartners/projmux.git
154
- cd projmux
155
- make install
156
- ```
43
+ The npm package installs a small Node.js shim plus the matching projmux binary
44
+ for Linux and macOS on x64 or arm64. npm is the primary distribution path for
45
+ normal users.
157
46
 
158
- `make install` builds, atomically replaces `$(go env GOPATH)/bin/projmux`, and
159
- runs `projmux tmux apply` so the live `-L projmux` server picks up new bindings
160
- without a restart. Override the destination with `INSTALL_DIR=/usr/local/bin`.
47
+ Manual Go, source checkout, GitHub Release, and packaging details live in
48
+ [Install](docs/install.md).
161
49
 
162
50
  ## Quick Start
163
51
 
164
- Launch the isolated projmux tmux app:
52
+ Open the isolated projmux tmux app:
165
53
 
166
54
  ```sh
167
55
  projmux shell
168
56
  ```
169
57
 
170
- projmux owns this tmux server, its generated config, status bar, and popup
171
- bindings. Cooperative terminals get `Alt-1`..`Alt-5` immediately, with no
172
- terminal config. The left status badge shows the current project name; the
173
- right side shows path, kube segment, git segment, and clock.
174
-
175
- If a key does not fire, run `projmux setup` outside tmux to see which
176
- sequences your terminal swallows. For supported terminals, preview the
177
- fallback with `projmux init [terminal]`, then apply it with
178
- `projmux init [terminal] --apply` (auto-detects when no terminal is given).
179
- Dotfiles users on multi-machine setups should pass
180
- `--allow-symlink` or `--config <path>` to make their intent explicit. Full
181
- flow and the manual CSI-u fallback are in
182
- [Terminal Keybindings](docs/keybindings.md).
183
-
184
- If anything looks off, `projmux doctor` reports which dependency is
185
- missing or stale and how to install it. See [Requirements](#requirements)
186
- for the supported versions.
187
-
188
- ## Upgrading
189
-
190
- The Settings About screen is the normal interactive update surface: it shows
191
- cached release status, installer source, Check Updates, Update Now, and
192
- release notes. The startup update prompt uses the same cache and never reaches
193
- the network.
194
- To refresh the cached release status manually, run:
195
-
196
- ```sh
197
- projmux update check
198
- ```
199
-
200
- Use Settings > About > Update or `projmux update apply` to update through the
201
- detected installer. See [Upgrading](docs/upgrading.md) for npm, Go, GitHub
202
- Release, and source-checkout details.
203
-
204
- ## Usage
205
-
206
- Day-to-day, projmux is driven by tmux keybindings inside `projmux shell` — see
207
- [Terminal Keybindings](docs/keybindings.md). For the full CLI surface (pins,
208
- preview state, status helpers, updates, etc.), run `projmux help` or
209
- `<command> --help`.
210
-
211
- ## How It Finds Projects
212
-
213
- `projmux switch` combines pinned directories, live tmux sessions, and discovered
214
- project roots. When no explicit search roots are configured, discovery uses
215
- weak common-folder probes such as `~/source`, `~/work`, `~/projects`, `~/src`,
216
- and `~/code` if they exist; it does not assume a canonical `~/source/repos`
217
- root. `projmux settings` also has `Project Picker > Add Project...`, which
218
- scans filesystem roots up to depth 3 so projects outside the weak probes can be
219
- added to the picker. Session names are derived from normalized directory paths,
220
- so a project keeps the same tmux session name across launches.
221
-
222
- For permanent search-root customization, the Project Picker section also
223
- includes:
224
-
225
- - `+ Add Workdir...` - append a single directory to the saved workdirs list.
226
- - `Workdirs` - review and remove saved workdirs. The same picker also surfaces
227
- any active `PROJMUX_MANAGED_ROOTS` / `TMUX_SESSIONIZER_ROOTS` env values as
228
- read-only rows so you can see why an env list might be overriding the saved
229
- file.
230
-
231
- `Add Workdir > Type path manually...` gives you a typed entry that skips the
232
- filesystem scan. Use it for paths you do not want crawled, e.g. WSL mounts
233
- (`/mnt/c/Users/...`), large NFS mounts, or per-project temp roots.
234
-
235
- The saved file lives at `~/.config/projmux/workdirs` (one absolute path per
236
- line, `#` comments allowed). It is consulted only when the env vars are unset.
58
+ Inside the app:
237
59
 
238
- ## Hooks
60
+ - `Alt-1` opens the project sidebar.
61
+ - `Alt-2` opens the notification list.
62
+ - `Alt-3` opens the existing-session picker.
63
+ - `Alt-4` opens the AI split picker.
64
+ - `Alt-5` opens settings.
65
+ - `Alt-6` opens the project switcher popup.
239
66
 
240
- projmux runs an optional user script at `~/.config/projmux/hooks/post-create`
241
- whenever it creates a new tmux session. Use it to inject per-session env via
242
- `tmux set-environment` (e.g. picking a `GH_TOKEN` based on the project path).
243
- Missing or non-executable hooks are skipped silently; failures never block
244
- session creation. See [Hooks](docs/hooks.md) for the env contract, examples,
245
- and troubleshooting.
67
+ See [Terminal Keybindings](docs/keybindings.md) for the full key map. If a key
68
+ does not fire, run `projmux setup` outside tmux, then use
69
+ `projmux init [terminal] --apply` for supported terminal fallbacks.
246
70
 
247
- ## Environment Variables
71
+ ## Day-To-Day Use
248
72
 
249
- | Variable | Purpose |
250
- | --- | --- |
251
- | `PROJMUX_PROJDIR` | Explicit primary project root for the current shell. Accepts an OS-native PATH-style multi-value: the first entry is the primary repo root (memoized to `~/.config/projmux/projdir`), and any additional entries are prepended to the managed-roots search list. |
252
- | `PROJMUX_MANAGED_ROOTS` | Colon-separated list of search roots. Overrides the saved/heuristic list. |
253
- | `PROJMUX_NOTIFY_HOOK` | External executable that receives AI desktop notifications instead of the built-in sender. |
254
- | `PROJMUX_USAGE_STATE_DIR` | Override directory for the AI-usage snapshot cache. Defaults to `<state>/projmux/usage`. Point this at a synced location (Dropbox, iCloud Drive, etc) to share authoritative usage between machines. |
255
- | `PROJMUX_USAGE_DEBUG` | When non-empty, surfaces adapter errors from `projmux status usage` to stderr instead of swallowing them. |
256
- | `PROJMUX_USAGE_LIMITS_PATH` | Deprecated. Limits now come from the upstream APIs (Anthropic OAuth usage endpoint, Codex `rate_limits`); this variable is read but ignored. |
73
+ - Pick a project directory and projmux creates or reuses its tmux session.
74
+ - Pin important projects so they stay easy to reach.
75
+ - Preview windows, panes, git branch, Kubernetes context, and AI pane state
76
+ before switching.
77
+ - Use Settings > Project Picker to add roots and workdirs without editing env
78
+ vars.
79
+ - Use Settings > About > Update or `projmux update apply` to upgrade.
257
80
 
258
- ## AI usage tracking
81
+ For detailed configuration, including `PROJMUX_PROJDIR`, managed roots,
82
+ notifications, and usage tracking, see [Configuration](docs/configuration.md).
83
+ For update behavior by installer type, see [Upgrading](docs/upgrading.md).
259
84
 
260
- `projmux usage` reports authoritative 5-hour and weekly utilisation for both
261
- Claude Code and the Codex CLI. Both adapters read from the upstream's own
262
- view of your account so the percentages match what `claude /usage` and
263
- `codex` show natively:
85
+ ## More Docs
264
86
 
265
- - **Claude** — calls `GET https://api.anthropic.com/api/oauth/usage` with the
266
- bearer token in `~/.claude/.credentials.json`. The adapter performs a
267
- single refresh round-trip on 401 and rewrites the credentials file with
268
- the rotated tokens. Tokens are never logged.
269
- - **Codex** — reads the most recent `rate_limits` payload from the newest
270
- `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl`. `primary` maps to the 5h
271
- window, `secondary` to the weekly window.
272
-
273
- Snapshots are persisted under `<state>/projmux/usage/snapshots.json` (or
274
- `PROJMUX_USAGE_STATE_DIR`) and refreshed at most every 30 seconds when
275
- `projmux status usage` runs in the tmux status bar.
276
-
277
- ## Scope
278
-
279
- `projmux` owns the portable session-management core: naming, discovery, pins,
280
- preview state, tmux orchestration, status segments, and generated tmux bindings.
87
+ - [Install](docs/install.md)
88
+ - [Configuration](docs/configuration.md)
89
+ - [Terminal Keybindings](docs/keybindings.md)
90
+ - [CLI Reference](docs/cli.md)
91
+ - [Statusbar](docs/statusbar.md)
92
+ - [Hooks](docs/hooks.md)
93
+ - [Usage tracking](docs/usage-tracking.md)
94
+ - [Agent Workflow](docs/agent-workflow.md)
281
95
 
282
96
  ## Development
283
97
 
284
- Useful commands:
285
-
286
98
  ```sh
287
99
  make build
288
100
  make fmt
289
101
  make fix
290
102
  make test
291
- make test-integration
292
- make test-e2e
293
- make verify
294
103
  ```
295
104
 
296
- More documentation:
297
-
298
- - [Architecture](docs/architecture.md)
299
- - [CLI Reference](docs/cli.md)
300
- - [Statusbar](docs/statusbar.md)
301
- - [Notify queue](docs/notify-queue.md)
302
- - [Usage tracking](docs/usage-tracking.md)
303
- - [Upgrading](docs/upgrading.md)
304
- - [Hooks](docs/hooks.md)
305
- - [Migration Plan](docs/migration-plan.md)
306
- - [Repo Layout](docs/repo-layout.md)
307
- - [Terminal Keybindings](docs/keybindings.md)
308
- - [Agent Workflow](docs/agent-workflow.md)
105
+ See [Testing](docs/testing.md), [Architecture](docs/architecture.md), and
106
+ [Repo Layout](docs/repo-layout.md) for contributor details.
309
107
 
310
108
  ## License
311
109
 
@@ -0,0 +1,56 @@
1
+ # Agent Workflow
2
+
3
+ ## Parallel Work With `wt`
4
+ - Start with `wt --version` and `wt list`.
5
+ - Create the task path with `wt path --create <branch>`.
6
+ - Work only inside the returned path.
7
+ - Keep file ownership clear. If two agents need the same file, one should finish or hand off before the other edits it.
8
+ - Use `wt cleanup` or `wt prune` only after previewing what will be removed.
9
+
10
+ ## Branch Naming
11
+ - `feat/<topic>` for new behavior.
12
+ - `fix/<topic>` for bug fixes.
13
+ - `docs/<topic>` for documentation-only changes.
14
+ - `refactor/<topic>` for structure changes without intended behavior changes.
15
+ - `chore/<topic>` for maintenance or tooling.
16
+
17
+ ## Standard Development Loop
18
+ 1. Sync the branch context and inspect `git status --short`.
19
+ 2. Implement the smallest coherent change.
20
+ 3. Run `make fmt`.
21
+ 4. Run `make fix`.
22
+ 5. Run `make test`.
23
+ 6. Run `make test-integration`.
24
+ 7. Run `make test-e2e`.
25
+ 8. Update the maintained test list below if behavior or coverage expectations changed.
26
+ 9. Prepare review notes with parity status, commands run, and remaining risks.
27
+
28
+ ## Maintained Test List
29
+ - `make fmt`: repository formatting for Go, shell snippets, and generated docs where applicable.
30
+ - `make fix`: safe automatic fixes such as `go fix` and repository-approved cleanup steps.
31
+ - `make npm-pack`: local npm binary package staging and `npm pack --dry-run` for the root package plus platform packages.
32
+ - `make test`: fast unit coverage for app-layer AI split native agent launch/selective popup-toggle/settings/status/notification parity including AI pane option metadata, watcher metadata bootstrap for existing panes, capture-backed reply detection, missing-pane watcher shutdown, armed focus-only reply badge clearing, busy attention preservation on focus clear, manual AI topic preservation while watcher status still updates, agent-labeled desktop notification message context with pane-title-first body text, global/project-local post-create hook dispatch, and agent-specific notification icons, and scoped even row/column resizing after shell and agent splits, status-bar git/kube segment parity including branch block styling, statusbar pwd click path popup/buffer-copy fallback, isolated `projmux shell` tmux app launch/config generation including home-project default session targeting, app-owned project-name statusbar layout, and distinct project badge color, shell startup update prompt actions for fresh installer-aware cached updates, pane/window keybindings, window rename bindings, pane rename helper/binding, pane-exit rebalance command/hooks, hook-pane and after-select-pane based attention focus hooks, attention badge toggle/clear/list/window rendering, attach/current/kill/pin/preview/prune/sessions/session-popup/settings commands, switch, tag, tmux helper commands, update status/check/apply cache and installer detection including GitHub Release binary asset selection/extraction/replacement, doctor install-missing command selection, and Settings About update status/check action wiring, untitled standalone popup-toggle marker close/config install, direct popup minimum sizing, AI picker minimum width and height, sidebar minimum width and compact badge spacing, preview select writes, popup render output after cycling, switch picker pin action behavior without inline settings rows, nested settings hub sections for AI defaults, project picker filesystem scan/pin actions, Project Root settings source/shadowing/set/current/clear flows, app/keybinding info including Ctrl-M rename forwarding, and About version/source rendering, switch picker focused-session kill, switch picker launcher-key abort bindings, switch explicit project-root, unconfigured-root, and weak managed-root heuristic parity, switch popup hiding new-session candidates while sidebar keeps create-capable rows, switch row project-name display with `~` pinned to the top and live-session-first sorting, pretty-path, preview-context including kube context/namespace, switch settings subcommand flows including add-current-pin, interactive add-pin picker, and settings label/preview polish, fzf preview wiring, fzf baseline surface parity including prompt/footer/header fallback without app-name filler and search-key scoped card matching, sidebar compact action-only key footer, sidebar preview-window/start-position behavior without focus-time session switching, sidebar row/window ANSI styling with pane-aggregated attention badge state and AI topic labels, Alt+2/Alt+3 legacy popup row, preview-window, pane metadata, and pane-snapshot parity, switch read0 card rows with active/inactive title styling, right-side status badges, combined directory/git metadata with muted inactive branch styling, statusbar-matched block window tabs with window attention badges, read0 expect-key action parsing, restored pin/tag card badges, and restrained selected-row marker styling, switch preview metadata without duplicated directory/git rows, preview metadata rendering, popup pane display names for AI agents, AI topics, and shell commands, switch preview cycle bindings, sessions picker preview/cycle/open/kill wiring including attached-session fallback behavior, sessions picker launcher-key abort bindings, popup/switch preview summary formatting, popup sessions tmux entry helpers, switch/popup/session rendering, session identity, candidate discovery, config path derivation, popup preview read-models, and pure state rules including preview, tag, and lifecycle stores.
33
+ - Current focused unit coverage also includes strict notify SOT behavior
34
+ (TTL does not remove rows, focus success acks, reconcile reports stale
35
+ rows), `notify list --live` queue/live explanations, notify sidebar
36
+ two-line card rendering with age/project/window/pane metadata plus focus/ack/clear-all
37
+ actions, and `focus` dispatch diagnostics for session fallback, unresolved
38
+ targets, window fallback, pane fallback, explicit id failures, and
39
+ notify-only fallback.
40
+ - Picker focused unit coverage includes backend-neutral picker item/action mapping, fzf adapter output, native title-focused filtering, numeric selection, and shared close actions.
41
+ - `make test-integration`: Docker-backed Linux integration smoke with real `tmux`, `fzf`, `git`, and `stty`; covers `doctor`, tmux config print/install/apply, and notify queue CRUD against isolated HOME/XDG paths.
42
+ - `make test-install-smoke`: Docker-backed source install smoke; covers `make install`, atomic binary replacement, `tmux apply` against a live `projmux` socket, and post-install notify queue initialization.
43
+ - `make test-e2e`: Docker-backed real-tmux workflow smoke for session/pane setup, app config sourcing, reply-state notify reconciliation, focus notify fallback, and status notify rendering with contextual project/state/agent badges. Host-only terminal, WSL, macOS, and GUI notification behavior remains outside Docker; see [docs/testing.md](testing.md).
44
+
45
+ ## When To Update This List
46
+ - A feature moves between unit, integration, and e2e coverage levels.
47
+ - A new subsystem introduces a new validation target or removes one.
48
+ - Behavior changes require new parity assertions or a different e2e scenario.
49
+ - A target stops being authoritative and must be replaced.
50
+
51
+ ## Review Checklist
52
+ - The branch stays within its stated scope.
53
+ - The change preserves boundaries between portable `projmux` behavior and local machine policy.
54
+ - The required `make` targets were run in order.
55
+ - Test inventory updates are included when behavior changed.
56
+ - Known parity gaps are explicit.
@@ -0,0 +1,199 @@
1
+ # Architecture
2
+
3
+ ## Core model
4
+
5
+ `projmux` is built around a small set of domain objects:
6
+
7
+ - `ProjectRoot`: a directory that may map to a tmux session
8
+ - `SessionIdentity`: the stable session name derived from a directory
9
+ - `SessionTarget`: the current selected session/window/pane target
10
+ - `CandidateSet`: the ordered list of project directories presented to the user
11
+ - `PinSet`: user-curated candidate priority state
12
+ - `PreviewState`: selected window/pane state used by popup and session previews
13
+
14
+ ## Layers
15
+
16
+ ### 1. Core
17
+ Pure rules and state transitions.
18
+
19
+ Responsibilities:
20
+ - directory normalization
21
+ - session naming
22
+ - candidate ordering
23
+ - pin state changes
24
+ - tagged selection state
25
+ - lifecycle decisions such as reuse, create, kill, fallback
26
+
27
+ This layer should not shell out directly.
28
+
29
+ ### 2. Integrations
30
+ Adapters for external systems.
31
+
32
+ Initial adapters:
33
+ - tmux
34
+ - kubeconfig per-session state
35
+ - filesystem
36
+ - git metadata for preview enrichment
37
+
38
+ Responsibilities:
39
+ - execute commands
40
+ - parse command output
41
+ - convert failures into typed errors
42
+
43
+ ### 3. UI orchestration
44
+ Picker data is modeled independently from fzf rows. The app builds
45
+ backend-neutral `picker.Item` values (`Title`, `Value`, `SearchText`,
46
+ `MetaLines`, `Badges`, `PreviewTarget`) and then adapts them to the selected
47
+ backend. `fzf` remains the default, stable backend; the native backend is
48
+ opt-in through `PROJMUX_PICKER_BACKEND=native` while it reaches full parity.
49
+
50
+ Responsibilities:
51
+ - rows for popup and sidebar views
52
+ - preview rendering
53
+ - keybind-to-action dispatch
54
+ - selection handoff into core actions
55
+ - picker-agnostic close/dismiss actions
56
+
57
+ Picker-specific display and search rules are tracked in
58
+ [picker-ui-plan.md](picker-ui-plan.md).
59
+
60
+ This keeps parity with the existing shell workflow while moving state and behavior into Go.
61
+
62
+ ### 4. Local environment
63
+ This repo owns the portable application behavior and generated tmux config.
64
+
65
+ Responsibilities that remain outside `projmux`:
66
+ - terminal emulator key dispatch
67
+ - shell startup policy
68
+ - install-time package checks
69
+ - machine-specific path and symlink choices
70
+
71
+ ## Configuration model
72
+
73
+ Config should be explicit and file-backed.
74
+
75
+ Candidate areas:
76
+ - managed roots
77
+ - default home-like roots
78
+ - preview preferences
79
+ - session naming exceptions
80
+ - kube session settings
81
+ - ephemeral session retention defaults
82
+
83
+ ## State model
84
+
85
+ Persistent state:
86
+ - pins
87
+ - lightweight user preferences
88
+
89
+ Ephemeral runtime state:
90
+ - preview selection
91
+ - popup marker files
92
+ - current tagged selection set
93
+
94
+ ## Notify queue
95
+
96
+ `projmux` keeps a single JSON-backed queue of pending notifications at
97
+ `<state>/projmux/notify.json` (typically `~/.local/state/projmux/notify.json`,
98
+ following XDG). Writes go through an `O_CREATE|O_EXCL` lock file
99
+ (`notify.json.lock`) with bounded retry + jittered backoff so the queue
100
+ is safe across concurrent producers (the AI flow, the manual `attention
101
+ toggle`, the `notify push` CLI) on a local filesystem.
102
+
103
+ Attention and notify are intentionally separate surfaces: attention is live
104
+ tmux pane state, while notify is the explicit-ack pending queue derived from
105
+ AI reply panes and explicit pushes. The queue helps clicks route to work; it
106
+ does not own the truth of every live badge.
107
+
108
+ - **Push** — `projmux notify push` (or the in-process producer in
109
+ `internal/app/notify_producer.go`) appends an entry. Entries carry a
110
+ stable id (caller-supplied or `ai:<session>:<pane>` for the producer
111
+ path), text (capped at 80 runes), severity (`info|warn|critical`),
112
+ source (`ai|k8s|git|external`), TTL freshness metadata (default 600s), and a
113
+ `Target{Socket, Session, Window, Pane}`. Re-pushing an existing id
114
+ refreshes the entry's text and timestamp.
115
+ - **List** — `projmux notify list` returns newest-first until explicit ack.
116
+ TTL is not a removal condition. `projmux notify list --live` adds a
117
+ read-only comparison against live pane state, explaining manual reply
118
+ badges without queue entries, live AI replies with/missing queue entries,
119
+ and stale `ai:` entries.
120
+ - **Ack** — `projmux notify ack <id>` removes one entry; `--all`
121
+ flushes everything. Focus/click handlers do not ack rows.
122
+ - **Reconcile** — `projmux notify reconcile` walks
123
+ `tmux list-panes -a` and back-fills entries for panes whose
124
+ attention state is `reply` AND whose AI agent option is set,
125
+ reporting stale `ai:` entries that no longer match a live pane without
126
+ acking them.
127
+ `make install` and `projmux upgrade` invoke it so the queue
128
+ recovers from any drift introduced by a lost daemon.
129
+
130
+ The producer is wired to the attention state machine: a pane
131
+ transitioning to `reply` with an AI agent option set pushes an
132
+ `ai:<session>:<pane>` entry; the matching `clear` (or the AI
133
+ flow's `status set idle`) leaves it pending until explicit ack. Manual `attention toggle` on a
134
+ shell pane does not push because the agent option is empty —
135
+ the queue is intentionally AI-driven only.
136
+
137
+ See [notify-queue.md](notify-queue.md) for the full reference.
138
+
139
+ ## Usage snapshots
140
+
141
+ `projmux usage` and `projmux status usage` share a single `Manager`
142
+ that walks two registered adapters (Claude, Codex) and persists the
143
+ result to `<state>/projmux/usage/snapshots.json` (or
144
+ `PROJMUX_USAGE_STATE_DIR`). The cache file is the authoritative source
145
+ for the HUD render path so the tmux status interval never blocks on a
146
+ network call.
147
+
148
+ - **Per-adapter throttle** — Claude reports a 5-minute hint via the
149
+ `ThrottleHinter` interface; Codex falls through to the global
150
+ `30s` floor used by `status usage`. `MaybeCollect` only invokes an
151
+ adapter when `now - last_collect >= throttle`. `--force` bypasses the
152
+ gate.
153
+ - **429 backoff** — Claude implements `BackoffStater`. On HTTP 429
154
+ the adapter persists `BackoffState{Until, Consecutive}`: the
155
+ default cooldown is 30 minutes, doubling per consecutive 429 up to a
156
+ 60-minute cap. A `Retry-After` header (when present) raises the floor.
157
+ During backoff `Collect` short-circuits (no network call). A clean
158
+ 200 resets the streak. `--force` clears the persisted state via the
159
+ `BackoffResetter` interface so the next call attempts the network
160
+ call regardless of streak.
161
+ - **Failure preservation** — adapter failures do not erase prior
162
+ rows. The Manager merges new snapshots over the on-disk slice, so a
163
+ transient 429 keeps the last known good numbers visible.
164
+
165
+ See [usage-tracking.md](usage-tracking.md) for adapter detail (token
166
+ refresh, rollout schema).
167
+
168
+ ## Two-line clickable status bar
169
+
170
+ projmux configures tmux with `status 2`. Line 0 is the existing
171
+ session/window/path/git/kube/clock row. Line 1 splits the notification bar
172
+ (left half, capped at 80 cells) and the AI usage HUD (right half, capped at
173
+ 120 cells) using tmux `#[align=left]` / `#[align=right]`. Each clickable
174
+ segment is wrapped in a tmux user-defined range (`#[range=user|<id>]...
175
+ #[norange]`) and dispatched through `projmux statusbar click <range-id>`. A
176
+ single `bind -n MouseDown1Status` covers both lines because tmux fires
177
+ `MouseDown1Status` from any line of a multi-line status bar with
178
+ `#{mouse_status_range}` resolving to whichever range the cursor was over.
179
+
180
+ | Range id | Line | Click action | Keybinding |
181
+ |----------|------|-------------------------------------------|--------------|
182
+ | session | 0 | popup `projmux sessions --ui=popup` | prefix+s s |
183
+ | pwd | 0 | copy pane_current_path and show path popup | prefix+s p |
184
+ | kube | 0 | popup `projmux switch --ui=popup` | prefix+s k |
185
+ | git | 0 | popup `projmux switch --ui=popup` | prefix+s g |
186
+ | usage | 1 | popup `projmux usage` | prefix+s u |
187
+ | notify | 1 | focus origin pane of newest notification | prefix+s n |
188
+
189
+ The keyboard chord uses `bind-key s switch-client -T projmux-status` so the
190
+ prefix-then-`s`-then-letter shortcut routes through the same dispatcher as
191
+ the mouse click. Empty `#{mouse_status_range}` (clicks on whitespace) is a
192
+ no-op so the binding never flashes a spurious error.
193
+
194
+ ## Non-goals
195
+
196
+ - replacing tmux
197
+ - owning terminal emulator bindings
198
+ - becoming a generic worktree orchestrator
199
+ - implementing a fully custom TUI before parity is reached
Binary file