aru-code 0.44.0__tar.gz → 0.45.0__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.
- {aru_code-0.44.0/aru_code.egg-info → aru_code-0.45.0}/PKG-INFO +1 -1
- aru_code-0.45.0/aru/__init__.py +1 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/app.py +127 -23
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/widgets/chat.py +78 -0
- {aru_code-0.44.0 → aru_code-0.45.0/aru_code.egg-info}/PKG-INFO +1 -1
- {aru_code-0.44.0 → aru_code-0.45.0}/aru_code.egg-info/SOURCES.txt +1 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/pyproject.toml +1 -1
- aru_code-0.45.0/tests/test_tui_layer12_recovery.py +120 -0
- aru_code-0.44.0/aru/__init__.py +0 -1
- {aru_code-0.44.0 → aru_code-0.45.0}/LICENSE +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/README.md +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/agent_factory.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/agents/__init__.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/agents/base.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/agents/catalog.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/agents/planner.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/cache_patch.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/checkpoints.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/cli.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/commands.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/completers.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/config.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/context.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/display.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/events.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/format/__init__.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/format/manager.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/format/runner.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/history_blocks.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/lsp/__init__.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/lsp/client.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/lsp/manager.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/lsp/protocol.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/memory/__init__.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/memory/extractor.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/memory/loader.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/memory/store.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/permissions.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/plugin_cache.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/plugins/__init__.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/plugins/custom_tools.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/plugins/hooks.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/plugins/manager.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/plugins/tool_api.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/providers.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/runner.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/runtime.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/select.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/session.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/sinks.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/streaming.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tool_policy.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/__init__.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/_diff.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/_shared.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/apply_patch.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/apply_patch_prompt.txt +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/ast_tools.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/codebase.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/delegate.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/delegate_prompt.txt +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/file_ops.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/gitignore.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/lsp.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/mcp_client.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/memory_tool.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/plan_mode.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/ranker.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/registry.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/search.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/shell.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/skill.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/tasklist.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/web.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tools/worktree.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/__init__.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/sanitize.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/screens/__init__.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/screens/choice.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/screens/confirm.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/screens/search.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/screens/text_input.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/sinks.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/slash_bridge.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/ui.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/widgets/__init__.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/widgets/completer.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/widgets/context_pane.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/widgets/header.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/widgets/inline_choice.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/widgets/loaded_pane.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/widgets/status.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/widgets/thinking.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/tui/widgets/tools.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru/ui.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru_code.egg-info/dependency_links.txt +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru_code.egg-info/entry_points.txt +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru_code.egg-info/requires.txt +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/aru_code.egg-info/top_level.txt +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/setup.cfg +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_agents_base.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_agents_md_coverage.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_apply_patch.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_async_tool_permission.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_cache_patch_metrics.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_cache_patch_stop_reason.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_catalog.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_chat_scrollable.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_checkpoints.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_cli.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_cli_advanced.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_cli_base.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_cli_completers.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_cli_new.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_cli_run_cli.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_cli_session.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_cli_shell.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_codebase.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_confabulation_regression.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_config.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_context.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_context_pane.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_cwd_awareness.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_delegate.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_events_backward_compat.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_events_schema.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_fork_ctx_concurrency.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_format.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_gitignore.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_guardrails_scenarios.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_invoke_skill.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_invoked_skills.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_loaded_pane_path.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_lsp.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_lsp_rename.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_main.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_markdown_to_text.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_mcp_client.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_mcp_health.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_memory.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_memory_tool.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_microcompact.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_permissions.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_plan_mode_refactor.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_plugin_cache.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_plugin_errors.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_plugin_hooks_v2.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_plugins.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_providers.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_ranker.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_reasoning.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_runner_interrupt.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_runner_recovery.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_runtime.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_select.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_skill_disallowed_tools.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_status_breakdown.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_status_cost.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_streaming_sink.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tasklist.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_thread_tool_timeout.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tool_policy.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_truncation_marker.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_app_boot.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_bindings.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_bus_flow.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_chat.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_chat_adversarial.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_completer.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_completer_dynamic.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_copy.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_input_behaviour.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_mention_expand.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_modals.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_mode_cycle.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_native_selection.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_permission_flow.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_plan_task_render.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_sidebar_toggle.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_slash_bridge.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_snapshot_smoke.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_thinking_and_boot.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_tui_widgets_visual.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_ui_adapter.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_worktree.py +0 -0
- {aru_code-0.44.0 → aru_code-0.45.0}/tests/test_worktree_session_restore.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.45.0"
|
|
@@ -32,6 +32,7 @@ from __future__ import annotations
|
|
|
32
32
|
|
|
33
33
|
import asyncio
|
|
34
34
|
import sys
|
|
35
|
+
import time
|
|
35
36
|
from typing import Any
|
|
36
37
|
|
|
37
38
|
from textual.app import App, ComposeResult
|
|
@@ -256,12 +257,24 @@ class AruApp(App):
|
|
|
256
257
|
"skills", "agents", "commands", "mcp", "yolo",
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
# Layer 10 — interval (seconds) between belt-and-suspenders re-emits
|
|
260
|
-
# the mouse-tracking enable sequences. 8s
|
|
261
|
-
#
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
|
|
260
|
+
# Layer 10 / 12 — interval (seconds) between belt-and-suspenders re-emits
|
|
261
|
+
# of the mouse-tracking enable sequences. Was 8s pre-Layer-12; user
|
|
262
|
+
# report on 2026-04-25 against ``final-fantasy-9/.aru/sessions/b33dfb99``
|
|
263
|
+
# was that the wheel never came back even after the turn ended in YOLO,
|
|
264
|
+
# and 8s was visibly long enough that the user gave up before the next
|
|
265
|
+
# tick. 3s is short enough that a corrupted state self-heals before the
|
|
266
|
+
# next mouse interaction, and the cost is still ~64 bytes per tick (the
|
|
267
|
+
# Layer 12 off-then-on shake — see ``_reenable_mouse_tracking``).
|
|
268
|
+
_MOUSE_REENABLE_INTERVAL: float = 3.0
|
|
269
|
+
|
|
270
|
+
# Layer 12 — minimum interval (seconds) between keypress-triggered
|
|
271
|
+
# mouse-tracking re-arms. Each keystroke is an opportunity to recover
|
|
272
|
+
# — if the user is typing it might be precisely BECAUSE the wheel just
|
|
273
|
+
# stopped working — but we don't want a fast typist to turn every
|
|
274
|
+
# keystroke into four extra terminal writes. 500 ms is below human
|
|
275
|
+
# noticeable retry latency yet caps the keystroke→write amplification
|
|
276
|
+
# at ~2 Hz worst case.
|
|
277
|
+
_KEYPRESS_REARM_DEBOUNCE: float = 0.5
|
|
265
278
|
|
|
266
279
|
def __init__(
|
|
267
280
|
self,
|
|
@@ -288,6 +301,12 @@ class AruApp(App):
|
|
|
288
301
|
# cleared) by on_input_submitted.
|
|
289
302
|
self._pending_paste: str | None = None
|
|
290
303
|
self._pending_paste_lines: int = 0
|
|
304
|
+
# Layer 12 — last time we re-emitted the mouse-tracking enable
|
|
305
|
+
# sequences via the keypress path. Used to debounce per-keystroke
|
|
306
|
+
# re-arming so a fast typist doesn't spam the terminal with re-
|
|
307
|
+
# enables. Initialised to negative infinity so the first keystroke
|
|
308
|
+
# always rearms.
|
|
309
|
+
self._last_mouse_reenable_at: float = float("-inf")
|
|
291
310
|
|
|
292
311
|
# ── Composition ──────────────────────────────────────────────────
|
|
293
312
|
|
|
@@ -549,7 +568,14 @@ class AruApp(App):
|
|
|
549
568
|
suggestion and fires ``Input.Submitted``, which produced the
|
|
550
569
|
"three Enters to run /help" glitch. Tab is the only key that
|
|
551
570
|
accepts the highlighted suggestion.
|
|
571
|
+
|
|
572
|
+
Layer 12 — every keystroke is also a recovery opportunity. The
|
|
573
|
+
Layer 10 periodic tick still runs every ``_MOUSE_REENABLE_INTERVAL``
|
|
574
|
+
but a typing user wants the wheel back NOW, not in three seconds.
|
|
575
|
+
Debounced via ``_KEYPRESS_REARM_DEBOUNCE`` so a fast typist
|
|
576
|
+
doesn't amplify each keystroke into four extra terminal writes.
|
|
552
577
|
"""
|
|
578
|
+
self._maybe_rearm_mouse_on_keypress()
|
|
553
579
|
try:
|
|
554
580
|
completer = self.query_one(SlashCompleter)
|
|
555
581
|
except Exception:
|
|
@@ -1097,34 +1123,112 @@ class AruApp(App):
|
|
|
1097
1123
|
# without waiting for the periodic Layer 10 tick.
|
|
1098
1124
|
self._reenable_mouse_tracking()
|
|
1099
1125
|
|
|
1126
|
+
# Layer 12 — DEC private-mode sequences for mouse tracking. Defined
|
|
1127
|
+
# at class scope so both the off-then-on shake below and any future
|
|
1128
|
+
# caller (Click handler, focus event) can reuse the exact same set
|
|
1129
|
+
# without drift.
|
|
1130
|
+
_MOUSE_DISABLE_SEQS: tuple[str, ...] = (
|
|
1131
|
+
"\x1b[?1000l",
|
|
1132
|
+
"\x1b[?1003l",
|
|
1133
|
+
"\x1b[?1015l",
|
|
1134
|
+
"\x1b[?1006l",
|
|
1135
|
+
)
|
|
1136
|
+
_MOUSE_ENABLE_SEQS: tuple[str, ...] = (
|
|
1137
|
+
"\x1b[?1000h",
|
|
1138
|
+
"\x1b[?1003h",
|
|
1139
|
+
"\x1b[?1015h",
|
|
1140
|
+
"\x1b[?1006h",
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1100
1143
|
def _reenable_mouse_tracking(self) -> None:
|
|
1101
|
-
"""Re-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
four short SGR sequences
|
|
1105
|
-
``?
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1144
|
+
"""Re-arm mouse tracking via an off-then-on shake (Layer 12).
|
|
1145
|
+
|
|
1146
|
+
Pre-Layer-12 this method delegated to the driver's
|
|
1147
|
+
``_enable_mouse_support`` which writes four short SGR sequences
|
|
1148
|
+
(``?1000h`` / ``?1003h`` / ``?1015h`` / ``?1006h``). That worked
|
|
1149
|
+
when the terminal forwarded the writes verbatim, but the user
|
|
1150
|
+
report on 2026-04-25 against
|
|
1151
|
+
``final-fantasy-9/.aru/sessions/b33dfb99`` was the wheel never
|
|
1152
|
+
coming back even though Layer 9 (turn-boundary call) and Layer 10
|
|
1153
|
+
(8s tick) both ran. Two failure modes the old code couldn't
|
|
1154
|
+
recover from:
|
|
1155
|
+
|
|
1156
|
+
1. **ConPTY enable cache.** Windows ConPTY tracks DEC private-mode
|
|
1157
|
+
state on its side and may treat ``?1000h`` as a no-op when its
|
|
1158
|
+
cache says "already enabled" — even when the underlying
|
|
1159
|
+
terminal lost the state. Sending ``?1000l`` first forces the
|
|
1160
|
+
cache through a state transition so the subsequent ``?1000h``
|
|
1161
|
+
is propagated.
|
|
1162
|
+
2. **Driver-side gate.** ``WindowsDriver._enable_mouse_support``
|
|
1163
|
+
opens with ``if not self._mouse: return`` (textual 8.2.4,
|
|
1164
|
+
windows_driver.py:55). If a future Textual flips the gate
|
|
1165
|
+
during shutdown / pause / alt-screen toggle, our recovery
|
|
1166
|
+
silently no-ops. Going through ``driver.write`` bypasses the
|
|
1167
|
+
gate and writes the bytes regardless.
|
|
1168
|
+
|
|
1169
|
+
Implementation: emit all four ``...l`` (off) sequences, then all
|
|
1170
|
+
four ``...h`` (on) sequences, then flush. Eight short writes
|
|
1171
|
+
(~64 bytes) bufferised into one terminal emit by ``WriterThread``.
|
|
1172
|
+
Idempotent on a healthy terminal — the off→on cycle leaves the
|
|
1173
|
+
final state identical to a single ``?1000h``, just with a
|
|
1174
|
+
microscopic gap during the transition (no observable wheel-event
|
|
1175
|
+
loss in practice).
|
|
1176
|
+
|
|
1177
|
+
Called from three sites:
|
|
1111
1178
|
* ``_run_turn`` finally-clause (Layer 9) — eager recovery at every
|
|
1112
1179
|
turn boundary so the first post-turn scroll always works.
|
|
1113
1180
|
* ``_self_heal_terminal_state`` periodic tick (Layer 10) — recovers
|
|
1114
|
-
mid-turn corruption
|
|
1115
|
-
|
|
1116
|
-
|
|
1181
|
+
mid-turn corruption within ``_MOUSE_REENABLE_INTERVAL``.
|
|
1182
|
+
* ``on_key`` keypress trigger (Layer 12) — recovers the moment the
|
|
1183
|
+
user touches the keyboard, since a keystroke is a strong signal
|
|
1184
|
+
they noticed the wheel is dead.
|
|
1185
|
+
|
|
1117
1186
|
Wrapped in ``try/except`` because the driver may be ``None`` in
|
|
1118
|
-
headless / test mode
|
|
1119
|
-
shift in a future Textual; better to no-op silently than crash.
|
|
1187
|
+
headless / test mode; we'd rather no-op silently than crash.
|
|
1120
1188
|
"""
|
|
1121
1189
|
try:
|
|
1122
1190
|
driver = self._driver
|
|
1123
|
-
if driver is
|
|
1124
|
-
|
|
1191
|
+
if driver is None:
|
|
1192
|
+
return
|
|
1193
|
+
for seq in self._MOUSE_DISABLE_SEQS:
|
|
1194
|
+
try:
|
|
1195
|
+
driver.write(seq)
|
|
1196
|
+
except Exception:
|
|
1197
|
+
pass
|
|
1198
|
+
for seq in self._MOUSE_ENABLE_SEQS:
|
|
1199
|
+
try:
|
|
1200
|
+
driver.write(seq)
|
|
1201
|
+
except Exception:
|
|
1202
|
+
pass
|
|
1203
|
+
try:
|
|
1204
|
+
driver.flush()
|
|
1205
|
+
except Exception:
|
|
1206
|
+
pass
|
|
1125
1207
|
except Exception:
|
|
1126
1208
|
pass
|
|
1127
1209
|
|
|
1210
|
+
def _maybe_rearm_mouse_on_keypress(self) -> None:
|
|
1211
|
+
"""Layer 12 — re-arm mouse tracking on each keystroke (debounced).
|
|
1212
|
+
|
|
1213
|
+
Trigger fires from ``on_key`` so any user keypress is treated as a
|
|
1214
|
+
recovery opportunity. A typing user is the strongest signal we
|
|
1215
|
+
have that the wheel just stopped working — they reached for the
|
|
1216
|
+
keyboard because the mouse stopped responding, or they're about
|
|
1217
|
+
to scroll back with PgUp and want it ready. Either way, paying
|
|
1218
|
+
~64 bytes per keypress (capped at 2 Hz by ``_KEYPRESS_REARM_DEBOUNCE``)
|
|
1219
|
+
is a trivial cost for sub-second recovery latency.
|
|
1220
|
+
|
|
1221
|
+
The debounce intentionally uses ``time.monotonic`` rather than the
|
|
1222
|
+
Textual scheduler so it survives across the async ``on_key``
|
|
1223
|
+
boundary without an extra task. ``-inf`` initial value guarantees
|
|
1224
|
+
the first keystroke always rearms.
|
|
1225
|
+
"""
|
|
1226
|
+
now = time.monotonic()
|
|
1227
|
+
if now - self._last_mouse_reenable_at < self._KEYPRESS_REARM_DEBOUNCE:
|
|
1228
|
+
return
|
|
1229
|
+
self._last_mouse_reenable_at = now
|
|
1230
|
+
self._reenable_mouse_tracking()
|
|
1231
|
+
|
|
1128
1232
|
def _self_heal_terminal_state(self) -> None:
|
|
1129
1233
|
"""Periodic recovery of mouse tracking and input focus (Layers 10 + 11).
|
|
1130
1234
|
|
|
@@ -345,6 +345,84 @@ virtualisation experiments) would close some of these by structure,
|
|
|
345
345
|
but at the cost of every other property the chat currently has
|
|
346
346
|
(selection, copy, mid-stream insertion of arbitrary Rich panels, plan
|
|
347
347
|
mounts). Layered defences are cheap and additive; the rewrite is not.
|
|
348
|
+
|
|
349
|
+
----
|
|
350
|
+
|
|
351
|
+
Post-mortem — "self-heal didn't recover the wheel" (2026-04-25,
|
|
352
|
+
``fix/scroll-analysis2``)
|
|
353
|
+
---------------------------------------------------------------------
|
|
354
|
+
**Symptom:** wheel-dead reproduced again post-Layer-11, this time
|
|
355
|
+
against ``final-fantasy-9/.aru/sessions/b33dfb99``. After a YOLO turn
|
|
356
|
+
finished, mouse wheel stopped working on every scrollable surface
|
|
357
|
+
simultaneously — the canonical Layer 7/9/10 fingerprint. User report:
|
|
358
|
+
*"não vi nenhum self healing seu funcionar até agora"* — Layers 9
|
|
359
|
+
(turn-boundary call) and 10 (8 s periodic tick) ran, the four
|
|
360
|
+
``?1000h``/``?1003h``/``?1015h``/``?1006h`` enables were emitted, and
|
|
361
|
+
yet the wheel never came back.
|
|
362
|
+
|
|
363
|
+
A byte scan of the persisted session turned up zero ``\\x1b`` bytes —
|
|
364
|
+
same shape as the Layer 9 incident. The leak is from a non-persisted
|
|
365
|
+
path (transient tool output, panel content, ConPTY-side state drift)
|
|
366
|
+
and we accept we won't trace its emitter. The real question Layer 12
|
|
367
|
+
answers is: **why didn't the recovery sequences work even when they
|
|
368
|
+
fired?**
|
|
369
|
+
|
|
370
|
+
**Two root causes the previous re-enable couldn't address:**
|
|
371
|
+
|
|
372
|
+
1. **ConPTY enable-cache.** Windows ConPTY tracks DEC private-mode
|
|
373
|
+
state on its side. When its cache says ``?1000`` is already ``h``
|
|
374
|
+
it can suppress the write to the underlying terminal — even when
|
|
375
|
+
the terminal itself lost the state. Sending ``?1000h`` while the
|
|
376
|
+
cache thinks it's already on is therefore a no-op against a real
|
|
377
|
+
leak. Our Layer-9/10 emit was exactly this no-op.
|
|
378
|
+
|
|
379
|
+
2. **Driver-side enable gate.** ``WindowsDriver._enable_mouse_support``
|
|
380
|
+
in textual 8.2.4 (``windows_driver.py:55``) opens with
|
|
381
|
+
``if not self._mouse: return``. ``_mouse`` is True by default and
|
|
382
|
+
shouldn't flip in normal use, but our recovery path was a single
|
|
383
|
+
``driver._enable_mouse_support()`` call — if any future Textual
|
|
384
|
+
refactor decides to flip the gate during pause / alt-screen toggle
|
|
385
|
+
/ shutdown, every layer 9 + 10 call silently no-ops and we'd never
|
|
386
|
+
see the failure.
|
|
387
|
+
|
|
388
|
+
**Layer 12 — three coordinated changes** (in ``aru/tui/app.py``):
|
|
389
|
+
|
|
390
|
+
a. **Off-then-on shake instead of enable-only.**
|
|
391
|
+
``_reenable_mouse_tracking`` now emits the four ``?...l`` (off)
|
|
392
|
+
sequences first, then the four ``?...h`` (on) sequences, then
|
|
393
|
+
flushes. The forced state transition defeats ConPTY's enable-cache —
|
|
394
|
+
the cache sees ``l→h`` and propagates the write regardless of what
|
|
395
|
+
it thinks the prior state was. Eight short writes (~64 bytes)
|
|
396
|
+
bufferised into one terminal emit by Textual's ``WriterThread``.
|
|
397
|
+
Idempotent: a healthy terminal lands in the same final state as
|
|
398
|
+
the old enable-only path, with a microscopic transition gap that
|
|
399
|
+
doesn't drop wheel events in practice.
|
|
400
|
+
|
|
401
|
+
b. **Bypass the driver's enable gate.** Recovery now calls
|
|
402
|
+
``driver.write(...)`` for each sequence directly instead of
|
|
403
|
+
``driver._enable_mouse_support()``. The four ``...l`` and four
|
|
404
|
+
``...h`` strings are kept as class-level tuples
|
|
405
|
+
(``_MOUSE_DISABLE_SEQS`` / ``_MOUSE_ENABLE_SEQS``) so any future
|
|
406
|
+
call site (Click handler, focus event) reuses the exact same set
|
|
407
|
+
without drift, and the path is robust against Textual API changes.
|
|
408
|
+
|
|
409
|
+
c. **Keypress trigger + tighter periodic tick.**
|
|
410
|
+
``_MOUSE_REENABLE_INTERVAL`` drops from 8 s to 3 s so worst-case
|
|
411
|
+
self-heal latency is sub-poll-cycle. ``on_key`` calls
|
|
412
|
+
``_maybe_rearm_mouse_on_keypress`` which fires
|
|
413
|
+
``_reenable_mouse_tracking`` on every keystroke debounced to 2 Hz
|
|
414
|
+
(``_KEYPRESS_REARM_DEBOUNCE = 0.5 s``). A typing user is the
|
|
415
|
+
strongest signal we have that they noticed the wheel just stopped
|
|
416
|
+
responding — recovery now happens on the very next keystroke, not
|
|
417
|
+
on the next tick.
|
|
418
|
+
|
|
419
|
+
Layer 12 differs from Layer 10 in the failure-mode it covers, not in
|
|
420
|
+
its shape. Layer 10 said "the bug will keep finding new emitters; let's
|
|
421
|
+
recover on a clock". Layer 12 says "even when we recover on the clock,
|
|
422
|
+
the recovery sequence itself can be ineffective; let's make the
|
|
423
|
+
recovery actually do something". The two stack: tick still runs,
|
|
424
|
+
keypress trigger gives it sub-second latency, off-on shake makes the
|
|
425
|
+
sequence the tick emits actually take effect.
|
|
348
426
|
"""
|
|
349
427
|
|
|
350
428
|
from __future__ import annotations
|
|
@@ -166,6 +166,7 @@ tests/test_tui_completer.py
|
|
|
166
166
|
tests/test_tui_completer_dynamic.py
|
|
167
167
|
tests/test_tui_copy.py
|
|
168
168
|
tests/test_tui_input_behaviour.py
|
|
169
|
+
tests/test_tui_layer12_recovery.py
|
|
169
170
|
tests/test_tui_mention_expand.py
|
|
170
171
|
tests/test_tui_modals.py
|
|
171
172
|
tests/test_tui_mode_cycle.py
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Layer 12 — mouse-tracking recovery: off-then-on shake + keypress trigger.
|
|
2
|
+
|
|
3
|
+
Background: ``aru/tui/widgets/chat.py`` post-mortem under "self-heal didn't
|
|
4
|
+
recover the wheel" (2026-04-25). Layers 9/10 emitted only the four ``?...h``
|
|
5
|
+
enable sequences; if ConPTY's enable-cache or the driver's gate suppressed
|
|
6
|
+
the write, no recovery happened. Layer 12 emits a forced ``?...l → ?...h``
|
|
7
|
+
state transition via ``driver.write`` and adds a per-keypress trigger so the
|
|
8
|
+
user gets sub-second recovery instead of waiting for the periodic tick.
|
|
9
|
+
|
|
10
|
+
These tests pin down the observable contracts:
|
|
11
|
+
* the eight DEC private-mode sequences are emitted in disable→enable order;
|
|
12
|
+
* the keypress trigger calls into ``_reenable_mouse_tracking`` debounced by
|
|
13
|
+
``_KEYPRESS_REARM_DEBOUNCE``.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
pytest.importorskip("textual")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _RecordingDriver:
|
|
24
|
+
"""Driver stub that records every ``write`` call."""
|
|
25
|
+
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self.writes: list[str] = []
|
|
28
|
+
self.flushes: int = 0
|
|
29
|
+
|
|
30
|
+
def write(self, data: str) -> None:
|
|
31
|
+
self.writes.append(data)
|
|
32
|
+
|
|
33
|
+
def flush(self) -> None:
|
|
34
|
+
self.flushes += 1
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.asyncio
|
|
38
|
+
async def test_reenable_mouse_tracking_emits_off_then_on_shake():
|
|
39
|
+
"""Layer 12: ``?...l`` (4) is emitted *before* ``?...h`` (4), then flush.
|
|
40
|
+
|
|
41
|
+
The off→on shake forces ConPTY's enable-cache through a state
|
|
42
|
+
transition, defeating the case where its cache claims ``?1000`` is
|
|
43
|
+
already ``h`` and suppresses the propagated write. Order matters —
|
|
44
|
+
if the on sequences came first the cache could no-op them.
|
|
45
|
+
"""
|
|
46
|
+
from aru.tui.app import AruApp
|
|
47
|
+
|
|
48
|
+
app = AruApp()
|
|
49
|
+
rec = _RecordingDriver()
|
|
50
|
+
# ``_driver`` is a private slot of ``App`` — assigning directly
|
|
51
|
+
# short-circuits the application-mode startup that would normally
|
|
52
|
+
# set it. The recovery method only reads ``self._driver``.
|
|
53
|
+
app._driver = rec
|
|
54
|
+
|
|
55
|
+
app._reenable_mouse_tracking()
|
|
56
|
+
|
|
57
|
+
expected_off = [
|
|
58
|
+
"\x1b[?1000l",
|
|
59
|
+
"\x1b[?1003l",
|
|
60
|
+
"\x1b[?1015l",
|
|
61
|
+
"\x1b[?1006l",
|
|
62
|
+
]
|
|
63
|
+
expected_on = [
|
|
64
|
+
"\x1b[?1000h",
|
|
65
|
+
"\x1b[?1003h",
|
|
66
|
+
"\x1b[?1015h",
|
|
67
|
+
"\x1b[?1006h",
|
|
68
|
+
]
|
|
69
|
+
assert rec.writes == expected_off + expected_on
|
|
70
|
+
# One flush at the end — the ``WriterThread`` bufferises everything
|
|
71
|
+
# before that into a single terminal emit.
|
|
72
|
+
assert rec.flushes == 1
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.asyncio
|
|
76
|
+
async def test_reenable_mouse_tracking_no_driver_is_noop():
|
|
77
|
+
"""If the driver is ``None`` (headless / pre-mount) the call is a quiet no-op."""
|
|
78
|
+
from aru.tui.app import AruApp
|
|
79
|
+
|
|
80
|
+
app = AruApp()
|
|
81
|
+
app._driver = None
|
|
82
|
+
# Must not raise.
|
|
83
|
+
app._reenable_mouse_tracking()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.asyncio
|
|
87
|
+
async def test_keypress_rearm_is_debounced(monkeypatch):
|
|
88
|
+
"""Layer 12 keypress trigger respects ``_KEYPRESS_REARM_DEBOUNCE``.
|
|
89
|
+
|
|
90
|
+
Two keystrokes within the debounce window should produce exactly one
|
|
91
|
+
``_reenable_mouse_tracking`` invocation; a third keystroke after the
|
|
92
|
+
window elapses should produce a second.
|
|
93
|
+
"""
|
|
94
|
+
from aru.tui import app as app_mod
|
|
95
|
+
from aru.tui.app import AruApp
|
|
96
|
+
|
|
97
|
+
app = AruApp()
|
|
98
|
+
rec = _RecordingDriver()
|
|
99
|
+
app._driver = rec
|
|
100
|
+
|
|
101
|
+
fake_now = [100.0]
|
|
102
|
+
|
|
103
|
+
def fake_monotonic() -> float:
|
|
104
|
+
return fake_now[0]
|
|
105
|
+
|
|
106
|
+
monkeypatch.setattr(app_mod.time, "monotonic", fake_monotonic)
|
|
107
|
+
|
|
108
|
+
# 1st keystroke at t=100 — fires.
|
|
109
|
+
app._maybe_rearm_mouse_on_keypress()
|
|
110
|
+
# 2nd keystroke 100 ms later — within 500 ms debounce → suppressed.
|
|
111
|
+
fake_now[0] += 0.1
|
|
112
|
+
app._maybe_rearm_mouse_on_keypress()
|
|
113
|
+
# 3rd keystroke 600 ms after 1st (i.e. 500 ms after debounce window
|
|
114
|
+
# opened) — fires.
|
|
115
|
+
fake_now[0] += 0.5
|
|
116
|
+
app._maybe_rearm_mouse_on_keypress()
|
|
117
|
+
|
|
118
|
+
# Each fired call emits 8 sequences (4 off + 4 on). Two fires = 16.
|
|
119
|
+
assert len(rec.writes) == 16
|
|
120
|
+
assert rec.flushes == 2
|
aru_code-0.44.0/aru/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.44.0"
|
|
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
|