diff --git a/evidence/pilot-pointer-ingress-20261006/QUALIFIED.json b/evidence/pilot-pointer-ingress-20261006/QUALIFIED.json new file mode 100644 index 0000000000..cf20325480 --- /dev/null +++ b/evidence/pilot-pointer-ingress-20261006/QUALIFIED.json @@ -0,0 +1,32 @@ +{ + "scope": "Native source App/Pilot/driver/capture final-delivery contract, including real run_async. Not Batch05 causal identification, installed Toad, physical motion or latency.", + "sources": { + "src/textual/app.py": "0bb5b50ff4260453c72b2e189cd10ef1c8152c9cbf2eeb97dd42aba9b1e50ca9", + "src/textual/events.py": "dd039e7a82fa34b8e2057f624d800dda03d0271f631e56339ac177083448c6ce", + "src/textual/message.py": "e140b2018efa08c02d75aeddb29ab7d315d8ab2a48ad98a9db84cb21279653c6", + "src/textual/message_pump.py": "b4c732994a8203e7d6b274f60ecf073c1e0219d6fd3401275baa06c3da9ad089", + "src/textual/pilot.py": "b2054a216c3c6954dda68855171e2c047d23102e837d52be1ffea7f85e88d585", + "src/textual/screen.py": "8672e6e6c451148b7dcf724138389ce96b8824c2d72f5e121bde3a91b752d86f", + "tests/test_app.py": "b8abd10603eac0f6d0c16eb80bfaedc2821fb48f30cba27cbd563307a9b74c64", + "tests/test_selection.py": "9c808b55c2011bd87b96ab4c205eb28d9c0b72ed6996fb4c9ba11ab54ece57a6", + "tests/test_pilot_pointer_ingress.py": "5274eb5529df68d21d6f4c68a63b8e19cac618b1a5ac668e2ecd6bd4f9c8f5a9" + }, + "keepers": { + "changed-path.log": "3ee2aa2b4c711d2da6cf28d1d0b397ca39bb4332b6abbca695712d823c5693ad", + "changed-path-final.log": "77ae39be709a48e9bfcae4df8ecbf81cd93293bc06ed5db6bdd054b90218db21", + "background-final.log": "2826a5e72c9f873a0370a41fe46a67989c4a682af951a8514eeb48dfd0b0e61b", + "overridden-consumer.log": "1d4a11fc90a7086c182cc06bc43bebeb59a36928082944ef103c86041aa4cf49", + "before.json": "26a0c05b07868e2f4f865463d871f1015c97c4c9e07ce19579a1db513e0659be", + "after.json": "3b8d68f02c9a132d49ceaeb9be89e68ec9afef754f9f3fbc4ab2a66f74f5e550" + }, + "checks": { + "initial_batch": "160 passed, 1 fixture failure, 22 deselected in 27.62s", + "final_changed_paths": "5 passed, 1 Screen-id fixture failure in 1.31s", + "corrected_screen": "1 passed in .71s", + "overridden_consumer": "1 passed in .83s" + }, + "provider_inputs": 0, + "physical_runs": 0, + "installed_changes": 0, + "competing_native_writer": "none; only this PR open at source completion" +} diff --git a/evidence/pilot-pointer-ingress-20261006/README.md b/evidence/pilot-pointer-ingress-20261006/README.md new file mode 100644 index 0000000000..1538129d6a --- /dev/null +++ b/evidence/pilot-pointer-ingress-20261006/README.md @@ -0,0 +1,19 @@ +# Pilot pointer delivery + +Pilot now queues raw pointer packets on App and borrows the existing dispatch completion. App alone matches a press/release and supplies the final Click receipt and chain. Screen fills the original MouseEvent recipient when it actually routes through capture/current geometry; Pilot no longer keeps the first hit or authors Clicks. The gesture's screen coordinates are acquired after initial layout settlement and stay fixed across the press/release. + +The existing completion future now carries the final message. Its release still occurs after dispatch and call-next work on the owning pump. Screen uses an offset event for its own delivery as it already does for child delivery, retaining the raw App packet's completion. A release consumes its press even if no click is admitted. + +Current consumer/source evidence is in before.json (native production/tests and read-only Toad production/tests/tools; zero parse omissions). Arbitrary external subclasses/dynamic getattr are not resolved by lexical AST. The existing overridden test on_event now returns the original owner's result. + +Changed-path source Apps are qualified, including the actual run_async/HeadlessDriver entrypoint: + +- Driver + Pilot share one click chain/modifiers; an unmatched second release produces no click. +- A target moved by press or hover is not reported as the final recipient. +- Capture governs release delivery even when another widget is under the pointer. +- A completed click retains its recipient if that handler removes the widget. +- Blank Screen delivery retains the raw App completion, and the changed inherited on_event/copy/drag consumer passes. + +The initial batch passed 160 checks (155 existing Pilot/click-chain checks and five new ones); one new Screen fixture assertion failed because its Horizontal covered the chosen point. The final five changed-path checks passed after moving the existing pause before coordinate acquisition rather than adding a delay. Its corrected blank-Screen fixture then failed only on an invented null Screen id; the original default Screen id is _default. The exact Screen-identity assertion passes separately (.71s), as does the changed inherited consumer (.83s). All three raw logs, both fixture negatives, and the original Batch05 evidence remain distinct. No production repair was needed for either fixture assertion. + +before.json/after.json include the actual MouseEvent.widget declaration/reads/writes. Native 249 production, 465 -> 466 tests; dependency Toad 288 production/403 tests/40 tools, zero omissions. Static Toad event.widget consumers were read: GridSelect walks recipient ancestors; Store and Conversation use the original Click recipient; no on_event override requires migration. Driver coroutine/thread delivery remains unchanged. Batch05 is preserved: its Shift click reported true but selected only batch-a. No click-time geometry was retained, so this source defect does not identify its sole cause. No Batch05 replay, installed pin change, provider or physical run. diff --git a/evidence/pilot-pointer-ingress-20261006/after.json b/evidence/pilot-pointer-ingress-20261006/after.json new file mode 100644 index 0000000000..fc98ec6ba8 --- /dev/null +++ b/evidence/pilot-pointer-ingress-20261006/after.json @@ -0,0 +1,1486 @@ +{ + "coverage": [ + { + "checkout": "/home/ts/wt/textual-frame-publication-20261006", + "revision": "54e153614f5e5355dd85355bdc135c0318faf99a", + "root": "src/textual", + "parsed": 249, + "omissions": [] + }, + { + "checkout": "/home/ts/wt/textual-frame-publication-20261006", + "revision": "54e153614f5e5355dd85355bdc135c0318faf99a", + "root": "tests", + "parsed": 466, + "omissions": [] + }, + { + "checkout": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "revision": "d41b42455cd5c085bee192f40b987f58867c91cc", + "root": "src/toad", + "parsed": 288, + "omissions": [] + }, + { + "checkout": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "revision": "d41b42455cd5c085bee192f40b987f58867c91cc", + "root": "tests", + "parsed": 403, + "omissions": [] + }, + { + "checkout": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "revision": "d41b42455cd5c085bee192f40b987f58867c91cc", + "root": "tools", + "parsed": 40, + "omissions": [] + } + ], + "sites": [ + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/_compositor.py", + "references": { + "widget:Attribute:Load": [ + 1019, + 1020 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/app.py", + "references": { + "on_event:AsyncFunctionDef:NoneType": [ + 4101 + ], + "mouse_captured:Attribute:Store": [ + 656, + 3255 + ], + "_mouse_down_widget:Attribute:Store": [ + 670, + 4129, + 4118, + 4123 + ], + "mouse_captured:Attribute:Load": [ + 3234, + 3251, + 3253, + 3254 + ], + "_dispatch_message:Attribute:Load": [ + 3787, + 3500, + 3452, + 3453, + 3458 + ], + "send_message:Attribute:Load": [ + 2076 + ], + "on_event:Attribute:Load": [ + 4106, + 4199 + ], + "_mouse_down_widget:Attribute:Load": [ + 4125 + ], + "_forward_event:Attribute:Load": [ + 4133, + 4191, + 4195, + 4197, + 4189, + 4167 + ], + "Click:Attribute:Load": [ + 4160 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/command.py", + "references": { + "Click:Name:Load": [ + 804 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/css/scalar_animation.py", + "references": { + "widget:Attribute:Store": [ + 31 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/demo/game.py", + "references": { + "Click:Attribute:Load": [ + 496, + 497 + ], + "widget:Attribute:Load": [ + 498, + 499 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/demo/widgets.py", + "references": { + "Click:Attribute:Load": [ + 613, + 614 + ], + "widget:Attribute:Load": [ + 618, + 619 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/driver.py", + "references": { + "send_message:FunctionDef:NoneType": [ + 67 + ], + "process_message:FunctionDef:NoneType": [ + 77 + ], + "send_message:Attribute:Load": [ + 132, + 114 + ], + "widget:Attribute:Load": [ + 116 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/drivers/linux_driver.py", + "references": { + "process_message:FunctionDef:NoneType": [ + 477 + ], + "process_message:Attribute:Load": [ + 492, + 461, + 484, + 488, + 459 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/drivers/linux_inline_driver.py", + "references": { + "process_message:Attribute:Load": [ + 163, + 158 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/drivers/web_driver.py", + "references": { + "process_message:Attribute:Load": [ + 205, + 200 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/drivers/windows_driver.py", + "references": { + "process_message:Attribute:Load": [ + 104 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/events.py", + "references": { + "Click:ClassDef:NoneType": [ + 637 + ], + "_apply_offset:FunctionDef:NoneType": [ + 557, + 703 + ], + "widget:Attribute:Store": [ + 383 + ], + "widget:Attribute:Load": [ + 501, + 870, + 887, + 481, + 559, + 705 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/layout.py", + "references": { + "widget:Attribute:Load": [ + 204, + 106, + 49, + 111, + 111 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/layouts/stream.py", + "references": { + "widget:Attribute:Load": [ + 72 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/message.py", + "references": { + "_complete_dispatch:FunctionDef:NoneType": [ + 168 + ], + "_dispatch_completion:Attribute:Store": [ + 60, + 166, + 175 + ], + "_dispatch_completion:Attribute:Load": [ + 162, + 172, + 173, + 161 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/message_pump.py", + "references": { + "_dispatch_message:AsyncFunctionDef:NoneType": [ + 781 + ], + "on_event:AsyncFunctionDef:NoneType": [ + 878 + ], + "_post_message_and_wait:FunctionDef:NoneType": [ + 930 + ], + "_dispatch_completion:Attribute:Store": [ + 945 + ], + "_dispatch_completion:Attribute:Load": [ + 951 + ], + "_dispatch_message:Attribute:Load": [ + 665, + 670, + 729, + 668 + ], + "_complete_dispatch:Attribute:Load": [ + 738, + 954, + 960 + ], + "on_event:Attribute:Load": [ + 802 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/messages.py", + "references": { + "widget:Attribute:Store": [ + 36, + 57 + ], + "widget:Attribute:Load": [ + 39, + 43, + 43, + 48, + 48 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/pilot.py", + "references": { + "_get_mouse_message_arguments:FunctionDef:NoneType": [ + 24 + ], + "_post_mouse_events:AsyncFunctionDef:NoneType": [ + 377 + ], + "_get_mouse_message_arguments:Name:Load": [ + 426 + ], + "_post_mouse_events:Attribute:Load": [ + 135, + 180, + 236, + 373 + ], + "Click:Name:Load": [ + 444, + 237, + 448 + ], + "widget:Attribute:Load": [ + 457 + ], + "_post_message_and_wait:Attribute:Load": [ + 452 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/screen.py", + "references": { + "_handle_mouse_move:FunctionDef:NoneType": [ + 1807 + ], + "_forward_event:FunctionDef:NoneType": [ + 2008 + ], + "widget:Attribute:Load": [ + 1502, + 1516, + 1519 + ], + "widget:Attribute:Store": [ + 1808, + 1829, + 2055, + 2134 + ], + "mouse_captured:Attribute:Load": [ + 1812, + 2162, + 1813, + 2116, + 2117, + 2076 + ], + "_apply_offset:Attribute:Load": [ + 1834, + 2137, + 2142 + ], + "_post_message_and_wait:Attribute:Load": [ + 1836, + 2139 + ], + "_forward_event:Attribute:Load": [ + 1840, + 2141 + ], + "_handle_mouse_move:Attribute:Load": [ + 2019 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/scrollbar.py", + "references": { + "Click:Attribute:Load": [ + 399 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widget.py", + "references": { + "_forward_event:FunctionDef:NoneType": [ + 4550 + ], + "_mouse_down_widget:Attribute:Store": [ + 4548 + ], + "mouse_captured:Attribute:Load": [ + 1796, + 4900, + 2161 + ], + "Click:Attribute:Load": [ + 4977 + ], + "widget:Attribute:Load": [ + 4978 + ], + "_post_message_and_wait:Attribute:Load": [ + 4553 + ], + "_dispatch_message:Attribute:Load": [ + 4768 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_button.py", + "references": { + "Click:Attribute:Load": [ + 416 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_collapsible.py", + "references": { + "Click:Attribute:Load": [ + 73 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_data_table.py", + "references": { + "Click:Attribute:Load": [ + 2669 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_header.py", + "references": { + "Click:Name:Load": [ + 43 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_list_item.py", + "references": { + "Click:Attribute:Load": [ + 29 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_masked_input.py", + "references": { + "Click:Attribute:Load": [ + 597 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_option_list.py", + "references": { + "Click:Attribute:Load": [ + 721 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_placeholder.py", + "references": { + "Click:Attribute:Load": [ + 178 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_radio_set.py", + "references": { + "Click:Name:Load": [ + 260 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_select.py", + "references": { + "Click:Attribute:Load": [ + 270 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_switch.py", + "references": { + "Click:Name:Load": [ + 202 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_tabs.py", + "references": { + "Click:Attribute:Load": [ + 89 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_toast.py", + "references": { + "Click:Name:Load": [ + 132 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_toggle_button.py", + "references": { + "Click:Name:Load": [ + 257 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_tree.py", + "references": { + "Click:Attribute:Load": [ + 1455 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_app.py", + "references": { + "_get_mouse_message_arguments:Name:Load": [ + 269 + ], + "Click:Attribute:Load": [ + 289, + 314, + 343, + 365 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_box_model_cache_lifetime.py", + "references": { + "widget:Attribute:Load": [ + 79 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_driver.py", + "references": { + "Click:Name:Load": [ + 77, + 91 + ], + "process_message:Attribute:Load": [ + 85, + 86, + 108, + 109, + 136, + 137, + 176, + 177 + ], + "send_message:Attribute:Load": [ + 32, + 33, + 64 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_pilot.py", + "references": { + "Click:Attribute:Load": [ + 441, + 451 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_pilot_pointer_ingress.py", + "references": { + "_mouse_down_widget:Attribute:Load": [ + 94 + ], + "widget:Attribute:Load": [ + 105, + 126, + 29 + ], + "mouse_captured:Attribute:Load": [ + 119 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_pointer_capture_order.py", + "references": { + "process_message:Attribute:Load": [ + 14 + ], + "mouse_captured:Attribute:Load": [ + 73, + 115, + 140, + 158, + 181 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_real_app_pump_lifetime.py", + "references": { + "mouse_captured:Attribute:Load": [ + 76 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_remove_transaction.py", + "references": { + "send_message:Attribute:Load": [ + 45 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_removed_scene_ownership.py", + "references": { + "mouse_captured:Attribute:Load": [ + 79 + ], + "widget:Attribute:Load": [ + 120, + 122 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_reparent.py", + "references": { + "mouse_captured:Attribute:Load": [ + 149 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_selection.py", + "references": { + "on_event:AsyncFunctionDef:NoneType": [ + 445 + ], + "_forward_event:Attribute:Load": [ + 254, + 259 + ], + "on_event:Attribute:Load": [ + 446 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/app.py", + "references": { + "Click:Attribute:Load": [ + 604 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/messages.py", + "references": { + "widget:Attribute:Load": [ + 25 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/screens/main.py", + "references": { + "widget:Attribute:Load": [ + 216, + 207 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/screens/store.py", + "references": { + "Click:Attribute:Load": [ + 90 + ], + "widget:Attribute:Load": [ + 514, + 499, + 496, + 500 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/session_presentation.py", + "references": { + "widget:Attribute:Store": [ + 148, + 227, + 192 + ], + "widget:Attribute:Load": [ + 193, + 187, + 240, + 251, + 153, + 158, + 163, + 217, + 223, + 153, + 249, + 271, + 157, + 162, + 272, + 305, + 312 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/comms_menu.py", + "references": { + "Click:Attribute:Load": [ + 169 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/conversation.py", + "references": { + "widget:Attribute:Load": [ + 1838 + ], + "Click:Attribute:Load": [ + 1827 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/goal_bar.py", + "references": { + "Click:Attribute:Load": [ + 31 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/grid_select.py", + "references": { + "Click:Attribute:Load": [ + 173 + ], + "widget:Attribute:Load": [ + 174, + 183 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/history_anchor.py", + "references": { + "widget:Attribute:Load": [ + 137, + 149, + 397, + 340, + 350, + 350, + 401 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/input_delivery.py", + "references": { + "Click:Attribute:Load": [ + 43 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/irc_message.py", + "references": { + "Click:Attribute:Load": [ + 43 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/mandelbrot.py", + "references": { + "Click:Attribute:Load": [ + 166, + 174 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/model_switcher.py", + "references": { + "Click:Attribute:Load": [ + 36 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/project_panel.py", + "references": { + "Click:Attribute:Load": [ + 45 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/prompt.py", + "references": { + "Click:Attribute:Load": [ + 560, + 564, + 74, + 102 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/prompt_popup.py", + "references": { + "widget:Attribute:Load": [ + 68 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/question.py", + "references": { + "Click:Attribute:Load": [ + 107 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/recovery_view.py", + "references": { + "Click:Attribute:Load": [ + 101 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/retiring_sidebar.py", + "references": { + "widget:Attribute:Load": [ + 53, + 54 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/route_header.py", + "references": { + "Click:Attribute:Load": [ + 63 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/session_sort.py", + "references": { + "Click:Attribute:Load": [ + 102 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/session_tabs.py", + "references": { + "Click:Attribute:Load": [ + 33, + 72, + 123 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/session_thread_sidebar.py", + "references": { + "widget:Attribute:Load": [ + 43 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/side_bar.py", + "references": { + "Click:Name:Load": [ + 199, + 264, + 307, + 502, + 928 + ], + "widget:Attribute:Load": [ + 908, + 755, + 909 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/terminal.py", + "references": { + "Click:Attribute:Load": [ + 266 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/tool_call.py", + "references": { + "Click:Attribute:Load": [ + 388, + 389 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/transcript_history.py", + "references": { + "Click:Attribute:Load": [ + 174 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/viewport_body.py", + "references": { + "widget:Attribute:Load": [ + 1076 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/async_tab_activation_pilot.py", + "references": { + "widget:Attribute:Load": [ + 63 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/canonical_wire_retained_real_installed_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 363, + 340 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/channel_display_basis_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 32, + 33 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/channel_partial_paint_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 30 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/channel_retirement_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 31 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/checkpoint_source_pivot_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 38 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/comms_pilot.py", + "references": { + "widget:Attribute:Load": [ + 74 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/divider_unread_paint_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 33 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/editor_key_burst_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 64 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/file_path_preview_pilot.py", + "references": { + "Click:Attribute:Load": [ + 355 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/goal_resize_pilot.py", + "references": { + "mouse_captured:Attribute:Load": [ + 87, + 43, + 90, + 47 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/historical_views_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 219, + 220, + 225, + 226 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/history_stationary_local_runway_pilot.py", + "references": { + "widget:Attribute:Load": [ + 74 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/late_native_after_join_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 44 + ], + "_dispatch_message:Attribute:Load": [ + 67 + ], + "_dispatch_message:Attribute:Store": [ + 73 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/late_native_application_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 44 + ], + "_dispatch_message:Attribute:Load": [ + 67 + ], + "_dispatch_message:Attribute:Store": [ + 73 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/markdown_return_reuse_pilot.py", + "references": { + "_post_mouse_events:Attribute:Load": [ + 99 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/mcp_decision_pty_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 180, + 205 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/message_dividers_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 298, + 299, + 58, + 262 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/native_loaded_return_cache_pilot.py", + "references": { + "widget:Attribute:Load": [ + 95, + 303 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/native_permission_ui.py", + "references": { + "widget:Attribute:Load": [ + 109 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/native_session_retention_pilot.py", + "references": { + "widget:Attribute:Load": [ + 196, + 485, + 487, + 534, + 187, + 232, + 256, + 190, + 404, + 405 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/prepared_height_contract_pilot.py", + "references": { + "widget:Attribute:Load": [ + 126, + 118, + 139 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/response_source_custody_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 72, + 73 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/saved_state_user_journey_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 846 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/scroll_select_latency_pilot.py", + "references": { + "_forward_event:Attribute:Load": [ + 65, + 209, + 317 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/selection_hover_pilot.py", + "references": { + "_forward_event:Attribute:Load": [ + 32, + 39 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/session_initial_prompt_custody_pilot.py", + "references": { + "widget:Attribute:Load": [ + 100, + 127 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/shared_channels_pilot.py", + "references": { + "widget:Attribute:Load": [ + 186, + 53, + 191 + ], + "mouse_captured:Attribute:Load": [ + 122, + 126 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/sidebar_custody_installed_journey.py", + "references": { + "widget:Attribute:Load": [ + 66 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/sidebar_drag_resize_pilot.py", + "references": { + "mouse_captured:Attribute:Load": [ + 68 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/sidebar_edge_anchor_installed_pilot.py", + "references": { + "mouse_captured:Attribute:Load": [ + 87 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/sidebar_retirement_installed_pilot.py", + "references": { + "widget:Attribute:Load": [ + 85, + 109, + 120, + 130 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/sidebar_retirement_pilot.py", + "references": { + "widget:Attribute:Load": [ + 94, + 97 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/stale_selection_pilot.py", + "references": { + "_forward_event:Attribute:Load": [ + 35 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/test_context_manifest_navigation.py", + "references": { + "Click:Attribute:Load": [ + 419, + 227 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/viewport_demand_custody_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 86 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/viewport_selection_diff_pilot.py", + "references": { + "_forward_event:Attribute:Load": [ + 50, + 51, + 79 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/watcher_readiness_burst.py", + "references": { + "on_event:Name:Store": [ + 20 + ], + "on_event:Name:Load": [ + 26 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tools/performance/capture_state.py", + "references": { + "widget:Attribute:Load": [ + 415 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tools/performance/sidebar_validation_driver.py", + "references": { + "send_message:FunctionDef:NoneType": [ + 562 + ], + "_dispatch_message:Attribute:Load": [ + 103 + ], + "_dispatch_message:Attribute:Store": [ + 133 + ], + "send_message:Attribute:Load": [ + 569 + ], + "mouse_captured:Attribute:Load": [ + 723 + ], + "widget:Attribute:Load": [ + 76, + 639, + 640 + ] + } + } + ], + "limitations": "Lexical references include imported and inherited calls; externally supplied subclasses and dynamic getattr remain unresolved." +} diff --git a/evidence/pilot-pointer-ingress-20261006/background-final.log b/evidence/pilot-pointer-ingress-20261006/background-final.log new file mode 100644 index 0000000000..be111bc63f --- /dev/null +++ b/evidence/pilot-pointer-ingress-20261006/background-final.log @@ -0,0 +1,2 @@ +. [100%] +1 passed in 0.71s diff --git a/evidence/pilot-pointer-ingress-20261006/before.json b/evidence/pilot-pointer-ingress-20261006/before.json new file mode 100644 index 0000000000..695591ee2d --- /dev/null +++ b/evidence/pilot-pointer-ingress-20261006/before.json @@ -0,0 +1,1457 @@ +{ + "coverage": [ + { + "checkout": "/home/ts/wt/textual-frame-publication-20261006", + "revision": "67af8df0cd6a5224a8ee6fb84244486ea011fcc9", + "root": "src/textual", + "parsed": 249, + "omissions": [] + }, + { + "checkout": "/home/ts/wt/textual-frame-publication-20261006", + "revision": "67af8df0cd6a5224a8ee6fb84244486ea011fcc9", + "root": "tests", + "parsed": 465, + "omissions": [] + }, + { + "checkout": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "revision": "d41b42455cd5c085bee192f40b987f58867c91cc", + "root": "src/toad", + "parsed": 288, + "omissions": [] + }, + { + "checkout": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "revision": "d41b42455cd5c085bee192f40b987f58867c91cc", + "root": "tests", + "parsed": 403, + "omissions": [] + }, + { + "checkout": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "revision": "d41b42455cd5c085bee192f40b987f58867c91cc", + "root": "tools", + "parsed": 40, + "omissions": [] + } + ], + "sites": [ + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/_compositor.py", + "references": { + "widget:Attribute:Load": [ + 1019, + 1020 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/app.py", + "references": { + "on_event:AsyncFunctionDef:NoneType": [ + 4101 + ], + "mouse_captured:Attribute:Store": [ + 656, + 3255 + ], + "_mouse_down_widget:Attribute:Store": [ + 670, + 4118, + 4123 + ], + "mouse_captured:Attribute:Load": [ + 3234, + 3251, + 3253, + 3254 + ], + "_dispatch_message:Attribute:Load": [ + 3787, + 3500, + 3452, + 3453, + 3458 + ], + "send_message:Attribute:Load": [ + 2076 + ], + "on_event:Attribute:Load": [ + 4106, + 4193 + ], + "_forward_event:Attribute:Load": [ + 4127, + 4185, + 4189, + 4191, + 4183, + 4162 + ], + "_mouse_down_widget:Attribute:Load": [ + 4133, + 4137 + ], + "Click:Attribute:Load": [ + 4155 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/command.py", + "references": { + "Click:Name:Load": [ + 804 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/css/scalar_animation.py", + "references": { + "widget:Attribute:Store": [ + 31 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/demo/game.py", + "references": { + "Click:Attribute:Load": [ + 496, + 497 + ], + "widget:Attribute:Load": [ + 498, + 499 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/demo/widgets.py", + "references": { + "Click:Attribute:Load": [ + 613, + 614 + ], + "widget:Attribute:Load": [ + 618, + 619 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/driver.py", + "references": { + "send_message:FunctionDef:NoneType": [ + 67 + ], + "process_message:FunctionDef:NoneType": [ + 77 + ], + "send_message:Attribute:Load": [ + 132, + 114 + ], + "widget:Attribute:Load": [ + 116 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/drivers/linux_driver.py", + "references": { + "process_message:FunctionDef:NoneType": [ + 477 + ], + "process_message:Attribute:Load": [ + 492, + 461, + 484, + 488, + 459 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/drivers/linux_inline_driver.py", + "references": { + "process_message:Attribute:Load": [ + 163, + 158 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/drivers/web_driver.py", + "references": { + "process_message:Attribute:Load": [ + 205, + 200 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/drivers/windows_driver.py", + "references": { + "process_message:Attribute:Load": [ + 104 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/events.py", + "references": { + "Click:ClassDef:NoneType": [ + 634 + ], + "_apply_offset:FunctionDef:NoneType": [ + 554, + 700 + ], + "widget:Attribute:Store": [ + 383 + ], + "widget:Attribute:Load": [ + 498, + 867, + 884, + 478, + 556, + 702 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/layout.py", + "references": { + "widget:Attribute:Load": [ + 204, + 106, + 49, + 111, + 111 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/layouts/stream.py", + "references": { + "widget:Attribute:Load": [ + 72 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/message.py", + "references": { + "_complete_dispatch:FunctionDef:NoneType": [ + 168 + ], + "_dispatch_completion:Attribute:Store": [ + 60, + 166, + 173 + ], + "_dispatch_completion:Attribute:Load": [ + 162, + 170, + 171, + 161 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/message_pump.py", + "references": { + "_dispatch_message:AsyncFunctionDef:NoneType": [ + 780 + ], + "on_event:AsyncFunctionDef:NoneType": [ + 875 + ], + "_post_message_and_wait:FunctionDef:NoneType": [ + 923 + ], + "_dispatch_completion:Attribute:Store": [ + 938 + ], + "_dispatch_completion:Attribute:Load": [ + 944 + ], + "_dispatch_message:Attribute:Load": [ + 665, + 670, + 728, + 668 + ], + "_complete_dispatch:Attribute:Load": [ + 737, + 947, + 953 + ], + "on_event:Attribute:Load": [ + 800 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/messages.py", + "references": { + "widget:Attribute:Store": [ + 36, + 57 + ], + "widget:Attribute:Load": [ + 39, + 43, + 43, + 48, + 48 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/pilot.py", + "references": { + "_get_mouse_message_arguments:FunctionDef:NoneType": [ + 24 + ], + "_post_mouse_events:AsyncFunctionDef:NoneType": [ + 383 + ], + "_get_mouse_message_arguments:Name:Load": [ + 431 + ], + "_post_mouse_events:Attribute:Load": [ + 135, + 180, + 238, + 379 + ], + "Click:Name:Load": [ + 457, + 239 + ], + "_forward_event:Attribute:Load": [ + 468 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/screen.py", + "references": { + "_handle_mouse_move:FunctionDef:NoneType": [ + 1807 + ], + "_forward_event:FunctionDef:NoneType": [ + 2004 + ], + "widget:Attribute:Load": [ + 1502, + 1516, + 1519 + ], + "mouse_captured:Attribute:Load": [ + 1811, + 2154, + 1812, + 2111, + 2112, + 2071 + ], + "_post_message_and_wait:Attribute:Load": [ + 1832, + 2131 + ], + "_forward_event:Attribute:Load": [ + 1836, + 2133 + ], + "_handle_mouse_move:Attribute:Load": [ + 2015 + ], + "_apply_offset:Attribute:Load": [ + 2134 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/scrollbar.py", + "references": { + "Click:Attribute:Load": [ + 399 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widget.py", + "references": { + "_forward_event:FunctionDef:NoneType": [ + 4550 + ], + "_mouse_down_widget:Attribute:Store": [ + 4548 + ], + "mouse_captured:Attribute:Load": [ + 1796, + 4900, + 2161 + ], + "Click:Attribute:Load": [ + 4977 + ], + "widget:Attribute:Load": [ + 4978 + ], + "_post_message_and_wait:Attribute:Load": [ + 4553 + ], + "_dispatch_message:Attribute:Load": [ + 4768 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_button.py", + "references": { + "Click:Attribute:Load": [ + 416 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_collapsible.py", + "references": { + "Click:Attribute:Load": [ + 73 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_data_table.py", + "references": { + "Click:Attribute:Load": [ + 2669 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_header.py", + "references": { + "Click:Name:Load": [ + 43 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_list_item.py", + "references": { + "Click:Attribute:Load": [ + 29 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_masked_input.py", + "references": { + "Click:Attribute:Load": [ + 597 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_option_list.py", + "references": { + "Click:Attribute:Load": [ + 721 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_placeholder.py", + "references": { + "Click:Attribute:Load": [ + 178 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_radio_set.py", + "references": { + "Click:Name:Load": [ + 260 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_select.py", + "references": { + "Click:Attribute:Load": [ + 270 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_switch.py", + "references": { + "Click:Name:Load": [ + 202 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_tabs.py", + "references": { + "Click:Attribute:Load": [ + 89 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_toast.py", + "references": { + "Click:Name:Load": [ + 132 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_toggle_button.py", + "references": { + "Click:Name:Load": [ + 257 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "src/textual/widgets/_tree.py", + "references": { + "Click:Attribute:Load": [ + 1455 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_app.py", + "references": { + "_get_mouse_message_arguments:Name:Load": [ + 270 + ], + "Click:Attribute:Load": [ + 290, + 315, + 344, + 366 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_box_model_cache_lifetime.py", + "references": { + "widget:Attribute:Load": [ + 79 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_driver.py", + "references": { + "Click:Name:Load": [ + 77, + 91 + ], + "process_message:Attribute:Load": [ + 85, + 86, + 108, + 109, + 136, + 137, + 176, + 177 + ], + "send_message:Attribute:Load": [ + 32, + 33, + 64 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_pilot.py", + "references": { + "Click:Attribute:Load": [ + 441, + 451 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_pointer_capture_order.py", + "references": { + "process_message:Attribute:Load": [ + 14 + ], + "mouse_captured:Attribute:Load": [ + 73, + 115, + 140, + 158, + 181 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_real_app_pump_lifetime.py", + "references": { + "mouse_captured:Attribute:Load": [ + 76 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_remove_transaction.py", + "references": { + "send_message:Attribute:Load": [ + 45 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_removed_scene_ownership.py", + "references": { + "mouse_captured:Attribute:Load": [ + 79 + ], + "widget:Attribute:Load": [ + 120, + 122 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_reparent.py", + "references": { + "mouse_captured:Attribute:Load": [ + 149 + ] + } + }, + { + "root": "/home/ts/wt/textual-frame-publication-20261006", + "file": "tests/test_selection.py", + "references": { + "on_event:AsyncFunctionDef:NoneType": [ + 445 + ], + "_forward_event:Attribute:Load": [ + 254, + 259 + ], + "on_event:Attribute:Load": [ + 446 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/app.py", + "references": { + "Click:Attribute:Load": [ + 604 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/messages.py", + "references": { + "widget:Attribute:Load": [ + 25 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/screens/main.py", + "references": { + "widget:Attribute:Load": [ + 216, + 207 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/screens/store.py", + "references": { + "Click:Attribute:Load": [ + 90 + ], + "widget:Attribute:Load": [ + 514, + 499, + 496, + 500 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/session_presentation.py", + "references": { + "widget:Attribute:Store": [ + 148, + 227, + 192 + ], + "widget:Attribute:Load": [ + 193, + 187, + 240, + 251, + 153, + 158, + 163, + 217, + 223, + 153, + 249, + 271, + 157, + 162, + 272, + 305, + 312 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/comms_menu.py", + "references": { + "Click:Attribute:Load": [ + 169 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/conversation.py", + "references": { + "widget:Attribute:Load": [ + 1838 + ], + "Click:Attribute:Load": [ + 1827 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/goal_bar.py", + "references": { + "Click:Attribute:Load": [ + 31 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/grid_select.py", + "references": { + "Click:Attribute:Load": [ + 173 + ], + "widget:Attribute:Load": [ + 174, + 183 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/history_anchor.py", + "references": { + "widget:Attribute:Load": [ + 137, + 149, + 397, + 340, + 350, + 350, + 401 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/input_delivery.py", + "references": { + "Click:Attribute:Load": [ + 43 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/irc_message.py", + "references": { + "Click:Attribute:Load": [ + 43 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/mandelbrot.py", + "references": { + "Click:Attribute:Load": [ + 166, + 174 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/model_switcher.py", + "references": { + "Click:Attribute:Load": [ + 36 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/project_panel.py", + "references": { + "Click:Attribute:Load": [ + 45 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/prompt.py", + "references": { + "Click:Attribute:Load": [ + 560, + 564, + 74, + 102 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/prompt_popup.py", + "references": { + "widget:Attribute:Load": [ + 68 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/question.py", + "references": { + "Click:Attribute:Load": [ + 107 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/recovery_view.py", + "references": { + "Click:Attribute:Load": [ + 101 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/retiring_sidebar.py", + "references": { + "widget:Attribute:Load": [ + 53, + 54 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/route_header.py", + "references": { + "Click:Attribute:Load": [ + 63 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/session_sort.py", + "references": { + "Click:Attribute:Load": [ + 102 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/session_tabs.py", + "references": { + "Click:Attribute:Load": [ + 33, + 72, + 123 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/session_thread_sidebar.py", + "references": { + "widget:Attribute:Load": [ + 43 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/side_bar.py", + "references": { + "Click:Name:Load": [ + 199, + 264, + 307, + 502, + 928 + ], + "widget:Attribute:Load": [ + 908, + 755, + 909 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/terminal.py", + "references": { + "Click:Attribute:Load": [ + 266 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/tool_call.py", + "references": { + "Click:Attribute:Load": [ + 388, + 389 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/transcript_history.py", + "references": { + "Click:Attribute:Load": [ + 174 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "src/toad/widgets/viewport_body.py", + "references": { + "widget:Attribute:Load": [ + 1076 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/async_tab_activation_pilot.py", + "references": { + "widget:Attribute:Load": [ + 63 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/canonical_wire_retained_real_installed_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 363, + 340 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/channel_display_basis_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 32, + 33 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/channel_partial_paint_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 30 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/channel_retirement_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 31 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/checkpoint_source_pivot_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 38 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/comms_pilot.py", + "references": { + "widget:Attribute:Load": [ + 74 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/divider_unread_paint_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 33 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/editor_key_burst_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 64 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/file_path_preview_pilot.py", + "references": { + "Click:Attribute:Load": [ + 355 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/goal_resize_pilot.py", + "references": { + "mouse_captured:Attribute:Load": [ + 87, + 43, + 90, + 47 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/historical_views_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 219, + 220, + 225, + 226 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/history_stationary_local_runway_pilot.py", + "references": { + "widget:Attribute:Load": [ + 74 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/late_native_after_join_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 44 + ], + "_dispatch_message:Attribute:Load": [ + 67 + ], + "_dispatch_message:Attribute:Store": [ + 73 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/late_native_application_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 44 + ], + "_dispatch_message:Attribute:Load": [ + 67 + ], + "_dispatch_message:Attribute:Store": [ + 73 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/markdown_return_reuse_pilot.py", + "references": { + "_post_mouse_events:Attribute:Load": [ + 99 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/mcp_decision_pty_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 180, + 205 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/message_dividers_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 298, + 299, + 58, + 262 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/native_loaded_return_cache_pilot.py", + "references": { + "widget:Attribute:Load": [ + 95, + 303 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/native_permission_ui.py", + "references": { + "widget:Attribute:Load": [ + 109 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/native_session_retention_pilot.py", + "references": { + "widget:Attribute:Load": [ + 196, + 485, + 487, + 534, + 187, + 232, + 256, + 190, + 404, + 405 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/prepared_height_contract_pilot.py", + "references": { + "widget:Attribute:Load": [ + 126, + 118, + 139 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/response_source_custody_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 72, + 73 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/saved_state_user_journey_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 846 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/scroll_select_latency_pilot.py", + "references": { + "_forward_event:Attribute:Load": [ + 65, + 209, + 317 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/selection_hover_pilot.py", + "references": { + "_forward_event:Attribute:Load": [ + 32, + 39 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/session_initial_prompt_custody_pilot.py", + "references": { + "widget:Attribute:Load": [ + 100, + 127 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/shared_channels_pilot.py", + "references": { + "widget:Attribute:Load": [ + 186, + 53, + 191 + ], + "mouse_captured:Attribute:Load": [ + 122, + 126 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/sidebar_custody_installed_journey.py", + "references": { + "widget:Attribute:Load": [ + 66 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/sidebar_drag_resize_pilot.py", + "references": { + "mouse_captured:Attribute:Load": [ + 68 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/sidebar_edge_anchor_installed_pilot.py", + "references": { + "mouse_captured:Attribute:Load": [ + 87 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/sidebar_retirement_installed_pilot.py", + "references": { + "widget:Attribute:Load": [ + 85, + 109, + 120, + 130 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/sidebar_retirement_pilot.py", + "references": { + "widget:Attribute:Load": [ + 94, + 97 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/stale_selection_pilot.py", + "references": { + "_forward_event:Attribute:Load": [ + 35 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/test_context_manifest_navigation.py", + "references": { + "Click:Attribute:Load": [ + 419, + 227 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/viewport_demand_custody_pilot.py", + "references": { + "send_message:Attribute:Load": [ + 86 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/viewport_selection_diff_pilot.py", + "references": { + "_forward_event:Attribute:Load": [ + 50, + 51, + 79 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tests/watcher_readiness_burst.py", + "references": { + "on_event:Name:Store": [ + 20 + ], + "on_event:Name:Load": [ + 26 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tools/performance/capture_state.py", + "references": { + "widget:Attribute:Load": [ + 415 + ] + } + }, + { + "root": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "file": "tools/performance/sidebar_validation_driver.py", + "references": { + "send_message:FunctionDef:NoneType": [ + 562 + ], + "_dispatch_message:Attribute:Load": [ + 103 + ], + "_dispatch_message:Attribute:Store": [ + 133 + ], + "send_message:Attribute:Load": [ + 569 + ], + "mouse_captured:Attribute:Load": [ + 723 + ], + "widget:Attribute:Load": [ + 76, + 639, + 640 + ] + } + } + ], + "limitations": "Lexical references include imported and inherited calls; externally supplied subclasses and dynamic getattr remain unresolved." +} diff --git a/evidence/pilot-pointer-ingress-20261006/changed-path-final.log b/evidence/pilot-pointer-ingress-20261006/changed-path-final.log new file mode 100644 index 0000000000..bea637588e --- /dev/null +++ b/evidence/pilot-pointer-ingress-20261006/changed-path-final.log @@ -0,0 +1,12 @@ +.....F [100%] +=================================== FAILURES =================================== +___________ test_background_screen_delivery_keeps_raw_app_completion ___________ +tests/test_pilot_pointer_ingress.py:149: in test_background_screen_delivery_keeps_raw_app_completion + assert app.delivered == [(None, 1, False, False, False)] +E AssertionError: assert [('_default',...False, False)] == [(None, 1, Fa...False, False)] +E +E At index 0 diff: ('_default', 1, False, False, False) != (None, 1, False, False, False) +E Use -v to get more diff +=========================== short test summary info ============================ +FAILED tests/test_pilot_pointer_ingress.py::test_background_screen_delivery_keeps_raw_app_completion +1 failed, 5 passed in 1.31s diff --git a/evidence/pilot-pointer-ingress-20261006/changed-path.log b/evidence/pilot-pointer-ingress-20261006/changed-path.log new file mode 100644 index 0000000000..1020af7a17 --- /dev/null +++ b/evidence/pilot-pointer-ingress-20261006/changed-path.log @@ -0,0 +1,17 @@ +.....F.................................................................. [ 44%] +........................................................................ [ 89%] +...........event: Click(Label(id='one'), x=0, y=0, pointer_x=0, pointer_y=0, style=Style(color=Color('#e0e0e0', ColorType.TRUECOLOR, triplet=ColorTriplet(red=224, green=224, blue=224)), bgcolor=Color('#121212', ColorType.TRUECOLOR, triplet=ColorTriplet(red=18, green=18, blue=18)), meta={'offset': (0, 0)}), chain=1) +.event: Click(Label(id='one'), x=0, y=0, pointer_x=0, pointer_y=0, style=Style(color=Color('#e0e0e0', ColorType.TRUECOLOR, triplet=ColorTriplet(red=224, green=224, blue=224)), bgcolor=Color('#121212', ColorType.TRUECOLOR, triplet=ColorTriplet(red=18, green=18, blue=18)), meta={'offset': (0, 0)}), chain=1) +event: Click(Label(id='one'), x=0, y=0, pointer_x=0, pointer_y=0, style=Style(color=Color('#e0e0e0', ColorType.TRUECOLOR, triplet=ColorTriplet(red=224, green=224, blue=224)), bgcolor=Color('#121212', ColorType.TRUECOLOR, triplet=ColorTriplet(red=18, green=18, blue=18)), meta={'offset': (0, 0)}), chain=2) +.event: Click(Label(id='one'), x=0, y=0, pointer_x=0, pointer_y=0, style=Style(color=Color('#e0e0e0', ColorType.TRUECOLOR, triplet=ColorTriplet(red=224, green=224, blue=224)), bgcolor=Color('#121212', ColorType.TRUECOLOR, triplet=ColorTriplet(red=18, green=18, blue=18)), meta={'offset': (0, 0)}), chain=1) +event: Click(Label(id='one'), x=0, y=0, pointer_x=0, pointer_y=0, style=Style(color=Color('#e0e0e0', ColorType.TRUECOLOR, triplet=ColorTriplet(red=224, green=224, blue=224)), bgcolor=Color('#121212', ColorType.TRUECOLOR, triplet=ColorTriplet(red=18, green=18, blue=18)), meta={'offset': (0, 0)}), chain=2) +event: Click(Label(id='one'), x=0, y=0, pointer_x=0, pointer_y=0, style=Style(color=Color('#e0e0e0', ColorType.TRUECOLOR, triplet=ColorTriplet(red=224, green=224, blue=224)), bgcolor=Color('#121212', ColorType.TRUECOLOR, triplet=ColorTriplet(red=18, green=18, blue=18)), meta={'offset': (0, 0)}), chain=3) +.... [100%] +=================================== FAILURES =================================== +___________ test_background_screen_delivery_keeps_raw_app_completion ___________ +tests/test_pilot_pointer_ingress.py:142: in test_background_screen_delivery_keeps_raw_app_completion + assert await pilot.click(app.screen, offset=(30, 7)) +E assert False +=========================== short test summary info ============================ +FAILED tests/test_pilot_pointer_ingress.py::test_background_screen_delivery_keeps_raw_app_completion +1 failed, 160 passed, 22 deselected in 27.62s diff --git a/evidence/pilot-pointer-ingress-20261006/overridden-consumer.log b/evidence/pilot-pointer-ingress-20261006/overridden-consumer.log new file mode 100644 index 0000000000..86b035a119 --- /dev/null +++ b/evidence/pilot-pointer-ingress-20261006/overridden-consumer.log @@ -0,0 +1,2 @@ +. [100%] +1 passed in 0.83s diff --git a/src/textual/app.py b/src/textual/app.py index c75df1c0fe..d80d8d7479 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -4098,7 +4098,7 @@ def handle_bindings_clash( """ pass - async def on_event(self, event: events.Event) -> None: + async def on_event(self, event: events.Event) -> events.MouseEvent | None: # Handle input events that haven't been forwarded # If the event has been forwarded it may have bubbled up back to the App if isinstance(event, events.Compose): @@ -4122,6 +4122,12 @@ async def on_event(self, event: events.Event) -> None: # Shouldn't occur, since at the very least this will find the Screen self._mouse_down_widget = None + mouse_down_widget = self._mouse_down_widget + if isinstance(event, events.MouseUp): + # A press is consumed by its release, including a refused + # click. A second release cannot reuse that old press. + self._mouse_down_widget = None + # The next pointer packet must use capture and geometry decided # by this delivery's original widget / ancestor queues. await self.screen._forward_event(event) @@ -4130,11 +4136,10 @@ async def on_event(self, event: events.Event) -> None: # consider it a click, and produce a Click event. if ( isinstance(event, events.MouseUp) - and self._mouse_down_widget is not None + and mouse_down_widget is not None ): try: screen_offset = event.screen_offset - mouse_down_widget = self._mouse_down_widget mouse_up_widget, _ = self.get_widget_at(*screen_offset) if mouse_up_widget is mouse_down_widget: same_offset = ( @@ -4160,6 +4165,7 @@ async def on_event(self, event: events.Event) -> None: self._click_chain_last_offset = screen_offset await self.screen._forward_event(click_event) + return click_event except NoWidget: pass diff --git a/src/textual/events.py b/src/textual/events.py index d3847f4ad4..58c90face3 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -381,7 +381,10 @@ def __init__( ) -> None: super().__init__() self.widget: Widget | None = widget - """The widget under the mouse at the time of a click.""" + """The recipient acquired by Screen for this delivery, including capture. + + Raw input has no recipient until it is routed. + """ self._x = x """The relative x coordinate.""" self._y = y diff --git a/src/textual/message.py b/src/textual/message.py index db023b9157..25b4b9c804 100644 --- a/src/textual/message.py +++ b/src/textual/message.py @@ -165,11 +165,13 @@ def _bubble_to(self, widget: MessagePump) -> None: if widget._task is not caller: self._dispatch_completion = (widget, caller, completion) - def _complete_dispatch(self, receiver: MessagePump) -> None: + def _complete_dispatch( + self, receiver: MessagePump, result: Message | None = None + ) -> None: """Release this delivery only from the queue which still owns it.""" if self._dispatch_completion is not None: owner, _, completion = self._dispatch_completion if owner is receiver: self._dispatch_completion = None if not completion.done(): - completion.set_result(None) + completion.set_result(self if result is None else result) diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index 1e3759101a..28323aa749 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -724,8 +724,9 @@ async def _process_messages_loop(self) -> None: except MessagePumpClosed: break + result: Message | None = None try: - await self._dispatch_message(message) + result = await self._dispatch_message(message) except CancelledError: raise except Exception as error: @@ -734,7 +735,7 @@ async def _process_messages_loop(self) -> None: self.app._handle_exception(error) break finally: - message._complete_dispatch(self) + message._complete_dispatch(self, result) # Subscription access initializes this optional signal. Do not # allocate its weak-key subscriber machinery for quiet pumps. message_signal = self.__dict__.get("message_signal") @@ -777,7 +778,7 @@ async def _flush_next_callbacks(self) -> None: self.app._handle_exception(error) break - async def _dispatch_message(self, message: Message) -> None: + async def _dispatch_message(self, message: Message) -> Message | None: """Dispatch a message received from the message queue. Args: @@ -794,10 +795,11 @@ async def _dispatch_message(self, message: Message) -> None: else: message_hook(message) + result: Message | None = None with self.prevent(*message._prevent): # Allow apps to treat events and messages separately if isinstance(message, Event): - await self.on_event(message) + result = await self.on_event(message) elif "debug" in self.app.features: start = perf_counter() await self._on_message(message) @@ -812,6 +814,7 @@ async def _dispatch_message(self, message: Message) -> None: await self._on_message(message) if self._next_callbacks: await self._flush_next_callbacks() + return result def _get_dispatch_methods( self, method_name: str, message: Message @@ -872,11 +875,15 @@ def _get_dispatch_methods( if method is not None and not getattr(method, "_textual_on", None): yield cls, method.__get__(self, cls) - async def on_event(self, event: events.Event) -> None: + async def on_event(self, event: events.Event) -> Message | None: """Called to process an event. Args: event: An Event object. + + Returns: + A final message produced by this delivery, or None for the original + event. Overrides should return the original owner's result. """ await self._on_message(event) @@ -927,7 +934,7 @@ def _post_message_and_wait(self, message: Message) -> AwaitComplete: message: A message object. Returns: - Optional completion of this delivery, including its DOM bubbling. + Optional completion with the final delivered message, including DOM bubbling. The originating pump processes its own bubbled copy after admission. """ from textual.await_complete import AwaitComplete @@ -937,7 +944,7 @@ def _post_message_and_wait(self, message: Message) -> AwaitComplete: completion = asyncio.get_running_loop().create_future() message._dispatch_completion = (self, asyncio.current_task(), completion) - async def wait_for_dispatch() -> None: + async def wait_for_dispatch() -> Message: # A removed receiver may stop before reaching this message. Borrow # its original task; never cancel that task or replay the delivery. while not completion.done(): @@ -951,6 +958,7 @@ async def wait_for_dispatch() -> None: ) if task.done() and not completion.done(): message._complete_dispatch(owner) + return completion.result() def check_self_wait() -> None: if asyncio.current_task() is self._task: diff --git a/src/textual/pilot.py b/src/textual/pilot.py index a4e37b9db2..08a7a4bff8 100644 --- a/src/textual/pilot.py +++ b/src/textual/pilot.py @@ -32,7 +32,7 @@ def _get_mouse_message_arguments( """Get the arguments to pass into mouse messages for the click and hover methods.""" click_x, click_y = target.region.offset + offset message_arguments = { - "widget": target, + "widget": None, "x": click_x, "y": click_y, "delta_x": 0, @@ -204,8 +204,6 @@ async def click( The final position to be clicked is computed based on the selector provided and the offset specified and it must be within the visible area of the screen. - Implementation note: This method bypasses the normal event processing in `App.on_event`. - Example: The code below runs an app and clicks its only button right in the middle: ```py @@ -231,8 +229,8 @@ async def click( OutOfBounds: If the position to be clicked is outside of the (visible) screen. Returns: - `True` if no selector was specified or if the selected widget was under the mouse - when the click was initiated. `False` is the selected widget was not under the pointer. + `True` if no selector was specified or if the final admitted click landed on the + selected widget. `False` if the click was refused or landed elsewhere. """ try: return await self._post_mouse_events( @@ -264,8 +262,6 @@ async def double_click( The final position to be clicked is computed based on the selector provided and the offset specified and it must be within the visible area of the screen. - Implementation note: This method bypasses the normal event processing in `App.on_event`. - Example: The code below runs an app and double-clicks its only button right in the middle: ```py @@ -290,8 +286,8 @@ async def double_click( OutOfBounds: If the position to be clicked is outside of the (visible) screen. Returns: - `True` if no selector was specified or if the selected widget was under the mouse - when the click was initiated. `False` is the selected widget was not under the pointer. + `True` if no selector was specified or if the final admitted click landed on the + selected widget. `False` if the click was refused or landed elsewhere. """ return await self.click( widget, offset, shift, meta, control, times=2, button=button @@ -313,8 +309,6 @@ async def triple_click( The final position to be clicked is computed based on the selector provided and the offset specified and it must be within the visible area of the screen. - Implementation note: This method bypasses the normal event processing in `App.on_event`. - Example: The code below runs an app and triple-clicks its only button right in the middle: ```py @@ -339,8 +333,8 @@ async def triple_click( OutOfBounds: If the position to be clicked is outside of the (visible) screen. Returns: - `True` if no selector was specified or if the selected widget was under the mouse - when the click was initiated. `False` is the selected widget was not under the pointer. + `True` if no selector was specified or if the final admitted click landed on the + selected widget. `False` if the click was refused or landed elsewhere. """ return await self.click( widget, offset, shift, meta, control, times=3, button=button @@ -418,6 +412,7 @@ async def _post_mouse_events( True if no selector was specified or if the *final* event landed on the selected widget, False otherwise. """ + await self.pause() app = self.app screen = app.screen target_widget: Widget @@ -443,32 +438,24 @@ async def _post_mouse_events( "Target offset is outside of currently-visible screen region." ) - widget_at = None - for chain in range(1, times + 1): + delivered: MouseEvent | None = None + for _ in range(times): for mouse_event_cls in events: - await self.pause() - # Get the widget under the mouse before the event because the app might - # react to the event and move things around. We override on each iteration - # because we assume the final event in `events` is the actual event we care - # about and that all the preceding events are just setup. - # E.g., the click event is preceded by MouseDown/MouseUp to emulate how - # the driver works and emits a click event. - kwargs = message_arguments if mouse_event_cls is Click: - kwargs = {**kwargs, "chain": chain} - - if widget_at is None: - widget_at, _ = app.get_widget_at(*offset) - event = mouse_event_cls(**kwargs) - # Bypass event processing in App.on_event. Because App.on_event - # is responsible for updating App.mouse_position, and because - # that's useful to other things (tooltip handling, for example), - # we patch the offset in there as well. - app.mouse_position = offset - await screen._forward_event(event) + # App owns press/release matching and produces the Click. + # This final gesture member requests its receipt, not a + # second synthetic delivery or a separately authored chain. + if not isinstance(delivered, Click): + delivered = None + continue + event = mouse_event_cls(**message_arguments) + results = await app._post_message_and_wait(event) + delivered = results[0] if results else None + await self.pause() - await self.pause() - return widget is None or widget_at is target_widget + return widget is None or ( + delivered is not None and delivered.widget is target_widget + ) async def _wait_for_screen(self, timeout: float = 30.0) -> bool: """Wait for the current screen and its children to have processed all pending events. diff --git a/src/textual/screen.py b/src/textual/screen.py index 109c0d0874..02097cec0a 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -1805,6 +1805,7 @@ def _handle_tooltip_timer(self, widget: Widget) -> None: tooltip.update(tooltip_content) def _handle_mouse_move(self, event: events.MouseMove) -> AwaitComplete: + event.widget = None completion = AwaitComplete.nothing() hover_widget: Widget | None = None try: @@ -1825,11 +1826,14 @@ def _handle_mouse_move(self, event: events.MouseMove) -> AwaitComplete: except NoMatches: pass else: + event.widget = widget self.app._set_mouse_over(widget, hover_widget) self.update_pointer_shape() widget.hover_style = event.style if widget is self: - completion = self._post_message_and_wait(event) + routed_event = event._apply_offset(0, 0) + routed_event._set_forwarded() + completion = self._post_message_and_wait(routed_event) else: mouse_event = self._translate_mouse_move_event(event, widget, region) mouse_event._set_forwarded() @@ -2048,6 +2052,7 @@ def _forward_event(self, event: events.Event) -> AwaitComplete: self._stop_auto_scroll() elif isinstance(event, events.MouseEvent): + event.widget = None if isinstance(event, events.MouseUp): self._flush_pending_selection() if ( @@ -2126,9 +2131,12 @@ def _forward_event(self, event: events.Event) -> AwaitComplete: event.style = self.get_style_at(event.screen_x, event.screen_y) if widget.loading: return completion + event.widget = widget if widget is self: event._set_forwarded() - completion = self._post_message_and_wait(event) + routed_event = event._apply_offset(0, 0) + routed_event._set_forwarded() + completion = self._post_message_and_wait(routed_event) else: completion = widget._forward_event( event._apply_offset(-region.x, -region.y) diff --git a/tests/test_app.py b/tests/test_app.py index b5ba0274dc..421e13a21c 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -264,8 +264,7 @@ async def test_search_with_empty_list(): async def raw_click(pilot: Pilot, selector: str, times: int = 1): - """A lower level click function that doesn't use the Pilot, - and so doesn't bypass the click chain logic in App.on_event.""" + """Queue a driver-style burst through the same App admission as Pilot.""" app = pilot.app kwargs = _get_mouse_message_arguments(app.query_one(selector)) for _ in range(times): diff --git a/tests/test_pilot_pointer_ingress.py b/tests/test_pilot_pointer_ingress.py new file mode 100644 index 0000000000..ac6d8f9ae2 --- /dev/null +++ b/tests/test_pilot_pointer_ingress.py @@ -0,0 +1,153 @@ +import asyncio + +from textual import events +from textual.app import App, ComposeResult +from textual.containers import Horizontal +from textual.geometry import Offset +from textual.widgets import Static +from tests.test_pointer_capture_order import send_pointer + + +class PointerCell(Static): + ALLOW_SELECT = False + + +class PointerApp(App): + CSS = "PointerCell {width: 10; height: 3;}" + + def __init__(self): + super().__init__() + self.delivered = [] + + def compose(self) -> ComposeResult: + with Horizontal(): + yield PointerCell("original", id="target") + yield PointerCell("outside", id="outside") + + def on_click(self, event): + self.delivered.append( + (event.widget.id, event.chain, event.shift, event.meta, event.ctrl) + ) + + +async def test_real_run_async_pilot_and_driver_share_click_chain_and_modifiers(): + app = PointerApp() + + async def drive(pilot): + target = app.query_one("#target") + # Driver and Pilot must update the same App-owned press/chain facts. + await pilot.pause() + point = target.region.offset + Offset(1, 1) + send_pointer(app, events.MouseDown, point) + send_pointer(app, events.MouseUp, point) + await pilot.pause() + assert await pilot.click( + target, offset=(1, 1), shift=True, meta=True, control=True + ) + assert app.delivered == [ + ("target", 1, False, False, False), + ("target", 2, True, True, True), + ] + # An unmatched second release must not borrow the consumed press. + send_pointer(app, events.MouseUp, point) + await pilot.pause() + assert len(app.delivered) == 2 + app.exit() + + await asyncio.wait_for( + app.run_async(headless=True, size=(40, 8), auto_pilot=drive), 10 + ) + assert app._exception is None + assert app._task is None + + +async def test_pilot_click_refuses_when_press_moves_original_target(): + class Moving(PointerCell): + def on_mouse_down(self, event): + self.styles.offset = (12, 0) + + class MovingApp(PointerApp): + def compose(self): + yield Moving("original", id="target") + + app = MovingApp() + async with app.run_test(size=(40, 8)) as pilot: + target = app.query_one("#target") + assert not await pilot.click(target, offset=(1, 1)) + assert target.region.x == 12 + assert app.delivered == [] + + +async def test_mouse_down_result_uses_final_packet_after_hover_changes_layout(): + class Moving(PointerCell): + def on_mouse_move(self, event): + self.styles.offset = (12, 0) + + class MovingApp(PointerApp): + def compose(self): + yield Moving("original", id="target") + + app = MovingApp() + async with app.run_test(size=(40, 8)) as pilot: + target = app.query_one("#target") + assert not await pilot.mouse_down(target, offset=(1, 1)) + assert app._mouse_down_widget is app.screen + assert target.region.x == 12 + + +async def test_mouse_up_result_uses_capture_rather_than_widget_under_pointer(): + class Capturing(PointerCell): + def on_mouse_down(self, event): + self.capture_mouse() + + def on_mouse_up(self, event): + self.release_mouse() + self.app.released = event.widget + + class CaptureApp(PointerApp): + def compose(self): + with Horizontal(): + yield Capturing("original", id="target") + yield PointerCell("outside", id="outside") + + app = CaptureApp() + async with app.run_test(size=(40, 8)) as pilot: + target = app.query_one("#target") + assert await pilot.mouse_down(target, offset=(1, 1)) + assert not await pilot.mouse_up("#outside", offset=(1, 1)) + assert app.released is target + assert app.mouse_captured is None + assert app.delivered == [] + + +async def test_click_receipt_survives_recipient_removal_after_delivery(): + class Removing(PointerCell): + async def on_click(self, event): + self.app.clicked = event.widget + await self.remove() + + class RemovingApp(PointerApp): + def compose(self): + yield Removing("original", id="target") + + app = RemovingApp() + async with app.run_test(size=(40, 8)) as pilot: + target = app.query_one("#target") + assert await pilot.click(target, offset=(1, 1)) + assert app.clicked is target + assert not target.is_attached + assert target._task is None + + +async def test_background_screen_delivery_keeps_raw_app_completion(): + class BlankApp(PointerApp): + def compose(self): + return () + + app = BlankApp() + async with app.run_test(size=(40, 8)) as pilot: + await pilot.pause() + assert app.get_widget_at(30, 7)[0] is app.screen + assert await pilot.click(app.screen, offset=(30, 7)) + assert await pilot.hover(app.screen, offset=(30, 7)) + assert app.delivered == [(app.screen.id, 1, False, False, False)] diff --git a/tests/test_selection.py b/tests/test_selection.py index bbad95017c..1498bb2c5a 100644 --- a/tests/test_selection.py +++ b/tests/test_selection.py @@ -443,11 +443,12 @@ def compose(self) -> ComposeResult: yield Tracked(f"record {index:03d}", id=f"record-{index}") async def on_event(self, event): - await super().on_event(event) + result = await super().on_event(event) if event is self.copy_event: # Copy in the middle of a queued burst must see this position, # rather than the last computed selection or a future one. self.during_drag = self.screen.get_selected_text() + return result def on_text_selected(self): self.after_release = self.screen.get_selected_text()