railmux 0.4.1.dev1__tar.gz → 0.4.1.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 (205) hide show
  1. {railmux-0.4.1.dev1/src/railmux.egg-info → railmux-0.4.1.dev2}/PKG-INFO +1 -1
  2. railmux-0.4.1.dev2/src/railmux/__init__.py +1 -0
  3. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fast_display_input.py +173 -0
  4. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_attach_relay.py +49 -5
  5. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2/src/railmux.egg-info}/PKG-INFO +1 -1
  6. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_selection.py +112 -0
  7. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_attach_relay.py +74 -0
  8. railmux-0.4.1.dev1/src/railmux/__init__.py +0 -1
  9. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/LICENSE +0 -0
  10. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/MANIFEST.in +0 -0
  11. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/README.md +0 -0
  12. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/pyproject.toml +0 -0
  13. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/setup.cfg +0 -0
  14. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/__main__.py +0 -0
  15. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/atomic_file.py +0 -0
  16. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/background_index.py +0 -0
  17. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/cli.py +0 -0
  18. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/codex_index.py +0 -0
  19. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/config.py +0 -0
  20. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/config_cli.py +0 -0
  21. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/diagnostic_contract.py +0 -0
  22. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/diagnostics.py +0 -0
  23. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/discovery.py +0 -0
  24. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/display_transport.py +0 -0
  25. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/entrypoint.py +0 -0
  26. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fast_display_client.py +0 -0
  27. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fast_display_history.py +0 -0
  28. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fast_display_protocol.py +0 -0
  29. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fast_display_server.py +0 -0
  30. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/favorites.py +0 -0
  31. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fuzzy.py +0 -0
  32. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/help_workspace.py +0 -0
  33. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/launcher.py +0 -0
  34. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/legacy_sessions.py +0 -0
  35. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/local_clipboard.py +0 -0
  36. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/local_open.py +0 -0
  37. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/models.py +0 -0
  38. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/modes.py +0 -0
  39. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/mouse_manager.py +0 -0
  40. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/orphan_marker.py +0 -0
  41. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/pane_surface.py +0 -0
  42. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/path_codec.py +0 -0
  43. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/preview_pager.py +0 -0
  44. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/project_favorites.py +0 -0
  45. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/provider_paths.py +0 -0
  46. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/release_version.py +0 -0
  47. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/remote_config.py +0 -0
  48. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/renames.py +0 -0
  49. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/restart_state.py +0 -0
  50. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/runtime_config.py +0 -0
  51. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/scroll_agent.py +0 -0
  52. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/scroll_manager.py +0 -0
  53. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/selection_isolation.py +0 -0
  54. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/self_update.py +0 -0
  55. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/session_cache.py +0 -0
  56. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/session_index.py +0 -0
  57. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/session_lease.py +0 -0
  58. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/setting_contracts.py +0 -0
  59. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/settings.py +0 -0
  60. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ssh_args.py +0 -0
  61. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ssh_compat.py +0 -0
  62. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ssh_display_diagnostics.py +0 -0
  63. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ssh_doctor.py +0 -0
  64. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ssh_preflight.py +0 -0
  65. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/system_deps.py +0 -0
  66. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/terminal_screen.py +0 -0
  67. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/terminal_status.py +0 -0
  68. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tmux_binding_manager.py +0 -0
  69. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tmux_capabilities.py +0 -0
  70. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tmux_ctl.py +0 -0
  71. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tmux_health.py +0 -0
  72. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tmux_server.py +0 -0
  73. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tool_panes.py +0 -0
  74. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/transcript.py +0 -0
  75. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/__init__.py +0 -0
  76. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/_widgets.py +0 -0
  77. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/app.py +0 -0
  78. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/keymap.py +0 -0
  79. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/modals.py +0 -0
  80. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/projects_pane.py +0 -0
  81. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/running_pane.py +0 -0
  82. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/sessions_pane.py +0 -0
  83. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/sidebar.py +0 -0
  84. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/statusbar.py +0 -0
  85. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/workspace.py +0 -0
  86. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_bootstrap.py +0 -0
  87. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_install_log.py +0 -0
  88. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_msys2.py +0 -0
  89. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_pacman.py +0 -0
  90. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_paths.py +0 -0
  91. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_tmux_lifecycle.py +0 -0
  92. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_ui_transition.py +0 -0
  93. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux.egg-info/SOURCES.txt +0 -0
  94. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux.egg-info/dependency_links.txt +0 -0
  95. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux.egg-info/entry_points.txt +0 -0
  96. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux.egg-info/requires.txt +0 -0
  97. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux.egg-info/top_level.txt +0 -0
  98. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/app_test_harness.py +0 -0
  99. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_agent_workspace.py +0 -0
  100. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_app_codex_placeholder.py +0 -0
  101. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_app_quit.py +0 -0
  102. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_app_restart_restore.py +0 -0
  103. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_app_running_recovery.py +0 -0
  104. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_atomic_file.py +0 -0
  105. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_background_index.py +0 -0
  106. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_cli.py +0 -0
  107. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_code_map.py +0 -0
  108. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_codex_delete.py +0 -0
  109. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_codex_index.py +0 -0
  110. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_codex_lineage.py +0 -0
  111. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_codex_transcript.py +0 -0
  112. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_config.py +0 -0
  113. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_config_cli.py +0 -0
  114. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_denested_transport_benchmark.py +0 -0
  115. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_diagnostics.py +0 -0
  116. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_discovery.py +0 -0
  117. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_display_transport.py +0 -0
  118. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_bootstrap.py +0 -0
  119. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_history.py +0 -0
  120. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_protocol.py +0 -0
  121. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_server.py +0 -0
  122. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_terminal.py +0 -0
  123. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_focus_highlight.py +0 -0
  124. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fullscreen_binding.py +0 -0
  125. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fuzzy.py +0 -0
  126. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_help_agent.py +0 -0
  127. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_help_workspace.py +0 -0
  128. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_history_cleanup.py +0 -0
  129. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_keymap.py +0 -0
  130. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_launcher.py +0 -0
  131. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_layout_profile.py +0 -0
  132. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_legacy_sessions.py +0 -0
  133. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_local_clipboard.py +0 -0
  134. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_local_open.py +0 -0
  135. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_modals.py +0 -0
  136. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_mode_selection.py +0 -0
  137. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_modes.py +0 -0
  138. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_mouse_manager.py +0 -0
  139. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_new_project.py +0 -0
  140. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_options.py +0 -0
  141. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_orphan_recovery.py +0 -0
  142. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_pane_surface.py +0 -0
  143. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_path_codec.py +0 -0
  144. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_preview_pager.py +0 -0
  145. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_project_favorites.py +0 -0
  146. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_projects_pane.py +0 -0
  147. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_provider_paths.py +0 -0
  148. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_release_notes.py +0 -0
  149. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_release_version.py +0 -0
  150. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_release_workflow.py +0 -0
  151. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_remote_config.py +0 -0
  152. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_renames.py +0 -0
  153. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_restart_state.py +0 -0
  154. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_running_pane.py +0 -0
  155. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_running_sort.py +0 -0
  156. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_runtime_config.py +0 -0
  157. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_scroll_agent.py +0 -0
  158. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_scroll_manager.py +0 -0
  159. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_selection_isolation.py +0 -0
  160. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_self_update.py +0 -0
  161. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_session_cache.py +0 -0
  162. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_session_index.py +0 -0
  163. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_session_lease.py +0 -0
  164. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_sessions_pane.py +0 -0
  165. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_setting_contracts.py +0 -0
  166. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_settings.py +0 -0
  167. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_sidebar.py +0 -0
  168. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_ssh_args.py +0 -0
  169. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_ssh_compat.py +0 -0
  170. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_ssh_display_benchmark.py +0 -0
  171. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_ssh_display_diagnostics.py +0 -0
  172. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_ssh_doctor.py +0 -0
  173. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_status.py +0 -0
  174. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_statusbar.py +0 -0
  175. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_system_deps.py +0 -0
  176. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_terminal_size.py +0 -0
  177. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_terminal_status.py +0 -0
  178. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_termux_local_touch.py +0 -0
  179. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_binding_manager.py +0 -0
  180. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_capabilities.py +0 -0
  181. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_ctl.py +0 -0
  182. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_health.py +0 -0
  183. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_integration.py +0 -0
  184. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_server.py +0 -0
  185. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_status.py +0 -0
  186. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tool_panes.py +0 -0
  187. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_transcript.py +0 -0
  188. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_utf8_state.py +0 -0
  189. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_web_demo_recorder.py +0 -0
  190. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_wheel_smoke.py +0 -0
  191. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_widgets.py +0 -0
  192. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_bootstrap.py +0 -0
  193. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_install_log.py +0 -0
  194. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_msys2.py +0 -0
  195. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_pacman.py +0 -0
  196. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_paths.py +0 -0
  197. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_synchronized_screen.py +0 -0
  198. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_tmux_lifecycle.py +0 -0
  199. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_ui_transition.py +0 -0
  200. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_wrapper_contract.py +0 -0
  201. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_yolo_prompt.py +0 -0
  202. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tools/denested_transport_benchmark.py +0 -0
  203. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tools/release_notes.py +0 -0
  204. {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tools/wheel_smoke.py +0 -0
  205. {railmux-0.4.1.dev1 → railmux-0.4.1.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.1.dev1
3
+ Version: 0.4.1.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.1.dev2"
@@ -550,6 +550,8 @@ _EMBEDDED_WINDOWS_PATH_RE = re.compile(
550
550
  r"(?:^|(?<=[^A-Za-z0-9_.@+~\\/-]))"
551
551
  r"(?:[A-Za-z]:[\\/]|\\\\[^\\/\s]+[\\/][^\\/\s]+(?:[\\/]|$))"
552
552
  )
553
+ _TOOL_COMMAND_START_RE = re.compile(r"^\s*[•●]\s+Ran\s+")
554
+ _TOOL_COMMAND_CONTINUATION_RE = re.compile(r"^\s*│\s?")
553
555
 
554
556
 
555
557
  def _clicked_character_index(
@@ -893,6 +895,169 @@ def _hard_wrapped_target(
893
895
  return best
894
896
 
895
897
 
898
+ def _decorated_tool_target(
899
+ rows: tuple[tuple[str | None, ...], ...],
900
+ row: int,
901
+ column: int,
902
+ *,
903
+ route: HistorySnapshot,
904
+ ) -> ClickTarget | None:
905
+ """Join a token split across Codex's decorated ``Ran`` command rows."""
906
+ if not 0 <= row < len(rows) or route.width <= 0:
907
+ return None
908
+
909
+ # A continuation glyph alone is not authority: it can occur in ordinary
910
+ # program output. Walk back through one bounded chain and require Codex's
911
+ # explicit ``Ran`` header before interpreting the prefixes as decoration.
912
+ block_first = row
913
+ while (
914
+ block_first > 0
915
+ and row - block_first < 7
916
+ and _TOOL_COMMAND_CONTINUATION_RE.match(_row_text(rows[block_first]))
917
+ ):
918
+ block_first -= 1
919
+ if _TOOL_COMMAND_START_RE.match(_row_text(rows[block_first])) is None:
920
+ return None
921
+
922
+ block_last = block_first
923
+ while (
924
+ block_last + 1 < len(rows)
925
+ and block_last - block_first < 7
926
+ and _TOOL_COMMAND_CONTINUATION_RE.match(_row_text(rows[block_last + 1]))
927
+ ):
928
+ block_last += 1
929
+ if not block_first <= row <= block_last:
930
+ return None
931
+
932
+ best: ClickTarget | None = None
933
+ for first in range(block_first, row + 1):
934
+ first_text = _row_text(rows[first])
935
+ prefix = (
936
+ _TOOL_COMMAND_START_RE.match(first_text)
937
+ if first == block_first
938
+ else _TOOL_COMMAND_CONTINUATION_RE.match(first_text)
939
+ )
940
+ if prefix is None:
941
+ continue
942
+ tail = re.search(r"""[^\s<>"'`]+\s*$""", first_text[prefix.end() :])
943
+ if tail is None:
944
+ continue
945
+ fragment = tail.group(0).rstrip()
946
+ fragment_index = prefix.end() + tail.start()
947
+ fragment, fragment_index, _fragment_end = _trim_click_token(
948
+ fragment,
949
+ fragment_index,
950
+ fragment_index + len(fragment),
951
+ )
952
+ lower = fragment.lower()
953
+ is_url = lower.startswith(("http://", "https://"))
954
+ is_path = (
955
+ _path_parts(fragment) is not None
956
+ or fragment.startswith(("/", "./", "../", "~/"))
957
+ or _WINDOWS_ABSOLUTE_PATH_RE.match(fragment) is not None
958
+ )
959
+ if not (is_url or is_path):
960
+ continue
961
+ fragment_column = _display_column(first_text, fragment_index)
962
+ fragments: list[tuple[int, int, str]] = [(first, fragment_column, fragment)]
963
+ joined = fragment
964
+ previous_end = fragment_column + sum(
965
+ _display_width(character) for character in fragment
966
+ )
967
+
968
+ for following in range(first + 1, block_last + 1):
969
+ # The formatter may place a new shell token on the following
970
+ # decorated row. Only a fragment ending near the physical edge is
971
+ # proof that the token itself, rather than the command, wrapped.
972
+ if route.width - previous_end > 8:
973
+ break
974
+ following_text = _row_text(rows[following])
975
+ following_prefix = _TOOL_COMMAND_CONTINUATION_RE.match(following_text)
976
+ if following_prefix is None:
977
+ break
978
+ continuation = re.match(
979
+ r"""([^\s<>"'`]+)""",
980
+ following_text[following_prefix.end() :],
981
+ )
982
+ if continuation is None:
983
+ break
984
+ next_fragment = continuation.group(1)
985
+ next_index = following_prefix.end() + continuation.start()
986
+ next_fragment, next_index, _next_end = _trim_click_token(
987
+ next_fragment,
988
+ next_index,
989
+ next_index + len(next_fragment),
990
+ )
991
+ if not next_fragment:
992
+ break
993
+ if is_path and (
994
+ next_fragment[0] not in "._~" and not next_fragment[0].isalnum()
995
+ ):
996
+ break
997
+ if is_url and (
998
+ next_fragment[0] not in "._~/%?&#"
999
+ and not next_fragment[0].isalnum()
1000
+ ):
1001
+ break
1002
+ next_column = _display_column(following_text, next_index)
1003
+ fragments.append((following, next_column, next_fragment))
1004
+ joined += next_fragment
1005
+ previous_end = next_column + sum(
1006
+ _display_width(character) for character in next_fragment
1007
+ )
1008
+
1009
+ clicked = any(
1010
+ fragment_row == row
1011
+ and start_column
1012
+ <= column
1013
+ < start_column
1014
+ + sum(_display_width(character) for character in fragment_text)
1015
+ for fragment_row, start_column, fragment_text in fragments
1016
+ )
1017
+ if not clicked:
1018
+ continue
1019
+ if is_url:
1020
+ value = _bounded_url_token(joined)
1021
+ parsed = urlsplit(value)
1022
+ if (
1023
+ parsed.scheme.lower() not in ("http", "https")
1024
+ or parsed.hostname is None
1025
+ or len(value.encode("utf-8")) > 8192
1026
+ ):
1027
+ continue
1028
+ kind = "url"
1029
+ line = None
1030
+ target_column = None
1031
+ else:
1032
+ path = _path_parts(joined)
1033
+ if path is None:
1034
+ continue
1035
+ value, line, target_column = path
1036
+ kind = "path"
1037
+ segments = tuple(
1038
+ (
1039
+ route.y + fragment_row,
1040
+ route.x + start_column,
1041
+ fragment_text.encode("utf-8"),
1042
+ )
1043
+ for fragment_row, start_column, fragment_text in fragments
1044
+ )
1045
+ candidate = ClickTarget(
1046
+ kind,
1047
+ value,
1048
+ route.pane_id or "",
1049
+ line,
1050
+ target_column,
1051
+ highlight_row=segments[0][0],
1052
+ highlight_column=segments[0][1],
1053
+ highlight_text=segments[0][2],
1054
+ highlight_segments=segments,
1055
+ )
1056
+ if best is None or len(candidate.value) > len(best.value):
1057
+ best = candidate
1058
+ return best
1059
+
1060
+
896
1061
  def _highlight_runs(
897
1062
  target: ClickTarget,
898
1063
  *,
@@ -963,6 +1128,14 @@ def _target_in_rows(
963
1128
  """Resolve a semantic target across one bounded visual-wrap chain."""
964
1129
  if not 0 <= row < len(rows) or route.width <= 0:
965
1130
  return None
1131
+ decorated = _decorated_tool_target(
1132
+ rows,
1133
+ row,
1134
+ column,
1135
+ route=route,
1136
+ )
1137
+ if decorated is not None:
1138
+ return decorated
966
1139
  # A full terminal row followed by a non-indented row is an authoritative
967
1140
  # visual soft-wrap chain. Do not let the more permissive agent hard-wrap
968
1141
  # heuristic accept a shorter prefix first: on a three-row URL that made
@@ -1236,6 +1236,8 @@ class LocalPtyClient:
1236
1236
  self._session_id = session_id
1237
1237
  self._input_decoder = TerminalInputDecoder()
1238
1238
  self._selection = LocalTextSelection()
1239
+ self._passthrough_left_gesture = False
1240
+ self._suppressed_left_gesture = False
1239
1241
  self._routes: tuple[HistorySnapshot, ...] = ()
1240
1242
  self._routes_checked_at = 0.0
1241
1243
  self._routes_force_refresh = True
@@ -1508,15 +1510,50 @@ class LocalPtyClient:
1508
1510
  part,
1509
1511
  logical_height=logical_height,
1510
1512
  )
1513
+ left_drag = (
1514
+ event.pressed
1515
+ and bool(event.button & 32)
1516
+ and event.button & 3 == 0
1517
+ and not event.button & 64
1518
+ )
1519
+ left_release = not event.pressed and event.button & 3 == 0
1520
+ plain_left_press = LocalTextSelection._is_plain_left_press(event)
1521
+ if getattr(self, "_suppressed_left_gesture", False):
1522
+ if left_drag or left_release:
1523
+ if left_release:
1524
+ self._suppressed_left_gesture = False
1525
+ return
1526
+ if plain_left_press:
1527
+ self._suppressed_left_gesture = False
1528
+ if getattr(self, "_passthrough_left_gesture", False):
1529
+ if left_drag or left_release:
1530
+ if left_release:
1531
+ self._passthrough_left_gesture = False
1532
+ _write_pty_input(self.master_fd, event.raw)
1533
+ return
1534
+ if plain_left_press:
1535
+ self._passthrough_left_gesture = False
1511
1536
  if not self._renderer.presentation_stable:
1512
- if self._selection.cancel():
1537
+ if not self._selection.capturing and self._selection.cancel():
1513
1538
  self._renderer.set_selection(self._selection.segments())
1514
1539
  # A queued frame can already represent a new tmux pane topology
1515
1540
  # while the user still sees the prior one. Dropping only pointer
1516
- # input during this bounded transition is safer than forwarding a
1517
- # click to Quit or a wheel event into stock tmux copy-mode.
1518
- return
1519
- source = self._selection_source(event)
1541
+ # input during this bounded transition is safer than forwarding it
1542
+ # to Quit or stock tmux copy-mode. A capture already owns an
1543
+ # immutable visible snapshot and therefore keeps the whole gesture.
1544
+ if not self._selection.capturing:
1545
+ if plain_left_press or left_drag:
1546
+ self._suppressed_left_gesture = not left_release
1547
+ return
1548
+ if self._selection.capturing:
1549
+ # Once the newer frame is physically visible, revalidate the
1550
+ # captured route before consuming another event. During a queued
1551
+ # transition the old snapshot remains the visible authority.
1552
+ if self._renderer.presentation_stable:
1553
+ self._refresh_routes()
1554
+ source = None
1555
+ else:
1556
+ source = self._selection_source(event)
1520
1557
  if event.is_hover_motion:
1521
1558
  if self._selection.hover(event, source):
1522
1559
  self._renderer.set_selection(self._selection.segments())
@@ -1533,6 +1570,13 @@ class LocalPtyClient:
1533
1570
  if action.open_target is not None:
1534
1571
  self._open_target(action.open_target)
1535
1572
  if not action.handled:
1573
+ if plain_left_press:
1574
+ self._passthrough_left_gesture = True
1575
+ elif left_drag or left_release:
1576
+ # Never give tmux half a left-button gesture. An orphan means
1577
+ # its press was suppressed or a captured route was invalidated.
1578
+ self._suppressed_left_gesture = left_drag
1579
+ return
1536
1580
  _write_pty_input(self.master_fd, event.raw)
1537
1581
 
1538
1582
  def poll(self) -> int | None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.1.dev1
3
+ Version: 0.4.1.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
@@ -677,6 +677,118 @@ def test_local_text_selection_resolves_all_three_soft_wrapped_url_rows():
677
677
  assert action.open_target.value == expected
678
678
 
679
679
 
680
+ def test_local_text_selection_joins_url_across_codex_ran_decoration():
681
+ first = "• Ran curl https://example.test/releases/rail"
682
+ second = " │ mux/0.4.1.dev1/index.html --head"
683
+ width = max(len(first), len(second))
684
+ route = HistorySnapshot(1, "%8", 3, 2, width, 2)
685
+ source = SelectionSource(
686
+ route,
687
+ tuple((line + " " * (width - len(line))).encode() for line in (first, second)),
688
+ 0,
689
+ )
690
+ expected = "https://example.test/releases/railmux/0.4.1.dev1/index.html"
691
+ expected_segments = (
692
+ (2, 3 + first.index("https://"), b"https://example.test/releases/rail"),
693
+ (3, 3 + second.index("mux/"), b"mux/0.4.1.dev1/index.html"),
694
+ )
695
+
696
+ for screen_row, text, token in (
697
+ (3, first, "https://"),
698
+ (4, second, "mux/"),
699
+ ):
700
+ selection = LocalTextSelection()
701
+ screen_column = 3 + text.index(token) + 2
702
+ assert selection.hover(
703
+ SgrMouseEvent(b"hover", 35, screen_column, screen_row, True),
704
+ source,
705
+ )
706
+ assert selection.segments() == expected_segments
707
+ selection.pointer_event(
708
+ SgrMouseEvent(b"down", 0, screen_column, screen_row, True),
709
+ source,
710
+ )
711
+ action = selection.pointer_event(
712
+ SgrMouseEvent(b"up", 0, screen_column, screen_row, False),
713
+ source,
714
+ )
715
+ assert action.open_target is not None
716
+ assert action.open_target.kind == "url"
717
+ assert action.open_target.value == expected
718
+
719
+
720
+ def test_local_text_selection_joins_path_across_codex_ran_decoration():
721
+ first = "• Ran /tmp/railmux-published-0.4.1.dev1-"
722
+ second = " │ 20260816/bin/pip --isolated"
723
+ width = max(len(first), len(second))
724
+ route = HistorySnapshot(1, "%8", 0, 0, width, 2)
725
+ source = SelectionSource(
726
+ route,
727
+ tuple((line + " " * (width - len(line))).encode() for line in (first, second)),
728
+ 0,
729
+ )
730
+ selection = LocalTextSelection()
731
+ column = second.index("20260816") + 2
732
+
733
+ assert selection.hover(SgrMouseEvent(b"hover", 35, column, 2, True), source)
734
+ assert selection.segments() == (
735
+ (0, first.index("/tmp/"), b"/tmp/railmux-published-0.4.1.dev1-"),
736
+ (1, second.index("20260816"), b"20260816/bin/pip"),
737
+ )
738
+ selection.pointer_event(SgrMouseEvent(b"down", 0, column, 2, True), source)
739
+ action = selection.pointer_event(
740
+ SgrMouseEvent(b"up", 0, column, 2, False),
741
+ source,
742
+ )
743
+ assert action.open_target is not None
744
+ assert action.open_target.kind == "path"
745
+ assert action.open_target.value == (
746
+ "/tmp/railmux-published-0.4.1.dev1-20260816/bin/pip"
747
+ )
748
+
749
+
750
+ def test_local_text_selection_does_not_join_undecorated_box_drawing_rows():
751
+ first = "Output /home/user/project/"
752
+ second = " │ sibling/file.py"
753
+ width = max(len(first), len(second))
754
+ route = HistorySnapshot(1, "%8", 0, 0, width, 2)
755
+ source = SelectionSource(
756
+ route,
757
+ tuple((line + " " * (width - len(line))).encode() for line in (first, second)),
758
+ 0,
759
+ )
760
+ selection = LocalTextSelection()
761
+
762
+ selection.pointer_event(SgrMouseEvent(b"down", 0, 12, 1, True), source)
763
+ action = selection.pointer_event(
764
+ SgrMouseEvent(b"up", 0, 12, 1, False),
765
+ source,
766
+ )
767
+ assert action.open_target is not None
768
+ assert action.open_target.value == "/home/user/project/"
769
+
770
+
771
+ def test_local_text_selection_does_not_join_next_decorated_shell_option():
772
+ first = "• Ran cat /home/user/project/"
773
+ second = " │ --verbose"
774
+ width = len(first)
775
+ route = HistorySnapshot(1, "%8", 0, 0, width, 2)
776
+ source = SelectionSource(
777
+ route,
778
+ tuple((line + " " * (width - len(line))).encode() for line in (first, second)),
779
+ 0,
780
+ )
781
+ selection = LocalTextSelection()
782
+
783
+ selection.pointer_event(SgrMouseEvent(b"down", 0, 12, 1, True), source)
784
+ action = selection.pointer_event(
785
+ SgrMouseEvent(b"up", 0, 12, 1, False),
786
+ source,
787
+ )
788
+ assert action.open_target is not None
789
+ assert action.open_target.value == "/home/user/project/"
790
+
791
+
680
792
  def test_local_text_selection_strips_label_before_absolute_path():
681
793
  route = HistorySnapshot(1, "%8", 0, 0, 40, 1)
682
794
  source = SelectionSource(
@@ -302,6 +302,41 @@ def test_local_semantic_drag_copies_without_forwarding_mouse(monkeypatch):
302
302
  child_socket.close()
303
303
 
304
304
 
305
+ def test_local_semantic_drag_keeps_ownership_during_frame_transition():
306
+ proxy_socket, child_socket = socket.socketpair()
307
+ client = windows_attach_relay.LocalPtyClient.__new__(
308
+ windows_attach_relay.LocalPtyClient
309
+ )
310
+ client.master_fd = proxy_socket.detach()
311
+ client._session_id = "$4"
312
+ client._selection = LocalTextSelection()
313
+ client._routes = (HistorySnapshot(0, "%8", 0, 0, 20, 1),)
314
+ client._routes_checked_at = time.monotonic()
315
+ client._path_prompt = None
316
+ client._path_prompt_mouse_button = None
317
+ client._size = (20, 1)
318
+ renderer = MagicMock()
319
+ renderer.presentation_stable = True
320
+ renderer.screen = SimpleNamespace(
321
+ width=20, height=1, cursor_x=1, cursor_y=0, rows=(b"select me",)
322
+ )
323
+ renderer.surface.translate_mouse_event.side_effect = lambda event, **_kwargs: event
324
+ client._renderer = renderer
325
+ child_socket.setblocking(False)
326
+ try:
327
+ client._forward_input_part(SgrMouseEvent(b"down", 0, 1, 1, True))
328
+ renderer.presentation_stable = False
329
+ client._forward_input_part(SgrMouseEvent(b"drag", 32, 6, 1, True))
330
+ client._forward_input_part(SgrMouseEvent(b"up", 0, 6, 1, False))
331
+
332
+ renderer.copy_to_clipboard.assert_called_once_with(b"select")
333
+ with pytest.raises(BlockingIOError):
334
+ child_socket.recv(64)
335
+ finally:
336
+ os.close(client.master_fd)
337
+ child_socket.close()
338
+
339
+
305
340
  def test_local_semantic_url_in_unfocused_agent_replays_focus_click():
306
341
  client = windows_attach_relay.LocalPtyClient.__new__(
307
342
  windows_attach_relay.LocalPtyClient
@@ -922,6 +957,9 @@ def test_local_pointer_is_dropped_while_visible_frame_is_transitioning():
922
957
  child_socket.setblocking(False)
923
958
  try:
924
959
  client._forward_input_part(SgrMouseEvent(b"stale-click", 0, 4, 5, True))
960
+ renderer.presentation_stable = True
961
+ client._forward_input_part(SgrMouseEvent(b"orphan-drag", 32, 8, 5, True))
962
+ client._forward_input_part(SgrMouseEvent(b"orphan-up", 0, 8, 5, False))
925
963
  with pytest.raises(BlockingIOError):
926
964
  child_socket.recv(64)
927
965
  finally:
@@ -929,6 +967,42 @@ def test_local_pointer_is_dropped_while_visible_frame_is_transitioning():
929
967
  child_socket.close()
930
968
 
931
969
 
970
+ def test_local_left_gesture_outside_agent_route_remains_tmux_owned():
971
+ client = windows_attach_relay.LocalPtyClient.__new__(
972
+ windows_attach_relay.LocalPtyClient
973
+ )
974
+ proxy_socket, child_socket = socket.socketpair()
975
+ client.master_fd = proxy_socket.detach()
976
+ client._session_id = "$4"
977
+ client._selection = LocalTextSelection()
978
+ client._routes = (HistorySnapshot(0, "%8", 0, 0, 20, 1),)
979
+ client._routes_checked_at = time.monotonic()
980
+ client._path_prompt = None
981
+ client._path_prompt_mouse_button = None
982
+ client._size = (20, 2)
983
+ renderer = MagicMock()
984
+ renderer.presentation_stable = True
985
+ renderer.screen = SimpleNamespace(
986
+ width=20,
987
+ height=2,
988
+ cursor_x=1,
989
+ cursor_y=0,
990
+ rows=(b"agent", b"status"),
991
+ )
992
+ renderer.surface.translate_mouse_event.side_effect = lambda event, **_kwargs: event
993
+ client._renderer = renderer
994
+ child_socket.settimeout(0.5)
995
+ try:
996
+ client._forward_input_part(SgrMouseEvent(b"down", 0, 2, 2, True))
997
+ client._forward_input_part(SgrMouseEvent(b"drag", 32, 4, 2, True))
998
+ client._forward_input_part(SgrMouseEvent(b"up", 0, 4, 2, False))
999
+
1000
+ assert child_socket.recv(64) == b"downdragup"
1001
+ finally:
1002
+ os.close(client.master_fd)
1003
+ child_socket.close()
1004
+
1005
+
932
1006
  def test_local_explicit_tmux_copy_mode_key_remains_byte_exact():
933
1007
  client = windows_attach_relay.LocalPtyClient.__new__(
934
1008
  windows_attach_relay.LocalPtyClient
@@ -1 +0,0 @@
1
- __version__ = "0.4.1.dev1"
File without changes
File without changes
File without changes
File without changes