projmux 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-ko.md +58 -282
- package/README.md +57 -273
- package/docs/agent-workflow.md +56 -0
- package/docs/architecture.md +199 -0
- package/docs/assets/projmux-icon.png +0 -0
- package/docs/assets/projmux-shell-sidebar.gif +0 -0
- package/docs/cli.md +432 -0
- package/docs/configuration.md +127 -0
- package/docs/hooks.md +118 -0
- package/docs/install.md +99 -0
- package/docs/keybindings.md +337 -0
- package/docs/migration-plan.md +82 -0
- package/docs/native-picker-no-fzf-poc.md +228 -0
- package/docs/native-picker-parity.md +145 -0
- package/docs/notify-queue.md +200 -0
- package/docs/npm-distribution.md +86 -0
- package/docs/picker-ui-plan.md +111 -0
- package/docs/pr-guideline.md +106 -0
- package/docs/repo-layout.md +50 -0
- package/docs/roadmap.md +95 -0
- package/docs/shell-autostart.md +33 -0
- package/docs/statusbar.md +157 -0
- package/docs/testing.md +57 -0
- package/docs/upgrading.md +100 -0
- package/docs/usage-tracking.md +153 -0
- package/package.json +7 -5
|
@@ -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 default native runner.
|
|
17
|
+
- `internal/ui/fzf` adapts that model into the historical fzf command line.
|
|
18
|
+
|
|
19
|
+
The fzf fallback backend 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
|
+
- Native is the default backend and renders the popup/sidebar surfaces.
|
|
104
|
+
- `PROJMUX_PICKER_BACKEND=fzf` opts into the external fzf runner. Native supports
|
|
105
|
+
multiline item rendering, title-focused search via `SearchText`, numeric
|
|
106
|
+
selection, and shared close actions.
|
|
107
|
+
- Switcher popup/sidebar use the selected backend for preview and key action
|
|
108
|
+
parity, including native preview panes, raw-key navigation, and sidebar focus
|
|
109
|
+
tracking.
|
|
110
|
+
|
|
111
|
+
fzf can stay as the stable fallback while the native picker continues to mature.
|
|
@@ -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
|
package/docs/roadmap.md
ADDED
|
@@ -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
|
+
- Native picker backend for multi-line card rows and title-focused search.
|
|
77
|
+
Done in the 0.5 picker contract slice, later promoted to the default picker
|
|
78
|
+
backend with `PROJMUX_PICKER_BACKEND=fzf` kept as the explicit fallback.
|
|
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.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Statusbar
|
|
2
|
+
|
|
3
|
+
`projmux shell` configures tmux with `status 2` and renders a two-line
|
|
4
|
+
clickable status bar. The same dispatcher (`projmux statusbar click`)
|
|
5
|
+
handles both mouse clicks and the keyboard chord, so adding a new
|
|
6
|
+
segment only requires one wiring point.
|
|
7
|
+
|
|
8
|
+
## Layout
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
row 0 [#S] #{pane_current_path} ⎈ <ctx>/<ns> <git> %H:%M
|
|
12
|
+
└────────── native tmux window list (one entry per window) ──────────┘
|
|
13
|
+
row 1 #[range=user|notify] <notify HUD pill> #[norange]
|
|
14
|
+
#[range=user|usage] <usage HUD bar> #[norange]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- Row 0 keeps tmux's native `window-status-format` so clicking a tab
|
|
18
|
+
selects the window. The bind uses `if-shell -F
|
|
19
|
+
"#{==:#{mouse_status_range},window}"` to run `select-window -t =`
|
|
20
|
+
natively when the click lands on the window list, so the
|
|
21
|
+
mouse-target context resolves the clicked window directly. All
|
|
22
|
+
other ranges fall through to `run-shell projmux statusbar click
|
|
23
|
+
...`, which dispatches by range id. The in-config short-circuit
|
|
24
|
+
is required because `#{mouse_window}` is empty for window-list
|
|
25
|
+
clicks on tmux 3.4+, so a `run-shell` handler can't recover the
|
|
26
|
+
target after the fact — the Go dispatcher's
|
|
27
|
+
`isWindowListRangeToken` fallback is now defense-in-depth only.
|
|
28
|
+
The session, pwd, kube, and git segments on this row are wrapped
|
|
29
|
+
in `#[range=user|<id>]` ranges and dispatched through the projmux
|
|
30
|
+
handler. The git segment shows the current branch or detached commit,
|
|
31
|
+
then compact state indicators when available: `*` for local changes,
|
|
32
|
+
`+N` for staged entries, and `↑N`/`↓N` for ahead/behind counts. Each
|
|
33
|
+
state token gets its own compact foreground color while preserving the
|
|
34
|
+
existing branch block background.
|
|
35
|
+
- Row 1 splits the line with `#[align=left]` (the pending AI notify
|
|
36
|
+
queue, capped at 80
|
|
37
|
+
cells) and `#[align=right]` (usage, capped at 120 cells). `notify` is the
|
|
38
|
+
explicit-ack pending queue; live pane attention badges are a separate
|
|
39
|
+
state surface. Both
|
|
40
|
+
segments degrade gracefully when the cell budget is tight; see
|
|
41
|
+
[notify-queue.md](notify-queue.md) and [usage-tracking.md](usage-tracking.md)
|
|
42
|
+
for the per-segment tier ladder.
|
|
43
|
+
|
|
44
|
+
A single tmux bind handles both lines:
|
|
45
|
+
|
|
46
|
+
```tmux
|
|
47
|
+
bind-key -n MouseDown1Status if-shell -F "#{==:#{mouse_status_range},window}" \
|
|
48
|
+
{ select-window -t = } \
|
|
49
|
+
{ run-shell "'<projmux>' statusbar click \"#{mouse_status_range}\" --mouse-window \"#{mouse_window}\"" }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`MouseDown1Status` fires from any line of a multi-line status bar with
|
|
53
|
+
`#{mouse_status_range}` resolving to the range under the cursor.
|
|
54
|
+
|
|
55
|
+
## Range catalogue
|
|
56
|
+
|
|
57
|
+
| Range id | Row | Click action | Keyboard |
|
|
58
|
+
| -------- | --- | ----------------------------------------- | ------------- |
|
|
59
|
+
| `session` | 0 | `projmux tmux popup-toggle sessionizer-sidebar` | `prefix s s` |
|
|
60
|
+
| `pwd` | 0 | copy `#{pane_current_path}` to tmux buffer and show a compact `Path copied` popup | `prefix s p` |
|
|
61
|
+
| `kube` | 0 | `projmux tmux popup-toggle sessionizer` | `prefix s k` |
|
|
62
|
+
| `git` | 0 | `projmux tmux popup-toggle sessionizer` | `prefix s g` |
|
|
63
|
+
| `usage` | 1 | `display-popup -E -h 60% -w 80% -- projmux usage`, then wait for Enter | `prefix s u` |
|
|
64
|
+
| `notify` | 1 | `projmux focus --target <newest> --source status-bar --kind segment-click`, then ack on focus success | `prefix s n` |
|
|
65
|
+
|
|
66
|
+
`notify` reads the pending queue only. For a live pane-state view that is
|
|
67
|
+
independent of queued reminders, use `projmux attention list`. To explain why
|
|
68
|
+
a live reply badge and the queue disagree, use `projmux notify list --live`.
|
|
69
|
+
The notify segment renders the newest queued item as a single notification
|
|
70
|
+
block: project, state (`NEED`/`INFO`/`WARN`/`CRIT`), optional agent, text,
|
|
71
|
+
age, and `+N` for older pending entries. Window/pane ids are not shown in the
|
|
72
|
+
compact status segment.
|
|
73
|
+
`usage` deliberately opens the detailed `projmux usage` table popup; it is the
|
|
74
|
+
clear action surface for the compact HUD bar.
|
|
75
|
+
|
|
76
|
+
The path popup uses a short title, one-line copy status, the current path, and
|
|
77
|
+
an `Enter closes this popup` prompt. If the tmux buffer write fails, it keeps
|
|
78
|
+
the same compact surface with `Current path` as the title and a copy-unavailable
|
|
79
|
+
message. The notification HUD detail surface (`Alt-2` / `User2`) opens the
|
|
80
|
+
right-side notification popup with newest-first rows. The popup itself is
|
|
81
|
+
untitled; when decoration mode is `symbol` or `emoji`, the bell appears before
|
|
82
|
+
the fzf header text instead. Selecting a row still focuses and acknowledges
|
|
83
|
+
that notification.
|
|
84
|
+
|
|
85
|
+
Empty `#{mouse_status_range}` (a click on whitespace) falls through to
|
|
86
|
+
`select-window -t @<mouse_window>` when `--mouse-window` is non-empty,
|
|
87
|
+
otherwise it is a no-op. Unknown user range ids are non-specialized
|
|
88
|
+
placeholder surfaces and no-op until a handler is wired into the dispatcher.
|
|
89
|
+
|
|
90
|
+
## Keyboard chord
|
|
91
|
+
|
|
92
|
+
```tmux
|
|
93
|
+
bind-key s switch-client -T projmux-status
|
|
94
|
+
bind-key -T projmux-status u run-shell '#{q:projmux} statusbar click usage'
|
|
95
|
+
bind-key -T projmux-status n run-shell '#{q:projmux} statusbar click notify'
|
|
96
|
+
bind-key -T projmux-status g run-shell '#{q:projmux} statusbar click git'
|
|
97
|
+
bind-key -T projmux-status k run-shell '#{q:projmux} statusbar click kube'
|
|
98
|
+
bind-key -T projmux-status p run-shell '#{q:projmux} statusbar click pwd'
|
|
99
|
+
bind-key -T projmux-status s run-shell '#{q:projmux} statusbar click session'
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The chord routes through the same dispatcher as the mouse click, so
|
|
103
|
+
keyboard and mouse paths are functionally identical.
|
|
104
|
+
|
|
105
|
+
## Click failure handling
|
|
106
|
+
|
|
107
|
+
Every status-bar click runs from tmux's `run-shell`. A non-zero exit
|
|
108
|
+
there triggers a tmux error popup, which is hostile UX for a casual
|
|
109
|
+
click. Each handler therefore swallows runtime failures and surfaces
|
|
110
|
+
them as `display-message` toasts:
|
|
111
|
+
|
|
112
|
+
- `notify` click whose focus dispatch exits 2 (target unresolved):
|
|
113
|
+
keep the entry pending, toast `notify target gone; ack to clear`.
|
|
114
|
+
- Any other focus failure: keep the entry, toast `focus failed:
|
|
115
|
+
<reason>`.
|
|
116
|
+
- `session`, `kube`, or `git` popup launch failure: toast
|
|
117
|
+
`statusbar <range>: popup failed`.
|
|
118
|
+
- `pwd` path popup failure: keep the copied path in the tmux paste
|
|
119
|
+
buffer when possible and fall back to a short `display-message`.
|
|
120
|
+
- `usage` popup failure: fall back to inlining the rendered table
|
|
121
|
+
into a single `display-message`.
|
|
122
|
+
|
|
123
|
+
`MouseX` / `MouseY` are accepted but not consumed today; the fields
|
|
124
|
+
are wired through so click telemetry can land without changing the
|
|
125
|
+
bind.
|
|
126
|
+
|
|
127
|
+
## Customizing
|
|
128
|
+
|
|
129
|
+
The status bar is generated by `projmux tmux print-config` /
|
|
130
|
+
`print-app-config`. To change a segment, edit the generator (it
|
|
131
|
+
emits a deterministic block per segment), regenerate, and re-apply:
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
projmux tmux apply
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Settings > Icons & Decorations controls the optional decoration mode used by
|
|
138
|
+
the path, git branch, and notification sidebar header. The persisted enum lives at
|
|
139
|
+
`~/.config/projmux/statusbar-decoration`; valid values are `off` (default,
|
|
140
|
+
font-safe), `symbol` (Nerd Font-style folder/GitHub/bell icons), and `emoji`.
|
|
141
|
+
Settings also updates tmux `@projmux_statusbar_decoration` for the live
|
|
142
|
+
server when run inside tmux.
|
|
143
|
+
|
|
144
|
+
To add a new clickable segment:
|
|
145
|
+
|
|
146
|
+
1. Add a `statusbarRangeID` constant in
|
|
147
|
+
`internal/app/statusbar.go`.
|
|
148
|
+
2. Wire its handler into `dispatchTable`.
|
|
149
|
+
3. Update the generator so the segment is wrapped in
|
|
150
|
+
`#[range=user|<id>]...#[norange]` on the chosen row.
|
|
151
|
+
4. Add a chord key under `projmux-status` if a keyboard binding is
|
|
152
|
+
wanted.
|
|
153
|
+
|
|
154
|
+
Custom user ranges can coexist with the built-in window-list range:
|
|
155
|
+
the dispatcher detects the `window` / `window|<idx>` token before
|
|
156
|
+
the user-range table, so a hostile range named `window` cannot
|
|
157
|
+
shadow the built-in.
|