From 71b582d96241ca927a16e67a444604bbee5becf7 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 8 Feb 2026 06:00:15 -0600 Subject: [PATCH 01/53] ai(rules[check:*,implement]) Add tmuxinator parity commands --- .claude/commands/check/parity.md | 78 ++++++++++++ .claude/commands/check/shortcomings.md | 51 ++++++++ .claude/commands/implement.md | 160 +++++++++++++++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 .claude/commands/check/parity.md create mode 100644 .claude/commands/check/shortcomings.md create mode 100644 .claude/commands/implement.md diff --git a/.claude/commands/check/parity.md b/.claude/commands/check/parity.md new file mode 100644 index 0000000000..533a06eb1e --- /dev/null +++ b/.claude/commands/check/parity.md @@ -0,0 +1,78 @@ +# /check:parity — Feature Parity Analysis + +Deep-dive analysis of tmuxp vs tmuxinator and teamocil. Updates comparison docs and parity notes. + +## Workflow + +1. **Read source code** of all three projects: + - tmuxp: `src/tmuxp/workspace/` (builder.py, loader.py, importers.py), `src/tmuxp/cli/load.py` + - tmuxinator: `~/study/ruby/tmuxinator/lib/tmuxinator/` (project.rb, window.rb, pane.rb, hooks/, assets/template.erb) + - teamocil: `~/study/ruby/teamocil/lib/teamocil/tmux/` (session.rb, window.rb, pane.rb) + +2. **Read existing docs** for baseline: + - `docs/about.md` — tmuxp's own feature description + - `docs/comparison.md` — feature comparison table (create if missing) + - `notes/parity-tmuxinator.md` — tmuxinator parity analysis (create if missing) + - `notes/parity-teamocil.md` — teamocil parity analysis (create if missing) + +3. **Update `docs/comparison.md`** with tabular feature comparison: + - Overview table (language, min tmux, config format, architecture) + - Configuration keys table (every key across all three, with ✓/✗) + - CLI commands table (side-by-side) + - Architecture comparison (ORM vs script generation vs command objects) + - Include version numbers for each project + +4. **Update `notes/parity-tmuxinator.md`** with: + - Features tmuxinator has that tmuxp lacks (with source locations) + - Import behavior analysis (what the current importer handles vs misses) + - WorkspaceBuilder requirements for 100% feature support + - Code quality issues in current importer + +5. **Update `notes/parity-teamocil.md`** with: + - Features teamocil has that tmuxp lacks (with source locations) + - v0.x vs v1.4.2 format differences (current importer targets v0.x only) + - Import behavior analysis + - WorkspaceBuilder requirements for full parity + +6. **Commit each file separately** + +## Key areas to verify + +- Check `importers.py` line-by-line against actual tmuxinator/teamocil config keys +- Verify `load_workspace()` actually reads config keys it claims to support +- Cross-reference CHANGELOGs for version-specific features +- Check test fixtures match real-world configs + +--- + +# Import Behavior + +Study tmuxp, teamocil, and tmuxinator source code. Find any syntax they support that tmuxp's native syntax doesn't. + +Create/update: +- `notes/import-teamocil.md` +- `notes/import-tmuxinator.md` + +## Syntax Level Differences / Limitations + +For each config key and syntax pattern discovered, classify as: + +### Differences (Translatable) + +Syntax that differs but can be automatically converted during import. Document the mapping. + +### Limitations (tmuxp needs to add support) + +Syntax/features that cannot be imported because tmuxp lacks the underlying capability. For each, note: +1. What the feature does in the source tool +2. Why it can't be imported +3. What tmuxp would need to add + +--- + +# WorkspaceBuilder + +Analyze what WorkspaceBuilder needs to: + +1. **Auto-detect config format** — Determine heuristics to identify tmuxinator vs teamocil vs tmuxp configs transparently +2. **100% feature support** — List every feature/behavior needed for complete compatibility, including behavioral idiosyncrasies diff --git a/.claude/commands/check/shortcomings.md b/.claude/commands/check/shortcomings.md new file mode 100644 index 0000000000..48a3669da0 --- /dev/null +++ b/.claude/commands/check/shortcomings.md @@ -0,0 +1,51 @@ +# /check:shortcomings — API Limitations Analysis + +Second-step command that reads parity analysis and outputs API blockers to `notes/plan.md`. + +## Input Files (from /check:parity) + +- `notes/parity-tmuxinator.md` +- `notes/parity-teamocil.md` +- `notes/import-tmuxinator.md` +- `notes/import-teamocil.md` + +## Workflow + +1. **Read parity analysis files** to understand feature gaps + +2. **Explore libtmux** at `~/work/python/libtmux/`: + - What APIs are missing? (e.g., no `pane.set_title()`) + - What's hardcoded? (e.g., `shutil.which("tmux")`) + +3. **Explore tmuxp** at `~/work/python/tmuxp/`: + - What config keys are dead data? + - What keys are missing from loader/builder? + - What CLI flags are missing? + +4. **Update `notes/plan.md`** with: + - libtmux limitations (what Server/Pane/Window/Session can't do) + - tmuxp limitations (what WorkspaceBuilder/loader/cli can't do) + - Dead config keys (imported but ignored) + - Required API additions for each gap + - Non-breaking implementation notes + +5. **Commit** `notes/plan.md` + +## Output Structure + +notes/plan.md should follow this format: + +### libtmux Limitations +Per-limitation: +- **Blocker**: What API is missing/hardcoded +- **Blocks**: What parity feature this prevents +- **Required**: What API addition is needed + +### tmuxp Limitations +Per-limitation: +- **Blocker**: What's missing/broken +- **Blocks**: What parity feature this prevents +- **Required**: What change is needed + +### Implementation Notes +Non-breaking approach for each limitation. diff --git a/.claude/commands/implement.md b/.claude/commands/implement.md new file mode 100644 index 0000000000..869bfb9805 --- /dev/null +++ b/.claude/commands/implement.md @@ -0,0 +1,160 @@ +# /implement — Plan and Implement from notes/plan.md + +Orchestrates the full implementation workflow: plan → implement → test → verify → commit → document. + +## Reference Codebases + +- **tmuxinator**: `~/study/ruby/tmuxinator/` +- **teamocil**: `~/study/ruby/teamocil/` +- **tmux**: `~/study/c/tmux/` +- **libtmux**: `~/work/python/libtmux/` +- **tmuxp**: `~/work/python/tmuxp/` + +## Workflow + +### Phase 1: Planning Mode + +1. **Read the plan**: Load `notes/plan.md` to understand what needs to be implemented +2. **Select a task**: Pick the highest priority incomplete item from the plan +3. **Research**: + - Read relevant tmuxinator/teamocil Ruby source for behavior reference + - Read libtmux Python source for available APIs + - Read tmuxp source for integration points + - **Study existing tests** for similar functionality (see Testing Pattern below) +4. **Create implementation plan**: Design the specific changes needed +5. **Exit planning mode** with the finalized approach + +### Phase 2: Implementation + +1. **Make changes**: Edit the necessary files +2. **Follow conventions**: Match existing code style, use type hints, add docstrings + +### Phase 3: Write Tests + +**CRITICAL**: Before running verification, write tests for new functionality. + +1. **Find similar tests**: Search `tests/` for existing tests of similar features +2. **Follow the project test pattern** (see Testing Pattern below) +3. **Add test cases**: Cover normal cases, edge cases, and error conditions + +### Phase 4: Verification + +Run the full QA suite: + +```bash +uv run ruff check . --fix --show-fixes +uv run ruff format . +uv run mypy +uv run py.test --reruns 0 -vvv +``` + +All checks must pass before proceeding. + +### Phase 5: Commit Implementation + +**Source and tests must be in separate commits.** + +1. **Commit source code first**: Implementation changes only (e.g., `fix(cli): Read socket_name/path and config from workspace config`) +2. **Commit tests second**: Test files only (e.g., `tests(cli): Add config key precedence tests for load_workspace`) + +Follow the project's commit conventions (e.g., `feat:`, `fix:`, `refactor:` for source; `tests:` or `tests():` for tests). + +### Phase 6: Update Documentation + +1. **Update `notes/completed.md`**: Add entry for what was implemented + - Date + - What was done + - Files changed + - Any notes or follow-ups + +2. **Update `notes/plan.md`**: Mark the item as complete or remove it + +3. **Commit notes separately**: Use message like `notes: Mark as complete` + +--- + +## Testing Pattern + +This project uses a consistent test pattern. **Always follow this pattern for new tests.** + +### 1. NamedTuple Fixture Class + +```python +import typing as t + +class MyFeatureTestFixture(t.NamedTuple): + """Test fixture for my feature tests.""" + + # pytest (internal): Test fixture name + test_id: str + + # test params + input_value: str + expected_output: str + expected_error: str | None = None +``` + +### 2. Fixture List + +```python +TEST_MY_FEATURE_FIXTURES: list[MyFeatureTestFixture] = [ + MyFeatureTestFixture( + test_id="normal-case", + input_value="foo", + expected_output="bar", + ), + MyFeatureTestFixture( + test_id="edge-case-empty", + input_value="", + expected_output="", + ), + MyFeatureTestFixture( + test_id="error-case", + input_value="bad", + expected_output="", + expected_error="Invalid input", + ), +] +``` + +### 3. Parametrized Test Function + +```python +@pytest.mark.parametrize( + "test", + TEST_MY_FEATURE_FIXTURES, + ids=[test.test_id for test in TEST_MY_FEATURE_FIXTURES], +) +def test_my_feature(test: MyFeatureTestFixture) -> None: + """Test my feature with various inputs.""" + result = my_function(test.input_value) + assert result == test.expected_output + + if test.expected_error: + # check error handling + pass +``` + +### Key Rules + +- **Function tests only** — No `class TestFoo:` groupings (per CLAUDE.md) +- **Use fixtures from `tests/fixtures/`** — Prefer real tmux fixtures over mocks +- **Use `tmp_path`** — Not Python's `tempfile` +- **Use `monkeypatch`** — Not `unittest.mock` + +--- + +## Output + +After completion, report: +- What was implemented +- Files changed (including test files) +- Test results summary +- What remains in the plan + +## Notes + +- If tests fail, fix the issues before committing +- If libtmux changes are needed, note them but don't modify libtmux in this workflow +- One logical change per run — don't implement multiple unrelated items +- **Always write tests** — No implementation is complete without tests From 61089e639938c143888864c76e8ba3551b8eeb02 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 8 Feb 2026 06:08:05 -0600 Subject: [PATCH 02/53] docs(comparison) Add feature comparison table for tmuxp/tmuxinator/teamocil Comprehensive side-by-side comparison covering architecture, config keys, CLI commands, hooks, and config file discovery across all three tools. --- docs/comparison.md | 172 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 docs/comparison.md diff --git a/docs/comparison.md b/docs/comparison.md new file mode 100644 index 0000000000..3ae0f42f7b --- /dev/null +++ b/docs/comparison.md @@ -0,0 +1,172 @@ +# Feature Comparison: tmuxp vs tmuxinator vs teamocil + +*Last updated: 2026-02-08* + +## Overview + +| | tmuxp | tmuxinator | teamocil | +|---|---|---|---| +| **Version** | 1.47.0+ | 3.3.7 | 1.4.2 | +| **Language** | Python | Ruby | Ruby | +| **Min tmux** | 3.2 | 1.5 | 3.2 | +| **Config formats** | YAML, JSON | YAML (with ERB) | YAML | +| **Architecture** | ORM (libtmux) | Script generation (ERB templates) | Command objects → shell exec | +| **License** | MIT | MIT | MIT | +| **Session building** | API calls via libtmux | Generates bash script, then execs it | Generates tmux command string, then `system()` | +| **Plugin system** | Yes (Python classes) | No | No | +| **Shell completion** | Yes | Yes (zsh/bash/fish) | No | + +## Architecture Comparison + +### tmuxp — ORM-Based + +tmuxp uses **libtmux**, an object-relational mapper for tmux. Each tmux entity (server, session, window, pane) has a Python object with methods that issue tmux commands via `tmux(1)`. Configuration is parsed into Python dicts, then the `WorkspaceBuilder` iterates through them, calling libtmux methods. + +**Advantages**: Programmatic control, error recovery mid-build, plugin hooks at each lifecycle stage, Python API for scripting. + +**Disadvantages**: Requires Python runtime, tightly coupled to libtmux API. + +### tmuxinator — Script Generation + +tmuxinator reads YAML (with ERB templating), builds a `Project` object graph, then renders a bash script via ERB templates. The generated script is `exec`'d, replacing the tmuxinator process. + +**Advantages**: Debuggable output (`tmuxinator debug`), wide tmux version support (1.5+), ERB allows config templating with variables. + +**Disadvantages**: No mid-build error recovery (script runs or fails), Ruby dependency. + +### teamocil — Command Objects + +teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Command` objects with `to_s()` methods. Commands are joined with `; ` and executed via `Kernel.system()`. + +**Advantages**: Simple, predictable, debuggable (`--debug`). + +**Disadvantages**: No error recovery, no hooks, no templating, minimal feature set. + +## Configuration Keys + +### Session-Level + +| Key | tmuxp | tmuxinator | teamocil | +|---|---|---|---| +| Session name | `session_name` | `name` / `project_name` | `name` | +| Root directory | `start_directory` | `root` / `project_root` | (none, per-window only) | +| Windows list | `windows` | `windows` / `tabs` | `windows` | +| Socket name | (CLI `-L`) | `socket_name` | (none) | +| Socket path | (CLI `-S`) | `socket_path` | (none) | +| Tmux config file | (CLI `-f`) | `tmux_options` / `cli_args` | (none) | +| Tmux command | (none) | `tmux_command` (e.g. `wemux`) | (none) | +| Session options | `options` | (none) | (none) | +| Global options | `global_options` | (none) | (none) | +| Environment vars | `environment` | (none) | (none) | +| Pre-build script | `before_script` | (none) | (none) | +| Shell cmd before (all panes) | `shell_command_before` | `pre_window` / `pre_tab` | (none) | +| Attach on create | (CLI `-d` to detach) | `attach` | (always attaches) | +| Startup window | (none) | `startup_window` | (none) | +| Startup pane | (none) | `startup_pane` | (none) | +| Plugins | `plugins` | (none) | (none) | +| ERB/variable interpolation | (none) | Yes (`key=value` args) | (none) | +| YAML anchors | Yes | Yes (`aliases: true`) | Yes | +| Pane titles enable | (none) | `enable_pane_titles` | (none) | +| Pane title position | (none) | `pane_title_position` | (none) | +| Pane title format | (none) | `pane_title_format` | (none) | + +### Session Hooks + +| Hook | tmuxp | tmuxinator | teamocil | +|---|---|---|---| +| Before session build | `before_script` | `on_project_start` | (none) | +| First start only | (none) | `on_project_first_start` | (none) | +| On reattach | Plugin: `reattach()` | `on_project_restart` | (none) | +| On exit/detach | (none) | `on_project_exit` | (none) | +| On stop/kill | (none) | `on_project_stop` | (none) | +| Before workspace build | Plugin: `before_workspace_builder()` | (none) | (none) | +| On window create | Plugin: `on_window_create()` | (none) | (none) | +| After window done | Plugin: `after_window_finished()` | (none) | (none) | +| Deprecated pre/post | (none) | `pre` / `post` | (none) | + +### Window-Level + +| Key | tmuxp | tmuxinator | teamocil | +|---|---|---|---| +| Window name | `window_name` | hash key | `name` | +| Window index | `window_index` | (auto, sequential) | (auto, sequential) | +| Root directory | `start_directory` | `root` (relative to project root) | `root` | +| Layout | `layout` | `layout` | `layout` | +| Panes list | `panes` | `panes` | `panes` | +| Window options | `options` | (none) | `options` | +| Post-create options | `options_after` | (none) | (none) | +| Shell cmd before | `shell_command_before` | `pre` | (none) | +| Shell for window | `window_shell` | (none) | (none) | +| Environment vars | `environment` | (none) | (none) | +| Suppress history | `suppress_history` | (none) | (none) | +| Focus | `focus` | (none) | `focus` | +| Synchronize panes | (none) | `synchronize` | (none) | +| Filters (before) | (none) | (none) | `filters.before` (v0.x) | +| Filters (after) | (none) | (none) | `filters.after` (v0.x) | + +### Pane-Level + +| Key | tmuxp | tmuxinator | teamocil | +|---|---|---|---| +| Commands | `shell_command` | (value: string/list) | `commands` | +| Root directory | `start_directory` | (none, inherits) | (none, inherits) | +| Shell | `shell` | (none) | (none) | +| Environment vars | `environment` | (none) | (none) | +| Press enter | `enter` | (always) | (always) | +| Sleep before | `sleep_before` | (none) | (none) | +| Sleep after | `sleep_after` | (none) | (none) | +| Suppress history | `suppress_history` | (none) | (none) | +| Focus | `focus` | (none) | `focus` | +| Pane title | (none) | hash key (named pane) | (none) | + +### Shorthand Syntax + +| Pattern | tmuxp | tmuxinator | teamocil | +|---|---|---|---| +| String pane | `- vim` | `- vim` | `- vim` | +| List of commands | `- [cmd1, cmd2]` | `- [cmd1, cmd2]` | `commands: [cmd1, cmd2]` | +| Empty/blank pane | `- blank` / `- pane` / `- null` | `- ` (nil) | (omit commands) | +| Named pane | (none) | `- name: cmd` | (none) | +| Window as string | (none) | `window_name: cmd` | (none) | +| Window as list | (none) | `window_name: [cmd1, cmd2]` | (none) | + +## CLI Commands + +| Function | tmuxp | tmuxinator | teamocil | +|---|---|---|---| +| Load/start session | `tmuxp load ` | `tmuxinator start ` | `teamocil ` | +| Load detached | `tmuxp load -d ` | `tmuxinator start -d` / `attach: false` | (none) | +| Load with name override | `tmuxp load -s ` | `tmuxinator start -n ` | (none) | +| Append to session | `tmuxp load -a` | `tmuxinator start --append` | (none) | +| List configs | `tmuxp ls` | `tmuxinator list` | `teamocil --list` | +| Edit config | `tmuxp edit ` | `tmuxinator edit ` / `new` | `teamocil --edit ` | +| Show/debug config | (none) | `tmuxinator debug ` | `teamocil --show` / `--debug` | +| Create new config | (none) | `tmuxinator new ` | (none) | +| Copy config | (none) | `tmuxinator copy ` | (none) | +| Delete config | (none) | `tmuxinator delete ` | (none) | +| Delete all configs | (none) | `tmuxinator implode` | (none) | +| Stop/kill session | (none) | `tmuxinator stop ` | (none) | +| Stop all sessions | (none) | `tmuxinator stop-all` | (none) | +| Freeze/export session | `tmuxp freeze ` | (none) | (none) | +| Convert format | `tmuxp convert ` | (none) | (none) | +| Import config | `tmuxp import ` | (none) | (none) | +| Search workspaces | `tmuxp search ` | (none) | (none) | +| Python shell | `tmuxp shell` | (none) | (none) | +| Debug/system info | `tmuxp debug-info` | `tmuxinator doctor` | (none) | +| Use here (current window) | (none) | (none) | `teamocil --here` | +| Skip pre_window | (none) | `--no-pre-window` | (none) | +| Pass variables | (none) | `key=value` args | (none) | +| Custom config path | `tmuxp load /path/to/file` | `-p /path/to/file` | `--layout /path/to/file` | +| Local config | `tmuxp load .` | `tmuxinator local` | (none) | + +## Config File Discovery + +| Feature | tmuxp | tmuxinator | teamocil | +|---|---|---|---| +| Global directory | `~/.tmuxp/` (legacy), `~/.config/tmuxp/` (XDG) | `~/.tmuxinator/`, `~/.config/tmuxinator/` (XDG), `$TMUXINATOR_CONFIG` | `~/.teamocil/` | +| Local config | `.tmuxp.yaml`, `.tmuxp.yml`, `.tmuxp.json` (traverses up to `~`) | `.tmuxinator.yml`, `.tmuxinator.yaml` (current dir only) | (none) | +| Env override | `$TMUXP_CONFIGDIR` | `$TMUXINATOR_CONFIG` | (none) | +| XDG support | Yes (`$XDG_CONFIG_HOME/tmuxp/`) | Yes (`$XDG_CONFIG_HOME/tmuxinator/`) | No | +| Extension search | `.yaml`, `.yml`, `.json` | `.yml`, `.yaml` | `.yml` | +| Recursive search | No | Yes (`Dir.glob("**/*.{yml,yaml}")`) | No | +| Upward traversal | Yes (cwd → `~`) | No | No | From 8abae2394446e4252cc46b18f243ef6d4b110ce2 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 8 Feb 2026 06:08:11 -0600 Subject: [PATCH 03/53] notes(parity) Add tmuxinator parity analysis Documents 12 feature gaps (hooks, stop command, pane sync, pane titles, ERB templating, wemux, debug/dry-run, config management CLIs), import behavior with bug inventory, and WorkspaceBuilder requirements. --- notes/parity-tmuxinator.md | 266 +++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 notes/parity-tmuxinator.md diff --git a/notes/parity-tmuxinator.md b/notes/parity-tmuxinator.md new file mode 100644 index 0000000000..83aba4e00f --- /dev/null +++ b/notes/parity-tmuxinator.md @@ -0,0 +1,266 @@ +# Tmuxinator Parity Analysis + +*Last updated: 2026-02-08* +*Tmuxinator version analyzed: 3.3.7* +*tmuxp version: 1.47.0+* + +## Features tmuxinator has that tmuxp lacks + +### 1. Project Hooks (Lifecycle Events) + +**Source**: `lib/tmuxinator/hooks/project.rb`, `assets/template.erb` + +tmuxinator has 5 lifecycle hooks: + +| Hook | Description | tmuxp equivalent | +|---|---|---| +| `on_project_start` | Runs on every `start` invocation | `before_script` (partial — runs commands, but kills session on failure) | +| `on_project_first_start` | Runs only when session doesn't exist yet | No equivalent | +| `on_project_restart` | Runs when attaching to existing session | Plugin `reattach()` (requires writing a plugin) | +| `on_project_exit` | Runs when detaching from session | No equivalent | +| `on_project_stop` | Runs on `tmuxinator stop` | No equivalent (tmuxp has no `stop` command) | + +**Gap**: tmuxp's `before_script` only covers the "first start" case and kills the session on failure. It has no hooks for detach/exit/stop events, and no distinction between first start vs. restart. + +**WorkspaceBuilder requirement**: Add config keys for `on_project_start`, `on_project_first_start`, `on_project_restart`, `on_project_exit`, `on_project_stop`. The exit/stop hooks require shell integration (trap signals, set-hook in tmux). + +### 2. Stop/Kill Session Command + +**Source**: `lib/tmuxinator/cli.rb` (`stop`, `stop_all`), `assets/template-stop.erb` + +tmuxinator provides: + +```bash +tmuxinator stop # Kill specific session + run on_project_stop hook +tmuxinator stop-all # Kill all tmuxinator-managed sessions +``` + +**Gap**: tmuxp has no `stop` or `kill` command. Users must use `tmux kill-session` directly, which skips any cleanup hooks. + +### 3. Session Name Override at Load Time + +**Source**: `lib/tmuxinator/cli.rb` (`--name` / `-n` option) + +```bash +tmuxinator start myproject --name custom-session-name +``` + +**Gap**: tmuxp has `tmuxp load -s ` which provides this. **No gap** — tmuxp already supports this. + +### 4. Startup Window / Startup Pane Selection + +**Source**: `lib/tmuxinator/project.rb` (`startup_window`, `startup_pane`) + +```yaml +startup_window: editor # Select this window after build +startup_pane: 1 # Select this pane within the startup window +``` + +**Gap**: tmuxp supports `focus: true` on windows and panes (boolean), which is equivalent but syntactically different. The `startup_window` key allows referencing by name or index. **Partial parity** — tmuxp can achieve this but uses a different mechanism (`focus` key on individual windows/panes). + +### 5. Pane Synchronization + +**Source**: `lib/tmuxinator/window.rb` (`synchronize`) + +```yaml +windows: + - editor: + synchronize: true # or "before" or "after" + panes: + - vim + - vim +``` + +- `synchronize: true` / `synchronize: before` — enable pane sync before running pane commands +- `synchronize: after` — enable pane sync after running pane commands + +**Gap**: tmuxp has no `synchronize` config key. Users would need to set `synchronize-panes on` via `options` manually, but this doesn't support the before/after distinction. + +**WorkspaceBuilder requirement**: Add `synchronize` key to window config with `before`/`after`/`true`/`false` values. + +### 6. Pane Titles + +**Source**: `lib/tmuxinator/project.rb`, `lib/tmuxinator/pane.rb` + +```yaml +enable_pane_titles: true +pane_title_position: top +pane_title_format: "#{pane_index}: #{pane_title}" +windows: + - editor: + panes: + - my-editor: vim # "my-editor" becomes the pane title +``` + +**Gap**: tmuxp has no pane title support. Named panes in tmuxinator (hash syntax: `pane_name: command`) set both a title and commands. + +**WorkspaceBuilder requirement**: Add session-level `enable_pane_titles`, `pane_title_position`, `pane_title_format` keys. Add per-pane `title` key. Issue `select-pane -T ` after pane creation. + +### 7. ERB Templating / Variable Interpolation + +**Source**: `lib/tmuxinator/project.rb` (`parse_settings`, `render_template`) + +```bash +tmuxinator start myproject env=production port=3000 +``` + +```yaml +# config.yml +root: ~/apps/<%= @settings["app"] %> +windows: + - server: + panes: + - rails server -p <%= @settings["port"] || 3000 %> +``` + +**Gap**: tmuxp has no config templating. Environment variable expansion (`$VAR`) is supported in `start_directory` paths, but not arbitrary variable interpolation in config values. + +**WorkspaceBuilder requirement**: This is an architectural difference. tmuxp could support Jinja2 templating or Python string formatting, but this is a significant feature addition. + +### 8. Wemux Support + +**Source**: `lib/tmuxinator/wemux_support.rb`, `assets/wemux_template.erb` + +```yaml +tmux_command: wemux +``` + +**Gap**: tmuxp has no wemux support. libtmux is tightly bound to the `tmux` command. + +**WorkspaceBuilder requirement**: Allow configurable tmux command binary. Requires libtmux changes. + +### 9. Debug / Dry-Run Output + +**Source**: `lib/tmuxinator/cli.rb` (`debug`) + +```bash +tmuxinator debug myproject +``` + +Outputs the generated shell script without executing it. + +**Gap**: tmuxp has no dry-run mode. Since tmuxp uses API calls rather than script generation, a dry-run would need to log the libtmux calls that *would* be made. + +### 10. Config Management Commands + +**Source**: `lib/tmuxinator/cli.rb` + +| Command | Description | +|---|---| +| `tmuxinator new <name>` | Create new config from template | +| `tmuxinator copy <src> <dst>` | Copy existing config | +| `tmuxinator delete <name>` | Delete config (with confirmation) | +| `tmuxinator implode` | Delete ALL configs | +| `tmuxinator stop <project>` | Stop session + run hooks | +| `tmuxinator stop-all` | Stop all managed sessions | + +**Gap**: tmuxp has `edit` but not `new`, `copy`, `delete`, `implode`, or `stop` commands. + +### 11. `--no-pre-window` Flag + +**Source**: `lib/tmuxinator/cli.rb` + +```bash +tmuxinator start myproject --no-pre-window +``` + +Skips `pre_window` commands. Useful for debugging. + +**Gap**: tmuxp has no equivalent flag to skip `shell_command_before`. + +### 12. `--here` Equivalent + +**Source**: teamocil provides `--here` to reuse the current window. tmuxinator has no `--here` per se but tmuxp also lacks this. + +**Gap**: Neither tmuxp nor tmuxinator has this; teamocil does. + +### 13. Create Config from Running Session + +**Source**: `lib/tmuxinator/cli.rb` (`new <name> <session>`) + +```bash +tmuxinator new myproject existing-session-name +``` + +Creates a config file pre-populated from a running tmux session. + +**Gap**: tmuxp has `tmuxp freeze` which exports to YAML/JSON. **Different approach, same result** — tmuxp's freeze is arguably more complete. + +## Import Behavior Analysis + +### Current Importer: `importers.py:import_tmuxinator` + +**What it handles:** + +| tmuxinator key | Mapped to | Status | +|---|---|---| +| `project_name` / `name` | `session_name` | ✓ Correct | +| `project_root` / `root` | `start_directory` | ✓ Correct | +| `cli_args` / `tmux_options` | `config` (extracts `-f`) | ⚠ Only handles `-f` flag, ignores `-L`, `-S` | +| `socket_name` | `socket_name` | ✓ Correct | +| `tabs` → `windows` | `windows` | ✓ Correct | +| `pre` + `pre_window` | `shell_command` + `shell_command_before` | ⚠ `shell_command` is not a valid tmuxp key | +| `pre` (alone) | `shell_command_before` | ✓ Correct | +| `rbenv` | appended to `shell_command_before` | ✓ Correct | +| Window hash key | `window_name` | ✓ Correct | +| Window `pre` | `shell_command_before` | ✓ Correct | +| Window `panes` | `panes` | ✓ Correct | +| Window `root` | `start_directory` | ✓ Correct | +| Window `layout` | `layout` | ✓ Correct | + +**What it misses or handles incorrectly:** + +| tmuxinator key | Issue | +|---|---| +| `attach` | Not imported. tmuxp uses CLI flags instead. | +| `startup_window` | Not imported. tmuxp uses `focus: true` on windows. | +| `startup_pane` | Not imported. tmuxp uses `focus: true` on panes. | +| `tmux_command` | Not imported. tmuxp has no equivalent. | +| `socket_path` | Not imported. tmuxp takes this via CLI. | +| `pre_tab` | Not imported (alias for `pre_window`). | +| `rvm` | Not imported (only `rbenv` is handled). | +| `post` | Not imported. tmuxp has no equivalent. | +| `synchronize` | Not imported. tmuxp has no equivalent. | +| `enable_pane_titles` | Not imported. tmuxp has no equivalent. | +| `pane_title_position` | Not imported. tmuxp has no equivalent. | +| `pane_title_format` | Not imported. tmuxp has no equivalent. | +| `on_project_start` | Not imported. tmuxp has no equivalent. | +| `on_project_first_start` | Not imported. Could map to `before_script`. | +| `on_project_restart` | Not imported. tmuxp has no equivalent. | +| `on_project_exit` | Not imported. tmuxp has no equivalent. | +| `on_project_stop` | Not imported. tmuxp has no equivalent. | +| Named panes (hash syntax) | Not imported. Pane names/titles are lost. | +| ERB templating | Not handled. YAML parsing will fail on ERB syntax. | +| `pre` + `pre_window` combo | Bug: sets `shell_command` which is not a tmuxp session-level key | + +### Code Quality Issues in Importer + +1. **Line 60**: When both `pre` and `pre_window` exist, the importer sets `tmuxp_workspace["shell_command"]` — but `shell_command` is not a valid session-level tmuxp key. The `pre` commands would be silently ignored. + +2. **Line 36-49**: The `cli_args`/`tmux_options` handler only extracts `-f` (config file). It ignores `-L` (socket name) and `-S` (socket path) which could also appear in these fields. + +3. **Line 79-101**: The window iteration uses `for k, v in window_dict.items()` which assumes windows are always dicts with a single key (the window name). This is correct for tmuxinator's format but fragile — if a window dict has multiple keys, only the last one is processed. + +4. **Missing `pre_tab`**: The `pre_tab` alias for `pre_window` is not handled. + +5. **Missing `rvm`**: Only `rbenv` is imported; `rvm` (another deprecated but still functional key) is ignored. + +6. **No validation or warnings**: The importer silently drops unsupported keys with no feedback to the user. + +## WorkspaceBuilder Requirements for 100% Feature Support + +### Must-Have for Parity + +1. **Pane synchronization** (`synchronize` window key) — `set-window-option synchronize-panes on/off` +2. **Pane titles** — `select-pane -T <title>`, `set-option pane-border-status top`, `set-option pane-border-format <fmt>` +3. **Startup window/pane selection** — Already achievable via `focus: true`, but could add `startup_window`/`startup_pane` as aliases +4. **Stop command** — `tmuxp stop <session>` to kill session + +### Nice-to-Have + +5. **Lifecycle hooks** — `on_project_start`, `on_project_first_start`, `on_project_restart`, `on_project_exit`, `on_project_stop` +6. **Config templating** — Jinja2 or Python format string support for config values +7. **Debug/dry-run** — Log tmux commands without executing +8. **Config management** — `tmuxp new`, `tmuxp copy`, `tmuxp delete` commands +9. **`--no-shell-command-before`** flag — Skip `shell_command_before` for debugging +10. **Custom tmux binary** — `tmux_command` key for wemux/byobu support (requires libtmux changes) From ca07632ee7cd5512db6b8b13fd2d16bc61fb883b Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 06:08:16 -0600 Subject: [PATCH 04/53] notes(parity) Add teamocil parity analysis Documents v0.x vs v1.x format differences, 3 feature gaps (--here flag, debug mode, shell_command_after), import bugs (v1.x incompatibility, redundant filter loops), and WorkspaceBuilder requirements. --- notes/parity-teamocil.md | 216 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 notes/parity-teamocil.md diff --git a/notes/parity-teamocil.md b/notes/parity-teamocil.md new file mode 100644 index 0000000000..1e4190c02f --- /dev/null +++ b/notes/parity-teamocil.md @@ -0,0 +1,216 @@ +# Teamocil Parity Analysis + +*Last updated: 2026-02-08* +*Teamocil version analyzed: 1.4.2* +*tmuxp version: 1.47.0+* + +## Version History Context + +Teamocil has had two distinct config formats: + +- **v0.x** (pre-1.0): Wrapped in `session:` key, used `splits` for panes, `filters` for before/after commands, `cmd` for pane commands +- **v1.x** (1.0–1.4.2): Simplified format — top-level `windows`, `panes` with `commands` key, `focus` support, window `options` + +The current tmuxp importer (`importers.py:import_teamocil`) **targets the v0.x format**. It handles the `session:` wrapper, `splits`, `filters`, and `cmd` keys — all of which are v0.x-only constructs. It does **not** handle the v1.x format natively, though v1.x configs may partially work since the `windows`/`panes` structure is similar. + +## Features teamocil has that tmuxp lacks + +### 1. `--here` Option (Reuse Current Window) + +**Source**: `lib/teamocil/tmux/window.rb`, `lib/teamocil/utils/option_parser.rb` + +```bash +teamocil --here my-layout +``` + +When `--here` is specified: +- First window: **renames** current window instead of creating a new one +- First window: sends `cd <root>` if root is specified +- Subsequent windows: created normally + +**Gap**: tmuxp always creates new windows. There is no way to populate the current window with a layout. + +**WorkspaceBuilder requirement**: Add `--here` CLI flag. For first window, use rename + cd instead of `new_window()`. This would require special handling in `WorkspaceBuilder.first_window_pass()`. + +### 2. `--show` Option (Show Raw Config) + +**Source**: `lib/teamocil/layout.rb` + +```bash +teamocil --show my-layout +``` + +Outputs the raw YAML content of the layout file. + +**Gap**: tmuxp has no equivalent. Users can `cat` the file manually. + +### 3. `--debug` Option (Show Commands Without Executing) + +**Source**: `lib/teamocil/layout.rb` + +```bash +teamocil --debug my-layout +``` + +Outputs the tmux commands that would be executed, one per line, without running them. + +**Gap**: tmuxp has no dry-run mode. Since tmuxp uses libtmux API calls rather than generating command strings, implementing this would require a logging/recording mode in the builder. + +### 4. Window-Level `focus` Key + +**Source**: `lib/teamocil/tmux/window.rb` + +```yaml +windows: + - name: editor + focus: true + panes: + - vim +``` + +**Gap**: tmuxp **does** support `focus: true` on windows. **No gap**. + +### 5. Pane-Level `focus` Key + +**Source**: `lib/teamocil/tmux/pane.rb` + +```yaml +panes: + - commands: + - vim + focus: true +``` + +**Gap**: tmuxp **does** support `focus: true` on panes. **No gap**. + +### 6. Window-Level `options` Key + +**Source**: `lib/teamocil/tmux/window.rb` + +```yaml +windows: + - name: editor + options: + main-pane-width: '100' +``` + +Maps to `set-window-option -t <window> <key> <value>`. + +**Gap**: tmuxp **does** support `options` on windows. **No gap**. + +### 7. Multiple Commands Joined by Semicolon + +**Source**: `lib/teamocil/tmux/pane.rb` + +Teamocil joins multiple pane commands with `; ` and sends them as a single `send-keys` invocation: + +```ruby +# Pane with commands: ["cd /path", "vim"] +# → send-keys "cd /path; vim" +``` + +**Gap**: tmuxp sends each command separately via individual `pane.send_keys()` calls. This is actually more reliable (each command gets its own Enter press), so this is a **behavioral difference** rather than a gap. + +## v0.x vs v1.x Format Differences + +| Feature | v0.x | v1.x (current) | +|---|---|---| +| Top-level wrapper | `session:` key | None (top-level `windows`) | +| Session name | `session.name` | `name` | +| Session root | `session.root` | (none, per-window only) | +| Panes key | `splits` | `panes` | +| Pane commands | `cmd` (string or list) | `commands` (list) | +| Before commands | `filters.before` (list) | (none) | +| After commands | `filters.after` (list) | (none) | +| Pane width | `width` (number) | (none) | +| Window clear | `clear` (boolean) | (none) | +| Pane focus | (none) | `focus` (boolean) | +| Window focus | (none) | `focus` (boolean) | +| Window options | (none) | `options` (hash) | +| Pane string shorthand | (none) | `- command_string` | + +## Import Behavior Analysis + +### Current Importer: `importers.py:import_teamocil` + +**What it handles (v0.x format):** + +| teamocil key | Mapped to | Status | +|---|---|---| +| `session` (wrapper) | Unwrapped | ✓ Correct | +| `session.name` | `session_name` | ✓ Correct | +| `session.root` | `start_directory` | ✓ Correct | +| Window `name` | `window_name` | ✓ Correct | +| Window `root` | `start_directory` | ✓ Correct | +| Window `layout` | `layout` | ✓ Correct | +| Window `clear` | `clear` | ⚠ Preserved but tmuxp doesn't use `clear` | +| Window `filters.before` | `shell_command_before` | ✓ Correct | +| Window `filters.after` | `shell_command_after` | ⚠ tmuxp doesn't support `shell_command_after` | +| `splits` → `panes` | `panes` | ✓ Correct | +| Pane `cmd` | `shell_command` | ✓ Correct | +| Pane `width` | Dropped | ⚠ Silently dropped with TODO comment | + +**What it misses:** + +| Feature | Issue | +|---|---| +| v1.x `commands` key | Not handled — only `cmd` (v0.x) is mapped | +| v1.x pane string shorthand | Not handled — expects dict with `cmd` key | +| v1.x `focus` (window) | Not imported | +| v1.x `focus` (pane) | Not imported | +| v1.x `options` (window) | Not imported | +| Session-level `name` (without `session:` wrapper) | Handled (uses `.get("name")`) | +| `with_env_var` (v0.x) | Not handled (noted in docstring TODO) | +| `cmd_separator` (v0.x) | Not handled (noted in docstring TODO) | + +### Code Quality Issues in Importer + +1. **Lines 145-149**: The `filters.before` and `filters.after` handling has redundant `for _b in` loops that serve no purpose. The inner assignment just reassigns the same value each iteration: + ```python + for _b in w["filters"]["before"]: + window_dict["shell_command_before"] = w["filters"]["before"] + ``` + This iterates N times but sets the same value each time. It should be a direct assignment. + +2. **Line 142**: `clear` is preserved in the config but tmuxp has no handling for it. It will be silently ignored during workspace building. + +3. **Line 149**: `shell_command_after` is not a tmuxp-supported key. It will be silently ignored. + +4. **Line 162-163**: `width` is silently dropped with a TODO comment. No warning to the user. + +5. **v1.x incompatibility**: The importer assumes v0.x format. A v1.x config with `commands` instead of `cmd`, or string panes, will not import correctly: + - String pane `"git status"` → error (tries to access `p["cmd"]` on a string) + - `commands: [...]` → not mapped to `shell_command` + +6. **No format detection**: The importer doesn't attempt to detect whether the input is v0.x or v1.x format. + +## WorkspaceBuilder Requirements for Full Parity + +### Already Supported (No Changes Needed) + +- Window `focus` — ✓ +- Pane `focus` — ✓ +- Window `options` — ✓ +- Window `layout` — ✓ +- Window `root`/`start_directory` — ✓ +- Pane commands — ✓ + +### Gaps Requiring New Features + +1. **`--here` flag** — Reuse current window for first window of layout. Requires `WorkspaceBuilder` to rename instead of create, and send `cd` for root directory. + +2. **`--debug` / dry-run mode** — Log commands without executing. Architectural challenge since tmuxp uses libtmux API, not command strings. + +3. **`shell_command_after`** — Commands run after pane commands. The importer preserves this from teamocil's `filters.after` but tmuxp has no support for it in the builder. + +### Import-Only Fixes (No Builder Changes) + +4. **v1.x format support** — The importer should handle: + - `commands` key (v1.x) in addition to `cmd` (v0.x) + - String pane shorthand + - `focus` on windows and panes + - `options` on windows + +5. **Redundant loop cleanup** — Fix the `filters` handling code. + +6. **Drop unsupported keys with warnings** — Instead of silently preserving `clear` or dropping `width`, warn the user. From e5bc5bb5b543f9aea1a91f49963710cf33c441e4 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 06:08:21 -0600 Subject: [PATCH 05/53] notes(import) Add tmuxinator import behavior analysis Classifies each config key as difference (translatable) or limitation (needs tmuxp feature). Identifies pre/pre_window bug, missing rvm/pre_tab mappings, and 5 features requiring new tmuxp capabilities. --- notes/import-tmuxinator.md | 221 +++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 notes/import-tmuxinator.md diff --git a/notes/import-tmuxinator.md b/notes/import-tmuxinator.md new file mode 100644 index 0000000000..df1ce4e137 --- /dev/null +++ b/notes/import-tmuxinator.md @@ -0,0 +1,221 @@ +# Tmuxinator Import Behavior + +*Last updated: 2026-02-08* +*Importer: `src/tmuxp/workspace/importers.py:import_tmuxinator`* + +## Syntax Differences (Translatable) + +These are config keys/patterns that differ syntactically but can be automatically converted during import. + +### 1. Session Name + +| tmuxinator | tmuxp | +|---|---| +| `name: myproject` | `session_name: myproject` | +| `project_name: myproject` | `session_name: myproject` | + +**Importer status**: ✓ Handled (lines 24-29) + +### 2. Root Directory + +| tmuxinator | tmuxp | +|---|---| +| `root: ~/project` | `start_directory: ~/project` | +| `project_root: ~/project` | `start_directory: ~/project` | + +**Importer status**: ✓ Handled (lines 31-34) + +### 3. Windows List Key + +| tmuxinator | tmuxp | +|---|---| +| `tabs:` | `windows:` | +| `windows:` | `windows:` | + +**Importer status**: ✓ Handled (lines 56-57) + +### 4. Window Name Syntax + +| tmuxinator | tmuxp | +|---|---| +| `- editor:` (hash key) | `- window_name: editor` | + +**Importer status**: ✓ Handled (lines 79-81) + +### 5. Window Root + +| tmuxinator | tmuxp | +|---|---| +| `root: ./src` (under window hash) | `start_directory: ./src` | + +**Importer status**: ✓ Handled (lines 96-97) + +### 6. Window Pre-Commands + +| tmuxinator | tmuxp | +|---|---| +| `pre: "source .env"` (under window hash) | `shell_command_before: ["source .env"]` | + +**Importer status**: ✓ Handled (lines 92-93) + +### 7. Socket Name + +| tmuxinator | tmuxp | +|---|---| +| `socket_name: myapp` | `socket_name: myapp` | + +**Importer status**: ✓ Handled (lines 51-52). Note: tmuxp doesn't use `socket_name` as a config key in `WorkspaceBuilder` — it's a CLI flag. The importer preserves it but it may not be used. + +### 8. CLI Args / Tmux Options → Config File + +| tmuxinator | tmuxp | +|---|---| +| `cli_args: "-f ~/.tmux.special.conf"` | `config: ~/.tmux.special.conf` | +| `tmux_options: "-f ~/.tmux.special.conf"` | `config: ~/.tmux.special.conf` | + +**Importer status**: ⚠ Partially handled (lines 36-49). Only extracts `-f` flag value. Other flags like `-L` (socket name) and `-S` (socket path) in `cli_args`/`tmux_options` are silently included in the `config` value, which is incorrect — `config` should only be a file path. + +### 9. Rbenv + +| tmuxinator | tmuxp | +|---|---| +| `rbenv: 2.7.0` | `shell_command_before: ["rbenv shell 2.7.0"]` | + +**Importer status**: ✓ Handled (lines 72-77) + +### 10. Pre / Pre-Window Commands + +| tmuxinator | tmuxp | +|---|---| +| `pre: "cmd"` (session-level, alone) | `shell_command_before: ["cmd"]` | +| `pre_window: "cmd"` + `pre: "cmd"` | `shell_command: "cmd"` + `shell_command_before: ["cmd"]` | + +**Importer status**: ⚠ Bug (lines 59-70). When both `pre` and `pre_window` exist, the importer sets `shell_command` (not a valid tmuxp session-level key) for `pre` and `shell_command_before` for `pre_window`. The `pre` commands are lost. + +**Correct mapping**: Both should map to `shell_command_before`, with `pre` commands first, then `pre_window` commands. + +### 11. Window as String/List + +| tmuxinator | tmuxp | +|---|---| +| `- editor: vim` | `- window_name: editor` + `panes: [vim]` | +| `- editor: [vim, "git status"]` | `- window_name: editor` + `panes: [vim, "git status"]` | + +**Importer status**: ✓ Handled (lines 83-90) + +### 12. `startup_window` → `focus` + +| tmuxinator | tmuxp | +|---|---| +| `startup_window: editor` | Set `focus: true` on the matching window | + +**Importer status**: ✗ Not handled. Could be translated by finding the matching window and adding `focus: true`. + +### 13. `startup_pane` → `focus` + +| tmuxinator | tmuxp | +|---|---| +| `startup_pane: 1` | Set `focus: true` on the matching pane | + +**Importer status**: ✗ Not handled. Could be translated by finding the pane at the given index and adding `focus: true`. + +### 14. `pre_tab` → `shell_command_before` + +| tmuxinator | tmuxp | +|---|---| +| `pre_tab: "source .env"` | `shell_command_before: ["source .env"]` | + +**Importer status**: ✗ Not handled. `pre_tab` is a deprecated alias for `pre_window`. + +### 15. `rvm` → `shell_command_before` + +| tmuxinator | tmuxp | +|---|---| +| `rvm: ruby-2.7@mygemset` | `shell_command_before: ["rvm use ruby-2.7@mygemset"]` | + +**Importer status**: ✗ Not handled. Only `rbenv` is mapped; `rvm` is ignored. + +### 16. `attach: false` → CLI Flag + +| tmuxinator | tmuxp | +|---|---| +| `attach: false` | `tmuxp load -d` (detached mode) | + +**Importer status**: ✗ Not handled. Could add a comment or warning suggesting `-d` flag. + +## Limitations (tmuxp Needs to Add Support) + +These are features that cannot be imported because tmuxp lacks the underlying capability. + +### 1. Lifecycle Hooks + +**What it does in tmuxinator**: Five project hooks (`on_project_start`, `on_project_first_start`, `on_project_restart`, `on_project_exit`, `on_project_stop`) allow running arbitrary commands at different lifecycle stages. + +**Why it can't be imported**: tmuxp only has `before_script` (partial equivalent to `on_project_first_start`). The exit/stop/restart hooks require tmux `set-hook` integration or signal trapping that tmuxp doesn't support. + +**What tmuxp would need to add**: Session-level `on_project_start`, `on_project_first_start`, `on_project_restart`, `on_project_exit`, `on_project_stop` config keys, plus builder logic to execute them at appropriate points. For exit/stop hooks, tmuxp would need a `stop` command and tmux `set-hook` for `client-detached`. + +### 2. Pane Synchronization + +**What it does in tmuxinator**: `synchronize: true/before/after` on windows enables `synchronize-panes` option, with control over whether sync happens before or after pane commands. + +**Why it can't be imported**: tmuxp has no `synchronize` config key. While users can set `synchronize-panes` via `options`, the before/after timing distinction requires builder support. + +**What tmuxp would need to add**: `synchronize` key on windows with `before`/`after`/`true`/`false` values. Builder should call `set-window-option synchronize-panes on` at the appropriate point. + +### 3. Pane Titles + +**What it does in tmuxinator**: Named pane syntax (`pane_name: command`) sets pane titles via `select-pane -T`. Session-level `enable_pane_titles`, `pane_title_position`, `pane_title_format` control display. + +**Why it can't be imported**: tmuxp has no pane title support. + +**What tmuxp would need to add**: Per-pane `title` key, session-level title configuration. Builder calls `select-pane -T <title>` after pane creation. + +### 4. ERB Templating + +**What it does in tmuxinator**: Config files are processed through ERB before YAML parsing. Supports `<%= @settings["key"] %>` interpolation and full Ruby expressions. Variables passed via `key=value` CLI args. + +**Why it can't be imported**: ERB is a Ruby templating system. The importer receives already-parsed YAML (ERB would have already been processed in Ruby). When importing a raw tmuxinator config file with ERB syntax, YAML parsing will fail. + +**What tmuxp would need to add**: Either a Jinja2 templating pass, Python string formatting, or environment variable expansion in config values. This is a significant architectural feature. + +### 5. Wemux Support + +**What it does in tmuxinator**: `tmux_command: wemux` uses an alternate template and wemux-specific commands. + +**Why it can't be imported**: tmuxp and libtmux are tightly bound to the `tmux` binary. + +**What tmuxp would need to add**: Configurable tmux binary path in libtmux's `Server` class. + +### 6. `--no-pre-window` Flag + +**What it does in tmuxinator**: Skips all `pre_window` commands when starting a session. Useful for debugging. + +**Why it can't be imported**: This is a runtime behavior, not a config key. + +**What tmuxp would need to add**: `--no-shell-command-before` CLI flag on `tmuxp load`. + +## Summary Table + +| tmuxinator Feature | Import Status | Classification | +|---|---|---| +| `name`/`project_name` → `session_name` | ✓ Handled | Difference | +| `root`/`project_root` → `start_directory` | ✓ Handled | Difference | +| `tabs` → `windows` | ✓ Handled | Difference | +| `socket_name` | ✓ Handled | Difference | +| `cli_args`/`tmux_options` → `config` | ⚠ Partial | Difference (needs fix) | +| `rbenv` → `shell_command_before` | ✓ Handled | Difference | +| `pre` → `shell_command_before` | ⚠ Bug when combined with `pre_window` | Difference (needs fix) | +| Window hash syntax | ✓ Handled | Difference | +| Window `root`/`pre`/`layout`/`panes` | ✓ Handled | Difference | +| `rvm` → `shell_command_before` | ✗ Missing | Difference (needs add) | +| `pre_tab` → `shell_command_before` | ✗ Missing | Difference (needs add) | +| `startup_window` → `focus` | ✗ Missing | Difference (needs add) | +| `startup_pane` → `focus` | ✗ Missing | Difference (needs add) | +| `attach: false` | ✗ Missing | Difference (needs add) | +| `on_project_*` hooks | ✗ Missing | **Limitation** | +| `synchronize` | ✗ Missing | **Limitation** | +| `enable_pane_titles` / titles | ✗ Missing | **Limitation** | +| ERB templating | ✗ Missing | **Limitation** | +| `tmux_command` (wemux) | ✗ Missing | **Limitation** | +| `--no-pre-window` | N/A (runtime flag) | **Limitation** | From ec3833a9c2cd22efcbe713db1e1aa4808cffa3d6 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 06:08:26 -0600 Subject: [PATCH 06/53] notes(import) Add teamocil import behavior analysis Documents v0.x-only targeting (v1.x unsupported), string pane TypeError bug, redundant filter loop bug, and 6 missing v1.x key mappings (commands, focus, options, string shorthand). --- notes/import-teamocil.md | 252 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 notes/import-teamocil.md diff --git a/notes/import-teamocil.md b/notes/import-teamocil.md new file mode 100644 index 0000000000..c078727f1e --- /dev/null +++ b/notes/import-teamocil.md @@ -0,0 +1,252 @@ +# Teamocil Import Behavior + +*Last updated: 2026-02-08* +*Importer: `src/tmuxp/workspace/importers.py:import_teamocil`* + +## Format Detection Problem + +Teamocil has two distinct config formats: + +- **v0.x** (pre-1.0): `session:` wrapper, `splits`, `filters`, `cmd` +- **v1.x** (1.0–1.4.2): Flat `windows`, `panes`, `commands`, `focus`, `options` + +The current importer **targets v0.x only**. It handles v0.x-specific constructs (`session:` wrapper, `splits`, `filters.before`, `filters.after`, `cmd`) but does not handle v1.x-specific constructs (`commands`, string pane shorthand, `focus`, window `options`). + +Since teamocil 1.4.2 uses the v1.x format, the importer is outdated for current teamocil configs. + +## Syntax Differences (Translatable) + +### 1. Session Wrapper (v0.x) + +| teamocil v0.x | tmuxp | +|---|---| +| `session:` + `name:` + `windows:` | `session_name:` + `windows:` | + +**Importer status**: ✓ Handled (lines 127-128). Unwraps the `session:` key. + +### 2. Session Name + +| teamocil | tmuxp | +|---|---| +| `name: my-layout` | `session_name: my-layout` | + +**Importer status**: ✓ Handled (line 130) + +### 3. Session Root (v0.x) + +| teamocil v0.x | tmuxp | +|---|---| +| `session.root: ~/project` | `start_directory: ~/project` | + +**Importer status**: ✓ Handled (lines 132-133). Note: v1.x teamocil has no session-level root. + +### 4. Window Name + +| teamocil | tmuxp | +|---|---| +| `name: editor` | `window_name: editor` | + +**Importer status**: ✓ Handled (line 138) + +### 5. Window Root + +| teamocil | tmuxp | +|---|---| +| `root: ~/project` | `start_directory: ~/project` | + +**Importer status**: ✓ Handled (lines 151-152) + +### 6. Window Layout + +| teamocil | tmuxp | +|---|---| +| `layout: main-vertical` | `layout: main-vertical` | + +**Importer status**: ✓ Handled (lines 166-167). Same key name, direct pass-through. + +### 7. Splits → Panes (v0.x) + +| teamocil v0.x | tmuxp | +|---|---| +| `splits:` | `panes:` | + +**Importer status**: ✓ Handled (lines 154-155). Renames key. + +### 8. Pane `cmd` → `shell_command` (v0.x) + +| teamocil v0.x | tmuxp | +|---|---| +| `cmd: vim` | `shell_command: vim` | +| `cmd: [cd /path, vim]` | `shell_command: [cd /path, vim]` | + +**Importer status**: ✓ Handled (lines 159-160). Renames key. + +### 9. Filters Before → Shell Command Before (v0.x) + +| teamocil v0.x | tmuxp | +|---|---| +| `filters: { before: [cmd1, cmd2] }` | `shell_command_before: [cmd1, cmd2]` | + +**Importer status**: ⚠ Handled but with redundant loop (lines 144-146). The `for _b in` loop iterates uselessly — the assignment inside is the same each iteration. Should be a direct assignment. + +### 10. Pane `commands` → `shell_command` (v1.x) + +| teamocil v1.x | tmuxp | +|---|---| +| `commands: [git pull, vim]` | `shell_command: [git pull, vim]` | + +**Importer status**: ✗ Not handled. The v1.x `commands` key is not mapped. Only `cmd` (v0.x) is recognized. + +### 11. String Pane Shorthand (v1.x) + +| teamocil v1.x | tmuxp | +|---|---| +| `- git status` (string in panes list) | `- shell_command: [git status]` | + +**Importer status**: ✗ Not handled. The importer expects each pane to be a dict (tries `p["cmd"]`). String panes will cause a `TypeError`. + +### 12. Window Focus (v1.x) + +| teamocil v1.x | tmuxp | +|---|---| +| `focus: true` (on window) | `focus: true` | + +**Importer status**: ✗ Not handled. The key is not imported. + +### 13. Pane Focus (v1.x) + +| teamocil v1.x | tmuxp | +|---|---| +| `focus: true` (on pane) | `focus: true` | + +**Importer status**: ✗ Not handled. The key is not imported. + +### 14. Window Options (v1.x) + +| teamocil v1.x | tmuxp | +|---|---| +| `options: { main-pane-width: '100' }` | `options: { main-pane-width: '100' }` | + +**Importer status**: ✗ Not handled. Same key name in tmuxp, but not imported from teamocil configs. + +## Limitations (tmuxp Needs to Add Support) + +### 1. `--here` Flag (Reuse Current Window) + +**What it does in teamocil**: First window is renamed and reused instead of creating a new one. Root directory applied via `cd` command. + +**Why it can't be imported**: This is a runtime CLI flag, not a config key. + +**What tmuxp would need to add**: `--here` flag on `tmuxp load` that tells WorkspaceBuilder to rename the current window for the first window instead of creating new. + +### 2. Filters After / `shell_command_after` (v0.x) + +**What it does in teamocil**: `filters.after` commands run after pane commands. + +**Why it can't be imported**: The importer maps this to `shell_command_after`, but tmuxp has no support for this key in the WorkspaceBuilder. The key is silently ignored. + +**What tmuxp would need to add**: `shell_command_after` key on windows/panes. Builder would send these commands after all pane `shell_command` entries. + +### 3. Pane Width (v0.x) + +**What it does in teamocil v0.x**: `width` on splits to set pane width. + +**Why it can't be imported**: tmuxp drops this with a TODO comment. tmuxp relies on tmux layouts for pane geometry. + +**What tmuxp would need to add**: Per-pane `width`/`height` keys. Builder would use `resize-pane -x <width>` or `resize-pane -y <height>` after split. Alternatively, support custom layout strings. + +### 4. Window Clear (v0.x) + +**What it does in teamocil v0.x**: `clear: true` on windows. + +**Why it can't be imported**: The importer preserves the `clear` key but tmuxp doesn't act on it. + +**What tmuxp would need to add**: `clear` key on windows. Builder would send `clear` (or `send-keys C-l`) after pane creation. + +### 5. `with_env_var` (v0.x) + +**What it does in teamocil v0.x**: Sets environment variables for panes. + +**Why it can't be imported**: Noted in importer docstring TODO. Not implemented. + +**What tmuxp would need to add**: tmuxp already has `environment` on sessions, windows, and panes. The import just needs to map `with_env_var` → `environment`. + +### 6. `cmd_separator` (v0.x) + +**What it does in teamocil v0.x**: Custom separator for joining multiple commands (default: `; `). + +**Why it can't be imported**: Noted in importer docstring TODO. Not implemented. + +**What tmuxp would need to add**: tmuxp sends commands individually (one `send_keys` per command), so this is a non-issue. The behavioral difference is actually better in tmuxp. + +## Code Issues in Current Importer + +### Bug: Redundant Filter Loop + +```python +# Lines 144-149 (current) +if "filters" in w: + if "before" in w["filters"]: + for _b in w["filters"]["before"]: + window_dict["shell_command_before"] = w["filters"]["before"] + if "after" in w["filters"]: + for _b in w["filters"]["after"]: + window_dict["shell_command_after"] = w["filters"]["after"] +``` + +The `for _b in` loops are pointless — they iterate over the list but set the same value each time. Should be: + +```python +if "filters" in w: + if "before" in w["filters"]: + window_dict["shell_command_before"] = w["filters"]["before"] + if "after" in w["filters"]: + window_dict["shell_command_after"] = w["filters"]["after"] +``` + +### Bug: v1.x String Panes Cause TypeError + +```python +# Lines 157-163 (current) +if "panes" in w: + for p in w["panes"]: + if "cmd" in p: # TypeError if p is a string + p["shell_command"] = p.pop("cmd") +``` + +If `p` is a string (v1.x shorthand), `"cmd" in p` will check for substring match in the string, not a dict key. This will either silently pass (if the command doesn't contain "cmd") or incorrectly match. + +### Missing: `with_env_var` and `cmd_separator` + +Noted in the docstring TODOs but never implemented. `with_env_var` could map to tmuxp's `environment` key. `cmd_separator` is irrelevant since tmuxp sends commands individually. + +### Silent Drops + +- `clear` is preserved but unused by tmuxp +- `width` is dropped with no user warning +- `shell_command_after` is set but unused by tmuxp + +## Summary Table + +| teamocil Feature | Import Status | Classification | +|---|---|---| +| `session:` wrapper (v0.x) | ✓ Handled | Difference | +| `name` → `session_name` | ✓ Handled | Difference | +| `root` → `start_directory` | ✓ Handled | Difference | +| Window `name` → `window_name` | ✓ Handled | Difference | +| Window `root` → `start_directory` | ✓ Handled | Difference | +| Window `layout` | ✓ Handled | Difference | +| `splits` → `panes` (v0.x) | ✓ Handled | Difference | +| Pane `cmd` → `shell_command` (v0.x) | ✓ Handled | Difference | +| `filters.before` → `shell_command_before` (v0.x) | ⚠ Bug (redundant loop) | Difference (needs fix) | +| Pane `commands` → `shell_command` (v1.x) | ✗ Missing | Difference (needs add) | +| String pane shorthand (v1.x) | ✗ Missing (causes error) | Difference (needs add) | +| Window `focus` (v1.x) | ✗ Missing | Difference (needs add) | +| Pane `focus` (v1.x) | ✗ Missing | Difference (needs add) | +| Window `options` (v1.x) | ✗ Missing | Difference (needs add) | +| `with_env_var` → `environment` (v0.x) | ✗ Missing | Difference (needs add) | +| `filters.after` → `shell_command_after` | ⚠ Imported but unused | **Limitation** | +| Pane `width` (v0.x) | ⚠ Dropped silently | **Limitation** | +| Window `clear` (v0.x) | ⚠ Preserved but unused | **Limitation** | +| `cmd_separator` (v0.x) | ✗ Missing | N/A (tmuxp is better) | +| `--here` flag | N/A (runtime flag) | **Limitation** | From 970c5768e8949bca9bd4407e692baf826c26a80b Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 06:15:20 -0600 Subject: [PATCH 07/53] fix(docs): Correct parity docs from verification pass - Remove duplicate 'Attach on create' row in comparison table, keep corrected version with '(default: true)' near socket_path - Annotate pre_tab as (deprecated) in comparison table - Annotate startup_window as accepting name or index - Fix pre_tab description: deprecated predecessor, not alias (it was renamed in tmuxinator, not aliased) - Clarify startup_window renders as "#{name}:#{value}" --- docs/comparison.md | 6 +++--- notes/import-tmuxinator.md | 2 +- notes/parity-tmuxinator.md | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/comparison.md b/docs/comparison.md index 3ae0f42f7b..87f5c8416c 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -53,15 +53,15 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Windows list | `windows` | `windows` / `tabs` | `windows` | | Socket name | (CLI `-L`) | `socket_name` | (none) | | Socket path | (CLI `-S`) | `socket_path` | (none) | +| Attach on create | (CLI `-d` to detach) | `attach` (default: true) | (always attaches) | | Tmux config file | (CLI `-f`) | `tmux_options` / `cli_args` | (none) | | Tmux command | (none) | `tmux_command` (e.g. `wemux`) | (none) | | Session options | `options` | (none) | (none) | | Global options | `global_options` | (none) | (none) | | Environment vars | `environment` | (none) | (none) | | Pre-build script | `before_script` | (none) | (none) | -| Shell cmd before (all panes) | `shell_command_before` | `pre_window` / `pre_tab` | (none) | -| Attach on create | (CLI `-d` to detach) | `attach` | (always attaches) | -| Startup window | (none) | `startup_window` | (none) | +| Shell cmd before (all panes) | `shell_command_before` | `pre_window` / `pre_tab` (deprecated) | (none) | +| Startup window | (none) | `startup_window` (name or index) | (none) | | Startup pane | (none) | `startup_pane` | (none) | | Plugins | `plugins` | (none) | (none) | | ERB/variable interpolation | (none) | Yes (`key=value` args) | (none) | diff --git a/notes/import-tmuxinator.md b/notes/import-tmuxinator.md index df1ce4e137..439037fd7f 100644 --- a/notes/import-tmuxinator.md +++ b/notes/import-tmuxinator.md @@ -125,7 +125,7 @@ These are config keys/patterns that differ syntactically but can be automaticall |---|---| | `pre_tab: "source .env"` | `shell_command_before: ["source .env"]` | -**Importer status**: ✗ Not handled. `pre_tab` is a deprecated alias for `pre_window`. +**Importer status**: ✗ Not handled. `pre_tab` is a deprecated predecessor to `pre_window` (not an alias — it was renamed). ### 15. `rvm` → `shell_command_before` diff --git a/notes/parity-tmuxinator.md b/notes/parity-tmuxinator.md index 83aba4e00f..abee871e10 100644 --- a/notes/parity-tmuxinator.md +++ b/notes/parity-tmuxinator.md @@ -56,7 +56,7 @@ startup_window: editor # Select this window after build startup_pane: 1 # Select this pane within the startup window ``` -**Gap**: tmuxp supports `focus: true` on windows and panes (boolean), which is equivalent but syntactically different. The `startup_window` key allows referencing by name or index. **Partial parity** — tmuxp can achieve this but uses a different mechanism (`focus` key on individual windows/panes). +**Gap**: tmuxp supports `focus: true` on windows and panes (boolean), which is equivalent but syntactically different. The `startup_window` key allows referencing by name or index (rendered as `"#{name}:#{value}"`). **Partial parity** — tmuxp can achieve this but uses a different mechanism (`focus` key on individual windows/panes rather than a centralized key). ### 5. Pane Synchronization @@ -217,7 +217,7 @@ Creates a config file pre-populated from a running tmux session. | `startup_pane` | Not imported. tmuxp uses `focus: true` on panes. | | `tmux_command` | Not imported. tmuxp has no equivalent. | | `socket_path` | Not imported. tmuxp takes this via CLI. | -| `pre_tab` | Not imported (alias for `pre_window`). | +| `pre_tab` | Not imported (deprecated predecessor to `pre_window`). | | `rvm` | Not imported (only `rbenv` is handled). | | `post` | Not imported. tmuxp has no equivalent. | | `synchronize` | Not imported. tmuxp has no equivalent. | @@ -241,7 +241,7 @@ Creates a config file pre-populated from a running tmux session. 3. **Line 79-101**: The window iteration uses `for k, v in window_dict.items()` which assumes windows are always dicts with a single key (the window name). This is correct for tmuxinator's format but fragile — if a window dict has multiple keys, only the last one is processed. -4. **Missing `pre_tab`**: The `pre_tab` alias for `pre_window` is not handled. +4. **Missing `pre_tab`**: The `pre_tab` deprecated predecessor to `pre_window` is not handled. 5. **Missing `rvm`**: Only `rbenv` is imported; `rvm` (another deprecated but still functional key) is ignored. From cc86ce004e038ffd1864c768d5b1fcf67cce0dd5 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 06:21:04 -0600 Subject: [PATCH 08/53] fix(comparison): Correct tmuxinator min tmux and detach flag - tmuxinator min tmux is 1.8 (recommended), not 1.5; tmux 2.5 is explicitly unsupported - teamocil has no documented min tmux version - tmuxinator detach is via `attach: false` config or `--no-attach` CLI flag, not `-d` (which doesn't exist in tmuxinator) --- docs/comparison.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/comparison.md b/docs/comparison.md index 87f5c8416c..480dd7f2be 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -8,7 +8,7 @@ |---|---|---|---| | **Version** | 1.47.0+ | 3.3.7 | 1.4.2 | | **Language** | Python | Ruby | Ruby | -| **Min tmux** | 3.2 | 1.5 | 3.2 | +| **Min tmux** | 3.2 | 1.8 (recommended; not 2.5) | (not specified) | | **Config formats** | YAML, JSON | YAML (with ERB) | YAML | | **Architecture** | ORM (libtmux) | Script generation (ERB templates) | Command objects → shell exec | | **License** | MIT | MIT | MIT | @@ -135,7 +135,7 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Function | tmuxp | tmuxinator | teamocil | |---|---|---|---| | Load/start session | `tmuxp load <config>` | `tmuxinator start <project>` | `teamocil <layout>` | -| Load detached | `tmuxp load -d <config>` | `tmuxinator start -d` / `attach: false` | (none) | +| Load detached | `tmuxp load -d <config>` | `attach: false` / `tmuxinator start --no-attach` | (none) | | Load with name override | `tmuxp load -s <name> <config>` | `tmuxinator start -n <name>` | (none) | | Append to session | `tmuxp load -a` | `tmuxinator start --append` | (none) | | List configs | `tmuxp ls` | `tmuxinator list` | `teamocil --list` | From e429d3b481c08b0061f2cb435be018851fa66c48 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 06:21:09 -0600 Subject: [PATCH 09/53] fix(import-tmuxinator): Add missing socket_path entry - Add socket_path as item 16 (tmuxinator config key not handled) - socket_path takes precedence over socket_name in tmuxinator - tmuxp only accepts socket path via CLI -S flag - Add to summary table as missing Difference --- notes/import-tmuxinator.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/notes/import-tmuxinator.md b/notes/import-tmuxinator.md index 439037fd7f..943f42448c 100644 --- a/notes/import-tmuxinator.md +++ b/notes/import-tmuxinator.md @@ -135,7 +135,15 @@ These are config keys/patterns that differ syntactically but can be automaticall **Importer status**: ✗ Not handled. Only `rbenv` is mapped; `rvm` is ignored. -### 16. `attach: false` → CLI Flag +### 16. `socket_path` + +| tmuxinator | tmuxp | +|---|---| +| `socket_path: /tmp/my.sock` | (CLI `-S /tmp/my.sock`) | + +**Importer status**: ✗ Not handled. `socket_path` is a tmuxinator config key (takes precedence over `socket_name`) but the importer ignores it. tmuxp takes socket path via CLI `-S` flag only. + +### 17. `attach: false` → CLI Flag | tmuxinator | tmuxp | |---|---| @@ -212,6 +220,7 @@ These are features that cannot be imported because tmuxp lacks the underlying ca | `pre_tab` → `shell_command_before` | ✗ Missing | Difference (needs add) | | `startup_window` → `focus` | ✗ Missing | Difference (needs add) | | `startup_pane` → `focus` | ✗ Missing | Difference (needs add) | +| `socket_path` | ✗ Missing | Difference (needs add) | | `attach: false` | ✗ Missing | Difference (needs add) | | `on_project_*` hooks | ✗ Missing | **Limitation** | | `synchronize` | ✗ Missing | **Limitation** | From cc4a5d3ef4ff02fa764e8a68b16fe3a314ef802d Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 06:21:16 -0600 Subject: [PATCH 10/53] fix(import-teamocil): Reclassify with_env_var and cmd_separator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - with_env_var is an import-only fix (tmuxp already has environment key), not a Limitation — moved to new "Import-Only Fixes" section - cmd_separator is irrelevant (tmuxp sends commands individually), clarified it needs no import --- notes/import-teamocil.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/notes/import-teamocil.md b/notes/import-teamocil.md index c078727f1e..7687222ca0 100644 --- a/notes/import-teamocil.md +++ b/notes/import-teamocil.md @@ -163,21 +163,21 @@ Since teamocil 1.4.2 uses the v1.x format, the importer is outdated for current **What tmuxp would need to add**: `clear` key on windows. Builder would send `clear` (or `send-keys C-l`) after pane creation. -### 5. `with_env_var` (v0.x) +## Import-Only Fixes (No Builder Changes) + +### 5. `with_env_var` → `environment` (v0.x) **What it does in teamocil v0.x**: Sets environment variables for panes. -**Why it can't be imported**: Noted in importer docstring TODO. Not implemented. +**Why it's not imported**: Noted in importer docstring TODO. Not implemented. -**What tmuxp would need to add**: tmuxp already has `environment` on sessions, windows, and panes. The import just needs to map `with_env_var` → `environment`. +**Fix**: tmuxp already has `environment` on sessions, windows, and panes. The importer just needs to map `with_env_var` → `environment`. No builder changes required. ### 6. `cmd_separator` (v0.x) **What it does in teamocil v0.x**: Custom separator for joining multiple commands (default: `; `). -**Why it can't be imported**: Noted in importer docstring TODO. Not implemented. - -**What tmuxp would need to add**: tmuxp sends commands individually (one `send_keys` per command), so this is a non-issue. The behavioral difference is actually better in tmuxp. +**Note**: tmuxp sends commands individually (one `send_keys` per command), so this is irrelevant. The behavioral difference is actually better in tmuxp — no import needed. ## Code Issues in Current Importer From 8e529660818493d1d2e0f7aeca906273f61fe5b6 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 06:26:08 -0600 Subject: [PATCH 11/53] fix(comparison): Correct tmuxinator version ref and clarify details - Fix "1.5+" to "1.8+" in architecture description (was already fixed in overview table but missed in prose) - Clarify YAML anchors: tmuxinator enables via YAML.safe_load aliases param, not a config key - Clarify tmuxinator edit is alias of new command --- docs/comparison.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/comparison.md b/docs/comparison.md index 480dd7f2be..c5501d74eb 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -30,7 +30,7 @@ tmuxp uses **libtmux**, an object-relational mapper for tmux. Each tmux entity ( tmuxinator reads YAML (with ERB templating), builds a `Project` object graph, then renders a bash script via ERB templates. The generated script is `exec`'d, replacing the tmuxinator process. -**Advantages**: Debuggable output (`tmuxinator debug`), wide tmux version support (1.5+), ERB allows config templating with variables. +**Advantages**: Debuggable output (`tmuxinator debug`), wide tmux version support (1.8+), ERB allows config templating with variables. **Disadvantages**: No mid-build error recovery (script runs or fails), Ruby dependency. @@ -65,7 +65,7 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Startup pane | (none) | `startup_pane` | (none) | | Plugins | `plugins` | (none) | (none) | | ERB/variable interpolation | (none) | Yes (`key=value` args) | (none) | -| YAML anchors | Yes | Yes (`aliases: true`) | Yes | +| YAML anchors | Yes | Yes (via `YAML.safe_load` `aliases: true`) | Yes | | Pane titles enable | (none) | `enable_pane_titles` | (none) | | Pane title position | (none) | `pane_title_position` | (none) | | Pane title format | (none) | `pane_title_format` | (none) | @@ -139,7 +139,7 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Load with name override | `tmuxp load -s <name> <config>` | `tmuxinator start -n <name>` | (none) | | Append to session | `tmuxp load -a` | `tmuxinator start --append` | (none) | | List configs | `tmuxp ls` | `tmuxinator list` | `teamocil --list` | -| Edit config | `tmuxp edit <config>` | `tmuxinator edit <project>` / `new` | `teamocil --edit <layout>` | +| Edit config | `tmuxp edit <config>` | `tmuxinator edit <project>` (alias of `new`) | `teamocil --edit <layout>` | | Show/debug config | (none) | `tmuxinator debug <project>` | `teamocil --show` / `--debug` | | Create new config | (none) | `tmuxinator new <project>` | (none) | | Copy config | (none) | `tmuxinator copy <src> <dst>` | (none) | From 2d25c140b663b7347b94f16cd3e91c1ab571c51c Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 06:36:22 -0600 Subject: [PATCH 12/53] fix(comparison): Annotate startup_window/startup_pane with tmuxp focus equivalent tmuxp doesn't have startup_window/startup_pane keys but achieves the same result via focus: true on individual windows/panes. Add cross-reference annotation so users aren't misled by (none). --- docs/comparison.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/comparison.md b/docs/comparison.md index c5501d74eb..4948202596 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -61,8 +61,8 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Environment vars | `environment` | (none) | (none) | | Pre-build script | `before_script` | (none) | (none) | | Shell cmd before (all panes) | `shell_command_before` | `pre_window` / `pre_tab` (deprecated) | (none) | -| Startup window | (none) | `startup_window` (name or index) | (none) | -| Startup pane | (none) | `startup_pane` | (none) | +| Startup window | (none; use `focus: true` on window) | `startup_window` (name or index) | (none; use `focus: true` on window) | +| Startup pane | (none; use `focus: true` on pane) | `startup_pane` | (none; use `focus: true` on pane) | | Plugins | `plugins` | (none) | (none) | | ERB/variable interpolation | (none) | Yes (`key=value` args) | (none) | | YAML anchors | Yes | Yes (via `YAML.safe_load` `aliases: true`) | Yes | From c8d1043294fc45006c4d9c2e780271fb2f43bcda Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 06:41:59 -0600 Subject: [PATCH 13/53] fix(parity-docs): Correct before_script hook mapping and --here details - before_script maps to on_project_first_start (runs only when session doesn't exist), not on_project_start (runs every invocation) - Add teamocil --here implementation details: sends cd via send-keys, decrements window count for index calculation --- docs/comparison.md | 4 ++-- notes/parity-teamocil.md | 9 +++++---- notes/parity-tmuxinator.md | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/comparison.md b/docs/comparison.md index 4948202596..6535d35942 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -74,8 +74,8 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Hook | tmuxp | tmuxinator | teamocil | |---|---|---|---| -| Before session build | `before_script` | `on_project_start` | (none) | -| First start only | (none) | `on_project_first_start` | (none) | +| Every start invocation | (none) | `on_project_start` | (none) | +| First start only | `before_script` | `on_project_first_start` | (none) | | On reattach | Plugin: `reattach()` | `on_project_restart` | (none) | | On exit/detach | (none) | `on_project_exit` | (none) | | On stop/kill | (none) | `on_project_stop` | (none) | diff --git a/notes/parity-teamocil.md b/notes/parity-teamocil.md index 1e4190c02f..d508c62d13 100644 --- a/notes/parity-teamocil.md +++ b/notes/parity-teamocil.md @@ -24,13 +24,14 @@ teamocil --here my-layout ``` When `--here` is specified: -- First window: **renames** current window instead of creating a new one -- First window: sends `cd <root>` if root is specified -- Subsequent windows: created normally +- First window: **renames** current window (`rename-window`) instead of creating a new one +- First window: sends `cd "<root>"` + `Enter` via `send-keys` to change directory (since no `-c` flag is available on an existing window) +- First window: decrements the window count when calculating base indices for subsequent windows +- Subsequent windows: created normally with `new-window` **Gap**: tmuxp always creates new windows. There is no way to populate the current window with a layout. -**WorkspaceBuilder requirement**: Add `--here` CLI flag. For first window, use rename + cd instead of `new_window()`. This would require special handling in `WorkspaceBuilder.first_window_pass()`. +**WorkspaceBuilder requirement**: Add `--here` CLI flag. For first window, use `rename-window` + `send-keys cd` instead of `new_window()`. Must also adjust window index calculation. This would require special handling in `WorkspaceBuilder.first_window_pass()`. ### 2. `--show` Option (Show Raw Config) diff --git a/notes/parity-tmuxinator.md b/notes/parity-tmuxinator.md index abee871e10..fd5f3daca1 100644 --- a/notes/parity-tmuxinator.md +++ b/notes/parity-tmuxinator.md @@ -14,13 +14,13 @@ tmuxinator has 5 lifecycle hooks: | Hook | Description | tmuxp equivalent | |---|---|---| -| `on_project_start` | Runs on every `start` invocation | `before_script` (partial — runs commands, but kills session on failure) | -| `on_project_first_start` | Runs only when session doesn't exist yet | No equivalent | +| `on_project_start` | Runs on every `start` invocation | No equivalent | +| `on_project_first_start` | Runs only when session doesn't exist yet | `before_script` (partial — runs before windows, but kills session on failure) | | `on_project_restart` | Runs when attaching to existing session | Plugin `reattach()` (requires writing a plugin) | | `on_project_exit` | Runs when detaching from session | No equivalent | | `on_project_stop` | Runs on `tmuxinator stop` | No equivalent (tmuxp has no `stop` command) | -**Gap**: tmuxp's `before_script` only covers the "first start" case and kills the session on failure. It has no hooks for detach/exit/stop events, and no distinction between first start vs. restart. +**Gap**: tmuxp's `before_script` is a partial equivalent of `on_project_first_start` — it runs before windows are created and kills the session on failure. tmuxp has no equivalent for `on_project_start` (runs every time, including reattach), no hooks for detach/exit/stop events, and no distinction between first start vs. restart. **WorkspaceBuilder requirement**: Add config keys for `on_project_start`, `on_project_first_start`, `on_project_restart`, `on_project_exit`, `on_project_stop`. The exit/stop hooks require shell integration (trap signals, set-hook in tmux). From abdf863a7051d3367b0af2a5252b8000cdbd4dcb Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 06:52:56 -0600 Subject: [PATCH 14/53] fix(parity-docs): Correct line number references in teamocil notes - import-teamocil.md: Code block comment said "Lines 144-149" but the `if "filters"` guard is on line 143, so range is 143-149 - parity-teamocil.md: Referenced "Line 142" for `clear` handling but actual code is lines 140-141 (line 142 is blank) --- notes/import-teamocil.md | 2 +- notes/parity-teamocil.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/notes/import-teamocil.md b/notes/import-teamocil.md index 7687222ca0..5d3a42de9a 100644 --- a/notes/import-teamocil.md +++ b/notes/import-teamocil.md @@ -184,7 +184,7 @@ Since teamocil 1.4.2 uses the v1.x format, the importer is outdated for current ### Bug: Redundant Filter Loop ```python -# Lines 144-149 (current) +# Lines 143-149 (current) if "filters" in w: if "before" in w["filters"]: for _b in w["filters"]["before"]: diff --git a/notes/parity-teamocil.md b/notes/parity-teamocil.md index d508c62d13..9ff48bd875 100644 --- a/notes/parity-teamocil.md +++ b/notes/parity-teamocil.md @@ -173,7 +173,7 @@ Teamocil joins multiple pane commands with `; ` and sends them as a single `send ``` This iterates N times but sets the same value each time. It should be a direct assignment. -2. **Line 142**: `clear` is preserved in the config but tmuxp has no handling for it. It will be silently ignored during workspace building. +2. **Lines 140-141**: `clear` is preserved in the config but tmuxp has no handling for it. It will be silently ignored during workspace building. 3. **Line 149**: `shell_command_after` is not a tmuxp-supported key. It will be silently ignored. From f2cd875a81404133a9201860b42dfff98b8b253d Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:03:53 -0600 Subject: [PATCH 15/53] fix(comparison): Correct tmuxinator min tmux, add session rename note, expand CLI table - Fix min tmux: 1.5+ (not "1.8 recommended; not 2.5"), per tmux_version.rb - Note teamocil renames session (rename-session) rather than creating new - Add teamocil auto-generated session name detail - Expand pre_window to show full deprecation chain (rbenv/rvm/pre_tab) - Add synchronize values (true/before/after) - Add --suppress-tmux-version-warning to CLI table - Split deprecated pre/post into separate rows with hook mappings - Fix tmuxp --append flag syntax - Fix pane focus to note startup_pane equivalent --- docs/comparison.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/comparison.md b/docs/comparison.md index 6535d35942..b9f53f8471 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -8,11 +8,11 @@ |---|---|---|---| | **Version** | 1.47.0+ | 3.3.7 | 1.4.2 | | **Language** | Python | Ruby | Ruby | -| **Min tmux** | 3.2 | 1.8 (recommended; not 2.5) | (not specified) | +| **Min tmux** | 3.2 | 1.5+ (1.5–3.6a tested) | (not specified) | | **Config formats** | YAML, JSON | YAML (with ERB) | YAML | | **Architecture** | ORM (libtmux) | Script generation (ERB templates) | Command objects → shell exec | | **License** | MIT | MIT | MIT | -| **Session building** | API calls via libtmux | Generates bash script, then execs it | Generates tmux command string, then `system()` | +| **Session building** | API calls via libtmux | Generates bash script, then execs it | Generates tmux command list, renames current session, then `system()` | | **Plugin system** | Yes (Python classes) | No | No | | **Shell completion** | Yes | Yes (zsh/bash/fish) | No | @@ -48,7 +48,7 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Key | tmuxp | tmuxinator | teamocil | |---|---|---|---| -| Session name | `session_name` | `name` / `project_name` | `name` | +| Session name | `session_name` | `name` / `project_name` | `name` (auto-generated if omitted) | | Root directory | `start_directory` | `root` / `project_root` | (none, per-window only) | | Windows list | `windows` | `windows` / `tabs` | `windows` | | Socket name | (CLI `-L`) | `socket_name` | (none) | @@ -60,7 +60,7 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Global options | `global_options` | (none) | (none) | | Environment vars | `environment` | (none) | (none) | | Pre-build script | `before_script` | (none) | (none) | -| Shell cmd before (all panes) | `shell_command_before` | `pre_window` / `pre_tab` (deprecated) | (none) | +| Shell cmd before (all panes) | `shell_command_before` | `pre_window` / `pre_tab` / `rbenv` / `rvm` (all deprecated) | (none) | | Startup window | (none; use `focus: true` on window) | `startup_window` (name or index) | (none; use `focus: true` on window) | | Startup pane | (none; use `focus: true` on pane) | `startup_pane` | (none; use `focus: true` on pane) | | Plugins | `plugins` | (none) | (none) | @@ -82,7 +82,8 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Before workspace build | Plugin: `before_workspace_builder()` | (none) | (none) | | On window create | Plugin: `on_window_create()` | (none) | (none) | | After window done | Plugin: `after_window_finished()` | (none) | (none) | -| Deprecated pre/post | (none) | `pre` / `post` | (none) | +| Deprecated pre | (none) | `pre` (deprecated → `on_project_start`/`on_project_restart`) | (none) | +| Deprecated post | (none) | `post` (deprecated → `on_project_stop`/`on_project_exit`) | (none) | ### Window-Level @@ -99,8 +100,8 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Shell for window | `window_shell` | (none) | (none) | | Environment vars | `environment` | (none) | (none) | | Suppress history | `suppress_history` | (none) | (none) | -| Focus | `focus` | (none) | `focus` | -| Synchronize panes | (none) | `synchronize` | (none) | +| Focus | `focus` | (none; use `startup_window`) | `focus` | +| Synchronize panes | (none) | `synchronize` (`true`/`before`/`after`) | (none) | | Filters (before) | (none) | (none) | `filters.before` (v0.x) | | Filters (after) | (none) | (none) | `filters.after` (v0.x) | @@ -116,8 +117,8 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Sleep before | `sleep_before` | (none) | (none) | | Sleep after | `sleep_after` | (none) | (none) | | Suppress history | `suppress_history` | (none) | (none) | -| Focus | `focus` | (none) | `focus` | -| Pane title | (none) | hash key (named pane) | (none) | +| Focus | `focus` | (none; use `startup_pane`) | `focus` | +| Pane title | (none) | hash key (named pane → `select-pane -T`) | (none) | ### Shorthand Syntax @@ -137,9 +138,9 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Load/start session | `tmuxp load <config>` | `tmuxinator start <project>` | `teamocil <layout>` | | Load detached | `tmuxp load -d <config>` | `attach: false` / `tmuxinator start --no-attach` | (none) | | Load with name override | `tmuxp load -s <name> <config>` | `tmuxinator start -n <name>` | (none) | -| Append to session | `tmuxp load -a` | `tmuxinator start --append` | (none) | +| Append to session | `tmuxp load --append` | `tmuxinator start --append` | (none) | | List configs | `tmuxp ls` | `tmuxinator list` | `teamocil --list` | -| Edit config | `tmuxp edit <config>` | `tmuxinator edit <project>` (alias of `new`) | `teamocil --edit <layout>` | +| Edit config | `tmuxp edit <config>` | `tmuxinator edit <project>` | `teamocil --edit <layout>` | | Show/debug config | (none) | `tmuxinator debug <project>` | `teamocil --show` / `--debug` | | Create new config | (none) | `tmuxinator new <project>` | (none) | | Copy config | (none) | `tmuxinator copy <src> <dst>` | (none) | @@ -156,6 +157,7 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Use here (current window) | (none) | (none) | `teamocil --here` | | Skip pre_window | (none) | `--no-pre-window` | (none) | | Pass variables | (none) | `key=value` args | (none) | +| Suppress version warning | (none) | `--suppress-tmux-version-warning` | (none) | | Custom config path | `tmuxp load /path/to/file` | `-p /path/to/file` | `--layout /path/to/file` | | Local config | `tmuxp load .` | `tmuxinator local` | (none) | From 381b734aa4cde3481b44f77d20e6a046b4c9151a Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:04:00 -0600 Subject: [PATCH 16/53] fix(parity-tmuxinator): Fix startup_window/pane semantics, pre_window chain, remove --here MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix startup_window: accepts name OR index (not just name) - Document pre_window fallback chain: rbenv → rvm → pre_tab → pre_window - Remove section 12 (--here) — this is a teamocil feature, not tmuxinator - Renumber section 13 → 12 - Clarify freeze vs tmuxinator new comparison - Add rvm source reference (project.rb:181) - Add tmuxinator version range to header --- notes/parity-tmuxinator.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/notes/parity-tmuxinator.md b/notes/parity-tmuxinator.md index fd5f3daca1..10bf491698 100644 --- a/notes/parity-tmuxinator.md +++ b/notes/parity-tmuxinator.md @@ -1,7 +1,7 @@ # Tmuxinator Parity Analysis *Last updated: 2026-02-08* -*Tmuxinator version analyzed: 3.3.7* +*Tmuxinator version analyzed: 3.3.7 (supports tmux 1.5–3.6a)* *tmuxp version: 1.47.0+* ## Features tmuxinator has that tmuxp lacks @@ -56,7 +56,7 @@ startup_window: editor # Select this window after build startup_pane: 1 # Select this pane within the startup window ``` -**Gap**: tmuxp supports `focus: true` on windows and panes (boolean), which is equivalent but syntactically different. The `startup_window` key allows referencing by name or index (rendered as `"#{name}:#{value}"`). **Partial parity** — tmuxp can achieve this but uses a different mechanism (`focus` key on individual windows/panes rather than a centralized key). +**Gap**: tmuxp supports `focus: true` on windows and panes (boolean), which is equivalent but syntactically different. The `startup_window` key allows referencing by window name or numeric index (rendered as `"#{name}:#{value}"`, defaults to `base_index` if omitted). The `startup_pane` is relative to the startup window (rendered as `"#{startup_window}.#{value}"`, defaults to `pane_base_index`). **Partial parity** — tmuxp can achieve this but uses a different mechanism (`focus` key on individual windows/panes rather than a centralized key). ### 5. Pane Synchronization @@ -166,15 +166,11 @@ tmuxinator start myproject --no-pre-window Skips `pre_window` commands. Useful for debugging. -**Gap**: tmuxp has no equivalent flag to skip `shell_command_before`. - -### 12. `--here` Equivalent +Note: tmuxinator's `pre_window` method has a fallback chain (`project.rb:175-188`): `rbenv` → `rvm` → `pre_tab` → `pre_window`. The `--no-pre-window` flag disables all of these, not just `pre_window`. -**Source**: teamocil provides `--here` to reuse the current window. tmuxinator has no `--here` per se but tmuxp also lacks this. - -**Gap**: Neither tmuxp nor tmuxinator has this; teamocil does. +**Gap**: tmuxp has no equivalent flag to skip `shell_command_before`. -### 13. Create Config from Running Session +### 12. Create Config from Running Session **Source**: `lib/tmuxinator/cli.rb` (`new <name> <session>`) @@ -182,9 +178,9 @@ Skips `pre_window` commands. Useful for debugging. tmuxinator new myproject existing-session-name ``` -Creates a config file pre-populated from a running tmux session. +Creates a config file pre-populated from a running tmux session. Note: tmuxinator captures only the window/pane structure and names, not running commands. -**Gap**: tmuxp has `tmuxp freeze` which exports to YAML/JSON. **Different approach, same result** — tmuxp's freeze is arguably more complete. +**Gap**: tmuxp has `tmuxp freeze` which exports to YAML/JSON with more detail (captures pane working directories and current commands). Different approach, functionally equivalent. ## Import Behavior Analysis @@ -243,7 +239,7 @@ Creates a config file pre-populated from a running tmux session. 4. **Missing `pre_tab`**: The `pre_tab` deprecated predecessor to `pre_window` is not handled. -5. **Missing `rvm`**: Only `rbenv` is imported; `rvm` (another deprecated but still functional key) is ignored. +5. **Missing `rvm`**: Only `rbenv` is imported; `rvm` (another deprecated but still functional key) is ignored. In tmuxinator, `rvm` maps to `rvm use #{value}` (`project.rb:181`). 6. **No validation or warnings**: The importer silently drops unsupported keys with no feedback to the user. From 5fa5efbe371a32bb927239b33c24c5c9bbde1a80 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:04:11 -0600 Subject: [PATCH 17/53] fix(parity-teamocil): Add session rename behavior, fix with_env_var/cmd_separator - Add section 1: teamocil renames session (rename-session), not creates - Note auto-generated session name (teamocil-session-RANDOM) - Add window focus implementation detail (session.rb:24-25) - Add --list and --edit note for teamocil CLI - Reclassify with_env_var and cmd_separator as unverified (not in source) - Add session rename mode to WorkspaceBuilder gaps - Fix line number references (144-149, 147-149, 161-163) - Renumber sections to account for new section 1 --- notes/parity-teamocil.md | 52 ++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/notes/parity-teamocil.md b/notes/parity-teamocil.md index 9ff48bd875..4d17f1eb1a 100644 --- a/notes/parity-teamocil.md +++ b/notes/parity-teamocil.md @@ -13,9 +13,19 @@ Teamocil has had two distinct config formats: The current tmuxp importer (`importers.py:import_teamocil`) **targets the v0.x format**. It handles the `session:` wrapper, `splits`, `filters`, and `cmd` keys — all of which are v0.x-only constructs. It does **not** handle the v1.x format natively, though v1.x configs may partially work since the `windows`/`panes` structure is similar. +Note: teamocil v1.x does not create new sessions — it **renames** the current session (`rename-session`) and adds windows to it. This is fundamentally different from tmuxp/tmuxinator which create fresh sessions. + ## Features teamocil has that tmuxp lacks -### 1. `--here` Option (Reuse Current Window) +### 1. Session Rename (Not Create) + +**Source**: `lib/teamocil/tmux/session.rb:18-20` + +teamocil does not create a new session. It **renames** the current session via `rename-session` and adds windows to it. If no `name` is provided, it auto-generates one: `"teamocil-session-#{rand(1_000_000)}"`. + +**Gap**: tmuxp always creates a new session (unless appending with `--append`). There is no way to rename and populate the current session. + +### 2. `--here` Option (Reuse Current Window) **Source**: `lib/teamocil/tmux/window.rb`, `lib/teamocil/utils/option_parser.rb` @@ -33,7 +43,7 @@ When `--here` is specified: **WorkspaceBuilder requirement**: Add `--here` CLI flag. For first window, use `rename-window` + `send-keys cd` instead of `new_window()`. Must also adjust window index calculation. This would require special handling in `WorkspaceBuilder.first_window_pass()`. -### 2. `--show` Option (Show Raw Config) +### 3. `--show` Option (Show Raw Config) **Source**: `lib/teamocil/layout.rb` @@ -45,7 +55,7 @@ Outputs the raw YAML content of the layout file. **Gap**: tmuxp has no equivalent. Users can `cat` the file manually. -### 3. `--debug` Option (Show Commands Without Executing) +### 4. `--debug` Option (Show Commands Without Executing) **Source**: `lib/teamocil/layout.rb` @@ -57,7 +67,9 @@ Outputs the tmux commands that would be executed, one per line, without running **Gap**: tmuxp has no dry-run mode. Since tmuxp uses libtmux API calls rather than generating command strings, implementing this would require a logging/recording mode in the builder. -### 4. Window-Level `focus` Key +Note: teamocil also has `--list` (lists available layouts in `~/.teamocil/`) and `--edit` (opens layout file in `$EDITOR`). Both are available in tmuxp (`tmuxp ls`, `tmuxp edit`). + +### 5. Window-Level `focus` Key **Source**: `lib/teamocil/tmux/window.rb` @@ -71,7 +83,9 @@ windows: **Gap**: tmuxp **does** support `focus: true` on windows. **No gap**. -### 5. Pane-Level `focus` Key +Note: teamocil handles window focus at the session level in `session.rb:24-25` — after all windows are created, it finds the focused window and issues `select-window`. tmuxp handles this the same way. + +### 6. Pane-Level `focus` Key **Source**: `lib/teamocil/tmux/pane.rb` @@ -84,7 +98,7 @@ panes: **Gap**: tmuxp **does** support `focus: true` on panes. **No gap**. -### 6. Window-Level `options` Key +### 7. Window-Level `options` Key **Source**: `lib/teamocil/tmux/window.rb` @@ -99,7 +113,7 @@ Maps to `set-window-option -t <window> <key> <value>`. **Gap**: tmuxp **does** support `options` on windows. **No gap**. -### 7. Multiple Commands Joined by Semicolon +### 8. Multiple Commands Joined by Semicolon **Source**: `lib/teamocil/tmux/pane.rb` @@ -161,12 +175,12 @@ Teamocil joins multiple pane commands with `; ` and sends them as a single `send | v1.x `focus` (pane) | Not imported | | v1.x `options` (window) | Not imported | | Session-level `name` (without `session:` wrapper) | Handled (uses `.get("name")`) | -| `with_env_var` (v0.x) | Not handled (noted in docstring TODO) | -| `cmd_separator` (v0.x) | Not handled (noted in docstring TODO) | +| `with_env_var` (importer TODO) | Not handled — does not exist in current teamocil source | +| `cmd_separator` (importer TODO) | Not handled — does not exist in current teamocil source | ### Code Quality Issues in Importer -1. **Lines 145-149**: The `filters.before` and `filters.after` handling has redundant `for _b in` loops that serve no purpose. The inner assignment just reassigns the same value each iteration: +1. **Lines 144-149**: The `filters.before` and `filters.after` handling has redundant `for _b in` loops that serve no purpose. The inner assignment just reassigns the same value each iteration: ```python for _b in w["filters"]["before"]: window_dict["shell_command_before"] = w["filters"]["before"] @@ -175,9 +189,9 @@ Teamocil joins multiple pane commands with `; ` and sends them as a single `send 2. **Lines 140-141**: `clear` is preserved in the config but tmuxp has no handling for it. It will be silently ignored during workspace building. -3. **Line 149**: `shell_command_after` is not a tmuxp-supported key. It will be silently ignored. +3. **Lines 147-149**: `shell_command_after` is set from `filters.after` but is not a tmuxp-supported key. It will be silently ignored during workspace building. -4. **Line 162-163**: `width` is silently dropped with a TODO comment. No warning to the user. +4. **Lines 161-163**: `width` is silently dropped with a TODO comment. No warning to the user. 5. **v1.x incompatibility**: The importer assumes v0.x format. A v1.x config with `commands` instead of `cmd`, or string panes, will not import correctly: - String pane `"git status"` → error (tries to access `p["cmd"]` on a string) @@ -198,20 +212,22 @@ Teamocil joins multiple pane commands with `; ` and sends them as a single `send ### Gaps Requiring New Features -1. **`--here` flag** — Reuse current window for first window of layout. Requires `WorkspaceBuilder` to rename instead of create, and send `cd` for root directory. +1. **Session rename mode** — teamocil renames the current session rather than creating a new one. tmuxp always creates a fresh session. + +2. **`--here` flag** — Reuse current window for first window of layout. Requires `WorkspaceBuilder` to rename instead of create, and send `cd` for root directory. -2. **`--debug` / dry-run mode** — Log commands without executing. Architectural challenge since tmuxp uses libtmux API, not command strings. +3. **`--debug` / dry-run mode** — Log commands without executing. Architectural challenge since tmuxp uses libtmux API, not command strings. -3. **`shell_command_after`** — Commands run after pane commands. The importer preserves this from teamocil's `filters.after` but tmuxp has no support for it in the builder. +4. **`shell_command_after`** — Commands run after pane commands. The importer preserves this from teamocil's `filters.after` but tmuxp has no support for it in the builder. ### Import-Only Fixes (No Builder Changes) -4. **v1.x format support** — The importer should handle: +5. **v1.x format support** — The importer should handle: - `commands` key (v1.x) in addition to `cmd` (v0.x) - String pane shorthand - `focus` on windows and panes - `options` on windows -5. **Redundant loop cleanup** — Fix the `filters` handling code. +6. **Redundant loop cleanup** — Fix the `filters` handling code. -6. **Drop unsupported keys with warnings** — Instead of silently preserving `clear` or dropping `width`, warn the user. +7. **Drop unsupported keys with warnings** — Instead of silently preserving `clear` or dropping `width`, warn the user. From dc5408eae192fd15fde86c38f7fc949e8a660a7a Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:04:19 -0600 Subject: [PATCH 18/53] fix(import-tmuxinator): Correct pre/pre_window semantics and cli_args analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix pre/pre_window mapping: pre → before_script (session-level, runs once), pre_window → shell_command_before (per-pane) - Add template.erb line references for execution order - Expand cli_args fragility analysis (str.replace is unsafe) - Add tmuxinator source references for tmux_options and socket handling --- notes/import-tmuxinator.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/notes/import-tmuxinator.md b/notes/import-tmuxinator.md index 943f42448c..eca2e54dcd 100644 --- a/notes/import-tmuxinator.md +++ b/notes/import-tmuxinator.md @@ -73,7 +73,9 @@ These are config keys/patterns that differ syntactically but can be automaticall | `cli_args: "-f ~/.tmux.special.conf"` | `config: ~/.tmux.special.conf` | | `tmux_options: "-f ~/.tmux.special.conf"` | `config: ~/.tmux.special.conf` | -**Importer status**: ⚠ Partially handled (lines 36-49). Only extracts `-f` flag value. Other flags like `-L` (socket name) and `-S` (socket path) in `cli_args`/`tmux_options` are silently included in the `config` value, which is incorrect — `config` should only be a file path. +**Importer status**: ⚠ Partially handled (lines 36-49). Only extracts `-f` flag value via `str.replace("-f", "").strip()`, which is fragile — it would also match strings containing `-f` as a substring (e.g. a path like `/opt/foobar`). Other flags like `-L` (socket name) and `-S` (socket path) that may appear in `cli_args`/`tmux_options` are silently included in the `config` value, which is incorrect — `config` should only be a file path. + +In tmuxinator, `cli_args` is deprecated in favor of `tmux_options` (`project.rb:17-19`). The actual tmux command is built as `"#{tmux_command}#{tmux_options}#{socket}"` (`project.rb:196`), where `socket` handles `-L`/`-S` separately from `socket_name`/`socket_path` config keys. ### 9. Rbenv @@ -90,9 +92,13 @@ These are config keys/patterns that differ syntactically but can be automaticall | `pre: "cmd"` (session-level, alone) | `shell_command_before: ["cmd"]` | | `pre_window: "cmd"` + `pre: "cmd"` | `shell_command: "cmd"` + `shell_command_before: ["cmd"]` | -**Importer status**: ⚠ Bug (lines 59-70). When both `pre` and `pre_window` exist, the importer sets `shell_command` (not a valid tmuxp session-level key) for `pre` and `shell_command_before` for `pre_window`. The `pre` commands are lost. +**Importer status**: ⚠ Bug (lines 59-70). When both `pre` and `pre_window` exist, the importer sets `shell_command` (not a valid tmuxp session-level key) for `pre` and `shell_command_before` for `pre_window`. The `pre` commands are silently lost. + +In tmuxinator, `pre` is a deprecated session-level command run once before creating windows (in `template.erb:19`, equivalent to `on_project_start`). `pre_window` is a per-pane command run before each pane's commands (in `template.erb:71-73`). These are different scopes. -**Correct mapping**: Both should map to `shell_command_before`, with `pre` commands first, then `pre_window` commands. +**Correct mapping**: +- `pre` → `before_script` (runs once before windows are created) +- `pre_window` → `shell_command_before` (runs per pane) ### 11. Window as String/List From 0a19edcdadb27a4b2282d6b1f35fff34ab369288 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:04:25 -0600 Subject: [PATCH 19/53] fix(import-teamocil): Mark with_env_var/cmd_separator as unverified stale TODOs - with_env_var does not exist in teamocil 1.4.2 source - cmd_separator does not exist in teamocil 1.4.2 source - Both are only in importer docstring TODOs (importers.py:121-123) - Reclassify both as unverified in summary table - Update code issues section to note stale TODOs --- notes/import-teamocil.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/notes/import-teamocil.md b/notes/import-teamocil.md index 5d3a42de9a..0a5b99c8dc 100644 --- a/notes/import-teamocil.md +++ b/notes/import-teamocil.md @@ -165,19 +165,17 @@ Since teamocil 1.4.2 uses the v1.x format, the importer is outdated for current ## Import-Only Fixes (No Builder Changes) -### 5. `with_env_var` → `environment` (v0.x) +### 5. `with_env_var` (listed in importer TODO) -**What it does in teamocil v0.x**: Sets environment variables for panes. +**Note**: `with_env_var` is listed in the importer's docstring TODOs (`importers.py:121`) but does **not exist** in teamocil's current source (v1.4.2) or in any teamocil file. This may have been a feature from a very old version that was removed, or it may never have existed. The TODO should be removed or verified against historical teamocil releases. -**Why it's not imported**: Noted in importer docstring TODO. Not implemented. +If it did exist, tmuxp's `environment` key would be the natural mapping. -**Fix**: tmuxp already has `environment` on sessions, windows, and panes. The importer just needs to map `with_env_var` → `environment`. No builder changes required. +### 6. `cmd_separator` (listed in importer TODO) -### 6. `cmd_separator` (v0.x) +**Note**: Like `with_env_var`, `cmd_separator` is listed in the importer's docstring TODOs but does **not exist** in teamocil's current source (v1.4.2). Teamocil v1.x hardcodes `commands.join('; ')` in `pane.rb:7`. There is no configurable separator. -**What it does in teamocil v0.x**: Custom separator for joining multiple commands (default: `; `). - -**Note**: tmuxp sends commands individually (one `send_keys` per command), so this is irrelevant. The behavioral difference is actually better in tmuxp — no import needed. +tmuxp sends commands individually (one `send_keys` per command), so even if this existed, it would be irrelevant. ## Code Issues in Current Importer @@ -216,9 +214,9 @@ if "panes" in w: If `p` is a string (v1.x shorthand), `"cmd" in p` will check for substring match in the string, not a dict key. This will either silently pass (if the command doesn't contain "cmd") or incorrectly match. -### Missing: `with_env_var` and `cmd_separator` +### Stale TODOs: `with_env_var` and `cmd_separator` -Noted in the docstring TODOs but never implemented. `with_env_var` could map to tmuxp's `environment` key. `cmd_separator` is irrelevant since tmuxp sends commands individually. +Listed in the importer's docstring TODOs (`importers.py:121-123`) but neither exists in teamocil's current source (v1.4.2). These TODOs may reference features from a very old teamocil version or may be incorrect. They should be removed or verified against historical releases. ### Silent Drops @@ -244,9 +242,9 @@ Noted in the docstring TODOs but never implemented. `with_env_var` could map to | Window `focus` (v1.x) | ✗ Missing | Difference (needs add) | | Pane `focus` (v1.x) | ✗ Missing | Difference (needs add) | | Window `options` (v1.x) | ✗ Missing | Difference (needs add) | -| `with_env_var` → `environment` (v0.x) | ✗ Missing | Difference (needs add) | +| `with_env_var` (in importer TODO) | ✗ Missing | Unverified (not in current teamocil source) | | `filters.after` → `shell_command_after` | ⚠ Imported but unused | **Limitation** | | Pane `width` (v0.x) | ⚠ Dropped silently | **Limitation** | | Window `clear` (v0.x) | ⚠ Preserved but unused | **Limitation** | -| `cmd_separator` (v0.x) | ✗ Missing | N/A (tmuxp is better) | +| `cmd_separator` (in importer TODO) | ✗ Missing | Unverified (not in current teamocil source) | | `--here` flag | N/A (runtime flag) | **Limitation** | From b0daaf432c03b07b639f13a348f36ffc867ffab1 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:13:39 -0600 Subject: [PATCH 20/53] docs(plan): Add parity implementation plan with API blockers Analyze libtmux and tmuxp limitations blocking feature parity with tmuxinator and teamocil. Document dead config keys, importer bugs, and required API additions organized by implementation phase. --- notes/plan.md | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 notes/plan.md diff --git a/notes/plan.md b/notes/plan.md new file mode 100644 index 0000000000..4511fcf408 --- /dev/null +++ b/notes/plan.md @@ -0,0 +1,235 @@ +# Parity Implementation Plan + +*Last updated: 2026-02-08* +*Based on: parity-tmuxinator.md, parity-teamocil.md, import-tmuxinator.md, import-teamocil.md* + +## libtmux Limitations + +### L1. No `Pane.set_title()` Method + +- **Blocker**: libtmux has no method wrapping `select-pane -T <title>`. The `pane_title` format variable was removed from the format query list (`formats.py:70`) in tmux 3.1+, but `select-pane -T` still works in tmux 3.2+. libtmux already knows about the display options (`pane_border_status`, `pane_border_format` in `constants.py:163-173`) but has no setter for the title itself. +- **Blocks**: Pane titles (tmuxinator feature: named pane syntax `pane_name: command` → `select-pane -T`). Also blocks `enable_pane_titles`, `pane_title_position`, `pane_title_format` session-level config. +- **Required**: Add `Pane.set_title(title: str)` method that calls `self.cmd("select-pane", "-T", title)`. This is a simple wrapper — `Pane.cmd()` already exists (`pane.py:177`) and `select-pane` is already used for `Pane.select()` (`pane.py:601`). +- **Non-breaking**: Pure addition, no existing API changes. + +### L2. Hardcoded tmux Binary Path + +- **Blocker**: `shutil.which("tmux")` is hardcoded in two places: + - `common.py:252` (`tmux_cmd.__init__`) + - `server.py:223` (`Server.is_alive`) + There is no way to use a custom tmux binary (wemux, byobu, or custom-built tmux). +- **Blocks**: Wemux support (tmuxinator `tmux_command: wemux`). Also blocks CI/container use with non-standard tmux locations. +- **Required**: Add optional `tmux_bin` parameter to `Server.__init__()` that propagates to `tmux_cmd`. Default remains `shutil.which("tmux")`. +- **Non-breaking**: Optional parameter with backward-compatible default. Existing code is unaffected. + +### L3. No Dry-Run / Command Preview Mode + +- **Blocker**: `tmux_cmd` (`common.py:252-296`) always executes commands. Debug logging exists (`logger.debug` at line 291) but only logs stdout after execution, not the command being sent. There is no facility to collect commands without executing them. +- **Blocks**: `--debug` / dry-run mode (both tmuxinator and teamocil have this). tmuxinator generates a bash script that can be previewed; teamocil's `--debug` outputs the tmux command list. +- **Required**: Either (a) add a `dry_run` flag to `tmux_cmd` that collects commands instead of executing, or (b) add pre-execution logging at DEBUG level that logs the full command before `subprocess.run()`. Option (b) is simpler and doesn't change behavior. +- **Non-breaking**: Logging change only. tmuxp would implement the user-facing `--debug` flag by capturing log output. +- **Note**: Since tmuxp uses libtmux API calls (not command strings), a true dry-run would require a recording layer in `WorkspaceBuilder` that logs each API call. This is architecturally different from tmuxinator/teamocil's approach and may not be worth full parity. + +### L4. Available APIs (No Blockers) + +These libtmux APIs already exist and do NOT need changes: + +| API | Location | Supports | +|---|---|---| +| `Session.rename_session(name)` | `session.py:412` | teamocil session rename mode | +| `Window.rename_window(name)` | `window.py:462` | teamocil `--here` flag | +| `Pane.resize(height, width)` | `pane.py:217` | teamocil v0.x pane `width` | +| `Pane.send_keys(cmd, enter)` | `pane.py:423` | All command sending | +| `Pane.select()` | `pane.py:581` | Pane focus | +| `Window.set_option(key, val)` | `options.py:578` (OptionsMixin) | `synchronize-panes`, window options | +| `Session.set_hook(hook, cmd)` | `hooks.py:111` (HooksMixin) | Lifecycle hooks (`client-detached`, etc.) | +| `Session.set_option(key, val)` | `options.py:578` (OptionsMixin) | `pane-border-status`, `pane-border-format` | +| `HooksMixin` on Session/Window/Pane | `session.py:55`, `window.py:56`, `pane.py:51` | All entities inherit hooks | + +## tmuxp Limitations + +### T1. No `synchronize` Config Key + +- **Blocker**: `WorkspaceBuilder` (`builder.py`) does not check for a `synchronize` key on window configs. The key is silently ignored if present. +- **Blocks**: Pane synchronization (tmuxinator `synchronize: true/before/after`). +- **Required**: Add `synchronize` handling in `builder.py`. For `before`/`true`: call `window.set_option("synchronize-panes", "on")` before pane commands in `iter_create_panes()`. For `after`: call it in `config_after_window()`. For `false`/omitted: no action. +- **Insertion point**: `iter_create_windows()` around line 424 (after window options are set) for `before`/`true`. `config_after_window()` around line 560 for `after`. +- **Non-breaking**: New optional config key. Existing configs are unaffected. + +### T2. No Pane Title Config Key + +- **Blocker**: `WorkspaceBuilder` has no handling for pane `title` key or session-level `enable_pane_titles` / `pane_title_position` / `pane_title_format`. +- **Blocks**: Pane titles (tmuxinator named pane syntax). +- **Required**: + 1. Session-level: set `pane-border-status` and `pane-border-format` options via `session.set_option()` in `build()` (around line 311). + 2. Pane-level: call `pane.cmd("select-pane", "-T", title)` after pane creation in `iter_create_panes()` (around line 538). Requires L1 (libtmux `set_title()`), or can use `pane.cmd()` directly. +- **Config keys**: `enable_pane_titles: true`, `pane_title_position: top`, `pane_title_format: "..."` (session-level). `title: "my-title"` (pane-level). +- **Non-breaking**: New optional config keys. + +### T3. No `shell_command_after` Config Key + +- **Blocker**: The teamocil importer produces `shell_command_after` (from `filters.after`, `importers.py:149`), but `WorkspaceBuilder` never reads it. The `trickle()` function in `loader.py` has no logic for it either. +- **Blocks**: teamocil v0.x `filters.after` — commands run after pane commands. +- **Required**: Add handling in `iter_create_panes()` after the `shell_command` loop (around line 534). Read `pane_config.get("shell_command_after", [])` and send each command via `pane.send_keys()`. +- **Non-breaking**: New optional config key. + +### T4. No `--here` CLI Flag + +- **Blocker**: `tmuxp load` (`cli/load.py`) has no `--here` flag. `WorkspaceBuilder.iter_create_windows()` always creates new windows via `session.new_window()` (line 406). +- **Blocks**: teamocil `--here` — reuse current window for first window. +- **Required**: + 1. Add `--here` flag to `cli/load.py` (around line 516, near `--append`). + 2. Pass `here=True` through to `WorkspaceBuilder.build()`. + 3. In `iter_create_windows()`, when `here=True` and first window: use `window.rename_window(name)` instead of `session.new_window()`, and send `cd <root>` via `pane.send_keys()` for directory change. + 4. Adjust `first_window_pass()` logic (line 584). +- **Depends on**: libtmux `Window.rename_window()` (already exists, L4). +- **Non-breaking**: New optional CLI flag. + +### T5. No `stop` / `kill` CLI Command + +- **Blocker**: tmuxp has no `stop` command. The CLI modules (`cli/__init__.py`) only register: `load`, `freeze`, `ls`, `search`, `shell`, `convert`, `import`, `edit`, `debug-info`. +- **Blocks**: tmuxinator `stop` / `stop-all` — kill session with cleanup hooks. +- **Required**: Add `tmuxp stop <session>` command. Implementation: find session by name via `server.sessions`, call `session.kill()`. For hook support, run `on_project_stop` hook before kill. +- **Non-breaking**: New CLI command. + +### T6. No Lifecycle Hook Config Keys + +- **Blocker**: tmuxp's plugin system (`plugin.py:216-292`) has 5 hooks: `before_workspace_builder`, `on_window_create`, `after_window_finished`, `before_script`, `reattach`. These are Python plugin hooks, not config-driven shell command hooks. There are no config keys for `on_project_start`, `on_project_exit`, etc. +- **Blocks**: tmuxinator lifecycle hooks (`on_project_start`, `on_project_first_start`, `on_project_restart`, `on_project_exit`, `on_project_stop`). +- **Required**: Add config-level hook keys. Mapping: + - `on_project_start` → run shell command at start of `build()`, before `before_script` + - `on_project_first_start` → already partially covered by `before_script` + - `on_project_restart` → run when reattaching (currently only plugin `reattach()` hook) + - `on_project_exit` → use tmux `set-hook client-detached` via `session.set_hook()` (libtmux L4) + - `on_project_stop` → run in new `tmuxp stop` command (T5) +- **Depends on**: T5 for `on_project_stop`. +- **Non-breaking**: New optional config keys. + +### T7. No `--no-shell-command-before` CLI Flag + +- **Blocker**: `tmuxp load` has no flag to skip `shell_command_before`. The `trickle()` function (`loader.py:245-256`) always prepends these commands. +- **Blocks**: tmuxinator `--no-pre-window` — skip per-pane pre-commands for debugging. +- **Required**: Add `--no-shell-command-before` flag to `cli/load.py`. When set, clear `shell_command_before` from all levels before calling `trickle()`. +- **Non-breaking**: New optional CLI flag. + +### T8. No Config Templating + +- **Blocker**: tmuxp has no variable interpolation in config values. Environment variable expansion (`$VAR`) works in `start_directory` paths via `os.path.expandvars()` in `loader.py`, but not in arbitrary config values. +- **Blocks**: tmuxinator ERB templating (`<%= @settings["key"] %>`). +- **Required**: Add a Jinja2 or Python `string.Template` pass before YAML parsing. Allow `key=value` CLI args to set template variables. This is a significant architectural addition. +- **Non-breaking**: Opt-in feature, existing configs are unaffected. + +### T9. No `--debug` / Dry-Run CLI Flag + +- **Blocker**: `tmuxp load` has no dry-run mode. Since tmuxp uses libtmux API calls rather than generating command strings, there's no natural command list to preview. +- **Blocks**: tmuxinator `debug` and teamocil `--debug` / `--show`. +- **Required**: Either (a) add a recording proxy layer around libtmux calls that logs what would be done, or (b) add verbose logging that shows each tmux command before execution (depends on L3). +- **Non-breaking**: New optional CLI flag. + +### T10. Missing Config Management Commands + +- **Blocker**: tmuxp only has `edit`. Missing: `new` (create from template), `copy` (duplicate config), `delete` (remove config with confirmation). +- **Blocks**: tmuxinator `new`, `copy`, `delete`, `implode` commands. +- **Required**: Add CLI commands. These are straightforward file operations. +- **Non-breaking**: New CLI commands. + +## Dead Config Keys + +Keys produced by importers but silently ignored by the builder: + +| Key | Producer | Importer Line | Builder Handling | Issue | +|---|---|---|---|---| +| `shell_command` (session-level) | tmuxinator importer | `importers.py:60` | Not a valid session key | **Bug**: `pre` commands lost when both `pre` and `pre_window` exist | +| `config` | tmuxinator importer | `importers.py:37,44` | Never read | Dead data — extracted `-f` path goes nowhere | +| `socket_name` | tmuxinator importer | `importers.py:52` | Never read | Dead data — CLI uses `-L` flag | +| `clear` | teamocil importer | `importers.py:141` | Never read | Dead data — tmuxp has no clear support | +| `shell_command_after` | teamocil importer | `importers.py:149` | Never read | Dead data — tmuxp has no after-command support | + +## Importer Bugs (No Builder Changes Needed) + +### I1. tmuxinator `pre` + `pre_window` Mapping Bug + +- **Bug**: When both `pre` and `pre_window` exist (`importers.py:59-65`), `pre` maps to `shell_command` (invalid session-level key) and `pre_window` maps to `shell_command_before`. The `pre` commands are silently lost. +- **Correct mapping**: `pre` → `before_script` (session-level, runs once before windows). `pre_window` → `shell_command_before` (per-pane). +- **Note**: `before_script` expects a file path, not inline commands. This may need a different approach — either write a temp script, or add an `on_project_start` config key (T6). + +### I2. tmuxinator `cli_args` / `tmux_options` Fragile Parsing + +- **Bug**: `str.replace("-f", "").strip()` (`importers.py:41,48`) matches `-f` as a substring anywhere in the string. A path like `/opt/foobar` would be corrupted. Also ignores `-L` (socket name) and `-S` (socket path) flags. +- **Fix**: Use proper argument parsing (e.g., `shlex.split()` + iterate to find `-f` flag and its value). + +### I3. teamocil Redundant Filter Loops + +- **Bug**: `for _b in w["filters"]["before"]:` loops (`importers.py:145-149`) iterate N times but set the same value each time. +- **Fix**: Replace with direct assignment. + +### I4. teamocil v1.x Format Not Supported + +- **Bug**: Importer assumes v0.x format. String panes cause incorrect behavior (`"cmd" in "git status"` checks substring, not dict key). `commands` key (v1.x) not mapped. +- **Fix**: Add format detection. Handle string panes, `commands` key, `focus`, and `options`. + +### I5. tmuxinator Missing Keys + +Not imported but translatable: +- `rvm` → `shell_command_before: ["rvm use {value}"]` +- `pre_tab` → `shell_command_before` (deprecated predecessor to `pre_window`) +- `startup_window` → find matching window, set `focus: true` +- `startup_pane` → find matching pane, set `focus: true` +- `on_project_first_start` → `before_script` +- `socket_path` → warn user to use CLI `-S` flag +- `attach: false` → warn user to use CLI `-d` flag + +### I6. teamocil Missing Keys (v1.x) + +Not imported but translatable (same key names in tmuxp): +- `commands` → `shell_command` +- `focus` (window) → `focus` (pass-through) +- `focus` (pane) → `focus` (pass-through) +- `options` (window) → `options` (pass-through) +- String pane shorthand → `shell_command: [command]` + +### I7. Stale Importer TODOs + +`importers.py:121-123` lists `with_env_var` and `cmd_separator` as TODOs, but neither exists in teamocil v1.4.2 source. These are stale references from ~2013 and should be removed. + +## Implementation Priority + +### Phase 1: Import Fixes (No Builder/libtmux Changes) + +These fix existing bugs and add missing translations without touching the builder: + +1. **I3**: Fix redundant filter loops (teamocil) +2. **I4**: Add v1.x teamocil format support +3. **I6**: Import teamocil v1.x keys (`commands`, `focus`, `options`, string panes) +4. **I5**: Import missing tmuxinator keys (`rvm`, `pre_tab`, `startup_window`, `startup_pane`) +5. **I1**: Fix `pre`/`pre_window` mapping (tmuxinator) +6. **I2**: Fix `cli_args` parsing (tmuxinator) +7. **I7**: Remove stale TODOs + +### Phase 2: Builder Additions (tmuxp Only) + +These add new config key handling to the builder: + +1. **T1**: `synchronize` config key — straightforward `set_option()` call +2. **T3**: `shell_command_after` config key — straightforward `send_keys()` loop +3. **T4**: `--here` CLI flag — moderate complexity, uses existing libtmux APIs + +### Phase 3: libtmux Additions + +These require changes to the libtmux package: + +1. **L1**: `Pane.set_title()` — simple wrapper, needed for T2 +2. **T2**: Pane title config keys — depends on L1 + +### Phase 4: New CLI Commands + +3. **T5**: `tmuxp stop` command +4. **T10**: `tmuxp new`, `tmuxp copy`, `tmuxp delete` commands + +### Phase 5: Larger Features (Nice-to-Have) + +5. **T6**: Lifecycle hook config keys — complex, needs design +6. **T7**: `--no-shell-command-before` flag — simple +7. **T8**: Config templating — significant architectural addition +8. **T9**: `--debug` / dry-run mode — depends on L3 +9. **L2**: Custom tmux binary — requires libtmux changes From 4ca606a1d404546e737ab783cf6c6aafa578f6e0 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:20:31 -0600 Subject: [PATCH 21/53] fix(plan): Correct line refs, add isinstance bug, expand T4/T8/L2 details - L2: Fix method name (raise_if_dead, not is_alive), document two independent code paths - L4: Fix Pane.select() line number (577, not 581) - T4: Add session rename mode alongside --here, note --append gap - T8: Correct env var expansion scope (works in most values, not just start_directory) - I1: Document isinstance check bug (checks pre type instead of pre_window type) --- notes/plan.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/notes/plan.md b/notes/plan.md index 4511fcf408..ea3194eced 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -14,12 +14,12 @@ ### L2. Hardcoded tmux Binary Path -- **Blocker**: `shutil.which("tmux")` is hardcoded in two places: - - `common.py:252` (`tmux_cmd.__init__`) - - `server.py:223` (`Server.is_alive`) +- **Blocker**: `shutil.which("tmux")` is hardcoded in two independent code paths: + - `common.py:252` — `tmux_cmd.__init__()`, the class through which all libtmux commands flow (called by `Server.cmd()` at `server.py:311`) + - `server.py:223` — `Server.raise_if_dead()`, a separate code path that calls `subprocess.check_call()` directly There is no way to use a custom tmux binary (wemux, byobu, or custom-built tmux). - **Blocks**: Wemux support (tmuxinator `tmux_command: wemux`). Also blocks CI/container use with non-standard tmux locations. -- **Required**: Add optional `tmux_bin` parameter to `Server.__init__()` that propagates to `tmux_cmd`. Default remains `shutil.which("tmux")`. +- **Required**: Add optional `tmux_bin` parameter to `Server.__init__()` that propagates to `tmux_cmd`. Both code paths must be updated. Default remains `shutil.which("tmux")`. - **Non-breaking**: Optional parameter with backward-compatible default. Existing code is unaffected. ### L3. No Dry-Run / Command Preview Mode @@ -40,7 +40,7 @@ These libtmux APIs already exist and do NOT need changes: | `Window.rename_window(name)` | `window.py:462` | teamocil `--here` flag | | `Pane.resize(height, width)` | `pane.py:217` | teamocil v0.x pane `width` | | `Pane.send_keys(cmd, enter)` | `pane.py:423` | All command sending | -| `Pane.select()` | `pane.py:581` | Pane focus | +| `Pane.select()` | `pane.py:577` | Pane focus | | `Window.set_option(key, val)` | `options.py:578` (OptionsMixin) | `synchronize-panes`, window options | | `Session.set_hook(hook, cmd)` | `hooks.py:111` (HooksMixin) | Lifecycle hooks (`client-detached`, etc.) | | `Session.set_option(key, val)` | `options.py:578` (OptionsMixin) | `pane-border-status`, `pane-border-format` | @@ -73,16 +73,17 @@ These libtmux APIs already exist and do NOT need changes: - **Required**: Add handling in `iter_create_panes()` after the `shell_command` loop (around line 534). Read `pane_config.get("shell_command_after", [])` and send each command via `pane.send_keys()`. - **Non-breaking**: New optional config key. -### T4. No `--here` CLI Flag +### T4. No Session Rename Mode / `--here` CLI Flag -- **Blocker**: `tmuxp load` (`cli/load.py`) has no `--here` flag. `WorkspaceBuilder.iter_create_windows()` always creates new windows via `session.new_window()` (line 406). -- **Blocks**: teamocil `--here` — reuse current window for first window. +- **Blocker**: `tmuxp load` (`cli/load.py`) has no `--here` flag. `WorkspaceBuilder.iter_create_windows()` always creates new windows via `session.new_window()` (line 406). Additionally, teamocil's session rename mode (rename current session instead of creating new) is partially covered by tmuxp's `--append` flag, but `--append` does not rename the session. +- **Blocks**: teamocil `--here` (reuse current window for first window) and teamocil session rename mode. - **Required**: 1. Add `--here` flag to `cli/load.py` (around line 516, near `--append`). 2. Pass `here=True` through to `WorkspaceBuilder.build()`. 3. In `iter_create_windows()`, when `here=True` and first window: use `window.rename_window(name)` instead of `session.new_window()`, and send `cd <root>` via `pane.send_keys()` for directory change. 4. Adjust `first_window_pass()` logic (line 584). -- **Depends on**: libtmux `Window.rename_window()` (already exists, L4). + 5. For session rename: when `--here` is used, also call `session.rename_session(name)` (line 262 area in `build()`). +- **Depends on**: libtmux `Window.rename_window()` and `Session.rename_session()` (both already exist, L4). - **Non-breaking**: New optional CLI flag. ### T5. No `stop` / `kill` CLI Command @@ -114,7 +115,7 @@ These libtmux APIs already exist and do NOT need changes: ### T8. No Config Templating -- **Blocker**: tmuxp has no variable interpolation in config values. Environment variable expansion (`$VAR`) works in `start_directory` paths via `os.path.expandvars()` in `loader.py`, but not in arbitrary config values. +- **Blocker**: tmuxp has no user-defined variable interpolation. Environment variable expansion (`$VAR` via `os.path.expandvars()`) already works in most config values — `session_name`, `window_name`, `start_directory`, `before_script`, `environment`, `options`, `global_options` (see `loader.py:108-160`). But there is no way to pass custom `key=value` variables at load time. - **Blocks**: tmuxinator ERB templating (`<%= @settings["key"] %>`). - **Required**: Add a Jinja2 or Python `string.Template` pass before YAML parsing. Allow `key=value` CLI args to set template variables. This is a significant architectural addition. - **Non-breaking**: Opt-in feature, existing configs are unaffected. @@ -149,9 +150,11 @@ Keys produced by importers but silently ignored by the builder: ### I1. tmuxinator `pre` + `pre_window` Mapping Bug -- **Bug**: When both `pre` and `pre_window` exist (`importers.py:59-65`), `pre` maps to `shell_command` (invalid session-level key) and `pre_window` maps to `shell_command_before`. The `pre` commands are silently lost. +- **Bug**: When both `pre` and `pre_window` exist (`importers.py:59-65`): + 1. `pre` maps to `shell_command` (line 60) — invalid session-level key, silently ignored by builder. The `pre` commands are lost. + 2. The `isinstance` check on line 62 tests `workspace_dict["pre"]` type to decide how to wrap `workspace_dict["pre_window"]` — it should check `pre_window`'s type, not `pre`'s. If `pre` is a list but `pre_window` is a string, `pre_window` won't be wrapped in a list. - **Correct mapping**: `pre` → `before_script` (session-level, runs once before windows). `pre_window` → `shell_command_before` (per-pane). -- **Note**: `before_script` expects a file path, not inline commands. This may need a different approach — either write a temp script, or add an `on_project_start` config key (T6). +- **Note**: `before_script` expects a file path or command (executed via `subprocess.Popen` after `shlex.split()` in `util.py:27-32`), not inline shell commands. For inline commands, either write a temp script, or add an `on_project_start` config key (T6). ### I2. tmuxinator `cli_args` / `tmux_options` Fragile Parsing From e0864e4bbb0d342da5d6b60a46ae0121a8173f05 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:28:19 -0600 Subject: [PATCH 22/53] fix(plan): Correct L1/T1/T3 details from tmux source verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L1: pane_title is excluded from libtmux's bulk format queries, not removed from tmux itself — tmux still supports #{pane_title} (format.c:205) and select-pane -T (added in tmux 2.6). T1: synchronize before/true insertion point is build() line 320 (after on_window_create hook, before iter_create_panes loop), not iter_create_windows() line 424 which is inside the generator. T3: shell_command_after is a window-level key (set by teamocil importer on window_dict), not per-pane. Correct insertion point is config_after_window() or after the pane loop in build(). --- notes/plan.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/notes/plan.md b/notes/plan.md index ea3194eced..00488cfec2 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -7,7 +7,7 @@ ### L1. No `Pane.set_title()` Method -- **Blocker**: libtmux has no method wrapping `select-pane -T <title>`. The `pane_title` format variable was removed from the format query list (`formats.py:70`) in tmux 3.1+, but `select-pane -T` still works in tmux 3.2+. libtmux already knows about the display options (`pane_border_status`, `pane_border_format` in `constants.py:163-173`) but has no setter for the title itself. +- **Blocker**: libtmux has no method wrapping `select-pane -T <title>`. The `pane_title` format variable is excluded from libtmux's bulk format queries (`formats.py:70`, commented out with note "removed in 3.1+"), but this is a libtmux-side exclusion — tmux itself still supports both `#{pane_title}` (in `format.c:205`) and `select-pane -T` (added in tmux 2.6). libtmux already knows about the display options (`pane_border_status`, `pane_border_format` in `constants.py:163-173`) but has no setter for the title itself. - **Blocks**: Pane titles (tmuxinator feature: named pane syntax `pane_name: command` → `select-pane -T`). Also blocks `enable_pane_titles`, `pane_title_position`, `pane_title_format` session-level config. - **Required**: Add `Pane.set_title(title: str)` method that calls `self.cmd("select-pane", "-T", title)`. This is a simple wrapper — `Pane.cmd()` already exists (`pane.py:177`) and `select-pane` is already used for `Pane.select()` (`pane.py:601`). - **Non-breaking**: Pure addition, no existing API changes. @@ -52,8 +52,8 @@ These libtmux APIs already exist and do NOT need changes: - **Blocker**: `WorkspaceBuilder` (`builder.py`) does not check for a `synchronize` key on window configs. The key is silently ignored if present. - **Blocks**: Pane synchronization (tmuxinator `synchronize: true/before/after`). -- **Required**: Add `synchronize` handling in `builder.py`. For `before`/`true`: call `window.set_option("synchronize-panes", "on")` before pane commands in `iter_create_panes()`. For `after`: call it in `config_after_window()`. For `false`/omitted: no action. -- **Insertion point**: `iter_create_windows()` around line 424 (after window options are set) for `before`/`true`. `config_after_window()` around line 560 for `after`. +- **Required**: Add `synchronize` handling in `builder.py`. For `before`/`true`: call `window.set_option("synchronize-panes", "on")` before pane commands are sent. For `after`: call it in `config_after_window()`. For `false`/omitted: no action. +- **Insertion point**: In `build()` around line 320 (after `on_window_create` plugin hook, before `iter_create_panes()` loop) for `before`/`true`. In `config_after_window()` around line 565 for `after`. Note: setting sync before pane creation works because `synchronize-panes` applies to all panes in the window, including those created later by split. - **Non-breaking**: New optional config key. Existing configs are unaffected. ### T2. No Pane Title Config Key @@ -68,9 +68,9 @@ These libtmux APIs already exist and do NOT need changes: ### T3. No `shell_command_after` Config Key -- **Blocker**: The teamocil importer produces `shell_command_after` (from `filters.after`, `importers.py:149`), but `WorkspaceBuilder` never reads it. The `trickle()` function in `loader.py` has no logic for it either. -- **Blocks**: teamocil v0.x `filters.after` — commands run after pane commands. -- **Required**: Add handling in `iter_create_panes()` after the `shell_command` loop (around line 534). Read `pane_config.get("shell_command_after", [])` and send each command via `pane.send_keys()`. +- **Blocker**: The teamocil importer produces `shell_command_after` on the **window** dict (from `filters.after`, `importers.py:149`), but `WorkspaceBuilder` never reads it. The `trickle()` function in `loader.py` has no logic for it either. +- **Blocks**: teamocil v0.x `filters.after` — commands run after all pane commands in a window. +- **Required**: Add handling in `config_after_window()` (around line 565) or in `build()` after the `iter_create_panes()` loop (around line 331). Read `window_config.get("shell_command_after", [])` and send each command to every pane via `pane.send_keys()`. Note: this is a **window-level** key set by the teamocil importer, not per-pane. - **Non-breaking**: New optional config key. ### T4. No Session Rename Mode / `--here` CLI Flag From 889dd211e1592c13d720d0403fdbe7e62cf5651d Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:34:50 -0600 Subject: [PATCH 23/53] fix(plan): Correct T2 insertion points and I7 line references T2: Session-level pane title options insertion is alongside other session options at lines 303-309, not "around line 311". Pane-level title should be set after commands are sent (around line 535), before focus handling at line 536. I7: Stale TODOs are at lines 121 and 123 (not 121-123), since line 122 is `clear` which is a real teamocil feature. --- notes/plan.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notes/plan.md b/notes/plan.md index 00488cfec2..3c417cded5 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -61,8 +61,8 @@ These libtmux APIs already exist and do NOT need changes: - **Blocker**: `WorkspaceBuilder` has no handling for pane `title` key or session-level `enable_pane_titles` / `pane_title_position` / `pane_title_format`. - **Blocks**: Pane titles (tmuxinator named pane syntax). - **Required**: - 1. Session-level: set `pane-border-status` and `pane-border-format` options via `session.set_option()` in `build()` (around line 311). - 2. Pane-level: call `pane.cmd("select-pane", "-T", title)` after pane creation in `iter_create_panes()` (around line 538). Requires L1 (libtmux `set_title()`), or can use `pane.cmd()` directly. + 1. Session-level: set `pane-border-status` and `pane-border-format` options via `session.set_option()` in `build()` alongside other session options (lines 303-309). + 2. Pane-level: call `pane.cmd("select-pane", "-T", title)` after commands are sent in `iter_create_panes()`, before focus handling (around line 535). Requires L1 (libtmux `set_title()`), or can use `pane.cmd()` directly. - **Config keys**: `enable_pane_titles: true`, `pane_title_position: top`, `pane_title_format: "..."` (session-level). `title: "my-title"` (pane-level). - **Non-breaking**: New optional config keys. @@ -193,7 +193,7 @@ Not imported but translatable (same key names in tmuxp): ### I7. Stale Importer TODOs -`importers.py:121-123` lists `with_env_var` and `cmd_separator` as TODOs, but neither exists in teamocil v1.4.2 source. These are stale references from ~2013 and should be removed. +`importers.py:121,123` lists `with_env_var` and `cmd_separator` as TODOs (with `clear` at line 122 in between), but neither `with_env_var` nor `cmd_separator` exists in teamocil v1.4.2 source. These are stale references from ~2013 and should be removed. ## Implementation Priority From 02c7c0a91adf97b1cf54edf627daf55ba3fa8ae0 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:41:33 -0600 Subject: [PATCH 24/53] fix(plan): Add tmux 3.2 sync scope detail and before_script limitation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T1: synchronize-panes became dual-scope (window|pane) in tmux 3.2 (options-table.c:1423). Since tmuxp requires 3.2+, window.set_option() works through option inheritance — panes inherit window-level setting. I5: on_project_first_start → before_script mapping only works for single commands or script paths. Multi-command strings with semicolons fail because Popen runs without shell=True. --- notes/plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notes/plan.md b/notes/plan.md index 3c417cded5..070ca03e70 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -53,7 +53,7 @@ These libtmux APIs already exist and do NOT need changes: - **Blocker**: `WorkspaceBuilder` (`builder.py`) does not check for a `synchronize` key on window configs. The key is silently ignored if present. - **Blocks**: Pane synchronization (tmuxinator `synchronize: true/before/after`). - **Required**: Add `synchronize` handling in `builder.py`. For `before`/`true`: call `window.set_option("synchronize-panes", "on")` before pane commands are sent. For `after`: call it in `config_after_window()`. For `false`/omitted: no action. -- **Insertion point**: In `build()` around line 320 (after `on_window_create` plugin hook, before `iter_create_panes()` loop) for `before`/`true`. In `config_after_window()` around line 565 for `after`. Note: setting sync before pane creation works because `synchronize-panes` applies to all panes in the window, including those created later by split. +- **Insertion point**: In `build()` around line 320 (after `on_window_create` plugin hook, before `iter_create_panes()` loop) for `before`/`true`. In `config_after_window()` around line 565 for `after`. Note: in tmux 3.2+ (tmuxp's minimum), `synchronize-panes` is a dual-scope option (window|pane, `options-table.c:1423`). Setting it at window level via `window.set_option()` makes all panes inherit it, including those created later by split. - **Non-breaking**: New optional config key. Existing configs are unaffected. ### T2. No Pane Title Config Key @@ -178,7 +178,7 @@ Not imported but translatable: - `pre_tab` → `shell_command_before` (deprecated predecessor to `pre_window`) - `startup_window` → find matching window, set `focus: true` - `startup_pane` → find matching pane, set `focus: true` -- `on_project_first_start` → `before_script` +- `on_project_first_start` → `before_script` (only if value is a single command or script path; multi-command strings joined by `;` won't work since `before_script` uses `Popen` without `shell=True`) - `socket_path` → warn user to use CLI `-S` flag - `attach: false` → warn user to use CLI `-d` flag From 05f5ad56610e981cc10c05c18e331905f4e930a3 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:48:34 -0600 Subject: [PATCH 25/53] fix(plan): Add missing width drop note and importer update dependencies I4: Note that v0.x pane width is silently dropped (importers.py:161-163) with no user warning. Pane.resize() exists in libtmux (L4) so this could be preserved. Phase 2/3: Note that builder additions (T1 synchronize, T2 pane titles) require corresponding tmuxinator importer updates to actually import those keys from tmuxinator configs. --- notes/plan.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/notes/plan.md b/notes/plan.md index 070ca03e70..8ead10f734 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -170,6 +170,7 @@ Keys produced by importers but silently ignored by the builder: - **Bug**: Importer assumes v0.x format. String panes cause incorrect behavior (`"cmd" in "git status"` checks substring, not dict key). `commands` key (v1.x) not mapped. - **Fix**: Add format detection. Handle string panes, `commands` key, `focus`, and `options`. +- **Also**: v0.x pane `width` is silently dropped (`importers.py:161-163`) with a TODO but no user warning. Since libtmux's `Pane.resize()` exists (L4), the importer could preserve `width` and the builder could call `pane.resize(width=value)` after split. Alternatively, warn the user that width is not supported. ### I5. tmuxinator Missing Keys @@ -211,10 +212,12 @@ These fix existing bugs and add missing translations without touching the builde ### Phase 2: Builder Additions (tmuxp Only) -These add new config key handling to the builder: +These add new config key handling to the builder. Each also needs a corresponding importer update: 1. **T1**: `synchronize` config key — straightforward `set_option()` call + - Then update tmuxinator importer to import `synchronize` key (pass-through, same name) 2. **T3**: `shell_command_after` config key — straightforward `send_keys()` loop + - teamocil importer already produces this key (I3 fixes the loop); builder just needs to read it 3. **T4**: `--here` CLI flag — moderate complexity, uses existing libtmux APIs ### Phase 3: libtmux Additions @@ -223,6 +226,7 @@ These require changes to the libtmux package: 1. **L1**: `Pane.set_title()` — simple wrapper, needed for T2 2. **T2**: Pane title config keys — depends on L1 + - Then update tmuxinator importer to import `enable_pane_titles`, `pane_title_position`, `pane_title_format`, and named pane syntax (`pane_name: command` → `title` + `shell_command`) ### Phase 4: New CLI Commands From ff128d39d1eeca8163a6bea1586f2fa1cc4def73 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 07:50:27 -0600 Subject: [PATCH 26/53] fix(plan): Fix phase numbering and add missing L3 to phase listing Phase 4/5 item numbers now restart per-phase (consistent with Phases 1-3). Previously continued numbering from prior phases. L3 (pre-execution command logging) was a dependency of T9 but was not listed in any phase. Now explicitly listed in Phase 5 before T9. --- notes/plan.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/notes/plan.md b/notes/plan.md index 8ead10f734..a5a940df26 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -230,13 +230,14 @@ These require changes to the libtmux package: ### Phase 4: New CLI Commands -3. **T5**: `tmuxp stop` command -4. **T10**: `tmuxp new`, `tmuxp copy`, `tmuxp delete` commands +1. **T5**: `tmuxp stop` command +2. **T10**: `tmuxp new`, `tmuxp copy`, `tmuxp delete` commands ### Phase 5: Larger Features (Nice-to-Have) -5. **T6**: Lifecycle hook config keys — complex, needs design -6. **T7**: `--no-shell-command-before` flag — simple -7. **T8**: Config templating — significant architectural addition -8. **T9**: `--debug` / dry-run mode — depends on L3 -9. **L2**: Custom tmux binary — requires libtmux changes +1. **T6**: Lifecycle hook config keys — complex, needs design +2. **T7**: `--no-shell-command-before` flag — simple +3. **T8**: Config templating — significant architectural addition +4. **L3**: Pre-execution command logging in libtmux — prerequisite for T9 +5. **T9**: `--debug` / dry-run mode — depends on L3 +6. **L2**: Custom tmux binary — requires libtmux changes From c174febdbe8e56c1d173808ef7cba7622e870e8f Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sun, 8 Feb 2026 08:25:23 -0600 Subject: [PATCH 27/53] fix(plan): Correct L3 logging description and I2 bug example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L3: common.py:291 debug log includes both cmd and stdout (not "only stdout"), but after execution — the blocker is lack of pre-execution logging. I2: Replace misleading /opt/foobar example with accurate demonstration of -L flag leaking into config value. --- notes/plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notes/plan.md b/notes/plan.md index a5a940df26..258e61d294 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -24,7 +24,7 @@ ### L3. No Dry-Run / Command Preview Mode -- **Blocker**: `tmux_cmd` (`common.py:252-296`) always executes commands. Debug logging exists (`logger.debug` at line 291) but only logs stdout after execution, not the command being sent. There is no facility to collect commands without executing them. +- **Blocker**: `tmux_cmd` (`common.py:252-296`) always executes commands. Debug logging exists (`logger.debug` at line 291) but logs the command and its stdout *after* execution, not before. There is no pre-execution logging or facility to collect commands without executing them. - **Blocks**: `--debug` / dry-run mode (both tmuxinator and teamocil have this). tmuxinator generates a bash script that can be previewed; teamocil's `--debug` outputs the tmux command list. - **Required**: Either (a) add a `dry_run` flag to `tmux_cmd` that collects commands instead of executing, or (b) add pre-execution logging at DEBUG level that logs the full command before `subprocess.run()`. Option (b) is simpler and doesn't change behavior. - **Non-breaking**: Logging change only. tmuxp would implement the user-facing `--debug` flag by capturing log output. @@ -158,7 +158,7 @@ Keys produced by importers but silently ignored by the builder: ### I2. tmuxinator `cli_args` / `tmux_options` Fragile Parsing -- **Bug**: `str.replace("-f", "").strip()` (`importers.py:41,48`) matches `-f` as a substring anywhere in the string. A path like `/opt/foobar` would be corrupted. Also ignores `-L` (socket name) and `-S` (socket path) flags. +- **Bug**: `str.replace("-f", "").strip()` (`importers.py:41,48`) does a global string replacement, not flag-aware parsing. A value like `"-f ~/.tmux.conf -L mysocket"` would produce `"~/.tmux.conf -L mysocket"` as the `config` value (including the `-L` flag in a file path). Also ignores `-L` (socket name) and `-S` (socket path) flags entirely. - **Fix**: Use proper argument parsing (e.g., `shlex.split()` + iterate to find `-f` flag and its value). ### I3. teamocil Redundant Filter Loops From 3e4ae0ec86c128dd3a149b46d349312c3445cc59 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:08:35 -0600 Subject: [PATCH 28/53] docs(comparison): Update version, fix hook descriptions, add auto-detection heuristics why: Keep parity analysis current with tmuxp 1.64.0 and add config format detection algorithm for transparent import support. what: - Update tmuxp version from 1.47.0+ to 1.64.0 - Fix deprecated pre/post hook descriptions to match template.erb behavior - Add config format auto-detection heuristics table and algorithm - Update timestamp to 2026-03-06 --- docs/comparison.md | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/comparison.md b/docs/comparison.md index b9f53f8471..7e3d06bde6 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -1,12 +1,12 @@ # Feature Comparison: tmuxp vs tmuxinator vs teamocil -*Last updated: 2026-02-08* +*Last updated: 2026-03-06* ## Overview | | tmuxp | tmuxinator | teamocil | |---|---|---|---| -| **Version** | 1.47.0+ | 3.3.7 | 1.4.2 | +| **Version** | 1.64.0 | 3.3.7 | 1.4.2 | | **Language** | Python | Ruby | Ruby | | **Min tmux** | 3.2 | 1.5+ (1.5–3.6a tested) | (not specified) | | **Config formats** | YAML, JSON | YAML (with ERB) | YAML | @@ -82,8 +82,8 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Before workspace build | Plugin: `before_workspace_builder()` | (none) | (none) | | On window create | Plugin: `on_window_create()` | (none) | (none) | | After window done | Plugin: `after_window_finished()` | (none) | (none) | -| Deprecated pre | (none) | `pre` (deprecated → `on_project_start`/`on_project_restart`) | (none) | -| Deprecated post | (none) | `post` (deprecated → `on_project_stop`/`on_project_exit`) | (none) | +| Deprecated pre | (none) | `pre` (deprecated; runs once before windows if session is new) | (none) | +| Deprecated post | (none) | `post` (deprecated; runs after attach/detach on every invocation) | (none) | ### Window-Level @@ -172,3 +172,33 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Extension search | `.yaml`, `.yml`, `.json` | `.yml`, `.yaml` | `.yml` | | Recursive search | No | Yes (`Dir.glob("**/*.{yml,yaml}")`) | No | | Upward traversal | Yes (cwd → `~`) | No | No | + +## Config Format Auto-Detection Heuristics + +If tmuxp were to auto-detect and transparently load tmuxinator/teamocil configs, these heuristics would distinguish the formats: + +| Indicator | tmuxp | tmuxinator | teamocil v0.x | teamocil v1.x | +|---|---|---|---|---| +| `session_name` key | Yes | No | No | No | +| `name` or `project_name` key | No | Yes | Yes (inside `session:`) | Yes | +| `session:` wrapper | No | No | Yes | No | +| `root` / `project_root` key | No | Yes | Yes | No | +| `start_directory` key | Yes | No | No | No | +| `windows` contains hash-key syntax | No | Yes (`- editor: ...`) | No | No | +| `windows` contains `window_name` key | Yes | No | No | No | +| `windows` contains `name` key | No | No | Yes | Yes | +| `splits` key in windows | No | No | Yes | No | +| `panes` with `cmd` key | No | No | Yes | No | +| `panes` with `commands` key | No | No | No | Yes | +| `panes` with `shell_command` key | Yes | No | No | No | +| `tabs` key | No | Yes (deprecated) | No | No | + +**Reliable detection algorithm:** + +1. If `session_name` exists or any window has `window_name` → **tmuxp** format +2. If `session:` wrapper exists → **teamocil v0.x** format +3. If `project_name`, `project_root`, or `tabs` exists → **tmuxinator** format +4. If windows use hash-key syntax (`- editor: {panes: ...}`) → **tmuxinator** format +5. If windows have `name` key and panes use `commands` → **teamocil v1.x** format +6. If `root` exists at top level and windows use `name` key → **tmuxinator** format (also has `root`) +7. Ambiguous → ask user or try tmuxp first From 489e922204558d4df7a40a06a84c3af27fd73846 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:08:38 -0600 Subject: [PATCH 29/53] docs(parity-tmuxinator): Update version, add template execution order why: Document exact execution order from template.erb to clarify hook timing. what: - Update tmuxp version to 1.64.0, timestamp to 2026-03-06 - Add execution order detail from template.erb analysis --- notes/parity-tmuxinator.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/notes/parity-tmuxinator.md b/notes/parity-tmuxinator.md index 10bf491698..cbdb27397c 100644 --- a/notes/parity-tmuxinator.md +++ b/notes/parity-tmuxinator.md @@ -1,8 +1,8 @@ # Tmuxinator Parity Analysis -*Last updated: 2026-02-08* +*Last updated: 2026-03-06* *Tmuxinator version analyzed: 3.3.7 (supports tmux 1.5–3.6a)* -*tmuxp version: 1.47.0+* +*tmuxp version: 1.64.0* ## Features tmuxinator has that tmuxp lacks @@ -22,6 +22,8 @@ tmuxinator has 5 lifecycle hooks: **Gap**: tmuxp's `before_script` is a partial equivalent of `on_project_first_start` — it runs before windows are created and kills the session on failure. tmuxp has no equivalent for `on_project_start` (runs every time, including reattach), no hooks for detach/exit/stop events, and no distinction between first start vs. restart. +**Execution order from `template.erb`**: `cd root` → `on_project_start` → (if new session: `pre` → `on_project_first_start` → `new-session` → create windows → build panes → select startup → attach) OR (if existing: `on_project_restart`) → `post` → `on_project_exit`. Note that `post` and `on_project_exit` run on every invocation (outside the new/existing conditional). + **WorkspaceBuilder requirement**: Add config keys for `on_project_start`, `on_project_first_start`, `on_project_restart`, `on_project_exit`, `on_project_stop`. The exit/stop hooks require shell integration (trap signals, set-hook in tmux). ### 2. Stop/Kill Session Command From 3d5440b801464e08122b5ed33927298e21899791 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:08:42 -0600 Subject: [PATCH 30/53] docs(parity-teamocil): Update version and timestamp why: Keep parity analysis current with tmuxp 1.64.0. what: - Update tmuxp version to 1.64.0, timestamp to 2026-03-06 --- notes/parity-teamocil.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notes/parity-teamocil.md b/notes/parity-teamocil.md index 4d17f1eb1a..a81acfb1bf 100644 --- a/notes/parity-teamocil.md +++ b/notes/parity-teamocil.md @@ -1,8 +1,8 @@ # Teamocil Parity Analysis -*Last updated: 2026-02-08* +*Last updated: 2026-03-06* *Teamocil version analyzed: 1.4.2* -*tmuxp version: 1.47.0+* +*tmuxp version: 1.64.0* ## Version History Context From 8f6fe9099a129764a8ee1656e7a271b4e6ada481 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:08:45 -0600 Subject: [PATCH 31/53] docs(import): Update timestamps for import behavior notes why: Refresh timestamps after source code re-verification. what: - Update timestamps to 2026-03-06 in import-tmuxinator.md and import-teamocil.md --- notes/import-teamocil.md | 2 +- notes/import-tmuxinator.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/notes/import-teamocil.md b/notes/import-teamocil.md index 0a5b99c8dc..a1bce59f8a 100644 --- a/notes/import-teamocil.md +++ b/notes/import-teamocil.md @@ -1,6 +1,6 @@ # Teamocil Import Behavior -*Last updated: 2026-02-08* +*Last updated: 2026-03-06* *Importer: `src/tmuxp/workspace/importers.py:import_teamocil`* ## Format Detection Problem diff --git a/notes/import-tmuxinator.md b/notes/import-tmuxinator.md index eca2e54dcd..b203c12757 100644 --- a/notes/import-tmuxinator.md +++ b/notes/import-tmuxinator.md @@ -1,6 +1,6 @@ # Tmuxinator Import Behavior -*Last updated: 2026-02-08* +*Last updated: 2026-03-06* *Importer: `src/tmuxp/workspace/importers.py:import_tmuxinator`* ## Syntax Differences (Translatable) From f8ea752b7d29613bea55736b25fd27cc89bba87d Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:10:20 -0600 Subject: [PATCH 32/53] docs(teamocil): Verify v0.x features from 0.4-stable branch why: Agent analysis of teamocil's 0.4-stable branch confirmed that with_env_var and cmd_separator were real v0.x features, not stale TODOs. what: - Verify with_env_var (exports TEAMOCIL=1) and cmd_separator as v0.x features - Add missing v0.x pane keys: height and target - Add v0.x ERB templating to format differences table - Update summary table classifications from "Unverified" to verified --- notes/import-teamocil.md | 30 ++++++++++++++++++++---------- notes/parity-teamocil.md | 7 ++++++- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/notes/import-teamocil.md b/notes/import-teamocil.md index a1bce59f8a..cfbd932bcd 100644 --- a/notes/import-teamocil.md +++ b/notes/import-teamocil.md @@ -165,17 +165,19 @@ Since teamocil 1.4.2 uses the v1.x format, the importer is outdated for current ## Import-Only Fixes (No Builder Changes) -### 5. `with_env_var` (listed in importer TODO) +### 5. `with_env_var` (v0.x only) -**Note**: `with_env_var` is listed in the importer's docstring TODOs (`importers.py:121`) but does **not exist** in teamocil's current source (v1.4.2) or in any teamocil file. This may have been a feature from a very old version that was removed, or it may never have existed. The TODO should be removed or verified against historical teamocil releases. +**Verified**: `with_env_var` exists in teamocil's v0.x (`0.4-stable` branch) at `lib/teamocil/layout/window.rb`. When `true` (the default), it exports `TEAMOCIL=1` environment variable in each pane's command chain. Removed in v1.x rewrite. -If it did exist, tmuxp's `environment` key would be the natural mapping. +tmuxp's `environment` key would be the natural mapping: `environment: { TEAMOCIL: "1" }`. However, since this was a default behavior in v0.x (auto-exported unless disabled), the importer should either: +- Always add `environment: { TEAMOCIL: "1" }` unless `with_env_var: false` +- Or simply drop it, since it's an implementation detail of teamocil -### 6. `cmd_separator` (listed in importer TODO) +### 6. `cmd_separator` (v0.x only) -**Note**: Like `with_env_var`, `cmd_separator` is listed in the importer's docstring TODOs but does **not exist** in teamocil's current source (v1.4.2). Teamocil v1.x hardcodes `commands.join('; ')` in `pane.rb:7`. There is no configurable separator. +**Verified**: `cmd_separator` exists in teamocil's v0.x at `lib/teamocil/layout/window.rb`. It's a per-window string (default `"; "`) used to join multiple pane commands before sending via `send-keys`. Removed in v1.x (hardcoded to `"; "`). -tmuxp sends commands individually (one `send_keys` per command), so even if this existed, it would be irrelevant. +tmuxp sends commands individually (one `send_keys` per command), so this is irrelevant — the importer can safely ignore it. ## Code Issues in Current Importer @@ -214,9 +216,15 @@ if "panes" in w: If `p` is a string (v1.x shorthand), `"cmd" in p` will check for substring match in the string, not a dict key. This will either silently pass (if the command doesn't contain "cmd") or incorrectly match. -### Stale TODOs: `with_env_var` and `cmd_separator` +### Verified TODOs: `with_env_var` and `cmd_separator` -Listed in the importer's docstring TODOs (`importers.py:121-123`) but neither exists in teamocil's current source (v1.4.2). These TODOs may reference features from a very old teamocil version or may be incorrect. They should be removed or verified against historical releases. +Listed in the importer's docstring TODOs (`importers.py:121-123`). Both verified as v0.x features (present in `0.4-stable` branch, removed in v1.x rewrite). `with_env_var` auto-exports `TEAMOCIL=1`; `cmd_separator` controls command joining. Since the importer targets v0.x, these are valid TODOs — but `cmd_separator` is irrelevant since tmuxp sends commands individually. + +### Missing v0.x Features: `height` and `target` + +Not mentioned in the importer TODOs but present in v0.x: +- `height` (pane): Percentage for vertical split (`split-window -p <height>`). Like `width`, silently dropped. +- `target` (pane): Target pane for split operation (`split-window -t <target>`). Not imported. ### Silent Drops @@ -242,9 +250,11 @@ Listed in the importer's docstring TODOs (`importers.py:121-123`) but neither ex | Window `focus` (v1.x) | ✗ Missing | Difference (needs add) | | Pane `focus` (v1.x) | ✗ Missing | Difference (needs add) | | Window `options` (v1.x) | ✗ Missing | Difference (needs add) | -| `with_env_var` (in importer TODO) | ✗ Missing | Unverified (not in current teamocil source) | +| `with_env_var` (v0.x) | ✗ Missing | Difference (v0.x only, can map to `environment`) | | `filters.after` → `shell_command_after` | ⚠ Imported but unused | **Limitation** | | Pane `width` (v0.x) | ⚠ Dropped silently | **Limitation** | | Window `clear` (v0.x) | ⚠ Preserved but unused | **Limitation** | -| `cmd_separator` (in importer TODO) | ✗ Missing | Unverified (not in current teamocil source) | +| `cmd_separator` (v0.x) | ✗ Missing | Difference (v0.x only, irrelevant — tmuxp sends individually) | +| `height` (v0.x pane) | ✗ Missing | **Limitation** (like `width`, no per-pane sizing) | +| `target` (v0.x pane) | ✗ Missing | **Limitation** (no split targeting) | | `--here` flag | N/A (runtime flag) | **Limitation** | diff --git a/notes/parity-teamocil.md b/notes/parity-teamocil.md index a81acfb1bf..6f8e91007d 100644 --- a/notes/parity-teamocil.md +++ b/notes/parity-teamocil.md @@ -137,8 +137,13 @@ Teamocil joins multiple pane commands with `; ` and sends them as a single `send | Pane commands | `cmd` (string or list) | `commands` (list) | | Before commands | `filters.before` (list) | (none) | | After commands | `filters.after` (list) | (none) | -| Pane width | `width` (number) | (none) | +| Pane width | `width` (number, horizontal split %) | (none) | +| Pane height | `height` (number, vertical split %) | (none) | +| Pane target | `target` (pane to split from) | (none) | | Window clear | `clear` (boolean) | (none) | +| TEAMOCIL env var | `with_env_var` (default true, exports `TEAMOCIL=1`) | (none) | +| Command separator | `cmd_separator` (default `"; "`) | Hardcoded `"; "` | +| ERB templating | Yes (layouts processed as ERB) | No | | Pane focus | (none) | `focus` (boolean) | | Window focus | (none) | `focus` (boolean) | | Window options | (none) | `options` (hash) | From c6df7757b12d2b1141443cf452030e923b7a8c3b Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:10:32 -0600 Subject: [PATCH 33/53] docs(comparison): Add teamocil v0.x pane sizing keys why: Complete pane-level comparison with verified v0.x features. what: - Add width, height, and target pane-level keys from teamocil v0.x --- docs/comparison.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/comparison.md b/docs/comparison.md index 7e3d06bde6..de2b70195a 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -119,6 +119,9 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Suppress history | `suppress_history` | (none) | (none) | | Focus | `focus` | (none; use `startup_pane`) | `focus` | | Pane title | (none) | hash key (named pane → `select-pane -T`) | (none) | +| Width | (none) | (none) | `width` (v0.x, horizontal split %) | +| Height | (none) | (none) | `height` (v0.x, vertical split %) | +| Split target | (none) | (none) | `target` (v0.x) | ### Shorthand Syntax From 5112ddef381fef40760e28a05ae52dadfa1ca942 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:23:42 -0600 Subject: [PATCH 34/53] docs(comparison): Refine auto-detection algorithm with v0.x fallback why: Add v0.x detection heuristic and fix teamocil v1.x pane detection. what: - Add step 7: detect teamocil v0.x by `cmd`/`splits` keys even without `session:` wrapper - Clarify step 5: v1.x string shorthand panes also indicate teamocil - Fix step 6: tmuxinator uses hash-key syntax, not just `name` key --- docs/comparison.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/comparison.md b/docs/comparison.md index de2b70195a..7304d9f451 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -202,6 +202,7 @@ If tmuxp were to auto-detect and transparently load tmuxinator/teamocil configs, 2. If `session:` wrapper exists → **teamocil v0.x** format 3. If `project_name`, `project_root`, or `tabs` exists → **tmuxinator** format 4. If windows use hash-key syntax (`- editor: {panes: ...}`) → **tmuxinator** format -5. If windows have `name` key and panes use `commands` → **teamocil v1.x** format -6. If `root` exists at top level and windows use `name` key → **tmuxinator** format (also has `root`) -7. Ambiguous → ask user or try tmuxp first +5. If windows have `name` key and panes use `commands` or string shorthand → **teamocil v1.x** format +6. If `root` exists at top level and windows use hash-key syntax → **tmuxinator** format +7. If windows have `name` key and panes use `cmd` or `splits` → **teamocil v0.x** format (even without `session:` wrapper) +8. Ambiguous → ask user or try tmuxp first From 8dc1cd9d438171d4204e91360cb9ee1dbd4de6c9 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:23:48 -0600 Subject: [PATCH 35/53] docs(import-teamocil): Document accidental focus/target passthrough why: Test fixtures reveal that pane focus and target survive the v0.x importer through in-place dict mutation, which was undocumented. what: - Document pane focus accidentally preserved (and actually works in builder) - Document pane target accidentally preserved (but builder ignores it) - Update summary table with corrected import statuses - Separate height (truly missing) from target/focus (passthrough) --- notes/import-teamocil.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/notes/import-teamocil.md b/notes/import-teamocil.md index cfbd932bcd..33894ec0fd 100644 --- a/notes/import-teamocil.md +++ b/notes/import-teamocil.md @@ -113,13 +113,13 @@ Since teamocil 1.4.2 uses the v1.x format, the importer is outdated for current **Importer status**: ✗ Not handled. The key is not imported. -### 13. Pane Focus (v1.x) +### 13. Pane Focus (v0.x and v1.x) -| teamocil v1.x | tmuxp | +| teamocil | tmuxp | |---|---| | `focus: true` (on pane) | `focus: true` | -**Importer status**: ✗ Not handled. The key is not imported. +**Importer status**: ✓ Accidentally handled in v0.x. The importer modifies pane dicts in-place (only renaming `cmd` → `shell_command` and dropping `width`), so `focus` survives as an unhandled key that passes through. Test fixture `test3.py` and `layouts.py` confirm this. For v1.x format, pane `focus` would also survive if the pane is a dict (but not if it's a string shorthand). ### 14. Window Options (v1.x) @@ -220,11 +220,16 @@ If `p` is a string (v1.x shorthand), `"cmd" in p` will check for substring match Listed in the importer's docstring TODOs (`importers.py:121-123`). Both verified as v0.x features (present in `0.4-stable` branch, removed in v1.x rewrite). `with_env_var` auto-exports `TEAMOCIL=1`; `cmd_separator` controls command joining. Since the importer targets v0.x, these are valid TODOs — but `cmd_separator` is irrelevant since tmuxp sends commands individually. -### Missing v0.x Features: `height` and `target` +### Missing v0.x Features: `height` Not mentioned in the importer TODOs but present in v0.x: - `height` (pane): Percentage for vertical split (`split-window -p <height>`). Like `width`, silently dropped. -- `target` (pane): Target pane for split operation (`split-window -t <target>`). Not imported. + +### Accidentally Preserved: `target` and `focus` + +Through in-place dict mutation, these v0.x pane keys survive the import without explicit handling: +- `target` (pane): Preserved in output (see `layouts.py:106-108`), but tmuxp's WorkspaceBuilder ignores it. +- `focus` (pane): Preserved in output (see `layouts.py:109`, `test3.py:39`), and tmuxp's WorkspaceBuilder **does** use `focus` — so pane focus actually works correctly through the v0.x importer by accident. ### Silent Drops @@ -248,7 +253,7 @@ Not mentioned in the importer TODOs but present in v0.x: | Pane `commands` → `shell_command` (v1.x) | ✗ Missing | Difference (needs add) | | String pane shorthand (v1.x) | ✗ Missing (causes error) | Difference (needs add) | | Window `focus` (v1.x) | ✗ Missing | Difference (needs add) | -| Pane `focus` (v1.x) | ✗ Missing | Difference (needs add) | +| Pane `focus` (v0.x/v1.x) | ✓ Accidentally preserved (v0.x dict passthrough) | Difference (explicit handling needed for v1.x) | | Window `options` (v1.x) | ✗ Missing | Difference (needs add) | | `with_env_var` (v0.x) | ✗ Missing | Difference (v0.x only, can map to `environment`) | | `filters.after` → `shell_command_after` | ⚠ Imported but unused | **Limitation** | @@ -256,5 +261,5 @@ Not mentioned in the importer TODOs but present in v0.x: | Window `clear` (v0.x) | ⚠ Preserved but unused | **Limitation** | | `cmd_separator` (v0.x) | ✗ Missing | Difference (v0.x only, irrelevant — tmuxp sends individually) | | `height` (v0.x pane) | ✗ Missing | **Limitation** (like `width`, no per-pane sizing) | -| `target` (v0.x pane) | ✗ Missing | **Limitation** (no split targeting) | +| `target` (v0.x pane) | ✓ Accidentally preserved (but builder ignores it) | **Limitation** (no split targeting) | | `--here` flag | N/A (runtime flag) | **Limitation** | From 3fffafb979d29d2bcda90bbac5194fb2229d6855 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:23:53 -0600 Subject: [PATCH 36/53] docs(parity-teamocil): Add accidental v0.x focus/target passthrough why: Test fixtures confirm pane focus and target survive import via in-place dict mutation, correcting the "What it misses" table. what: - Add v0.x focus and target as accidentally preserved in import table - Update with_env_var and cmd_separator descriptions --- notes/parity-teamocil.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/notes/parity-teamocil.md b/notes/parity-teamocil.md index 6f8e91007d..5c597bf979 100644 --- a/notes/parity-teamocil.md +++ b/notes/parity-teamocil.md @@ -180,8 +180,10 @@ Teamocil joins multiple pane commands with `; ` and sends them as a single `send | v1.x `focus` (pane) | Not imported | | v1.x `options` (window) | Not imported | | Session-level `name` (without `session:` wrapper) | Handled (uses `.get("name")`) | -| `with_env_var` (importer TODO) | Not handled — does not exist in current teamocil source | -| `cmd_separator` (importer TODO) | Not handled — does not exist in current teamocil source | +| v0.x `focus` (pane) | ✓ Accidentally preserved (in-place dict mutation keeps unhandled keys) | +| v0.x `target` (pane) | ✓ Accidentally preserved (same reason) | +| `with_env_var` (v0.x) | Not handled — silently dropped by importer | +| `cmd_separator` (v0.x) | Not handled — silently dropped by importer | ### Code Quality Issues in Importer From 0f1bcbbe57ecc64f10120cc64f8ed0661e789db2 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:35:34 -0600 Subject: [PATCH 37/53] docs(comparison): Fix pre-build script and deprecated hook mappings why: The comparison table incorrectly showed tmuxinator had no pre-build script equivalent, and deprecated hook descriptions lacked successor info. what: - Map tmuxinator on_project_first_start/pre to pre-build script row - Add deprecation successor hooks to pre and post rows --- docs/comparison.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/comparison.md b/docs/comparison.md index 7304d9f451..f9a60cab11 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -59,7 +59,7 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Session options | `options` | (none) | (none) | | Global options | `global_options` | (none) | (none) | | Environment vars | `environment` | (none) | (none) | -| Pre-build script | `before_script` | (none) | (none) | +| Pre-build script | `before_script` | `on_project_first_start` / `pre` (deprecated; see Hooks) | (none) | | Shell cmd before (all panes) | `shell_command_before` | `pre_window` / `pre_tab` / `rbenv` / `rvm` (all deprecated) | (none) | | Startup window | (none; use `focus: true` on window) | `startup_window` (name or index) | (none; use `focus: true` on window) | | Startup pane | (none; use `focus: true` on pane) | `startup_pane` | (none; use `focus: true` on pane) | @@ -82,8 +82,8 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Before workspace build | Plugin: `before_workspace_builder()` | (none) | (none) | | On window create | Plugin: `on_window_create()` | (none) | (none) | | After window done | Plugin: `after_window_finished()` | (none) | (none) | -| Deprecated pre | (none) | `pre` (deprecated; runs once before windows if session is new) | (none) | -| Deprecated post | (none) | `post` (deprecated; runs after attach/detach on every invocation) | (none) | +| Deprecated pre | (none) | `pre` (deprecated → `on_project_start`+`on_project_restart`; runs before session create) | (none) | +| Deprecated post | (none) | `post` (deprecated → `on_project_stop`+`on_project_exit`; runs after attach on every invocation) | (none) | ### Window-Level From 321c064bd3401cb70340ec8c0a04e8c14bde4044 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:35:39 -0600 Subject: [PATCH 38/53] =?UTF-8?q?docs(import-tmuxinator):=20Document=20pre?= =?UTF-8?q?=E2=86=92before=5Fscript=20semantic=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: The importer maps tmuxinator's `pre` to `shell_command_before` (per-pane), but `pre` runs once before session creation like `before_script`. This changes "run once" to "run in every pane." what: - Add three-column table showing correct vs current importer mapping - Document both bugs: wrong scope (pre alone) and invalid key (pre+pre_window) - Update summary table to reflect correct target key --- notes/import-tmuxinator.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/notes/import-tmuxinator.md b/notes/import-tmuxinator.md index b203c12757..ff22cf14f4 100644 --- a/notes/import-tmuxinator.md +++ b/notes/import-tmuxinator.md @@ -87,14 +87,17 @@ In tmuxinator, `cli_args` is deprecated in favor of `tmux_options` (`project.rb: ### 10. Pre / Pre-Window Commands -| tmuxinator | tmuxp | -|---|---| -| `pre: "cmd"` (session-level, alone) | `shell_command_before: ["cmd"]` | -| `pre_window: "cmd"` + `pre: "cmd"` | `shell_command: "cmd"` + `shell_command_before: ["cmd"]` | +| tmuxinator | tmuxp (correct) | tmuxp (current importer) | +|---|---|---| +| `pre: "cmd"` (session-level, alone) | `before_script: "cmd"` | `shell_command_before: ["cmd"]` (wrong scope) | +| `pre_window: "cmd"` | `shell_command_before: ["cmd"]` | ✓ Correct (when alone) | +| `pre: "cmd"` + `pre_window: "cmd2"` | `before_script: "cmd"` + `shell_command_before: ["cmd2"]` | `shell_command: "cmd"` (invalid key, lost) + `shell_command_before: ["cmd2"]` | -**Importer status**: ⚠ Bug (lines 59-70). When both `pre` and `pre_window` exist, the importer sets `shell_command` (not a valid tmuxp session-level key) for `pre` and `shell_command_before` for `pre_window`. The `pre` commands are silently lost. +**Importer status**: ⚠ Bug (lines 59-70). Two issues: +1. When both `pre` and `pre_window` exist, the importer sets `shell_command` (not a valid tmuxp session-level key) for `pre`. The `pre` commands are silently lost. +2. When only `pre` exists, the importer maps it to `shell_command_before` — but `pre` runs once before session creation (like `before_script`), not per-pane. This changes the semantics from "run once" to "run in every pane." -In tmuxinator, `pre` is a deprecated session-level command run once before creating windows (in `template.erb:19`, equivalent to `on_project_start`). `pre_window` is a per-pane command run before each pane's commands (in `template.erb:71-73`). These are different scopes. +In tmuxinator, `pre` is a deprecated session-level command run once before creating windows (in `template.erb:19`, inside the new-session conditional). Its deprecation message says it's replaced by `on_project_start` + `on_project_restart`. `pre_window` is a per-pane command run before each pane's commands (in `template.erb:71-73`). These are different scopes. **Correct mapping**: - `pre` → `before_script` (runs once before windows are created) @@ -219,7 +222,7 @@ These are features that cannot be imported because tmuxp lacks the underlying ca | `socket_name` | ✓ Handled | Difference | | `cli_args`/`tmux_options` → `config` | ⚠ Partial | Difference (needs fix) | | `rbenv` → `shell_command_before` | ✓ Handled | Difference | -| `pre` → `shell_command_before` | ⚠ Bug when combined with `pre_window` | Difference (needs fix) | +| `pre` → `before_script` | ⚠ Bug: maps to wrong key (`shell_command_before` alone, `shell_command` with `pre_window`) | Difference (needs fix) | | Window hash syntax | ✓ Handled | Difference | | Window `root`/`pre`/`layout`/`panes` | ✓ Handled | Difference | | `rvm` → `shell_command_before` | ✗ Missing | Difference (needs add) | From 1f2eee2ce71885918f6548ca946e2900ddf5f39c Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 17:35:44 -0600 Subject: [PATCH 39/53] =?UTF-8?q?docs(parity-tmuxinator):=20Add=20pre?= =?UTF-8?q?=E2=86=92before=5Fscript=20scope=20bug=20to=20importer=20analys?= =?UTF-8?q?is?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: The pre-alone case was marked as correct but has a semantic bug — tmuxinator's pre runs once, but mapping to shell_command_before runs it in every pane. what: - Mark pre-alone mapping as wrong scope in import behavior table - Expand code quality issue #1 to document both pre bugs - Update "misses" table with combined bug description --- notes/parity-tmuxinator.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/notes/parity-tmuxinator.md b/notes/parity-tmuxinator.md index cbdb27397c..fe3c171226 100644 --- a/notes/parity-tmuxinator.md +++ b/notes/parity-tmuxinator.md @@ -197,8 +197,8 @@ Creates a config file pre-populated from a running tmux session. Note: tmuxinato | `cli_args` / `tmux_options` | `config` (extracts `-f`) | ⚠ Only handles `-f` flag, ignores `-L`, `-S` | | `socket_name` | `socket_name` | ✓ Correct | | `tabs` → `windows` | `windows` | ✓ Correct | -| `pre` + `pre_window` | `shell_command` + `shell_command_before` | ⚠ `shell_command` is not a valid tmuxp key | -| `pre` (alone) | `shell_command_before` | ✓ Correct | +| `pre` + `pre_window` | `shell_command` + `shell_command_before` | ⚠ `shell_command` is not a valid tmuxp session key; should be `before_script` + `shell_command_before` | +| `pre` (alone) | `shell_command_before` | ⚠ Wrong scope: `pre` runs once (like `before_script`), not per-pane | | `rbenv` | appended to `shell_command_before` | ✓ Correct | | Window hash key | `window_name` | ✓ Correct | | Window `pre` | `shell_command_before` | ✓ Correct | @@ -229,13 +229,15 @@ Creates a config file pre-populated from a running tmux session. Note: tmuxinato | `on_project_stop` | Not imported. tmuxp has no equivalent. | | Named panes (hash syntax) | Not imported. Pane names/titles are lost. | | ERB templating | Not handled. YAML parsing will fail on ERB syntax. | -| `pre` + `pre_window` combo | Bug: sets `shell_command` which is not a tmuxp session-level key | +| `pre` mapping | Bug: maps to `shell_command_before` (per-pane) instead of `before_script` (once); combo with `pre_window` uses invalid `shell_command` key | ### Code Quality Issues in Importer -1. **Line 60**: When both `pre` and `pre_window` exist, the importer sets `tmuxp_workspace["shell_command"]` — but `shell_command` is not a valid session-level tmuxp key. The `pre` commands would be silently ignored. +1. **Lines 59-70 (`pre` handling)**: Two bugs: + - When both `pre` and `pre_window` exist (line 60), the importer sets `tmuxp_workspace["shell_command"]` — but `shell_command` is not a valid session-level tmuxp key. The `pre` commands are silently lost. + - When only `pre` exists (line 68), it maps to `shell_command_before` — but tmuxinator's `pre` runs *once* before session creation (`template.erb:19`), not per-pane. The correct mapping is `before_script`. -2. **Line 36-49**: The `cli_args`/`tmux_options` handler only extracts `-f` (config file). It ignores `-L` (socket name) and `-S` (socket path) which could also appear in these fields. +2. **Lines 36-49**: The `cli_args`/`tmux_options` handler only extracts `-f` (config file). It ignores `-L` (socket name) and `-S` (socket path) which could also appear in these fields. 3. **Line 79-101**: The window iteration uses `for k, v in window_dict.items()` which assumes windows are always dicts with a single key (the window name). This is correct for tmuxinator's format but fragile — if a window dict has multiple keys, only the last one is processed. From a052b235210e58ce8bd8c887af45bc8ee58dbde5 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 18:23:03 -0600 Subject: [PATCH 40/53] =?UTF-8?q?docs(plan):=20Add=20solo=20pre=E2=86=92be?= =?UTF-8?q?fore=5Fscript=20scope=20bug=20and=20update=20I1=20analysis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: Fresh parity analysis (2026-03-06) found a second bug in the tmuxinator pre key mapping not previously documented in the plan. what: - Add Bug A: solo pre maps to shell_command_before (per-pane) instead of before_script (session-level) - Clarify existing Bug B with dead config keys table cross-reference - Document before_script shell limitation (no shell=True in Popen) - Update plan date to 2026-03-06 --- notes/plan.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/notes/plan.md b/notes/plan.md index 258e61d294..3cb2a0f352 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -1,6 +1,6 @@ # Parity Implementation Plan -*Last updated: 2026-02-08* +*Last updated: 2026-03-06* *Based on: parity-tmuxinator.md, parity-teamocil.md, import-tmuxinator.md, import-teamocil.md* ## libtmux Limitations @@ -140,7 +140,7 @@ Keys produced by importers but silently ignored by the builder: | Key | Producer | Importer Line | Builder Handling | Issue | |---|---|---|---|---| -| `shell_command` (session-level) | tmuxinator importer | `importers.py:60` | Not a valid session key | **Bug**: `pre` commands lost when both `pre` and `pre_window` exist | +| `shell_command` (session-level) | tmuxinator importer | `importers.py:60` | Not a valid session key | **Bug** (I1 Bug B): `pre` commands lost when both `pre` and `pre_window` exist | | `config` | tmuxinator importer | `importers.py:37,44` | Never read | Dead data — extracted `-f` path goes nowhere | | `socket_name` | tmuxinator importer | `importers.py:52` | Never read | Dead data — CLI uses `-L` flag | | `clear` | teamocil importer | `importers.py:141` | Never read | Dead data — tmuxp has no clear support | @@ -148,13 +148,29 @@ Keys produced by importers but silently ignored by the builder: ## Importer Bugs (No Builder Changes Needed) -### I1. tmuxinator `pre` + `pre_window` Mapping Bug +### I1. tmuxinator `pre` / `pre_window` Mapping Bugs + +Two bugs in `importers.py:59-70`, covering both code paths for the `pre` key: + +#### Bug A: Solo `pre` maps to wrong key (NEW — 2026-03-06) + +- **Bug**: When only `pre` exists (no `pre_window`) (`importers.py:66-70`), it maps to `shell_command_before` — a per-pane key that runs before each pane's commands. But tmuxinator's `pre` is a session-level hook that runs **once** before any windows are created. The correct target is `before_script`. +- **Effect**: Instead of running once at session start, the `pre` commands run N times (once per pane) as pane setup commands. This changes both the semantics (pre-session → per-pane) and the execution count. + +#### Bug B: Combo `pre` + `pre_window` loses `pre` commands - **Bug**: When both `pre` and `pre_window` exist (`importers.py:59-65`): - 1. `pre` maps to `shell_command` (line 60) — invalid session-level key, silently ignored by builder. The `pre` commands are lost. + 1. `pre` maps to `shell_command` (line 60) — invalid session-level key, silently ignored by builder. The `pre` commands are lost entirely (see Dead Config Keys table). 2. The `isinstance` check on line 62 tests `workspace_dict["pre"]` type to decide how to wrap `workspace_dict["pre_window"]` — it should check `pre_window`'s type, not `pre`'s. If `pre` is a list but `pre_window` is a string, `pre_window` won't be wrapped in a list. -- **Correct mapping**: `pre` → `before_script` (session-level, runs once before windows). `pre_window` → `shell_command_before` (per-pane). -- **Note**: `before_script` expects a file path or command (executed via `subprocess.Popen` after `shlex.split()` in `util.py:27-32`), not inline shell commands. For inline commands, either write a temp script, or add an `on_project_start` config key (T6). + +#### Correct mapping + +- `pre` → `before_script` (session-level, runs once before windows) +- `pre_window` → `shell_command_before` (per-pane, runs before each pane's commands) + +#### `before_script` shell limitation + +`before_script` is executed via `subprocess.Popen` after `shlex.split()` in `util.py:27-32` — **without `shell=True`**. This means shell constructs (pipes `|`, `&&`, redirects `>`, subshells `$(...)`) won't work in `before_script` values. For inline shell commands, the forward path is the `on_project_start` config key (T6), which would use `shell=True` or write a temp script. ### I2. tmuxinator `cli_args` / `tmux_options` Fragile Parsing From 1df946348218bb2b1300a5a785e34bc33f4e67b3 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 18:34:40 -0600 Subject: [PATCH 41/53] docs(plan): Expand L4 APIs, fix I7 stale claim, add height/with_env_var gaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: Fresh /check:shortcomings pass found libtmux has more APIs than documented (Pane.clear, Pane.split target, Session.set_environment), I7 incorrectly called with_env_var/cmd_separator "stale", and teamocil height pane key was undocumented as a dead key. what: - Add 5 libtmux APIs to L4 table (set_hooks, set_environment, clear, reset, split target) - Fix I7: with_env_var/cmd_separator are verified v0.x features, not stale — triage instead of remove - Add with_env_var and height to I6 as v0.x translatable keys - Add height to dead config keys table (not popped like width) - Update clear dead key note to reference Pane.clear() in libtmux - Update I4 to cover height alongside width --- notes/plan.md | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/notes/plan.md b/notes/plan.md index 3cb2a0f352..afdf78b3e4 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -45,6 +45,11 @@ These libtmux APIs already exist and do NOT need changes: | `Session.set_hook(hook, cmd)` | `hooks.py:111` (HooksMixin) | Lifecycle hooks (`client-detached`, etc.) | | `Session.set_option(key, val)` | `options.py:578` (OptionsMixin) | `pane-border-status`, `pane-border-format` | | `HooksMixin` on Session/Window/Pane | `session.py:55`, `window.py:56`, `pane.py:51` | All entities inherit hooks | +| `HooksMixin.set_hooks()` (bulk) | `hooks.py:430` | Efficient multi-hook setup (dict/list input) | +| `Session.set_environment(key, val)` | `session.py:53` (EnvironmentMixin) | Session-level env vars (teamocil `with_env_var`) | +| `Pane.clear()` | `pane.py:818` | Sends `reset` to clear pane (teamocil `clear`) | +| `Pane.reset()` | `pane.py:823` | `send-keys -R \; clear-history` (full reset) | +| `Pane.split(target=...)` | `pane.py:625` | Split targeting (teamocil v0.x `target`) | ## tmuxp Limitations @@ -143,7 +148,8 @@ Keys produced by importers but silently ignored by the builder: | `shell_command` (session-level) | tmuxinator importer | `importers.py:60` | Not a valid session key | **Bug** (I1 Bug B): `pre` commands lost when both `pre` and `pre_window` exist | | `config` | tmuxinator importer | `importers.py:37,44` | Never read | Dead data — extracted `-f` path goes nowhere | | `socket_name` | tmuxinator importer | `importers.py:52` | Never read | Dead data — CLI uses `-L` flag | -| `clear` | teamocil importer | `importers.py:141` | Never read | Dead data — tmuxp has no clear support | +| `clear` | teamocil importer | `importers.py:141` | Never read | Dead data — builder doesn't read it, but libtmux has `Pane.clear()` (L4) | +| `height` (pane) | teamocil importer | passthrough (not popped) | Never read | Dead data — `width` is popped but `height` passes through silently | | `shell_command_after` | teamocil importer | `importers.py:149` | Never read | Dead data — tmuxp has no after-command support | ## Importer Bugs (No Builder Changes Needed) @@ -186,7 +192,7 @@ Two bugs in `importers.py:59-70`, covering both code paths for the `pre` key: - **Bug**: Importer assumes v0.x format. String panes cause incorrect behavior (`"cmd" in "git status"` checks substring, not dict key). `commands` key (v1.x) not mapped. - **Fix**: Add format detection. Handle string panes, `commands` key, `focus`, and `options`. -- **Also**: v0.x pane `width` is silently dropped (`importers.py:161-163`) with a TODO but no user warning. Since libtmux's `Pane.resize()` exists (L4), the importer could preserve `width` and the builder could call `pane.resize(width=value)` after split. Alternatively, warn the user that width is not supported. +- **Also**: v0.x pane `width` is silently dropped (`importers.py:161-163`) with a TODO but no user warning. `height` is not even popped — it passes through as a dead key. Since libtmux's `Pane.resize()` exists (L4), the importer could preserve both `width` and `height` and the builder could call `pane.resize(width=value)` or `pane.resize(height=value)` after split. Alternatively, warn the user. ### I5. tmuxinator Missing Keys @@ -199,18 +205,28 @@ Not imported but translatable: - `socket_path` → warn user to use CLI `-S` flag - `attach: false` → warn user to use CLI `-d` flag -### I6. teamocil Missing Keys (v1.x) +### I6. teamocil Missing Keys -Not imported but translatable (same key names in tmuxp): +Not imported but translatable: + +**v1.x keys** (same key names in tmuxp): - `commands` → `shell_command` - `focus` (window) → `focus` (pass-through) - `focus` (pane) → `focus` (pass-through) - `options` (window) → `options` (pass-through) - String pane shorthand → `shell_command: [command]` -### I7. Stale Importer TODOs +**v0.x keys**: +- `with_env_var` → `environment: { TEAMOCIL: "1" }` (default `true` in v0.x; maps to session-level `environment` key) +- `height` (pane) → should be popped like `width` (currently passes through as dead key) + +### I7. Importer TODOs Need Triage + +`importers.py:121,123` lists `with_env_var` and `cmd_separator` as TODOs (with `clear` at line 122 in between). Both are verified v0.x features (present in teamocil's `0.4-stable` branch at `lib/teamocil/layout/window.rb`), not stale references: -`importers.py:121,123` lists `with_env_var` and `cmd_separator` as TODOs (with `clear` at line 122 in between), but neither `with_env_var` nor `cmd_separator` exists in teamocil v1.4.2 source. These are stale references from ~2013 and should be removed. +- **`with_env_var`** (line 121): When `true` (the default in v0.x), exports `TEAMOCIL=1` in each pane. Should map to `environment: { TEAMOCIL: "1" }` (tmuxp's `environment` key works at session level via `Session.set_environment()`, L4). Implement, don't remove. +- **`clear`** (line 122): Already imported at line 141 but builder ignores it. libtmux has `Pane.clear()` (L4), so builder support is feasible. +- **`cmd_separator`** (line 123): Per-window string (default `"; "`) used to join commands before `send-keys`. Irrelevant for tmuxp since it sends commands individually. Remove TODO. ## Implementation Priority @@ -224,7 +240,7 @@ These fix existing bugs and add missing translations without touching the builde 4. **I5**: Import missing tmuxinator keys (`rvm`, `pre_tab`, `startup_window`, `startup_pane`) 5. **I1**: Fix `pre`/`pre_window` mapping (tmuxinator) 6. **I2**: Fix `cli_args` parsing (tmuxinator) -7. **I7**: Remove stale TODOs +7. **I7**: Triage importer TODOs (implement `with_env_var`, remove `cmd_separator`) ### Phase 2: Builder Additions (tmuxp Only) From 887c6fda17a146dfea85cc207fa3ce26f2fff8d5 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 6 Mar 2026 18:45:04 -0600 Subject: [PATCH 42/53] docs(plan): Add missing post and target keys from parity cross-reference why: Systematic cross-reference of all parity summary tables against plan found two items not yet documented. what: - Add tmuxinator post to I5 missing keys (deprecated on_project_exit) - Add teamocil target to dead config keys table (passthrough, builder ignores, but libtmux Pane.split(target=...) exists) --- notes/plan.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/notes/plan.md b/notes/plan.md index afdf78b3e4..28f472a5f0 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -150,6 +150,7 @@ Keys produced by importers but silently ignored by the builder: | `socket_name` | tmuxinator importer | `importers.py:52` | Never read | Dead data — CLI uses `-L` flag | | `clear` | teamocil importer | `importers.py:141` | Never read | Dead data — builder doesn't read it, but libtmux has `Pane.clear()` (L4) | | `height` (pane) | teamocil importer | passthrough (not popped) | Never read | Dead data — `width` is popped but `height` passes through silently | +| `target` (pane) | teamocil importer | passthrough (not popped) | Never read | Dead data — accidentally preserved via dict mutation, but libtmux has `Pane.split(target=...)` (L4) | | `shell_command_after` | teamocil importer | `importers.py:149` | Never read | Dead data — tmuxp has no after-command support | ## Importer Bugs (No Builder Changes Needed) @@ -202,6 +203,7 @@ Not imported but translatable: - `startup_window` → find matching window, set `focus: true` - `startup_pane` → find matching pane, set `focus: true` - `on_project_first_start` → `before_script` (only if value is a single command or script path; multi-command strings joined by `;` won't work since `before_script` uses `Popen` without `shell=True`) +- `post` → deprecated predecessor to `on_project_exit`; runs after windows are built on every invocation. No tmuxp equivalent until T6 lifecycle hooks exist. - `socket_path` → warn user to use CLI `-S` flag - `attach: false` → warn user to use CLI `-d` flag From c0fb7637367c6bed47d1d3047b883629f31c727c Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sat, 7 Mar 2026 04:26:34 -0600 Subject: [PATCH 43/53] docs(comparison): Add synchronize deprecation, pane shell_command_before, multi-file load why: Source code analysis revealed details not captured in comparison table. what: - Note tmuxinator synchronize true/before deprecated in favor of after - Add pane-level shell_command_before row (tmuxp-unique feature) - Add multi-file loading CLI row (tmuxp load f1 f2) --- docs/comparison.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/comparison.md b/docs/comparison.md index f9a60cab11..007df4adb7 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -1,6 +1,6 @@ # Feature Comparison: tmuxp vs tmuxinator vs teamocil -*Last updated: 2026-03-06* +*Last updated: 2026-03-07* ## Overview @@ -101,7 +101,7 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Environment vars | `environment` | (none) | (none) | | Suppress history | `suppress_history` | (none) | (none) | | Focus | `focus` | (none; use `startup_window`) | `focus` | -| Synchronize panes | (none) | `synchronize` (`true`/`before`/`after`) | (none) | +| Synchronize panes | (none) | `synchronize` (`true`/`before`/`after`; `true`/`before` deprecated → use `after`) | (none) | | Filters (before) | (none) | (none) | `filters.before` (v0.x) | | Filters (after) | (none) | (none) | `filters.after` (v0.x) | @@ -118,6 +118,7 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Sleep after | `sleep_after` | (none) | (none) | | Suppress history | `suppress_history` | (none) | (none) | | Focus | `focus` | (none; use `startup_pane`) | `focus` | +| Shell cmd before | `shell_command_before` | (none; inherits from window/session) | (none) | | Pane title | (none) | hash key (named pane → `select-pane -T`) | (none) | | Width | (none) | (none) | `width` (v0.x, horizontal split %) | | Height | (none) | (none) | `height` (v0.x, vertical split %) | @@ -162,6 +163,7 @@ teamocil parses YAML into `Session`/`Window`/`Pane` objects, each producing `Com | Pass variables | (none) | `key=value` args | (none) | | Suppress version warning | (none) | `--suppress-tmux-version-warning` | (none) | | Custom config path | `tmuxp load /path/to/file` | `-p /path/to/file` | `--layout /path/to/file` | +| Load multiple configs | `tmuxp load f1 f2 ...` (all but last detached) | (none) | (none) | | Local config | `tmuxp load .` | `tmuxinator local` | (none) | ## Config File Discovery From 09728e77f5f50e02fbbff7859e9dcdfd547e1051 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sat, 7 Mar 2026 04:26:39 -0600 Subject: [PATCH 44/53] docs(parity-tmuxinator): Add synchronize deprecation and pane_title_format default why: tmuxinator source confirms synchronize true/before is deprecated. what: - Note synchronize true/before deprecated in project.rb:21-29 - Add context that import should still honor original semantics per value - Document pane_title_format default and pane_title_position default --- notes/parity-tmuxinator.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/notes/parity-tmuxinator.md b/notes/parity-tmuxinator.md index fe3c171226..d9692c0572 100644 --- a/notes/parity-tmuxinator.md +++ b/notes/parity-tmuxinator.md @@ -1,6 +1,6 @@ # Tmuxinator Parity Analysis -*Last updated: 2026-03-06* +*Last updated: 2026-03-07* *Tmuxinator version analyzed: 3.3.7 (supports tmux 1.5–3.6a)* *tmuxp version: 1.64.0* @@ -73,8 +73,10 @@ windows: - vim ``` -- `synchronize: true` / `synchronize: before` — enable pane sync before running pane commands -- `synchronize: after` — enable pane sync after running pane commands +- `synchronize: true` / `synchronize: before` — enable pane sync before running pane commands (**deprecated** in tmuxinator `project.rb:21-29`) +- `synchronize: after` — enable pane sync after running pane commands (recommended) + +Note: tmuxinator deprecates `synchronize: true` and `synchronize: before` in favor of `synchronize: after`. The deprecation message says `before` was the original behavior but `after` is the recommended pattern. Import should still honor the original semantics of each value. **Gap**: tmuxp has no `synchronize` config key. Users would need to set `synchronize-panes on` via `options` manually, but this doesn't support the before/after distinction. @@ -86,8 +88,8 @@ windows: ```yaml enable_pane_titles: true -pane_title_position: top -pane_title_format: "#{pane_index}: #{pane_title}" +pane_title_position: top # default: "top" +pane_title_format: "#{pane_index}: #{pane_title}" # this is the default format windows: - editor: panes: From f2bca4bf3e46868c5f733e86c1c7e6be113c2918 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sat, 7 Mar 2026 04:26:44 -0600 Subject: [PATCH 45/53] docs(parity-teamocil): Add v1.0 rewrite context from README why: teamocil agent confirmed v1.0 explicitly dropped v0.x features. what: - Add paragraph explaining v1.0 rewrite dropped hooks, env vars, DSL - Provides context for why v0.x import has more keys than v1.x --- notes/parity-teamocil.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/notes/parity-teamocil.md b/notes/parity-teamocil.md index 5c597bf979..7378fba806 100644 --- a/notes/parity-teamocil.md +++ b/notes/parity-teamocil.md @@ -1,6 +1,6 @@ # Teamocil Parity Analysis -*Last updated: 2026-03-06* +*Last updated: 2026-03-07* *Teamocil version analyzed: 1.4.2* *tmuxp version: 1.64.0* @@ -15,6 +15,8 @@ The current tmuxp importer (`importers.py:import_teamocil`) **targets the v0.x f Note: teamocil v1.x does not create new sessions — it **renames** the current session (`rename-session`) and adds windows to it. This is fundamentally different from tmuxp/tmuxinator which create fresh sessions. +**v1.0 rewrite context** (from teamocil README): Teamocil 1.0 was a complete rewrite that explicitly dropped several v0.x features: no hook system (pre/post execution scripts), no pane-specific environment variables (`with_env_var`), no inline scripting or complex DSL, and no `cmd_separator` customization. The focus narrowed to core declarative window/pane creation. + ## Features teamocil has that tmuxp lacks ### 1. Session Rename (Not Create) From ce860c29411e8d1260ed8e0bcb601a294a9ce555 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sat, 7 Mar 2026 04:26:49 -0600 Subject: [PATCH 46/53] docs(import-tmuxinator): Note synchronize deprecation in summary table why: Deprecation context helps prioritize import implementation. what: - Add deprecation note to synchronize row in summary table --- notes/import-tmuxinator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notes/import-tmuxinator.md b/notes/import-tmuxinator.md index ff22cf14f4..921376beee 100644 --- a/notes/import-tmuxinator.md +++ b/notes/import-tmuxinator.md @@ -1,6 +1,6 @@ # Tmuxinator Import Behavior -*Last updated: 2026-03-06* +*Last updated: 2026-03-07* *Importer: `src/tmuxp/workspace/importers.py:import_tmuxinator`* ## Syntax Differences (Translatable) @@ -232,7 +232,7 @@ These are features that cannot be imported because tmuxp lacks the underlying ca | `socket_path` | ✗ Missing | Difference (needs add) | | `attach: false` | ✗ Missing | Difference (needs add) | | `on_project_*` hooks | ✗ Missing | **Limitation** | -| `synchronize` | ✗ Missing | **Limitation** | +| `synchronize` | ✗ Missing (`true`/`before` deprecated in tmuxinator → `after` recommended) | **Limitation** | | `enable_pane_titles` / titles | ✗ Missing | **Limitation** | | ERB templating | ✗ Missing | **Limitation** | | `tmux_command` (wemux) | ✗ Missing | **Limitation** | From 7f33565bf6ddaba690848cba2afa387a14e2a69c Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sat, 7 Mar 2026 04:26:53 -0600 Subject: [PATCH 47/53] docs(import-teamocil): Update date after source verification why: Cross-referenced against teamocil source, no corrections needed. what: - Update last-updated date to 2026-03-07 --- notes/import-teamocil.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notes/import-teamocil.md b/notes/import-teamocil.md index 33894ec0fd..3b1f4af697 100644 --- a/notes/import-teamocil.md +++ b/notes/import-teamocil.md @@ -1,6 +1,6 @@ # Teamocil Import Behavior -*Last updated: 2026-03-06* +*Last updated: 2026-03-07* *Importer: `src/tmuxp/workspace/importers.py:import_teamocil`* ## Format Detection Problem From 8998f049dcef9a2a6448c5f41b45313522275c54 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sat, 7 Mar 2026 04:26:58 -0600 Subject: [PATCH 48/53] docs(plan): Add synchronize deprecation context to T1 why: Deprecation affects import behavior and priority decisions. what: - Note tmuxinator deprecates true/before in favor of after - Clarify that import should still honor original semantics per value --- notes/plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notes/plan.md b/notes/plan.md index 28f472a5f0..0c15ada86f 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -1,6 +1,6 @@ # Parity Implementation Plan -*Last updated: 2026-03-06* +*Last updated: 2026-03-07* *Based on: parity-tmuxinator.md, parity-teamocil.md, import-tmuxinator.md, import-teamocil.md* ## libtmux Limitations @@ -56,7 +56,7 @@ These libtmux APIs already exist and do NOT need changes: ### T1. No `synchronize` Config Key - **Blocker**: `WorkspaceBuilder` (`builder.py`) does not check for a `synchronize` key on window configs. The key is silently ignored if present. -- **Blocks**: Pane synchronization (tmuxinator `synchronize: true/before/after`). +- **Blocks**: Pane synchronization (tmuxinator `synchronize: true/before/after`). Note: tmuxinator deprecates `true`/`before` in favor of `after` (`project.rb:21-29`), but all three values still function. The import should honor original semantics of each value. - **Required**: Add `synchronize` handling in `builder.py`. For `before`/`true`: call `window.set_option("synchronize-panes", "on")` before pane commands are sent. For `after`: call it in `config_after_window()`. For `false`/omitted: no action. - **Insertion point**: In `build()` around line 320 (after `on_window_create` plugin hook, before `iter_create_panes()` loop) for `before`/`true`. In `config_after_window()` around line 565 for `after`. Note: in tmux 3.2+ (tmuxp's minimum), `synchronize-panes` is a dual-scope option (window|pane, `options-table.c:1423`). Setting it at window level via `window.set_option()` makes all panes inherit it, including those created later by split. - **Non-breaking**: New optional config key. Existing configs are unaffected. From ff0e8959e871d432bfd81b05fd59898c62a38f7c Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sat, 7 Mar 2026 04:43:06 -0600 Subject: [PATCH 49/53] docs(plan): Fix accuracy issues from issue #1016 review why: Cross-referencing issue #1016 against source code found 2 inaccuracies. what: - I1 Bug B: Add double-wrapping consequence when pre/pre_window types mismatch - T4: Clarify teamocil always renames session regardless of --here flag --- notes/plan.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notes/plan.md b/notes/plan.md index 0c15ada86f..d312e6b1ab 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -80,8 +80,8 @@ These libtmux APIs already exist and do NOT need changes: ### T4. No Session Rename Mode / `--here` CLI Flag -- **Blocker**: `tmuxp load` (`cli/load.py`) has no `--here` flag. `WorkspaceBuilder.iter_create_windows()` always creates new windows via `session.new_window()` (line 406). Additionally, teamocil's session rename mode (rename current session instead of creating new) is partially covered by tmuxp's `--append` flag, but `--append` does not rename the session. -- **Blocks**: teamocil `--here` (reuse current window for first window) and teamocil session rename mode. +- **Blocker**: `tmuxp load` (`cli/load.py`) has no `--here` flag. `WorkspaceBuilder.iter_create_windows()` always creates new windows via `session.new_window()` (line 406). Additionally, teamocil always renames the current session (`session.rb:18-20`), regardless of `--here`; the `--here` flag only affects **window** behavior (reuse current window for first window instead of creating new). tmuxp's `--append` flag partially covers session rename mode, but does not rename the session. +- **Blocks**: teamocil `--here` (reuse current window for first window) and teamocil session rename (always active, not conditional on `--here`). - **Required**: 1. Add `--here` flag to `cli/load.py` (around line 516, near `--append`). 2. Pass `here=True` through to `WorkspaceBuilder.build()`. @@ -168,7 +168,7 @@ Two bugs in `importers.py:59-70`, covering both code paths for the `pre` key: - **Bug**: When both `pre` and `pre_window` exist (`importers.py:59-65`): 1. `pre` maps to `shell_command` (line 60) — invalid session-level key, silently ignored by builder. The `pre` commands are lost entirely (see Dead Config Keys table). - 2. The `isinstance` check on line 62 tests `workspace_dict["pre"]` type to decide how to wrap `workspace_dict["pre_window"]` — it should check `pre_window`'s type, not `pre`'s. If `pre` is a list but `pre_window` is a string, `pre_window` won't be wrapped in a list. + 2. The `isinstance` check on line 62 tests `workspace_dict["pre"]` type to decide how to wrap `workspace_dict["pre_window"]` — it should check `pre_window`'s type, not `pre`'s. When `pre` is a string but `pre_window` is a list, `pre_window` gets double-wrapped as `[["cmd1", "cmd2"]]` (nested list). When `pre` is a list but `pre_window` is a string, `pre_window` won't be wrapped in a list — leaving a bare string where a list is expected. #### Correct mapping From 61860db92d9b0887dcd6c3c0ad92e2bef8cbd356 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sat, 7 Mar 2026 05:03:19 -0600 Subject: [PATCH 50/53] docs(parity-tmuxinator): Fix fallback order, add missing CLI details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: Second parity pass found inaccurate fallback chain order and missing CLI commands/flags. what: - Fix pre_window fallback chain: pre_window → pre_tab → rbenv → rvm (was reversed) - Add doctor command and completions to commands table - Add start --append, --no-pre-window, --project-config flags - Add list --active, --newline flags --- notes/parity-tmuxinator.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/notes/parity-tmuxinator.md b/notes/parity-tmuxinator.md index d9692c0572..bd1f0ce3de 100644 --- a/notes/parity-tmuxinator.md +++ b/notes/parity-tmuxinator.md @@ -157,8 +157,20 @@ Outputs the generated shell script without executing it. | `tmuxinator implode` | Delete ALL configs | | `tmuxinator stop <project>` | Stop session + run hooks | | `tmuxinator stop-all` | Stop all managed sessions | +| `tmuxinator doctor` | Check system setup (tmux installed, version) | +| `tmuxinator completions` | Shell completion helper | -**Gap**: tmuxp has `edit` but not `new`, `copy`, `delete`, `implode`, or `stop` commands. +**Gap**: tmuxp has `edit` but not `new`, `copy`, `delete`, `implode`, `stop`, or `doctor` commands. + +Additional CLI flags on `start`: +- `--append` — append windows to existing session (tmuxp has `--append`) +- `--no-pre-window` — skip pre_window commands (tmuxp lacks this) +- `--project-config` / `-p` — use specific config file (tmuxp uses positional arg) +- `--suppress-tmux-version-warning` — skip tmux version check + +Additional flags on `list`: +- `--active` / `-a` — filter by active sessions (tmuxp lacks this) +- `--newline` / `-n` — one entry per line ### 11. `--no-pre-window` Flag @@ -170,7 +182,7 @@ tmuxinator start myproject --no-pre-window Skips `pre_window` commands. Useful for debugging. -Note: tmuxinator's `pre_window` method has a fallback chain (`project.rb:175-188`): `rbenv` → `rvm` → `pre_tab` → `pre_window`. The `--no-pre-window` flag disables all of these, not just `pre_window`. +Note: tmuxinator's `pre_window` method has a fallback chain (`project.rb:175-186`): `pre_window` → `pre_tab` → `rbenv` → `rvm` (highest priority first, using Ruby's `||` operator). The `--no-pre-window` flag disables all of these, not just `pre_window`. **Gap**: tmuxp has no equivalent flag to skip `shell_command_before`. From 2c1de29a3081581664fab75990964cd42344f55e Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sat, 7 Mar 2026 05:03:26 -0600 Subject: [PATCH 51/53] docs(parity-teamocil): Add layout-per-pane behavior and path expansion why: Second parity pass found undocumented behavioral differences. what: - Add section 8: teamocil applies layout after each pane split (vs tmuxp once at end) - Add section 10: root path expansion comparison (no gap, both tools expand) - Renumber existing sections 8-9 to 9-10 --- notes/parity-teamocil.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/notes/parity-teamocil.md b/notes/parity-teamocil.md index 7378fba806..1d590bdee0 100644 --- a/notes/parity-teamocil.md +++ b/notes/parity-teamocil.md @@ -115,7 +115,15 @@ Maps to `set-window-option -t <window> <key> <value>`. **Gap**: tmuxp **does** support `options` on windows. **No gap**. -### 8. Multiple Commands Joined by Semicolon +### 8. Layout Applied After Each Pane + +**Source**: `lib/teamocil/tmux/pane.rb:9` + +teamocil applies `select-layout` after EACH pane is created (not once at the end). This means the layout is re-applied after every `split-window`, keeping panes evenly distributed as they're added. tmuxp applies layout once after all panes are created (`builder.py:511`). + +**Gap**: This is a behavioral difference. For named layouts (`main-vertical`, `tiled`, etc.) the result is the same. For custom layout strings, the timing matters — tmuxp's approach is correct for custom strings since the string encodes the final geometry. **No action needed** — tmuxp's behavior is actually better for custom layouts. + +### 9. Multiple Commands Joined by Semicolon **Source**: `lib/teamocil/tmux/pane.rb` @@ -128,6 +136,14 @@ Teamocil joins multiple pane commands with `; ` and sends them as a single `send **Gap**: tmuxp sends each command separately via individual `pane.send_keys()` calls. This is actually more reliable (each command gets its own Enter press), so this is a **behavioral difference** rather than a gap. +### 10. Root Path Expansion + +**Source**: `lib/teamocil/tmux/window.rb:8` + +teamocil expands `root` to absolute path via `File.expand_path(root)` at window initialization. This resolves `~` and relative paths before passing to `new-window -c`. + +**Gap**: tmuxp also does this via `expandshell()` in `loader.py` (`os.path.expandvars(os.path.expanduser(value))`). **No gap** — both tools expand paths. + ## v0.x vs v1.x Format Differences | Feature | v0.x | v1.x (current) | From 949ac098f37f2e3341d4785d7e605aa1bacdc873 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sat, 7 Mar 2026 05:11:14 -0600 Subject: [PATCH 52/53] docs(plan): Add test coverage gaps section from fixture analysis why: Third parity pass found importer test fixtures cover only ~40% of real-world patterns. what: - Add Tier 1 crash risks: v1.x teamocil string panes, commands key, rvm - Add Tier 2 missing coverage: YAML aliases, emoji names, pane titles, etc. - List required new fixtures for Phase 1 implementation --- notes/plan.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/notes/plan.md b/notes/plan.md index d312e6b1ab..ef5023999f 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -230,6 +230,35 @@ Not imported but translatable: - **`clear`** (line 122): Already imported at line 141 but builder ignores it. libtmux has `Pane.clear()` (L4), so builder support is feasible. - **`cmd_separator`** (line 123): Per-window string (default `"; "`) used to join commands before `send-keys`. Irrelevant for tmuxp since it sends commands individually. Remove TODO. +## Test Coverage Gaps + +Current importer test fixtures cover ~40% of real-world config patterns. Key gaps by severity: + +### Tier 1: Will Crash or Silently Lose Data + +- **v1.x teamocil string panes**: `panes: ["git status"]` → `TypeError` (importer tries `"cmd" in p` on string) +- **v1.x teamocil `commands` key**: `commands: [...]` → silently dropped (only `cmd` recognized) +- **tmuxinator `rvm`**: Completely ignored by importer (only `rbenv` handled) +- **tmuxinator `pre` scope bug**: Tests pass because fixtures don't verify execution semantics + +### Tier 2: Missing Coverage + +- **YAML aliases/anchors**: Real tmuxinator configs use `&defaults` / `*defaults` — no test coverage +- **Numeric/emoji window names**: `222:`, `true:`, `🍩:` — YAML type coercion edge cases untested +- **Pane title syntax**: `pane_name: command` dict form — no fixtures +- **`startup_window`/`startup_pane`**: Not tested +- **`pre_tab`** (deprecated): Not tested +- **Window-level `root` with relative paths**: Not tested +- **`tmux_options` with non-`-f` flags**: Not tested (importer bug I2) + +### Required New Fixtures + +When implementing Phase 1 import fixes, each item needs corresponding test fixtures. See `tests/fixtures/import_tmuxinator/` and `tests/fixtures/import_teamocil/` for existing patterns. + +**tmuxinator fixtures needed**: YAML aliases, emoji names, numeric names, `rvm`, `pre_tab`, `startup_window`/`startup_pane`, pane titles, `socket_path`, multi-flag `tmux_options` + +**teamocil fixtures needed**: v1.x format (`commands`, string panes, window `focus`/`options`), pane `height`, `with_env_var`, mixed v0.x/v1.x detection + ## Implementation Priority ### Phase 1: Import Fixes (No Builder/libtmux Changes) From 1f937ad40e4488cf8456b024f65e5321e7832d0c Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Sat, 7 Mar 2026 05:11:19 -0600 Subject: [PATCH 53/53] docs(import-tmuxinator): Add YAML aliases and numeric/emoji name findings why: Third parity pass found untested edge cases from tmuxinator spec fixtures. what: - Add section 18: YAML aliases/anchors (transparent, but untested) - Add section 19: numeric/emoji window names (YAML type coercion risk) - Update summary table with new entries --- notes/import-tmuxinator.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/notes/import-tmuxinator.md b/notes/import-tmuxinator.md index 921376beee..d5c6b9e576 100644 --- a/notes/import-tmuxinator.md +++ b/notes/import-tmuxinator.md @@ -160,6 +160,22 @@ In tmuxinator, `pre` is a deprecated session-level command run once before creat **Importer status**: ✗ Not handled. Could add a comment or warning suggesting `-d` flag. +### 18. YAML Aliases/Anchors + +| tmuxinator | tmuxp | +|---|---| +| `defaults: &defaults` + `<<: *defaults` | Same (YAML 1.1 feature) | + +**Importer status**: ✓ Handled transparently. YAML aliases are resolved by the YAML parser before the importer sees the dict. No special handling needed. However, tmuxp's test fixtures have **no coverage** of this pattern — real tmuxinator configs commonly use anchors to DRY up repeated settings (see `tmuxinator/spec/fixtures/sample_alias.yml`). + +### 19. Numeric/Emoji Window Names + +| tmuxinator | tmuxp | +|---|---| +| `- 222:` or `- true:` or `- 🍩:` | `window_name: "222"` or `window_name: "True"` or `window_name: "🍩"` | + +**Importer status**: ⚠ Potentially handled but **untested**. YAML parsers coerce bare `222` to int and `true` to bool. tmuxinator handles this via Ruby's `.to_s` method. The importer iterates `window_dict.items()` (line 80) which will produce `(222, ...)` or `(True, ...)` — the `window_name` will be an int/bool, not a string. tmuxp's builder may or may not handle non-string window names correctly. Needs test coverage. + ## Limitations (tmuxp Needs to Add Support) These are features that cannot be imported because tmuxp lacks the underlying capability. @@ -231,6 +247,8 @@ These are features that cannot be imported because tmuxp lacks the underlying ca | `startup_pane` → `focus` | ✗ Missing | Difference (needs add) | | `socket_path` | ✗ Missing | Difference (needs add) | | `attach: false` | ✗ Missing | Difference (needs add) | +| YAML aliases/anchors | ✓ Transparent (YAML parser resolves) | No action needed | +| Numeric/emoji window names | ⚠ Untested (YAML type coercion risk) | Difference (needs tests) | | `on_project_*` hooks | ✗ Missing | **Limitation** | | `synchronize` | ✗ Missing (`true`/`before` deprecated in tmuxinator → `after` recommended) | **Limitation** | | `enable_pane_titles` / titles | ✗ Missing | **Limitation** |