Skip to content

docs: tmuxinator/teamocil parity analysis and feature comparison#1014

Draft
tony wants to merge 89 commits intomasterfrom
tmuxinator-parity
Draft

docs: tmuxinator/teamocil parity analysis and feature comparison#1014
tony wants to merge 89 commits intomasterfrom
tmuxinator-parity

Conversation

@tony
Copy link
Copy Markdown
Member

@tony tony commented Feb 8, 2026

Summary

  • Add docs/comparison.md — comprehensive feature comparison table (tmuxp vs tmuxinator vs teamocil) covering architecture, config keys, CLI commands, hooks, and config file discovery
  • Add notes/parity-tmuxinator.md — identifies 12 feature gaps, import bugs, and WorkspaceBuilder requirements for tmuxinator parity
  • Add notes/parity-teamocil.md — documents v0.x/v1.x format differences, 3 feature gaps, and import bugs
  • Add notes/import-tmuxinator.md — classifies each tmuxinator config key as translatable difference or tmuxp limitation, identifies pre/pre_window bug
  • Add notes/import-teamocil.md — documents v0.x-only importer targeting (v1.x unsupported), string pane TypeError bug, redundant filter loops
  • Update notes/plan.md — mark L1/L2/L3 as resolved (libtmux v0.55.0), fix 20+ stale line number references, reorganize phases to reflect unblocked items (T2, T9)

Key findings

Importer bugs discovered:

  • import_tmuxinator: when both pre and pre_window exist, writes to shell_command (not a valid tmuxp session key) — pre commands silently lost
  • import_teamocil: targets v0.x format only; v1.x string panes cause TypeError; redundant for _b in loops in filter handling

tmuxinator feature gaps (12):
Lifecycle hooks (5 hooks vs tmuxp's 1), pane synchronization, pane titles, ERB templating, wemux support, stop/kill command, debug/dry-run, config management CLIs, --no-pre-window flag

teamocil feature gaps (3):
--here flag (reuse current window), --debug dry-run mode, shell_command_after

tmuxp advantages (unique features):
Per-pane environment/shell/sleep/history control, plugin system, JSON config, session freeze, workspace search, upward config traversal

Test plan

  • All documents created and committed individually
  • Verify importer bug descriptions match importers.py line numbers
  • Review accuracy of feature mapping tables against source code

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 8, 2026

Codecov Report

❌ Patch coverage is 85.23810% with 62 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.43%. Comparing base (281eb9a) to head (1268774).

Files with missing lines Patch % Lines
src/tmuxp/workspace/importers.py 85.00% 17 Missing and 7 partials ⚠️
src/tmuxp/cli/load.py 51.16% 17 Missing and 4 partials ⚠️
src/tmuxp/cli/manage.py 89.28% 9 Missing and 3 partials ⚠️
src/tmuxp/cli/stop.py 92.30% 2 Missing and 1 partial ⚠️
src/tmuxp/util.py 81.81% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1014      +/-   ##
==========================================
+ Coverage   81.96%   82.43%   +0.47%     
==========================================
  Files          28       31       +3     
  Lines        2545     2921     +376     
  Branches      485      556      +71     
==========================================
+ Hits         2086     2408     +322     
- Misses        328      371      +43     
- Partials      131      142      +11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tony tony force-pushed the tmuxinator-parity branch from 304ff40 to 8e9425c Compare February 8, 2026 13:37
@tony tony force-pushed the tmuxinator-parity branch 5 times, most recently from 19b04a3 to 1f937ad Compare March 9, 2026 01:33
@tony tony force-pushed the tmuxinator-parity branch 5 times, most recently from bb72be2 to ca74e75 Compare March 21, 2026 10:53
@tony tony force-pushed the tmuxinator-parity branch from ca74e75 to b446e6b Compare April 26, 2026 10:25
@tony tony force-pushed the tmuxinator-parity branch from b446e6b to 2d03c24 Compare May 9, 2026 10:56
tony added 16 commits May 9, 2026 07:10
…amocil

Comprehensive side-by-side comparison covering architecture, config keys,
CLI commands, hooks, and config file discovery across all three tools.
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.
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.
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.
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).
- 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}"
- 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)
- 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
- 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
- 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
…s 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).
- 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
- 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)
…, 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
… chain, remove --here

- 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
tony added 29 commits May 9, 2026 16:24
why: Lock in the I5 behavior for the new tmuxinator key translations.

what:
- New fixtures: test_rvm, test_startup_window_by_{name,index},
  test_socket_path. All wired into parametrized test_config_to_dict.
- New unit tests:
  - test_import_tmuxinator_warns_on_attach_false (I5: attach false ->
    WARNING about CLI -d).
  - test_import_tmuxinator_on_project_first_start_falls_back_to_pre
    (I5: hook key mapped to before_script when `pre` absent).
  - test_import_tmuxinator_pre_window_chain_first_match_wins (I5:
    rbenv wins over rvm/pre_tab/pre_window per project.rb:175-188).
  - test_import_tmuxinator_warns_on_unresolved_startup_window (I5:
    name not found -> WARNING with tmux_key=startup_window).

ref: notes/plan.md I5
…/clear

why: Triage the v0.x-only TODOs that were in the importer's docstring.

what:
- with_env_var (default true in v0.x per teamocil 0.4-stable): when the
  `session:` wrapper is present (v0.x marker) and `with_env_var` is not
  explicitly false, emit `environment: {TEAMOCIL: "1"}` at the session
  level so each pane inherits the env var.
- cmd_separator: emit a WARNING that it has no effect in tmuxp, since
  tmuxp sends commands individually via separate send-keys calls.
- clear: still preserved on the window dict, but emit a WARNING that
  the builder does not yet act on it (deferred to a future T-item).
- Refactor the v0.x detection: capture `is_v0x = "session" in
  workspace_dict` once for the with_env_var gate.
- Update docstring TODOs: remove the now-handled keys, document the
  per-key behavior in a Notes section.
- Existing fixture `three_windows_within_a_session` (the only v0.x
  multisession layout) updated to expect the new TEAMOCIL env var.

ref: notes/plan.md I7
why: Lock in I7 behavior for the v0.x TODO key triage.

what:
- New fixtures: test_with_env_var_default (default true -> TEAMOCIL=1)
  and test_with_env_var_false (explicit false -> no env var). Wired
  into parametrized test_config_to_dict.
- New unit tests:
  - test_import_teamocil_warns_on_cmd_separator (I7: WARNING with
    extra.tmux_key=cmd_separator).
  - test_import_teamocil_warns_on_clear (I7: WARNING with
    extra.tmux_key=clear).
  - test_import_teamocil_v1x_skips_env_var (I7 regression: a v1.x
    config (no session: wrapper) gets no TEAMOCIL env var).

ref: notes/plan.md I7
…itted

why: Real-world v0.x configs sometimes omit the `session:` wrapper but
still use v0.x pane keys (`splits`, `cmd`) and `filters`. Routing them
to the v1.x path silently dropped commands.

what:
- New `_has_v0x_window_markers()` helper inspects the inner workspace
  for v0.x signals: window has `splits` or `filters`, or any pane has
  `cmd`. Routes to `_import_teamocil_v0x` if found.
- All existing v0.x test fixtures (test1-test4 and the layouts.py
  multisession scenarios) updated to expect the new
  `environment: {TEAMOCIL: "1"}` from I7's with_env_var default.
- The v1.x skip test (`test_import_teamocil_v1x_skips_env_var`) now
  uses the real v1.x `commands` key (was using v0.x `cmd` and
  incorrectly being treated as v1.x by the old detection).

ref: notes/plan.md I4
why: Lock in I4 behavior — string pane shorthand, `commands` key,
per-window/pane `focus`, window `options`, and the v1.x format
dispatch.

what:
- New fixture test_v1x_string_pane: bare string panes
  (`panes: [vim, top]`) -> `[{shell_command: [vim]}, {shell_command: [top]}]`.
- New fixture test_v1x_full: covers `commands`, window `focus`,
  pane `focus`, window `options`, mixed string + dict panes,
  session-level `root`, window-level `root`.
- Both wired into parametrized test_config_to_dict.

ref: notes/plan.md I4
why: `width` was already popped silently. `height` and `target` were
left on the pane dict (silently passing through to the builder, which
ignored them). Make all three pop with an audible WARNING so users
know the importer dropped per-pane geometry.

what:
- Loop over ("width", "height", "target") inside the v0.x pane loop;
  pop each and emit `logger.warning` with extra={"tmux_key": <key>}.
- Update existing layouts fixture expected output to drop the
  previously-preserved `target` key.

ref: notes/plan.md I6
why: Lock in I6 behavior — width/height/target each warn when popped.

what: New test_import_teamocil_warns_on_v0x_pane_geometry asserts
all three keys produce WARNING records with extra.tmux_key set.

ref: notes/plan.md I6
…ummaries

why: Phase 1 (Import Fixes) is complete. Update plan.md status so
the table reflects shipped behavior.

what:
- I1: pre → before_script with shell-metachar warning.
- I2: shlex parsing for cli_args/tmux_options.
- I3: direct assignment replaces redundant filter loops.
- I4: v0.x/v1.x dispatch, detection by session: wrapper OR
  splits/filters/cmd markers; v1.x handles string panes, commands,
  focus, options.
- I5: pre_window OR-fallback chain (rbenv→rvm→pre_tab→pre_window);
  startup_window/pane resolved to focus: true; on_project_first_start
  fallback; socket_path pass-through; attach: false warns.
- I6: v0.x height/target now popped with WARNING.
- I7: with_env_var → environment.TEAMOCIL=1 for v0.x; cmd_separator
  and clear warn (clear builder support deferred).

Total Phase 1 commits: 14 (7 source + 7 tests).
ref: notes/plan.md Phase 1
why: Phase 1 of the tmuxinator-parity branch (PR #1014) is behavior-
changing for users importing tmuxinator/teamocil configs. Document
what changed so upgraders know what to expect.

what: Adds a "Behavior changes — tmuxinator/teamocil import" section to
the upcoming-release placeholder, summarizing all 7 import-side fixes
and the v1.x teamocil format support.

Also drops a stray blank line in tests/workspace/test_import_teamocil.py
left by an autosquash conflict resolution (cosmetic, ruff-format).
why: Tmuxinator's `synchronize` key turns on `synchronize-panes` for a
window. tmuxp silently ignored it. With this change, configs that import
from tmuxinator preserve the sync semantics.

what:
- builder.iter_create_windows: when `synchronize` is True or "before",
  call `window.set_option("synchronize-panes", "on")` after the
  window's `options` block is applied. Pane commands run with sync
  enabled.
- builder.config_after_window: when `synchronize` is "after", call the
  same option after pane commands complete. Matches tmuxinator's
  recommended modern usage (true/before are deprecated upstream but
  still accepted).
- import_tmuxinator: pass `synchronize` through unchanged on the
  window dict so the builder picks it up.

ref: notes/plan.md T1
why: Lock in T1 behavior across before/after/omitted/true.

what:
- 3 new fixtures (synchronize_before/after/omitted.yaml).
- Parametrized test_synchronize_panes asserts
  `window.show_option('synchronize-panes')` is truthy after build for
  before/after, falsy when omitted.
- test_synchronize_panes_true_treated_as_before locks in the
  true/before equivalence inline (no fixture needed).

ref: notes/plan.md T1
… pane

why: teamocil's `filters.after` (and the I3-fixed importer) emits
`shell_command_after` on a window dict, but tmuxp silently ignored it.
Builder now reads the key and sends each command to every pane after
the main `shell_command` block runs.

what:
- builder.config_after_window: iterate
  `window_config.get("shell_command_after", [])` and for each command
  call `pane.send_keys(cmd, suppress_history=False)` on every pane in
  the window. Window-level only — no trickle to/from session, matching
  teamocil's filter semantics.

ref: notes/plan.md T3
why: Lock in the new builder behavior — the after-commands must
actually appear in every pane's output.

what:
- New fixture shell_command_after.yaml: 1 window with
  shell_command_after: [echo TMUXP_T3_MARKER] and 2 panes.
- New test_shell_command_after_runs_in_each_pane uses retry_until on
  pane.capture-pane to check for the marker in each pane.
- @pytest.mark.flaky(reruns=5) since send_keys output capture is
  timing-sensitive (matches existing test_window_options_after).

ref: notes/plan.md T3
why: tmuxinator users have `--no-pre-window` to skip per-pane prep
commands during debugging. tmuxp's equivalent: skip the trickle of
shell_command_before from session/window/pane levels into each pane's
shell_command list.

what:
- New `--no-shell-command-before` CLI flag on `tmuxp load`
  (action=store_true, default False). Added to CLILoadNamespace too.
- load_workspace gains a `no_shell_command_before` kwarg which is
  forwarded to loader.trickle().
- loader.trickle now accepts a keyword-only `no_shell_command_before`
  parameter; when True, skip the entire shell_command_before
  propagation block for every pane.
- Behavior difference from tmuxinator's --no-pre-window noted in the
  trickle docstring: tmuxinator's flag only suppresses pre_window
  proper, while tmuxp's flag is broader (skips all
  shell_command_before regardless of source).

ref: notes/plan.md T7
why: The T7 source commit (`a97730a8`) added the kwarg to trickle's
signature but a stale edit lost the actual `if not no_shell_command_before:`
guard around the prepend block. The kwarg was a no-op until now.

what:
- loader.trickle: wrap the three `shell_command_before` extend calls
  in `if not no_shell_command_before:` so the flag actually skips
  propagation.
- tests/workspace/test_config.py: add
  test_trickle_no_shell_command_before_skips_propagation covering both
  flag values; deepcopy between calls since trickle mutates nested
  dicts.

ref: notes/plan.md T7
why: tmuxinator's named-pane syntax sets both a pane title and its
command (assets/template.erb:54, pane.rb:35-39). tmuxp had no equivalent.
With L1 (Pane.set_title) shipped in libtmux v0.55.0, the builder can
now wire it through.

what:
- builder.build session-level options block: read enable_pane_titles
  (toggle), pane_title_position (default "top"), pane_title_format
  (default "#{pane_index}: #{pane_title}"). Apply via
  session.set_option("pane-border-status"/"pane-border-format").
- builder.iter_create_panes: after send_keys, before focus handling,
  call pane.set_title(pane_config["title"]) when title is present.
  Title is stored on the pane regardless of border-status (per tmux
  cmd-select-pane.c:215-221).
- import_tmuxinator: new _normalize_tmuxinator_pane helper translates
  tmuxinator's named-pane shorthand {name: cmd} into
  {title: name, shell_command: [cmd]} so the builder picks it up.

ref: notes/plan.md T2
why: Lock in T2 behavior. pane-border-status / pane-border-format are
window-scope options; the initial source landed without global_=True
which silently no-op'd at session level (tests caught this).

what:
- builder: set pane-border-status / pane-border-format with
  global_=True so the values apply across every window in the
  session (matches tmuxinator's `set-option -g`).
- New fixture pane_titles.yaml.
- 3 new tests: explicit position+format, per-pane title verified via
  display-message format query, defaults-only path. All read options
  via show_option(..., global_=True).

ref: notes/plan.md T2
why: tmuxinator's hook system fires shell commands at well-defined
phases (template.erb:14, hooks/project.rb:1-44). tmuxp had `before_script`
covering only the first-start case. With T6 the full hook set lands.

what:
- util.run_lifecycle_hook: new helper accepting str | list[str] | None.
  Iterates list values sequentially and calls run_before_script for
  each item. No shell=True (matches before_script's semantics — users
  wrap shell logic in script files). Aborts list iteration on first
  non-zero exit code.
- builder.build: after `before_workspace_builder` plugin and before
  `before_script`, fire on_project_start (always), then on_project_restart
  (when append=True) or on_project_first_start (otherwise).
- Hooks share the same `cwd` resolution (session start_directory) and
  output callback (on_script_output) as before_script.
- on_project_exit and on_project_stop wire in via cli/load.py and
  cli/stop.py respectively in subsequent commits.

ref: notes/plan.md T6
why: Lock in the T6 helper's contract — string vs list, sequential
execution, abort-on-failure, exception propagation.

what:
- test_run_lifecycle_hook_none_is_noop: None value returns 0.
- test_run_lifecycle_hook_string: single string is run once.
- test_run_lifecycle_hook_list_runs_each: list iterates in order
  (verified by appending to a log file).
- test_run_lifecycle_hook_aborts_on_first_failure: when an item
  exits non-zero, BeforeLoadScriptError raises and remaining items
  are not invoked.
- test_run_lifecycle_hook_propagates_not_found: missing script path
  raises BeforeLoadScriptNotExists (same as run_before_script).

ref: notes/plan.md T6
why: tmuxinator ships new, copy, delete, and implode for workspace
config file management (cli.rb:78-457). tmuxp had only `edit`. This
brings parity for the full lifecycle of workspace files.

what:
- New module src/tmuxp/cli/manage.py groups the 4 commands so the
  shared file-IO helpers (_confirm, _config_path, _open_in_editor,
  _ensure_dir) live in one place. Each command has its own typed
  Namespace, subparser builder, and entry function — same shape as
  the existing per-command modules (load.py, freeze.py).
- new <name>: writes a starter YAML to the user's workspace dir if
  missing, then opens in $EDITOR.
- copy <src> <dst>: validates src via find_workspace_file, copies to
  dst path, prompts on overwrite (unless -y), opens dst in $EDITOR.
- delete <name>...: per-name confirmation prompt (unless -y),
  unlinks each. Skips non-existent with a warning.
- implode: deletes ALL tmuxp config directories (legacy ~/.tmuxp +
  XDG ~/.config/tmuxp). Single global confirmation (unless -y).
- Wired into cli/__init__.py: imports, subparsers, dispatch, and the
  CLISubparserName Literal.

ref: notes/plan.md T10
why: Lock in T10 behavior across all 4 management commands.

what:
- configdir/no_editor fixtures: monkeypatch TMUXP_CONFIGDIR to tmp_path
  and replace subprocess.call (the $EDITOR invocation) with a no-op
  so tests run headless.
- new: writes starter YAML when missing; preserves an existing file.
- copy: duplicates src content to dst; aborts when user declines
  overwrite (input mocked).
- delete: -y removes the file; missing name doesn't raise.
- implode: removes every dir from _implode_dirs() with -y; aborts
  when user declines (dir + content survive).

ref: notes/plan.md T10
why: tmuxinator pre-processes its YAML through Erubi (full Ruby) so
config files can interpolate runtime values. tmuxp gets the equivalent
without any new dependency: a ~50-LOC stdlib regex-based engine that
handles only ${var} and ${var:-default} (no conditionals/loops/Ruby
escape hatches). Pre-YAML rendering matches tmuxinator's
ERB-before-YAML semantics.

what:
- New module src/tmuxp/_internal/template.py exporting render() and
  parse_cli_vars(). Doctests cover the canonical cases.
  UnresolvedVariableError subclasses KeyError so existing
  except-KeyError sites still catch.
- cli/load.py: new repeatable `-D KEY=VALUE` / `--var KEY=VALUE` flag.
  Plumbed through CLILoadNamespace, command_load, and load_workspace.
- load_workspace: when template_vars is non-empty AND the workspace
  file is YAML, read the raw text, render with strict=False, then
  hand to ConfigReader._load("yaml", rendered). JSON files are
  parsed as-is (escaping headaches not worth the marginal benefit).
- strict=False at the load site so unresolved ${HOME}-style refs flow
  through to loader.expandshell, preserving the existing env-var
  expansion behaviour.

ref: notes/plan.md T8
why: Lock in the T8 template engine's contract.

what:
- 12 RENDER_FIXTURES via NamedTuple parametrize (test_id-based ids):
  plain text, braced, defaults, defaults overridden, missing-strict
  raises, multiple substitutions, special chars in defaults,
  underscore names, bare $ untouched, env-var-like syntax, empty
  default, dash in default.
- test_render_non_strict_leaves_missing_alone +
  test_render_non_strict_still_substitutes_known: cover the load.py
  integration mode where unresolved vars flow to expandshell.
- test_render_default_does_not_apply_when_explicit_none and
  test_render_unresolved_error_is_keyerror_subclass: lock in the
  exception contract.
- 6 PARSE_FIXTURES for parse_cli_vars: empty, single, multiple,
  value-contains-equals (URL with query string), empty value,
  missing-equals raises ValueError.

ref: notes/plan.md T8
why: tmuxinator's `stop <project>` (cli.rb:300-322 + template-stop.erb)
fires the project's on_project_stop hook then kills the session. tmuxp
had no equivalent; users had to `tmux kill-session` directly which
skipped any cleanup hooks.

what:
- New module src/tmuxp/cli/stop.py with command_stop / CLIStopNamespace
  / create_stop_subparser, mirroring the existing per-command module
  shape (load.py, freeze.py).
- Resolves workspace by name via find_workspace_file, reads
  session_name + on_project_stop from the config, fires the hook (via
  T6's run_lifecycle_hook) then calls Server.kill_session.
- Idempotent: if no server is alive or session doesn't exist, prints
  a muted "nothing to stop" and returns without error.
- Wired into cli/__init__.py imports, subparsers, dispatch, and the
  CLISubparserName Literal.

Per template-stop.erb tmuxinator runs on_project_stop only (NOT
on_project_exit). tmuxp matches that.

ref: notes/plan.md T5
why: teamocil's --here lets users layer a workspace onto the tmux
session they're already attached to. tmuxp had no equivalent.

what:
- New --here flag on tmuxp load. Mutually exclusive with --append.
  Refuses to run outside a tmux session (TMUX env var must be set).
- When --here is set, force append=True so the existing append
  codepath handles the build (windows added to current session).
- args.here flows through CLILoadNamespace and load_workspace().

ref: notes/plan.md T4
why: Lock in the precondition checks the T4 source commit added to
load_workspace.

what:
- test_load_with_here_outside_tmux_errors: with TMUX env var unset,
  --here raises TmuxpException with the documented message.
- test_load_with_here_and_append_errors: with TMUX set (to bypass the
  outside-tmux check) but both --here and --append given, raises
  TmuxpException with "mutually exclusive".

The full --here-into-real-session smoke test requires running tmuxp
from inside an actual nested tmux, deferred to manual verification.

ref: notes/plan.md T4
why: tmuxinator's `debug` outputs the rendered shell script for preview.
tmuxp uses libtmux API calls instead of script generation, so the
honest-equivalent preview is to run the build against an isolated tmux
server (so the user's main tmux is untouched), surface libtmux's
existing DEBUG-level `tmux command dispatched` log at INFO, then kill
the temp server.

what:
- New `--dry-run` flag on `tmuxp load`. When set:
  - Generate a unique socket name (tmuxp-dryrun-<pid>-<rand>) and
    override args.socket_name so the temp server is isolated.
  - Force detached=True so we don't attach to a sandbox session.
  - Bump libtmux + tmuxp loggers to DEBUG so every libtmux call's
    `tmux command dispatched` record (libtmux common.py:284-289) is
    visible to the user.
  - Wrap the load_workspace call in try/finally; on exit, call
    Server(socket_name=...).kill_server() to clean up. Cleanup is
    best-effort (suppressed if the build raised early).
- args.dry_run flows through CLILoadNamespace.

Trade-off vs a true zero-execution dry-run: real tmux processes spawn
briefly but the user's main tmux server is never touched. That's the
honest-preview compromise documented in notes/plan.md T9.

ref: notes/plan.md T9
…ons resolved

why: This branch closes the remaining feature gaps with tmuxinator and
teamocil. Document the new config keys, CLI flags, and CLI commands in
user-facing language so the changelog reads like product news, not an
internal task list. Stamp the plan's gap section as historical so the
next reader knows it's design context, not current backlog.

what:
- CHANGES: new "New features — tmuxinator/teamocil parity" section above
  the existing "Behavior changes" entry. Grouped into "New config keys",
  "New CLI flags on tmuxp load", and "New CLI commands" with one bullet
  per feature describing what the user can now do.
- notes/plan.md: short status callout under "## tmuxp Limitations"
  pointing readers to CHANGES for current behavior. The gap-by-gap
  sections below remain intact as design notes for future maintainers.
@tony tony force-pushed the tmuxinator-parity branch from ba5d71f to 1268774 Compare May 9, 2026 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant