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.
- {railmux-0.4.2.dev7/src/railmux.egg-info → railmux-0.4.2.dev8}/PKG-INFO +1 -1
- railmux-0.4.2.dev8/src/railmux/__init__.py +1 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fast_display_server.py +31 -6
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/transcript.py +39 -14
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_server.py +16 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_transcript.py +12 -1
- railmux-0.4.2.dev7/src/railmux/__init__.py +0 -1
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/LICENSE +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/MANIFEST.in +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/README.md +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/pyproject.toml +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/setup.cfg +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/__main__.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/atomic_file.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/background_index.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/cli.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/codex_index.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/config.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/config_cli.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/diagnostic_contract.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/diagnostics.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/discovery.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/display_transport.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/entrypoint.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fast_display_client.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/favorites.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/fuzzy.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/help_workspace.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/image_paste.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/launcher.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/local_open.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/models.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/modes.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/pane_surface.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/path_codec.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/preview_pager.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/project_favorites.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/provider_paths.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/release_version.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/remote_config.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/renames.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/restart_state.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/runtime_config.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/self_update.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/session_cache.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/session_index.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/session_lease.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/settings.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ssh_args.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ssh_preflight.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/system_deps.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/terminal_screen.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/terminal_status.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tmux_capabilities.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tmux_health.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tmux_server.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/tool_panes.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/app.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/modals.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_attach_relay.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_bootstrap.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_install_log.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_msys2.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_pacman.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_paths.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux/windows_ui_transition.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux.egg-info/SOURCES.txt +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/app_test_harness.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_agent_workspace.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_app_codex_placeholder.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_app_quit.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_app_restart_restore.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_app_running_recovery.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_atomic_file.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_background_index.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_claude_continuation.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_cli.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_code_map.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_codex_delete.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_codex_index.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_codex_lineage.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_codex_transcript.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_config.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_config_cli.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_diagnostics.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_discovery.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_display_transport.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_bootstrap.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_history.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_protocol.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_selection.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fast_display_terminal.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_focus_highlight.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_fuzzy.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_help_agent.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_help_workspace.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_history_cleanup.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_image_paste.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_keymap.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_launcher.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_layout_profile.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_local_clipboard.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_local_open.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_modals.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_mode_selection.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_modes.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_mouse_manager.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_new_project.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_options.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_pane_surface.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_path_codec.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_preview_pager.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_project_favorites.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_projects_pane.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_provider_paths.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_release_notes.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_release_version.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_release_workflow.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_remote_config.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_renames.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_restart_state.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_running_pane.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_running_sort.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_runtime_config.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_scroll_agent.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_scroll_manager.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_selection_isolation.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_self_update.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_session_cache.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_session_index.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_session_lease.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_sessions_pane.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_setting_contracts.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_settings.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_sidebar.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_ssh_args.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_ssh_compat.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_status.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_statusbar.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_system_deps.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_terminal_size.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_terminal_status.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_capabilities.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_health.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_integration.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_server.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tmux_status.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_tool_panes.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_utf8_state.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_widgets.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_attach_relay.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_bootstrap.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_install_log.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_msys2.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_pacman.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_paths.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_synchronized_screen.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_ui_transition.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_windows_wrapper_contract.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tools/release_notes.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tools/wheel_smoke.py +0 -0
- {railmux-0.4.2.dev7 → railmux-0.4.2.dev8}/tools/windows_archive_smoke.py +0 -0
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
|
|
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
|
|
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 =
|
|
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
|
|
339
|
-
onto a cell's next fragment must not leak across the borders
|
|
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
|
|
346
|
-
if code
|
|
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
|
-
|
|
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 [*
|
|
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
|
|
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)
|
|
@@ -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(
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|