railmux 0.4.2.dev1__tar.gz → 0.4.2.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. {railmux-0.4.2.dev1/src/railmux.egg-info → railmux-0.4.2.dev2}/PKG-INFO +1 -1
  2. railmux-0.4.2.dev2/src/railmux/__init__.py +1 -0
  3. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/tmux_binding_manager.py +1 -1
  4. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/tmux_ctl.py +108 -24
  5. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ui/app.py +63 -2
  6. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2/src/railmux.egg-info}/PKG-INFO +1 -1
  7. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_app_codex_placeholder.py +60 -0
  8. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_tmux_ctl.py +49 -6
  9. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_tmux_integration.py +2 -0
  10. railmux-0.4.2.dev1/src/railmux/__init__.py +0 -1
  11. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/LICENSE +0 -0
  12. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/MANIFEST.in +0 -0
  13. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/README.md +0 -0
  14. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/pyproject.toml +0 -0
  15. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/setup.cfg +0 -0
  16. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/__main__.py +0 -0
  17. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/atomic_file.py +0 -0
  18. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/background_index.py +0 -0
  19. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/cli.py +0 -0
  20. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/codex_index.py +0 -0
  21. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/config.py +0 -0
  22. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/config_cli.py +0 -0
  23. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/diagnostic_contract.py +0 -0
  24. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/diagnostics.py +0 -0
  25. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/discovery.py +0 -0
  26. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/display_transport.py +0 -0
  27. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/entrypoint.py +0 -0
  28. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/fast_display_client.py +0 -0
  29. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/fast_display_history.py +0 -0
  30. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/fast_display_input.py +0 -0
  31. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/fast_display_protocol.py +0 -0
  32. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/fast_display_server.py +0 -0
  33. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/favorites.py +0 -0
  34. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/fuzzy.py +0 -0
  35. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/help_workspace.py +0 -0
  36. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/image_paste.py +0 -0
  37. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/launcher.py +0 -0
  38. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/legacy_sessions.py +0 -0
  39. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/local_clipboard.py +0 -0
  40. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/local_open.py +0 -0
  41. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/models.py +0 -0
  42. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/modes.py +0 -0
  43. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/mouse_manager.py +0 -0
  44. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/orphan_marker.py +0 -0
  45. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/pane_surface.py +0 -0
  46. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/path_codec.py +0 -0
  47. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/preview_pager.py +0 -0
  48. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/project_favorites.py +0 -0
  49. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/provider_paths.py +0 -0
  50. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/release_version.py +0 -0
  51. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/remote_config.py +0 -0
  52. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/renames.py +0 -0
  53. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/restart_state.py +0 -0
  54. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/runtime_config.py +0 -0
  55. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/scroll_agent.py +0 -0
  56. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/scroll_manager.py +0 -0
  57. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/selection_isolation.py +0 -0
  58. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/self_update.py +0 -0
  59. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/session_cache.py +0 -0
  60. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/session_index.py +0 -0
  61. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/session_lease.py +0 -0
  62. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/setting_contracts.py +0 -0
  63. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/settings.py +0 -0
  64. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ssh_args.py +0 -0
  65. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ssh_compat.py +0 -0
  66. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ssh_display_diagnostics.py +0 -0
  67. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ssh_doctor.py +0 -0
  68. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ssh_preflight.py +0 -0
  69. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/system_deps.py +0 -0
  70. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/terminal_screen.py +0 -0
  71. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/terminal_status.py +0 -0
  72. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/tmux_capabilities.py +0 -0
  73. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/tmux_health.py +0 -0
  74. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/tmux_server.py +0 -0
  75. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/tool_panes.py +0 -0
  76. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/transcript.py +0 -0
  77. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ui/__init__.py +0 -0
  78. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ui/_widgets.py +0 -0
  79. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ui/keymap.py +0 -0
  80. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ui/modals.py +0 -0
  81. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ui/projects_pane.py +0 -0
  82. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ui/running_pane.py +0 -0
  83. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ui/sessions_pane.py +0 -0
  84. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ui/sidebar.py +0 -0
  85. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ui/statusbar.py +0 -0
  86. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/ui/workspace.py +0 -0
  87. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/windows_attach_relay.py +0 -0
  88. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/windows_bootstrap.py +0 -0
  89. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/windows_install_log.py +0 -0
  90. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/windows_msys2.py +0 -0
  91. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/windows_pacman.py +0 -0
  92. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/windows_paths.py +0 -0
  93. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/windows_tmux_lifecycle.py +0 -0
  94. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux/windows_ui_transition.py +0 -0
  95. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux.egg-info/SOURCES.txt +0 -0
  96. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux.egg-info/dependency_links.txt +0 -0
  97. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux.egg-info/entry_points.txt +0 -0
  98. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux.egg-info/requires.txt +0 -0
  99. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/src/railmux.egg-info/top_level.txt +0 -0
  100. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/app_test_harness.py +0 -0
  101. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_agent_workspace.py +0 -0
  102. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_app_quit.py +0 -0
  103. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_app_restart_restore.py +0 -0
  104. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_app_running_recovery.py +0 -0
  105. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_atomic_file.py +0 -0
  106. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_background_index.py +0 -0
  107. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_cli.py +0 -0
  108. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_code_map.py +0 -0
  109. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_codex_delete.py +0 -0
  110. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_codex_index.py +0 -0
  111. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_codex_lineage.py +0 -0
  112. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_codex_transcript.py +0 -0
  113. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_config.py +0 -0
  114. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_config_cli.py +0 -0
  115. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_denested_transport_benchmark.py +0 -0
  116. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_diagnostics.py +0 -0
  117. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_discovery.py +0 -0
  118. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_display_transport.py +0 -0
  119. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_fast_display_bootstrap.py +0 -0
  120. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_fast_display_history.py +0 -0
  121. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_fast_display_protocol.py +0 -0
  122. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_fast_display_selection.py +0 -0
  123. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_fast_display_server.py +0 -0
  124. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_fast_display_terminal.py +0 -0
  125. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_focus_highlight.py +0 -0
  126. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_fullscreen_binding.py +0 -0
  127. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_fuzzy.py +0 -0
  128. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_help_agent.py +0 -0
  129. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_help_workspace.py +0 -0
  130. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_history_cleanup.py +0 -0
  131. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_image_paste.py +0 -0
  132. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_keymap.py +0 -0
  133. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_launcher.py +0 -0
  134. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_layout_profile.py +0 -0
  135. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_legacy_sessions.py +0 -0
  136. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_local_clipboard.py +0 -0
  137. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_local_open.py +0 -0
  138. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_modals.py +0 -0
  139. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_mode_selection.py +0 -0
  140. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_modes.py +0 -0
  141. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_mouse_manager.py +0 -0
  142. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_new_project.py +0 -0
  143. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_options.py +0 -0
  144. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_orphan_recovery.py +0 -0
  145. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_pane_surface.py +0 -0
  146. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_path_codec.py +0 -0
  147. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_preview_pager.py +0 -0
  148. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_project_favorites.py +0 -0
  149. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_projects_pane.py +0 -0
  150. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_provider_paths.py +0 -0
  151. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_release_notes.py +0 -0
  152. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_release_version.py +0 -0
  153. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_release_workflow.py +0 -0
  154. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_remote_config.py +0 -0
  155. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_renames.py +0 -0
  156. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_restart_state.py +0 -0
  157. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_running_pane.py +0 -0
  158. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_running_sort.py +0 -0
  159. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_runtime_config.py +0 -0
  160. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_scroll_agent.py +0 -0
  161. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_scroll_manager.py +0 -0
  162. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_selection_isolation.py +0 -0
  163. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_self_update.py +0 -0
  164. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_session_cache.py +0 -0
  165. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_session_index.py +0 -0
  166. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_session_lease.py +0 -0
  167. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_sessions_pane.py +0 -0
  168. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_setting_contracts.py +0 -0
  169. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_settings.py +0 -0
  170. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_sidebar.py +0 -0
  171. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_ssh_args.py +0 -0
  172. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_ssh_compat.py +0 -0
  173. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_ssh_display_benchmark.py +0 -0
  174. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_ssh_display_diagnostics.py +0 -0
  175. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_ssh_doctor.py +0 -0
  176. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_status.py +0 -0
  177. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_statusbar.py +0 -0
  178. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_system_deps.py +0 -0
  179. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_terminal_size.py +0 -0
  180. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_terminal_status.py +0 -0
  181. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_termux_local_touch.py +0 -0
  182. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_tmux_binding_manager.py +0 -0
  183. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_tmux_capabilities.py +0 -0
  184. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_tmux_health.py +0 -0
  185. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_tmux_server.py +0 -0
  186. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_tmux_status.py +0 -0
  187. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_tool_panes.py +0 -0
  188. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_transcript.py +0 -0
  189. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_utf8_state.py +0 -0
  190. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_web_demo_recorder.py +0 -0
  191. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_wheel_smoke.py +0 -0
  192. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_widgets.py +0 -0
  193. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_windows_attach_relay.py +0 -0
  194. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_windows_bootstrap.py +0 -0
  195. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_windows_install_log.py +0 -0
  196. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_windows_msys2.py +0 -0
  197. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_windows_pacman.py +0 -0
  198. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_windows_paths.py +0 -0
  199. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_windows_synchronized_screen.py +0 -0
  200. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_windows_tmux_lifecycle.py +0 -0
  201. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_windows_ui_transition.py +0 -0
  202. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_windows_wrapper_contract.py +0 -0
  203. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tests/test_yolo_prompt.py +0 -0
  204. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tools/denested_transport_benchmark.py +0 -0
  205. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tools/release_notes.py +0 -0
  206. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tools/wheel_smoke.py +0 -0
  207. {railmux-0.4.2.dev1 → railmux-0.4.2.dev2}/tools/windows_archive_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.2.dev1
3
+ Version: 0.4.2.dev2
4
4
  Summary: Terminal UI to navigate, resume, and start Claude Code and Codex sessions
5
5
  Author-email: Rightglow <zhang.taian@foxmail.com>
6
6
  License-Expression: MIT
@@ -0,0 +1 @@
1
+ __version__ = "0.4.2.dev2"
@@ -110,7 +110,7 @@ class SharedTmuxBindingManager:
110
110
  selection_hook_index = raw.get("selection_hook_index")
111
111
  prefix_valid = (
112
112
  isinstance(prefix_backup, dict)
113
- and set(prefix_backup) == {"Tab"}
113
+ and set(prefix_backup) in ({"Tab"}, {"Tab", "Tab.note"})
114
114
  and all(value is None or isinstance(value, str)
115
115
  for value in prefix_backup.values())
116
116
  )
@@ -29,6 +29,7 @@ class ServerSnapshot:
29
29
  sessions: frozenset[str]
30
30
  panes: frozenset[str]
31
31
  session_pids: tuple[tuple[str, int], ...] = ()
32
+ pane_titles: tuple[tuple[str, str, str], ...] = ()
32
33
 
33
34
  def pane_pid_for(self, session_name: str) -> int | None:
34
35
  """Return the first pane PID captured for *session_name*."""
@@ -37,6 +38,22 @@ class ServerSnapshot:
37
38
  return pid
38
39
  return None
39
40
 
41
+ def pane_title_for(self, pane_id: str) -> str | None:
42
+ """Return one live pane's terminal title from this same snapshot."""
43
+ titles = {
44
+ title for _session_name, candidate, title in self.pane_titles
45
+ if candidate == pane_id
46
+ }
47
+ return next(iter(titles)) if len(titles) == 1 else None
48
+
49
+ def pane_title_for_session(self, session_name: str) -> str | None:
50
+ """Return the title of a single-live-pane provider session."""
51
+ matches = [
52
+ title for candidate, _pane_id, title in self.pane_titles
53
+ if candidate == session_name
54
+ ]
55
+ return matches[0] if len(matches) == 1 else None
56
+
40
57
 
41
58
  @dataclass(frozen=True)
42
59
  class PaneIdentity:
@@ -277,7 +294,8 @@ def server_snapshot() -> ServerSnapshot | None:
277
294
  out = subprocess.check_output(
278
295
  [
279
296
  "tmux", "list-panes", "-a", "-F",
280
- "#{session_name}\t#{pane_id}\t#{pane_pid}\t#{pane_dead}",
297
+ "#{session_name}\t#{pane_id}\t#{pane_pid}\t#{pane_dead}"
298
+ "\t#{q:pane_title}",
281
299
  ],
282
300
  stderr=subprocess.DEVNULL,
283
301
  ).decode()
@@ -287,26 +305,45 @@ def server_snapshot() -> ServerSnapshot | None:
287
305
  sessions: set[str] = set()
288
306
  panes: set[str] = set()
289
307
  session_pids: dict[str, int] = {}
308
+ pane_titles: list[tuple[str, str, str]] = []
290
309
  for line in out.splitlines():
291
- fields = line.split("\t", 3)
292
- if len(fields) != 4 or not all(fields):
310
+ fields = line.split("\t", 4)
311
+ if (len(fields) != 5 or not fields[0] or not fields[1]
312
+ or not fields[3]):
293
313
  return None
294
- session_name, pane_id, raw_pid, pane_dead = fields
314
+ session_name, pane_id, raw_pid, pane_dead, raw_title = fields
315
+ if raw_title:
316
+ try:
317
+ decoded = shlex.split(raw_title)
318
+ except ValueError:
319
+ return None
320
+ if len(decoded) != 1:
321
+ return None
322
+ pane_title = decoded[0]
323
+ else:
324
+ pane_title = ""
295
325
  if pane_dead not in {"0", "1"}:
296
326
  return None
327
+ if pane_dead == "1":
328
+ # tmux 3.8 renders pane_pid as empty for a dead remain-on-exit
329
+ # pane. It is excluded from this live snapshot, so no PID is
330
+ # required here.
331
+ continue
297
332
  try:
298
333
  pane_pid = int(raw_pid)
299
334
  except ValueError:
300
335
  return None
301
- if pane_dead == "1":
302
- continue
336
+ if pane_pid <= 0:
337
+ return None
303
338
  sessions.add(session_name)
304
339
  panes.add(pane_id)
305
340
  session_pids.setdefault(session_name, pane_pid)
341
+ pane_titles.append((session_name, pane_id, pane_title))
306
342
  return ServerSnapshot(
307
343
  frozenset(sessions),
308
344
  frozenset(panes),
309
345
  tuple(sorted(session_pids.items())),
346
+ tuple(sorted(pane_titles)),
310
347
  )
311
348
 
312
349
 
@@ -653,13 +690,20 @@ def pane_identity(pane_id: str) -> PaneIdentity | None:
653
690
  fields = raw.split("\t")
654
691
  if len(fields) != 8:
655
692
  return None
693
+ dead = fields[5] == "1"
694
+ # tmux 3.8 leaves ``pane_pid`` empty after a pane dies. Keep zero as
695
+ # an internal dead-pane sentinel so callers can still identify and
696
+ # reap the exact remain-on-exit pane; live panes retain positive PIDs.
697
+ pane_pid = 0 if dead and not fields[1] else int(fields[1])
698
+ if pane_pid < 0 or (not dead and pane_pid == 0):
699
+ return None
656
700
  return PaneIdentity(
657
701
  pane_id=fields[0],
658
- pane_pid=int(fields[1]),
702
+ pane_pid=pane_pid,
659
703
  session_name=fields[2],
660
704
  session_id=fields[3],
661
705
  window_id=fields[4],
662
- dead=fields[5] == "1",
706
+ dead=dead,
663
707
  width=int(fields[6]),
664
708
  height=int(fields[7]),
665
709
  )
@@ -735,17 +779,24 @@ def _tmux_state_snapshot(
735
779
  ):
736
780
  return None
737
781
  attached = int(attached_raw)
782
+ dead = dead_raw == "1"
783
+ pane_pid = (
784
+ 0 if dead and not pane_pid_raw else int(pane_pid_raw)
785
+ )
738
786
  pane = PaneIdentity(
739
787
  pane_id=pane_id,
740
- pane_pid=int(pane_pid_raw),
788
+ pane_pid=pane_pid,
741
789
  session_name=actual_name,
742
790
  session_id=session_id,
743
791
  window_id=window_id,
744
- dead=dead_raw == "1",
792
+ dead=dead,
745
793
  width=int(width_raw),
746
794
  height=int(height_raw),
747
795
  )
748
- if attached < 0 or pane.pane_pid <= 0 or pane.width <= 0 or pane.height <= 0:
796
+ if (attached < 0
797
+ or (pane.dead and pane.pane_pid < 0)
798
+ or (not pane.dead and pane.pane_pid <= 0)
799
+ or pane.width <= 0 or pane.height <= 0):
749
800
  return None
750
801
  key = (actual_name, session_id)
751
802
  group = grouped.setdefault(
@@ -3221,6 +3272,37 @@ def read_prefix_target_binding() -> PrefixTargetBindingBackup:
3221
3272
  return {_PREFIX_TARGET_KEY: _read_key_binding("prefix", _PREFIX_TARGET_KEY)}
3222
3273
 
3223
3274
 
3275
+ def _prefix_target_note() -> str | None:
3276
+ """Return Prefix-Tab's descriptive note without its rendered key prefix."""
3277
+ if tmux_version() < (3, 2):
3278
+ return None
3279
+ try:
3280
+ raw = subprocess.check_output(
3281
+ ["tmux", "list-keys", "-N", "-T", "prefix", "-1",
3282
+ _PREFIX_TARGET_KEY],
3283
+ stderr=subprocess.DEVNULL,
3284
+ ).decode().strip()
3285
+ except (OSError, subprocess.CalledProcessError, UnicodeError):
3286
+ return None
3287
+ fields = raw.split(None, 2)
3288
+ return fields[2] if len(fields) == 3 else None
3289
+
3290
+
3291
+ def _tmux_38_default_prefix_target(binding: str, note: str | None) -> bool:
3292
+ """Whether *binding* is tmux 3.8's annotated stock Prefix-Tab action."""
3293
+ if tmux_version() < (3, 8) or note != "Switch to a window":
3294
+ return False
3295
+ try:
3296
+ command = tuple(shlex.split(_binding_command(binding)))
3297
+ except ValueError:
3298
+ return False
3299
+ return command == (
3300
+ "new-pane", "-E", "-X", "0", "-Y", "0", "-x", "75%", "-y",
3301
+ "30%", ";", "move-pane", "-P", "bottom-centre", ";",
3302
+ "switch-mode", "-kw",
3303
+ )
3304
+
3305
+
3224
3306
  def prepare_prefix_target_binding() -> PrefixTargetBindingBackup | None:
3225
3307
  if tmux_version() < (2, 7):
3226
3308
  return None
@@ -3229,8 +3311,9 @@ def prepare_prefix_target_binding() -> PrefixTargetBindingBackup | None:
3229
3311
  if binding and _PREFIX_TARGET_MARKER in binding:
3230
3312
  return None
3231
3313
  # A server-global wrapper cannot preserve repeat semantics only outside
3232
- # Railmux. Notes are likewise absent from normal list-keys output and would
3233
- # be lost on restoration, so leave either kind of user binding untouched.
3314
+ # Railmux. Preserve tmux 3.8's known stock note explicitly; leave unknown
3315
+ # annotated user bindings untouched rather than risking lossy restoration.
3316
+ note: str | None = None
3234
3317
  if binding is not None:
3235
3318
  if re.match(r"^bind-key\s+-r\b", binding):
3236
3319
  return None
@@ -3239,17 +3322,10 @@ def prepare_prefix_target_binding() -> PrefixTargetBindingBackup | None:
3239
3322
  except ValueError:
3240
3323
  return None
3241
3324
  if tmux_version() >= (3, 2):
3242
- try:
3243
- note = subprocess.check_output(
3244
- ["tmux", "list-keys", "-N", "-T", "prefix", "-1",
3245
- _PREFIX_TARGET_KEY],
3246
- stderr=subprocess.DEVNULL,
3247
- ).decode().strip()
3248
- except (subprocess.CalledProcessError, FileNotFoundError):
3249
- return None
3250
- if note:
3325
+ note = _prefix_target_note()
3326
+ if note and not _tmux_38_default_prefix_target(binding, note):
3251
3327
  return None
3252
- return backup
3328
+ return {**backup, "Tab.note": note}
3253
3329
 
3254
3330
 
3255
3331
  def set_prefix_target_binding(
@@ -3345,7 +3421,15 @@ def restore_prefix_target_binding(
3345
3421
  prefix="railmux-prefix-target-", suffix=".conf")
3346
3422
  try:
3347
3423
  with os.fdopen(fd, "w", encoding="utf-8") as fh:
3348
- fh.write(original + "\n")
3424
+ note = backup.get("Tab.note")
3425
+ restored = original
3426
+ if note:
3427
+ restored = original.replace(
3428
+ "bind-key",
3429
+ f"bind-key -N {shlex.quote(note)}",
3430
+ 1,
3431
+ )
3432
+ fh.write(restored + "\n")
3349
3433
  subprocess.check_call(
3350
3434
  ["tmux", "source-file", path],
3351
3435
  stdout=subprocess.DEVNULL,
@@ -9193,7 +9193,7 @@ class App:
9193
9193
  # clicking it spawns a duplicate session; and `force_projects` above
9194
9194
  # stays stuck True, defeating the 3s project-scan cache. Codex
9195
9195
  # resolution must run too or neither ever clears.
9196
- self._resolve_placeholders(projects)
9196
+ self._resolve_placeholders(projects, server)
9197
9197
  running_ids = self._running_session_ids()
9198
9198
  if self._selected_project is not None:
9199
9199
  matched = next((p for p in projects if p.encoded_name == self._selected_project.encoded_name), None)
@@ -10002,7 +10002,66 @@ class App:
10002
10002
  ]
10003
10003
  self._running_pane.set_running(entries)
10004
10004
 
10005
- def _resolve_placeholders(self, projects: list[Project]) -> None:
10005
+ @staticmethod
10006
+ def _codex_terminal_session_title(
10007
+ pane_title: str | None,
10008
+ project: Project,
10009
+ ) -> str | None:
10010
+ """Extract a bounded Codex session title from its terminal title.
10011
+
10012
+ Current Codex releases publish ``<title> | <workspace>`` and prefix
10013
+ the title with one braille spinner cell while a turn is active. Treat
10014
+ every other shape as untrusted display text: a shell or tool running
10015
+ in the pane can also set the terminal title.
10016
+ """
10017
+ if pane_title is None:
10018
+ return None
10019
+ value = pane_title.strip()
10020
+ if value and "\u2800" <= value[0] <= "\u28ff":
10021
+ value = value[1:].lstrip()
10022
+ suffix = f" | {project.display_name}"
10023
+ if not value.endswith(suffix):
10024
+ return None
10025
+ title = value[:-len(suffix)].strip()
10026
+ if not title or len(title) > 512 or not title.isprintable():
10027
+ return None
10028
+ return title
10029
+
10030
+ def _refresh_codex_placeholder_label(
10031
+ self,
10032
+ running: _Running,
10033
+ project: Project,
10034
+ server: tmux_ctl.ServerSnapshot | None,
10035
+ ) -> None:
10036
+ """Refresh only an unresolved placeholder's human-readable label.
10037
+
10038
+ New Codex app-server releases no longer keep their rollout JSONL open
10039
+ in the pane process tree, so exact UUID promotion can correctly remain
10040
+ unresolved. The terminal title still belongs to this exact pane and
10041
+ is safe as a *display* hint because it is read from the exact owned
10042
+ pane and accepted only in Codex's workspace-qualified title shape. It
10043
+ deliberately does not promote identity: Resume/Delete keep their
10044
+ existing fail-closed rules.
10045
+ """
10046
+ if server is None or running.session_type != "codex":
10047
+ return
10048
+ real_pane = self._display_transport().displayed_real_pane(
10049
+ running.tmux_name)
10050
+ pane_title = (
10051
+ server.pane_title_for(real_pane)
10052
+ if isinstance(real_pane, str) and real_pane
10053
+ else server.pane_title_for_session(running.tmux_name)
10054
+ )
10055
+ title = self._codex_terminal_session_title(pane_title, project)
10056
+ if title is None:
10057
+ return
10058
+ running.label = f"{project.display_name}/{title}"
10059
+
10060
+ def _resolve_placeholders(
10061
+ self,
10062
+ projects: list[Project],
10063
+ server: tmux_ctl.ServerSnapshot | None = None,
10064
+ ) -> None:
10006
10065
  """Re-key any `__new__-N` placeholder to its real session_id.
10007
10066
 
10008
10067
  For each live placeholder, look at its project's sessions and pick the
@@ -10059,6 +10118,8 @@ class App:
10059
10118
  and s.session_id not in r.pre_launch_ids # another process's (#12)
10060
10119
  and s.last_mtime + 1.0 >= r.created_at
10061
10120
  ]
10121
+ self._refresh_codex_placeholder_label(
10122
+ r, project, server)
10062
10123
  # #12: prefer EXACT child→rollout correlation over the heuristic. The
10063
10124
  # codex process in this placeholder's pane holds its own rollout open;
10064
10125
  # its filename UUID is the exact session_id. This defeats the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.2.dev1
3
+ Version: 0.4.2.dev2
4
4
  Summary: Terminal UI to navigate, resume, and start Claude Code and Codex sessions
5
5
  Author-email: Rightglow <zhang.taian@foxmail.com>
6
6
  License-Expression: MIT
@@ -483,6 +483,66 @@ def test_resolve_placeholders_empty_correlation_waits_not_fallback(monkeypatch):
483
483
  assert unrelated not in app._running # heuristic did NOT fire
484
484
 
485
485
 
486
+ @pytest.mark.parametrize(
487
+ "pane_title",
488
+ (
489
+ "Real session | test-proj",
490
+ "\u2827 Real session | test-proj",
491
+ ),
492
+ )
493
+ def test_unresolved_codex_placeholder_uses_exact_pane_title_for_label(
494
+ pane_title,
495
+ ):
496
+ """A newer Codex may publish no rollout fd, but its exact pane can still
497
+ replace the stale ``(new)`` display label without weakening UUID binding."""
498
+ proj = _project()
499
+ running = _Running(
500
+ key="__new__-tok-1",
501
+ tmux_name="cx-new----tok-1",
502
+ label="test-proj/(new)",
503
+ project=proj,
504
+ placeholder_path=proj.real_path,
505
+ created_at=999.0,
506
+ session_type="codex",
507
+ allow_heuristic_resolution=False,
508
+ )
509
+ app = App.__new__(App)
510
+ app._running = {running.key: running}
511
+ app._codex_index = MagicMock()
512
+ # The pane title is available before the background index publishes the
513
+ # rollout, so display refresh must not depend on candidate discovery.
514
+ app._codex_index.sessions_for_cwd.return_value = []
515
+ app._correlate_codex_rollout = MagicMock(return_value=set())
516
+ app._display_transport_manager = MagicMock()
517
+ app._display_transport_manager.displayed_real_pane.return_value = None
518
+ server = tmux_ctl.ServerSnapshot(
519
+ sessions=frozenset({running.tmux_name}),
520
+ panes=frozenset({"%7"}),
521
+ pane_titles=((running.tmux_name, "%7", pane_title),),
522
+ )
523
+
524
+ app._resolve_placeholders([proj], server)
525
+
526
+ assert running.key in app._running
527
+ assert running.is_placeholder
528
+ assert running.label == "test-proj/Real session"
529
+
530
+
531
+ @pytest.mark.parametrize(
532
+ "pane_title",
533
+ (
534
+ "bash",
535
+ "Real session | another-project",
536
+ "Real\n session | test-proj",
537
+ ),
538
+ )
539
+ def test_unresolved_codex_placeholder_rejects_untrusted_pane_title(
540
+ pane_title,
541
+ ):
542
+ proj = _project()
543
+ assert App._codex_terminal_session_title(pane_title, proj) is None
544
+
545
+
486
546
  def test_correlate_codex_rollout_fails_closed_without_config(monkeypatch):
487
547
  """A helper failure on procfs must wait, never unlock the heuristic."""
488
548
  monkeypatch.setattr(tmux_ctl, "proc_fs_available", lambda: True)
@@ -195,10 +195,10 @@ def test_copy_to_clipboard_rejects_unsafe_or_unbounded_payload():
195
195
 
196
196
  def test_server_snapshot_collects_sessions_and_panes():
197
197
  output = (
198
- "cc-one\t%1\t101\t0\n"
199
- "cc-one\t%2\t102\t1\n"
200
- "cc-two\t%3\t201\t0\n"
201
- "cc-dead\t%4\t301\t1\n"
198
+ "cc-one\t%1\t101\t0\tReview\\ code\\ \\|\\ repo\n"
199
+ "cc-one\t%2\t102\t1\told\n"
200
+ "cc-two\t%3\t201\t0\tSecond\n"
201
+ "cc-dead\t%4\t301\t1\tdead\n"
202
202
  )
203
203
  with patch("railmux.tmux_ctl.in_tmux", return_value=True), \
204
204
  _mock_check_output(output) as call:
@@ -210,12 +210,30 @@ def test_server_snapshot_collects_sessions_and_panes():
210
210
  assert snapshot.session_pids == (("cc-one", 101), ("cc-two", 201))
211
211
  assert snapshot.pane_pid_for("cc-one") == 101
212
212
  assert snapshot.pane_pid_for("missing") is None
213
+ assert snapshot.pane_title_for("%1") == "Review code | repo"
214
+ assert snapshot.pane_title_for_session("cc-one") == "Review code | repo"
215
+ assert snapshot.pane_title_for_session("missing") is None
213
216
  assert call.call_args.args[0] == [
214
217
  "tmux", "list-panes", "-a", "-F",
215
- "#{session_name}\t#{pane_id}\t#{pane_pid}\t#{pane_dead}",
218
+ "#{session_name}\t#{pane_id}\t#{pane_pid}\t#{pane_dead}"
219
+ "\t#{q:pane_title}",
216
220
  ]
217
221
 
218
222
 
223
+ def test_server_snapshot_accepts_tmux_38_empty_dead_pane_pid():
224
+ output = (
225
+ "cc-live\t%1\t101\t0\tlive\n"
226
+ "cc-dead\t%2\t\t1\tdead\n"
227
+ )
228
+ with patch("railmux.tmux_ctl.in_tmux", return_value=True), \
229
+ _mock_check_output(output):
230
+ snapshot = server_snapshot()
231
+
232
+ assert snapshot is not None
233
+ assert snapshot.sessions == frozenset({"cc-live"})
234
+ assert snapshot.panes == frozenset({"%1"})
235
+
236
+
219
237
  def test_server_snapshot_rejects_malformed_output():
220
238
  with patch("railmux.tmux_ctl.in_tmux", return_value=True), \
221
239
  _mock_check_output("cc-one %1\n"):
@@ -224,7 +242,7 @@ def test_server_snapshot_rejects_malformed_output():
224
242
 
225
243
  def test_server_snapshot_rejects_unknown_dead_state():
226
244
  with patch("railmux.tmux_ctl.in_tmux", return_value=True), \
227
- _mock_check_output("cc-one\t%1\t101\tmaybe\n"):
245
+ _mock_check_output("cc-one\t%1\t101\tmaybe\ttitle\n"):
228
246
  assert server_snapshot() is None
229
247
 
230
248
 
@@ -289,6 +307,15 @@ def test_pane_identity_parses_stable_location_outside_tmux():
289
307
  "tmux", "display-message", "-p", "-t", "%7"]
290
308
 
291
309
 
310
+ def test_pane_identity_accepts_tmux_38_empty_dead_pane_pid():
311
+ output = "%7\t\tagent\t$2\t@3\t1\t91\t31\n"
312
+ with _mock_check_output(output):
313
+ pane = tmux_ctl.pane_identity("%7")
314
+
315
+ assert pane == tmux_ctl.PaneIdentity(
316
+ "%7", 0, "agent", "$2", "@3", True, 91, 31)
317
+
318
+
292
319
  def test_session_topology_requires_exact_server_results(monkeypatch):
293
320
  output = b"agent\t$2\t0\t@3\t%7\t4242\t0\t91\t31\n"
294
321
  check = MagicMock(return_value=output)
@@ -1142,6 +1169,22 @@ def test_prefix_target_binding_rejects_unpreservable_user_binding():
1142
1169
  {"Tab": "not replayable tmux config"}, "owner123")
1143
1170
 
1144
1171
 
1172
+ def test_prefix_target_binding_accepts_tmux_38_stock_note():
1173
+ stock = (
1174
+ 'bind-key -T prefix Tab new-pane -E -X 0 -Y 0 -x "75%" '
1175
+ '-y "30%" \\; move-pane -P bottom-centre \\; switch-mode -kw'
1176
+ )
1177
+ with patch.object(tmux_ctl, "tmux_version", return_value=(3, 8)), \
1178
+ patch.object(tmux_ctl, "read_prefix_target_binding",
1179
+ return_value={"Tab": stock}), \
1180
+ patch.object(tmux_ctl, "_prefix_target_note",
1181
+ return_value="Switch to a window"):
1182
+ assert tmux_ctl.prepare_prefix_target_binding() == {
1183
+ "Tab": stock,
1184
+ "Tab.note": "Switch to a window",
1185
+ }
1186
+
1187
+
1145
1188
  def test_unset_window_user_option_requires_exact_owner_value():
1146
1189
  with _mock_check_output("%1"), _mock_check_call() as call:
1147
1190
  assert tmux_ctl.unset_window_user_option_if_value(
@@ -3039,6 +3039,7 @@ def test_real_tmux_binding_manager_round_trip_and_user_reload(
3039
3039
  )
3040
3040
  original = tmux_ctl.read_root_function_bindings()
3041
3041
  original_prefix_tab = tmux_ctl.read_prefix_target_binding()
3042
+ original_prefix_note = tmux_ctl._prefix_target_note()
3042
3043
  original_right_click = tmux_ctl.read_root_right_click_binding()
3043
3044
  original_status_click = tmux_ctl.read_root_status_click_binding()
3044
3045
  original_termux_tap = tmux_ctl.read_root_termux_tap_binding()
@@ -3229,6 +3230,7 @@ def test_real_tmux_binding_manager_round_trip_and_user_reload(
3229
3230
  assert restored["F8"] == original["F8"]
3230
3231
  assert restored["F9"] is not None and "new-user-f9" in restored["F9"]
3231
3232
  assert tmux_ctl.read_prefix_target_binding() == original_prefix_tab
3233
+ assert tmux_ctl._prefix_target_note() == original_prefix_note
3232
3234
  assert tmux_ctl.read_root_right_click_binding() == original_right_click
3233
3235
  assert tmux_ctl.read_root_status_click_binding() == original_status_click
3234
3236
  assert tmux_ctl.read_root_termux_tap_binding() == original_termux_tap
@@ -1 +0,0 @@
1
- __version__ = "0.4.2.dev1"
File without changes
File without changes
File without changes
File without changes