railmux 0.4.2.dev7__tar.gz → 0.4.2.dev8__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 (208) hide show
  1. {railmux-0.4.2.dev7/src/railmux.egg-info → railmux-0.4.2.dev8}/PKG-INFO +1 -1
  2. railmux-0.4.2.dev8/src/railmux/__init__.py +1 -0
  3. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fast_display_server.py +31 -6
  4. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/transcript.py +39 -14
  5. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8/src/railmux.egg-info}/PKG-INFO +1 -1
  6. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_server.py +16 -0
  7. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_transcript.py +12 -1
  8. railmux-0.4.2.dev7/src/railmux/__init__.py +0 -1
  9. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/LICENSE +0 -0
  10. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/MANIFEST.in +0 -0
  11. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/README.md +0 -0
  12. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/pyproject.toml +0 -0
  13. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/setup.cfg +0 -0
  14. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/__main__.py +0 -0
  15. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/atomic_file.py +0 -0
  16. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/background_index.py +0 -0
  17. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/cli.py +0 -0
  18. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/codex_index.py +0 -0
  19. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/config.py +0 -0
  20. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/config_cli.py +0 -0
  21. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/diagnostic_contract.py +0 -0
  22. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/diagnostics.py +0 -0
  23. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/discovery.py +0 -0
  24. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/display_transport.py +0 -0
  25. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/entrypoint.py +0 -0
  26. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fast_display_client.py +0 -0
  27. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fast_display_history.py +0 -0
  28. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fast_display_input.py +0 -0
  29. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fast_display_protocol.py +0 -0
  30. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/favorites.py +0 -0
  31. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fuzzy.py +0 -0
  32. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/help_workspace.py +0 -0
  33. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/image_paste.py +0 -0
  34. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/launcher.py +0 -0
  35. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/legacy_sessions.py +0 -0
  36. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/local_clipboard.py +0 -0
  37. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/local_open.py +0 -0
  38. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/models.py +0 -0
  39. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/modes.py +0 -0
  40. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/mouse_manager.py +0 -0
  41. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/orphan_marker.py +0 -0
  42. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/pane_surface.py +0 -0
  43. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/path_codec.py +0 -0
  44. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/preview_pager.py +0 -0
  45. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/project_favorites.py +0 -0
  46. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/provider_paths.py +0 -0
  47. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/release_version.py +0 -0
  48. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/remote_config.py +0 -0
  49. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/renames.py +0 -0
  50. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/restart_state.py +0 -0
  51. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/runtime_config.py +0 -0
  52. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/scroll_agent.py +0 -0
  53. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/scroll_manager.py +0 -0
  54. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/selection_isolation.py +0 -0
  55. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/self_update.py +0 -0
  56. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/session_cache.py +0 -0
  57. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/session_index.py +0 -0
  58. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/session_lease.py +0 -0
  59. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/setting_contracts.py +0 -0
  60. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/settings.py +0 -0
  61. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ssh_args.py +0 -0
  62. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ssh_compat.py +0 -0
  63. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ssh_display_diagnostics.py +0 -0
  64. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ssh_doctor.py +0 -0
  65. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ssh_preflight.py +0 -0
  66. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/system_deps.py +0 -0
  67. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/terminal_screen.py +0 -0
  68. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/terminal_status.py +0 -0
  69. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tmux_binding_manager.py +0 -0
  70. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tmux_capabilities.py +0 -0
  71. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tmux_ctl.py +0 -0
  72. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tmux_health.py +0 -0
  73. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tmux_server.py +0 -0
  74. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tool_panes.py +0 -0
  75. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/__init__.py +0 -0
  76. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/_widgets.py +0 -0
  77. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/app.py +0 -0
  78. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/keymap.py +0 -0
  79. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/modals.py +0 -0
  80. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/projects_pane.py +0 -0
  81. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/running_pane.py +0 -0
  82. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/sessions_pane.py +0 -0
  83. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/sidebar.py +0 -0
  84. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/statusbar.py +0 -0
  85. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/workspace.py +0 -0
  86. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_attach_relay.py +0 -0
  87. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_bootstrap.py +0 -0
  88. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_install_log.py +0 -0
  89. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_msys2.py +0 -0
  90. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_pacman.py +0 -0
  91. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_paths.py +0 -0
  92. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_tmux_lifecycle.py +0 -0
  93. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_ui_transition.py +0 -0
  94. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux.egg-info/SOURCES.txt +0 -0
  95. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux.egg-info/dependency_links.txt +0 -0
  96. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux.egg-info/entry_points.txt +0 -0
  97. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux.egg-info/requires.txt +0 -0
  98. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux.egg-info/top_level.txt +0 -0
  99. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/app_test_harness.py +0 -0
  100. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_agent_workspace.py +0 -0
  101. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_app_codex_placeholder.py +0 -0
  102. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_app_quit.py +0 -0
  103. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_app_restart_restore.py +0 -0
  104. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_app_running_recovery.py +0 -0
  105. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_atomic_file.py +0 -0
  106. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_background_index.py +0 -0
  107. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_claude_continuation.py +0 -0
  108. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_cli.py +0 -0
  109. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_code_map.py +0 -0
  110. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_codex_delete.py +0 -0
  111. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_codex_index.py +0 -0
  112. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_codex_lineage.py +0 -0
  113. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_codex_transcript.py +0 -0
  114. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_config.py +0 -0
  115. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_config_cli.py +0 -0
  116. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_denested_transport_benchmark.py +0 -0
  117. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_diagnostics.py +0 -0
  118. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_discovery.py +0 -0
  119. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_display_transport.py +0 -0
  120. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_bootstrap.py +0 -0
  121. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_history.py +0 -0
  122. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_protocol.py +0 -0
  123. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_selection.py +0 -0
  124. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_terminal.py +0 -0
  125. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_focus_highlight.py +0 -0
  126. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fullscreen_binding.py +0 -0
  127. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fuzzy.py +0 -0
  128. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_help_agent.py +0 -0
  129. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_help_workspace.py +0 -0
  130. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_history_cleanup.py +0 -0
  131. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_image_paste.py +0 -0
  132. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_keymap.py +0 -0
  133. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_launcher.py +0 -0
  134. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_layout_profile.py +0 -0
  135. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_legacy_sessions.py +0 -0
  136. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_local_clipboard.py +0 -0
  137. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_local_open.py +0 -0
  138. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_modals.py +0 -0
  139. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_mode_selection.py +0 -0
  140. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_modes.py +0 -0
  141. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_mouse_manager.py +0 -0
  142. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_new_project.py +0 -0
  143. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_options.py +0 -0
  144. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_orphan_recovery.py +0 -0
  145. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_pane_surface.py +0 -0
  146. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_path_codec.py +0 -0
  147. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_preview_pager.py +0 -0
  148. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_project_favorites.py +0 -0
  149. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_projects_pane.py +0 -0
  150. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_provider_paths.py +0 -0
  151. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_release_notes.py +0 -0
  152. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_release_version.py +0 -0
  153. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_release_workflow.py +0 -0
  154. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_remote_config.py +0 -0
  155. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_renames.py +0 -0
  156. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_restart_state.py +0 -0
  157. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_running_pane.py +0 -0
  158. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_running_sort.py +0 -0
  159. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_runtime_config.py +0 -0
  160. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_scroll_agent.py +0 -0
  161. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_scroll_manager.py +0 -0
  162. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_selection_isolation.py +0 -0
  163. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_self_update.py +0 -0
  164. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_session_cache.py +0 -0
  165. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_session_index.py +0 -0
  166. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_session_lease.py +0 -0
  167. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_sessions_pane.py +0 -0
  168. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_setting_contracts.py +0 -0
  169. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_settings.py +0 -0
  170. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_sidebar.py +0 -0
  171. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_ssh_args.py +0 -0
  172. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_ssh_compat.py +0 -0
  173. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_ssh_display_benchmark.py +0 -0
  174. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_ssh_display_diagnostics.py +0 -0
  175. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_ssh_doctor.py +0 -0
  176. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_status.py +0 -0
  177. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_statusbar.py +0 -0
  178. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_system_deps.py +0 -0
  179. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_terminal_size.py +0 -0
  180. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_terminal_status.py +0 -0
  181. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_termux_local_touch.py +0 -0
  182. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_binding_manager.py +0 -0
  183. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_capabilities.py +0 -0
  184. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_ctl.py +0 -0
  185. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_health.py +0 -0
  186. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_integration.py +0 -0
  187. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_server.py +0 -0
  188. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_status.py +0 -0
  189. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tool_panes.py +0 -0
  190. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_utf8_state.py +0 -0
  191. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_web_demo_recorder.py +0 -0
  192. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_wheel_smoke.py +0 -0
  193. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_widgets.py +0 -0
  194. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_attach_relay.py +0 -0
  195. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_bootstrap.py +0 -0
  196. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_install_log.py +0 -0
  197. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_msys2.py +0 -0
  198. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_pacman.py +0 -0
  199. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_paths.py +0 -0
  200. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_synchronized_screen.py +0 -0
  201. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_tmux_lifecycle.py +0 -0
  202. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_ui_transition.py +0 -0
  203. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_wrapper_contract.py +0 -0
  204. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_yolo_prompt.py +0 -0
  205. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tools/denested_transport_benchmark.py +0 -0
  206. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tools/release_notes.py +0 -0
  207. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tools/wheel_smoke.py +0 -0
  208. {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/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.dev7
3
+ Version: 0.4.2.dev8
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.dev8"
@@ -89,6 +89,7 @@ from railmux.terminal_screen import (
89
89
  HyperlinkTrackingStream,
90
90
  Osc52ClipboardDecoder as _Osc52ClipboardDecoder,
91
91
  ScreenState as _ScreenState,
92
+ _safe_hyperlink,
92
93
  build_screen_update,
93
94
  extended_pyte as _extended_pyte,
94
95
  render_rows,
@@ -1616,11 +1617,17 @@ def _read_transcript_tail(fd: int) -> tuple[str, bool]:
1616
1617
  def _wrap_transcript_rows(
1617
1618
  pyte: object, text: str, width: int
1618
1619
  ) -> tuple[tuple[bytes, ...], bool, int]:
1619
- """Wrap allowlisted transcript ANSI into independently paintable rows."""
1620
+ """Wrap allowlisted transcript ANSI into independently paintable rows.
1621
+
1622
+ SGR styles and HTTP(S) OSC 8 links are kept; a link that crosses a row
1623
+ break is closed at the row end and reopened on the next row, as
1624
+ :func:`render_rows` emits links for captured screen rows.
1625
+ """
1620
1626
  rows: deque[bytes] = deque(maxlen=MAX_HISTORY_LINES)
1621
1627
  row = bytearray(b"\033[0m")
1622
1628
  column = 0
1623
1629
  active: list[bytes] = []
1630
+ link = b""
1624
1631
  dropped = False
1625
1632
  total_rows = 0
1626
1633
 
@@ -1629,12 +1636,28 @@ def _wrap_transcript_rows(
1629
1636
  total_rows += 1
1630
1637
  if len(rows) == rows.maxlen:
1631
1638
  dropped = True
1632
- rows.append(bytes(row) + b"\033[0m")
1633
- row = bytearray(b"\033[0m" + b"".join(active))
1639
+ rows.append(bytes(row) + (_LINK_END if link else b"") + b"\033[0m")
1640
+ row = bytearray(b"\033[0m" + b"".join(active) + link)
1634
1641
  column = 0
1635
1642
 
1636
- for match in re.finditer(r"\x1b\[[0-9;]*m|[^\x1b]+", text):
1643
+ for match in re.finditer(
1644
+ r"\x1b\[[0-9;]*m|\x1b\]8;;([^\x1b]*)\x1b\\|[^\x1b]+", text
1645
+ ):
1637
1646
  token = match.group(0)
1647
+ if token.startswith("\x1b]"):
1648
+ target = match.group(1)
1649
+ if target and _safe_hyperlink(target.encode("utf-8")) is None:
1650
+ continue
1651
+ encoded = token.encode("utf-8")
1652
+ if link and target:
1653
+ row.extend(_LINK_END)
1654
+ if target:
1655
+ link = encoded
1656
+ row.extend(encoded)
1657
+ elif link:
1658
+ link = b""
1659
+ row.extend(_LINK_END)
1660
+ continue
1638
1661
  if token.startswith("\x1b["):
1639
1662
  encoded = token.encode("ascii")
1640
1663
  params = token[2:-1].split(";")
@@ -1672,7 +1695,7 @@ def _wrap_transcript_rows(
1672
1695
  finish()
1673
1696
  row.extend(char.encode("utf-8"))
1674
1697
  column += max(0, cell_width)
1675
- if column or len(row) > len(b"\033[0m" + b"".join(active)):
1698
+ if column or len(row) > len(b"\033[0m" + b"".join(active) + link):
1676
1699
  finish()
1677
1700
  return tuple(rows), dropped, total_rows
1678
1701
 
@@ -1750,7 +1773,9 @@ def _transcript_rows(
1750
1773
  os.close(fd)
1751
1774
 
1752
1775
 
1753
- _OVERLAP_STYLE_RE = re.compile(rb"\x1b\[[0-9;]*m")
1776
+ _LINK_END = b"\033]8;;\033\\"
1777
+ _OVERLAP_STYLE_RE = re.compile(
1778
+ rb"\x1b\[[0-9;]*m|\x1b\]8;;[^\x1b]*\x1b\\")
1754
1779
  _OVERLAP_SPACE_RE = re.compile(r"\s+")
1755
1780
  _OVERLAP_ANCHOR_CHARS = 12
1756
1781
  _OVERLAP_SINGLE_ANCHOR_CHARS = 24
@@ -13,6 +13,7 @@ import os
13
13
  import re
14
14
  import sys
15
15
  from pathlib import Path
16
+ from urllib.parse import urlsplit
16
17
 
17
18
  # ── ANSI styles ──────────────────────────────────────────────────────────
18
19
  BOLD = "\033[1m"
@@ -28,6 +29,7 @@ ITALIC = "\033[3m"
28
29
  BOLD_OFF = "\033[22m"
29
30
  ITALIC_OFF = "\033[23m"
30
31
  FG_DEFAULT = "\033[39m"
32
+ LINK_END = "\033]8;;\033\\"
31
33
 
32
34
  _CODEX_SYNTHETIC_PREFIXES = (
33
35
  "<environment_context>",
@@ -241,8 +243,10 @@ _MD_TABLE_SEPARATOR_RE = re.compile(
241
243
  r"^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$")
242
244
  _MD_TABLE_CELL_SPLIT_RE = re.compile(r"(?<!\\)\|")
243
245
  _LIST_HANG_RE = re.compile(r" *(?:(?:[-*+]|\d+[.)]) +)?")
244
- _SGR_RE = re.compile(r"\x1b\[[0-9;]*m")
245
- _WRAP_TOKEN_RE = re.compile(r"\x1b\[[0-9;]*m| +|[^ \x1b]+")
246
+ # Zero-width escapes Railmux emits itself: SGR styles and OSC 8 links.
247
+ _ESCAPE_RE = re.compile(r"\x1b\[[0-9;]*m|\x1b\]8;;[^\x1b]*\x1b\\")
248
+ _WRAP_TOKEN_RE = re.compile(
249
+ r"\x1b\[[0-9;]*m|\x1b\]8;;[^\x1b]*\x1b\\| +|[^ \x1b]+")
246
250
  _TASK_SUMMARY_RE = re.compile(r"<summary>(.*?)</summary>", re.DOTALL)
247
251
 
248
252
  _char_width_function = None
@@ -268,7 +272,7 @@ def _visible_width(text: str) -> int:
268
272
  # Claude pads an emoji-presentation selector (as in "⚠️") to two cells.
269
273
  return sum(
270
274
  1 if char == "\ufe0f" else _char_width(char)
271
- for char in _SGR_RE.sub("", text)
275
+ for char in _ESCAPE_RE.sub("", text)
272
276
  )
273
277
 
274
278
 
@@ -328,29 +332,49 @@ def _wrap_styled(line: str, width: int, hang: int) -> list[str]:
328
332
 
329
333
  def _hang_for(row: str, prefix: int) -> int:
330
334
  """Hanging indent for a row whose own content starts after *prefix* cells."""
331
- content = _SGR_RE.sub("", row)[prefix:]
335
+ content = _ESCAPE_RE.sub("", row)[prefix:]
332
336
  return prefix + len(_LIST_HANG_RE.match(content).group(0))
333
337
 
334
338
 
335
339
  def _close_fragments(fragments: list[str]) -> list[str]:
336
- """Make each wrapped fragment carry and close its own SGR state.
340
+ """Make each wrapped fragment carry and close its own SGR and link state.
337
341
 
338
- Table cells sit beside other cells on one row, so a style that continues
339
- onto a cell's next fragment must not leak across the borders between.
342
+ Table cells sit beside other cells on one row, so a style or link that
343
+ continues onto a cell's next fragment must not leak across the borders.
340
344
  """
341
345
  closed: list[str] = []
342
346
  active: list[str] = []
347
+ link = ""
343
348
  for fragment in fragments:
344
- prefix = "".join(active)
345
- for code in _SGR_RE.findall(fragment):
346
- if code == RESET:
349
+ prefix = "".join(active) + link
350
+ for code in _ESCAPE_RE.findall(fragment):
351
+ if code.startswith("\x1b]"):
352
+ link = "" if code == LINK_END else code
353
+ elif code == RESET:
347
354
  active.clear()
348
355
  else:
349
356
  active.append(code)
350
- closed.append(f"{prefix}{fragment}{RESET}")
357
+ suffix = LINK_END if link else ""
358
+ closed.append(f"{prefix}{fragment}{suffix}{RESET}")
351
359
  return closed
352
360
 
353
361
 
362
+ def _link(text: str, url: str) -> str:
363
+ """Wrap *text* in an OSC 8 link when *url* is a plain HTTP(S) target."""
364
+ try:
365
+ parsed = urlsplit(url)
366
+ valid = (
367
+ parsed.scheme.lower() in ("http", "https")
368
+ and parsed.hostname is not None
369
+ and len(url) <= 8192
370
+ )
371
+ except ValueError:
372
+ valid = False
373
+ if not valid:
374
+ return text
375
+ return f"\033]8;;{url}\033\\{text}{LINK_END}"
376
+
377
+
354
378
  def _table_cells(line: str) -> list[str]:
355
379
  stripped = line.strip()
356
380
  if stripped.startswith("|"):
@@ -391,7 +415,7 @@ def _markdown_table(lines: list[str], width: int) -> list[str] | None:
391
415
  max(
392
416
  _visible_width(word)
393
417
  for row in rows
394
- for word in [*_SGR_RE.sub("", row[index]).split(" "), ""]
418
+ for word in [*_ESCAPE_RE.sub("", row[index]).split(" "), ""]
395
419
  )
396
420
  for index in range(count)
397
421
  ]
@@ -448,7 +472,7 @@ def _markdown_inline(line: str, *, bold: bool = False) -> str:
448
472
  Only targeted "off" codes close each span, so an enclosing style (for
449
473
  example a bold heading) survives an inner code or italic span. *bold*
450
474
  marks an already-bold context whose nested strong spans must not end it.
451
- A link shows only its text, as Claude's clickable link does.
475
+ A link shows only its text and stays clickable through OSC 8.
452
476
  """
453
477
 
454
478
  def replace(match: re.Match[str]) -> str:
@@ -457,7 +481,8 @@ def _markdown_inline(line: str, *, bold: bool = False) -> str:
457
481
  if code is not None:
458
482
  return f"{CLAUDE_ACCENT}{code}{FG_DEFAULT}"
459
483
  if match.group(6) is not None:
460
- return _markdown_inline(match.group(6), bold=bold)
484
+ return _link(_markdown_inline(match.group(6), bold=bold),
485
+ match.group(7))
461
486
  if strong is not None or strong_alt is not None:
462
487
  inner = _markdown_inline(
463
488
  strong if strong is not None else strong_alt, bold=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.2.dev7
3
+ Version: 0.4.2.dev8
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
@@ -2357,3 +2357,19 @@ def test_server_projects_only_allowlisted_private_terminal_modes():
2357
2357
  mode = {1000 << 5, 1006 << 5, 9999 << 5}
2358
2358
 
2359
2359
  assert terminal_modes_for_screen(OtherModes()) is TerminalMode.NONE
2360
+
2361
+
2362
+ def test_transcript_rows_reopen_links_across_wraps_and_drop_unsafe_targets():
2363
+ pyte = fast_display_server._extended_pyte(__import__("pyte"))
2364
+ link = "\x1b]8;;https://example.com/a\x1b\\"
2365
+ close = "\x1b]8;;\x1b\\"
2366
+ text = f"{link}abcdef{close} \x1b]8;;file:///etc/passwd\x1b\\x{close}\n"
2367
+
2368
+ rows, _dropped, _total = fast_display_server._wrap_transcript_rows(
2369
+ pyte, text, 4)
2370
+
2371
+ encoded_link, encoded_close = link.encode(), close.encode()
2372
+ assert rows[0] == b"\x1b[0m" + encoded_link + b"abcd" + encoded_close + b"\x1b[0m"
2373
+ assert rows[1].startswith(b"\x1b[0m" + encoded_link + b"ef" + encoded_close)
2374
+ assert b"file:" not in b"".join(rows)
2375
+ assert fast_display_server._overlap_text(rows[0]) == "abcd"
@@ -905,7 +905,9 @@ def _native(records, width=None):
905
905
 
906
906
 
907
907
  def _plain_rows(rendered: str) -> list[str]:
908
- return re.sub(r"\x1b\[[0-9;]*m", "", rendered).split("\n")
908
+ return re.sub(
909
+ r"\x1b\[[0-9;]*m|\x1b\]8;;[^\x1b]*\x1b\\", "", rendered
910
+ ).split("\n")
909
911
 
910
912
 
911
913
  def _assistant(text: str) -> dict:
@@ -1005,3 +1007,12 @@ def test_claude_native_wrap_moves_long_word_when_that_saves_a_break():
1005
1007
  assert _plain_rows(kept)[1:-1] == [
1006
1008
  "● intro " + "x" * 12, " " + "x" * 18,
1007
1009
  ]
1010
+
1011
+
1012
+ def test_claude_native_links_stay_clickable_and_reject_other_schemes():
1013
+ rendered = _native([_assistant(
1014
+ "See [#1](https://example.com/pull/1) and [x](javascript:alert).")])
1015
+
1016
+ assert "\x1b]8;;https://example.com/pull/1\x1b\\#1\x1b]8;;\x1b\\" in rendered
1017
+ assert "javascript" not in rendered
1018
+ assert "See #1 and x." in "\n".join(_plain_rows(rendered))
@@ -1 +0,0 @@
1
- __version__ = "0.4.2.dev7"
File without changes
File without changes
File without changes
File without changes