diff --git a/.github/workflows/debt-ratchet.yml b/.github/workflows/debt-ratchet.yml index b91f2cd985..ffb69c7de3 100644 --- a/.github/workflows/debt-ratchet.yml +++ b/.github/workflows/debt-ratchet.yml @@ -26,7 +26,7 @@ jobs: python-version: '3.14' - name: Install the packaged ratchet pinned by Toad main # Same Core source as Toad's tool.uv.sources.agent-comms.rev. - run: python -m pip install --disable-pip-version-check 'agent-comms @ git+https://github.com/OpenHCSDev/agent-comms.git@7a5c5fc2e79f2e32e4f340eaba573bf1bfec69bf' + run: python -m pip install --disable-pip-version-check 'agent-comms @ git+https://github.com/OpenHCSDev/agent-comms.git@9195325146f70886d7d879edbfcae74b12bd63c8' - name: Reject increases in production debt env: PR_BASE: ${{ github.event.pull_request.base.sha }} diff --git a/evidence/markdown-publication-custody-20261004/NATIVE-APP-QUALIFIED.json b/evidence/markdown-publication-custody-20261004/NATIVE-APP-QUALIFIED.json new file mode 100644 index 0000000000..c9a569e894 --- /dev/null +++ b/evidence/markdown-publication-custody-20261004/NATIVE-APP-QUALIFIED.json @@ -0,0 +1,345 @@ +{ + "qualification": "native source App controls; installed Toad and physical performance pending", + "production_head": "64c0f1b0a113d398307ccf0ccb434836a0b92182", + "required_debt": { + "run": 37197836512, + "head": "64c0f1b0a113d398307ccf0ccb434836a0b92182", + "conclusion": "success" + }, + "final_changed_interaction_batch": { + "command": "PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=src timeout 60s python -m pytest -q -p no:cacheprovider tests/test_removed_scene_ownership.py tests/test_reparent.py tests/test_selection.py", + "exit": 0, + "result": "33 passed in 9.29s", + "evidence": { + "path": "/home/ts/wt/textual-native-subtree-strips-20261002/.artifacts/native59-visible-stack-source-20261004/interaction-complete-batch.log", + "sha256": "9083cf4cf2b0c80111fb5ad91b6276003e5791bd697e633cde2033c786372787" + } + }, + "earlier_scope_controls": [ + { + "head": "3732047fad8d9af0e1d981ece2618d2881d8f45b", + "result": "48 passed in 4.53s", + "evidence": { + "path": "/home/ts/wt/textual-native-subtree-strips-20261002/.artifacts/native59-visible-stack-source-20261004/native-final-batch.log", + "sha256": "0366059be59db33725a4b829af1a64ecca41e8896eb6b82457c6a05f9480e062" + } + }, + { + "head": "3ca202db", + "result": "15 passed in 2.06s", + "evidence": { + "path": "/home/ts/wt/textual-native-subtree-strips-20261002/.artifacts/native59-visible-stack-source-20261004/projection-final-batch.log", + "sha256": "e50bccde34d3da30601f6b116ae588bf11ddc3b358e3ad535096df01b46ba4f2" + } + } + ], + "preserved_negatives": [ + { + "source": "3732047fad8d9af0e1d981ece2618d2881d8f45b", + "kind": "required Debt GodClassExcess Compositor +2, corrected at original projection owner", + "evidence": { + "path": "/home/ts/wt/textual-native-subtree-strips-20261002/.artifacts/native59-visible-stack-source-20261004/debt-3732047f-negative.log", + "sha256": "aca3b37ffc23cd4d5ab41461be8221398c970eadc08c4ad779924bb68facf476" + } + }, + { + "source": "64c0f1b0a113d398307ccf0ccb434836a0b92182", + "kind": "new control fixture had nonexistent Pilot.mouse_move and initial cleanup did not release held unmount; bounded attempt exit124, fixture corrected", + "evidence": { + "path": "/home/ts/wt/textual-native-subtree-strips-20261002/.artifacts/native59-visible-stack-source-20261004/interaction-final-batch.log", + "sha256": "cdb4ee2aea69cc6a83331bbe96dc2caa9a299d21329efb0336fc02a82e1839a8" + } + }, + { + "source": "64c0f1b0a113d398307ccf0ccb434836a0b92182", + "kind": "fixture editing error: collection SyntaxError; corrected without product change", + "evidence": { + "path": "/home/ts/wt/textual-native-subtree-strips-20261002/.artifacts/native59-visible-stack-source-20261004/interaction-resource-custody.log", + "sha256": "16f34f84e249d7784483fe12f31f6d86c2184661fead6063bcfe178c9ba40e34" + } + }, + { + "source": "64c0f1b0a113d398307ccf0ccb434836a0b92182", + "kind": "fixture AttributeError Pilot.mouse_move; mouse_up already owns original Move+Up event sequence; corrected no oracle waiver", + "evidence": { + "path": "/home/ts/wt/textual-native-subtree-strips-20261002/.artifacts/native59-visible-stack-source-20261004/interaction-fixture-boundary.log", + "sha256": "200834806f66b553d0e0eaf91f7cb3bbfd7c7cc244fd8dfaad1ff158a972dcfd" + } + } + ], + "production_numstat_vs_main58": "24\t29\tsrc/textual/_compositor.py\n12\t54\tsrc/textual/app.py\n55\t0\tsrc/textual/await_remove.py\n1\t3\tsrc/textual/css/query.py\n49\t52\tsrc/textual/screen.py\n29\t6\tsrc/textual/selection.py\n4\t5\tsrc/textual/widget.py\n2\t2\tsrc/textual/widgets/_markdown.py\n", + "source_inventory_sha256": { + "src/textual/__init__.py": "fcd1524975df6075497bbb5c47fe564c6909d2b4b9651708e28ead65e7492671", + "src/textual/__main__.py": "ae6103905f2037024dcb9764b9b707c88905a02c574a580ceb6b5f56c5aaa72e", + "src/textual/_animator.py": "1f5c69c2c2defc294c4b93abfe01e838dcc9331d00cef9b87feb3312e76bf0b7", + "src/textual/_ansi_sequences.py": "0b4037a3f36997ca78d7970091dfd99a346323976f5a723064d243827cb53a03", + "src/textual/_ansi_theme.py": "7c15def3c7bc073bcf9defd21e33dbb5ad8bf4833339f3f6ee1de90e40d7408a", + "src/textual/_arrange.py": "542725eb4c58e4bee72da63e06f7b8f537c63cde64dcb2760bfc4bb4573b4d81", + "src/textual/_auto_scroll.py": "d108b1feb10f6fbd618286d716234051ecc5cdf2392b088690ab14a39891e5ae", + "src/textual/_binary_encode.py": "749edb834efac687e4c34fe41bcc778100c0029a93f97ffcb2abda8c4566265c", + "src/textual/_border.py": "ee88b9f1c15b57dfdcab1ad09845b08e69aa39e1637f464ce8877bf391f5688a", + "src/textual/_box_drawing.py": "06edac2595de45be3378662b6b1850711b4318179dbb84c63dd3ea50779f8750", + "src/textual/_callback.py": "35bce48ec27b2f35a58f34cc084875e453b034edbf83a08469c3ff6bac041f77", + "src/textual/_cells.py": "ce82f4606f990ba38c275276246c6b788954da7ef57e0184b101f86138d7e08d", + "src/textual/_color_constants.py": "43d650c2da192a446adb773058bf79ae3b60edf1d4342debc089aa7992e07306", + "src/textual/_compat.py": "572ba79156458bf5df509ce856168e2cee770e783349a7b34e320b58a575cd9e", + "src/textual/_compositor.py": "07226127adb0a550136062f4bd29de952b7fea0f1a016c5b91438afc60c56f06", + "src/textual/_context.py": "523b25f71607193b69f260aab70c698eb4ff59c87594ad44a3acb59c59e389c0", + "src/textual/_debug.py": "a59e58b6b01a5a4783628088f7054caf0c19ef597822e72d1f26d5f8c2927450", + "src/textual/_dispatch_key.py": "a9eb091c9301e9d3546cc4eda4f05fb4b128f9efd2ca66e11333b753a86724f4", + "src/textual/_doc.py": "ed398d55f553c6c60d8f2cb24ed1b9dcf6cf4e4f0ca86e78396051d099e321dd", + "src/textual/_duration.py": "26b172a95794afa831c5aa32a3d4b71a5a0a0472de3739010f47b2ba5b53cd09", + "src/textual/_easing.py": "af725bef58a39df8ed94be869e6854c66742bd31262c2ee3fb62aba7162896e5", + "src/textual/_event_broker.py": "065e51dfc401a3cf4e0fcc639b5b21740a3dd2fe13dc778831dd7e8ef2de1a00", + "src/textual/_extrema.py": "a89164717c7f8f314d0028fb6f44d2558a7b86deff60b1e992ba36f699f5cf35", + "src/textual/_files.py": "93a9e4cf69d1561cf19fc1d22c298242820cf0c5fc7d4abf8c271f01f0b263a5", + "src/textual/_immutable_sequence_view.py": "85d2b6979f82564af035bdf92e70c0d0a58bcba76bad0b398b2bebcd95d25fb1", + "src/textual/_import_app.py": "4a32750acc351b59e5998e53254639733faeb74a9bcaec0f225d09441cc72472", + "src/textual/_keyboard_protocol.py": "a82e341351e82128d2d2a4014eea10561a2c126167c95058e13298deb7cab34b", + "src/textual/_layout_resolve.py": "b48ff724239c52199d661003fbe7823b4a49c0bacb65f412ebec59d150ee7a79", + "src/textual/_line_split.py": "be56d8b67535b0d3bc9f8080a4c0db53d3ead70999f7a016c6b5b849b70acd26", + "src/textual/_log.py": "25c5aaa68f7c9ed8e8f3749e220984baeef262310bf125216fdfb6e1e5849b34", + "src/textual/_loop.py": "d8dd862cc7691bd06cef655c033ab97133bc6b693315ba3592251b0fad8020ab", + "src/textual/_markup_playground.py": "377ac54d67f521c2cb0379f2ab6efc860442efe1414f33ef6b92be9555626a3c", + "src/textual/_measurement.py": "aa3c167ca72cf38ccb5687457e9022a8b78f72e393ecdc38133fd285cf53363b", + "src/textual/_node_list.py": "8822f69c85ce569e1eb155d44c4bb5011b4bb471ef1ee9b45f0ce0aefe3b77f5", + "src/textual/_on.py": "18242aadab09687e704eeb67744cde8b823fc19561367681148f9537b4c0870d", + "src/textual/_opacity.py": "ea4b17b40bbafc0622afccb7be4a73f4ea7ead85c1f6c2603eb9dbc3a3a27a7a", + "src/textual/_paint_state.py": "7b8684141ad11e1d77d224420ca3e28585c6d3e057371bb16be6afb980952a28", + "src/textual/_parser.py": "82b19697cd0cdcb175ea03f4480808caf526b5609034d970da200f68bece2b34", + "src/textual/_partition.py": "db5e79bf2ef32467170512a7e6a233562bb50a95bd40d1524a55bda428c63320", + "src/textual/_path.py": "9c553d99a50578c80ab11656cd022bb35c4f024092be1d317afa02b437868713", + "src/textual/_profile.py": "4c7c4c2611c8f4d1729187649eeb87d73dc96644e7b84ea1bbb5c5a5e560cc21", + "src/textual/_queue.py": "a913c2366fe58a30f513fd982e0d97ed744ff8437e92c0b244d53e6bb245e058", + "src/textual/_resolve.py": "856b156847f21f50de33fb38f1c31f8ba25f50e8fd48eb4a8176177e89314174", + "src/textual/_segment_tools.py": "14ed589d93995ba5e6b3b63070f264e50a9114ff3414f57c5cb1351986fb33f8", + "src/textual/_sleep.py": "fb823eaed2d61cbd2c23b6f489101672339cc18a111d1992ab3e5dc1a050f27c", + "src/textual/_slug.py": "a2af29d730d2bad41a46440182bbcb11d6560c548054bc8125b948db4e392958", + "src/textual/_spatial_map.py": "26de0d118b9be883247a265786e54ceb01e37bfe0f9e3b8dba5f5d0ddbea7bef", + "src/textual/_styles_cache.py": "cc089c0cf2e6c2dbb708206a187d1677a67fdea1b99358fa3964bf9b2983ef80", + "src/textual/_text_area_theme.py": "7726574f559412d7011a35eef4f28e23fcbcfaabe5e095299c7f2f0077635c4d", + "src/textual/_time.py": "eaec17624b89a8db1a51b7433c82d8155206dcc02dc983b13a0c497e22642225", + "src/textual/_tree_sitter.py": "255c3645b9293d85429b8b8ab5657d1ccece1e05c9150f5bffdc4dfa369f9a5d", + "src/textual/_two_way_dict.py": "8e08e7649aae3d35c95c0547d136e6c45605153ffac031ac4c06d9bbc891f8f4", + "src/textual/_types.py": "d62be14e4056c67de0b31cf0cc96802090cb1d3bf221322d8ada202994dbbf30", + "src/textual/_wait.py": "9410b518aeabc9146401ee2465192f23f13d25b3e7c8b4d726bd015b1cee109d", + "src/textual/_widget_navigation.py": "95768394a39832cf0defbf1cf30d4fdfa138abf291445c5ee6effe078e9287d5", + "src/textual/_win_sleep.py": "8503abeba55941187f74b7e5cadabe16252e027c5428fb719379dfa84689c2ca", + "src/textual/_work_decorator.py": "9c556f28590ae56634c89a8f065603c5daa9aa8fa016149cf9625894ee04759d", + "src/textual/_wrap.py": "5ae6feea1af93f8258c9bfa9ad6f71d1828f9784381f998d68dfec4d4a9bca12", + "src/textual/_xterm_parser.py": "c5c7512defc06d5d28044c8b021d0209d806f10443c3a04907a86d8cccf26761", + "src/textual/actions.py": "e145621f54dd4d6f0168fc62af1039c7bb6b353a464d808722a9e2bc1c6ad03a", + "src/textual/app.py": "f5e9d5f8fae532b37b5cb870f6dafa564daf0f872044804638ca06de5c8adbf7", + "src/textual/await_complete.py": "615b00ffb020ec4ea13225a85ede21013cc9222d2d801ba498330c6fb0bcb17a", + "src/textual/await_remove.py": "83cfad1ba4be0445fd50cb6d2e6bbce5e10e47cce74f740d963dc552bed0391a", + "src/textual/binding.py": "ca3948026d29185595845eefa3b396bab17353bd4fb4576c89f7affcdcd415a9", + "src/textual/box_model.py": "619c15050f9c5b0d0b6eebc9d18ecccc107ab220c8832c4e4a7856eca13e9ab6", + "src/textual/cache.py": "f201d5e4f9e3fc26c54e566eb1fe58fa59892dc972cbee3df36c8cae31241a0d", + "src/textual/canvas.py": "990ef6ee3bfee2b7bcdca1acce7cb2c85bfa26df165e6308f5de4506b1c93960", + "src/textual/case.py": "a693a1e28270ad5d7383e3b4c4c030898dca4169178b67cc707a26e29ee50700", + "src/textual/clock.py": "731759ff72e2f5dfaecca4485987bd66ed178b5a37795d2dad38450c40bd0348", + "src/textual/color.py": "2b2d4532067b62dd76be5a10cea2e4d3f91d94b4d5c6ad4f45b06014da80431d", + "src/textual/command.py": "257e6bb1e743fcf4a6ebd719dd0f3faa1e6b59af431823ebe79a34c364263657", + "src/textual/compose.py": "2edc02872fbc40a4b4e528dd197b43fae229119b9c4e7cd6b3795e74f3f91e44", + "src/textual/constants.py": "46cb5e88d24e3aa3bb7f2488c0788a118d12fc10e5861bfad8ca498dada115a5", + "src/textual/containers.py": "fdc5fa8bfe2dc36e32c5bce52544393aed428c12d91e2c02fa8f13907e8a576e", + "src/textual/content.py": "e22aba09aa8d05e7ea0953697a5af26f5a2c3a75fc43d0dde516b3b3bc8108a8", + "src/textual/coordinate.py": "3932a81410a0bb4505dd00316646db1555f691c5f38bb045bd1810e7d2e11c82", + "src/textual/css/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "src/textual/css/_error_tools.py": "6ab1ffa6ae67e214bdd38473535dd6d4adf685b526df13a8e35fb3851dd530d0", + "src/textual/css/_help_renderables.py": "78378d902fdd51727e88c917f8d2d527141555cdeba956c7f7e9cb54d8f6a36f", + "src/textual/css/_help_text.py": "68b3a8d54b4b13c0965d67f5f1a1775a64a3aff2048d885710357ad91aa6856a", + "src/textual/css/_style_properties.py": "30e4b66ad786c62e8395cac4d638b907879a24fe247b2fd9b3a86285bcb5bec4", + "src/textual/css/_styles_builder.py": "2faf6193053d165b43e87ab04be3290e887868bc50ad00a0e42cf8309b8ffe10", + "src/textual/css/constants.py": "dc5fc983b22b31bae42c9f7de908ea3f8d654565dad33a8d74c86102a8b0eb65", + "src/textual/css/errors.py": "ee72e37dc002dfa64639976cd73a9b1c0c6173481f12f8440f6d136adf20a164", + "src/textual/css/match.py": "743646f00a8876ee5b3cf6663439acbfa7d8616ad7938fef0d5cf4e06948b7cf", + "src/textual/css/model.py": "a3ddcbc7d331cc3761fcd7796e0bc02740765da6078c8c29eeee9c3f95f5a276", + "src/textual/css/parse.py": "ae14eeddc21b1adae059de9934c6abefb79fa4edbffb8a71ae03ec9cc27e1f14", + "src/textual/css/query.py": "b925c7cd18f98cd3e784b5f62c550a34b1e5e2ae312d2bb5d8f452ba4c9b40bd", + "src/textual/css/scalar.py": "0ffccab3b82b8552ebfe1e6d5ba92f6e313b29497e638371b5662c2efda44731", + "src/textual/css/scalar_animation.py": "6023e2bdab3af755caead96bb84e52d49daea34f5c1507015dcf29a8a5628072", + "src/textual/css/styles.py": "3e93a79ab3f5c8b4bf6f5b914005a448a8f3849b9c7e34de273bafd543eca9b1", + "src/textual/css/stylesheet.py": "88f4df62aa01ecab08c3609cc1c0d7ff5e6048d966d78a1edad374b5556d390a", + "src/textual/css/tokenize.py": "0313e90e442854027cf1652dd4c2a77592b5efb97fd5ad3f141c3d9f92fe29dd", + "src/textual/css/tokenizer.py": "937d81f3711d7d450618faa1142ece34085e33e3f2e17b9c529af0f1f27bee50", + "src/textual/css/transition.py": "22e7ef30e9423a77d3b7726dcc5569df4fcd9a2e8f42cba56803578055b63763", + "src/textual/css/types.py": "41b388e0243645c81e7e88fa584ce455f8c300388c8114a93c1cb5045e85e5b0", + "src/textual/demo/__main__.py": "8a6e0380c73e3421f266c83dcd0fde1cbfdc403df4be7a6ff3f7da8acb6397e5", + "src/textual/demo/_project_data.py": "d67dcbb8e0d93b6b722a0e6c5836a53833cfd5b0c3775767f9e3ab426aa389b6", + "src/textual/demo/_project_stargazer_updater.py": "ee3235ea3adcfafe47efef98e41c400ca6f9c7270d5c7a64dd95bd251144e950", + "src/textual/demo/_project_stars.py": "73303d116ae8a760e88aa75133bf17010fdc461e8809380c8addf3bbfa08b9c5", + "src/textual/demo/data.py": "2023bd93ee15242d7fcb246766e303062e9841fc8637db10ac07af7ca491dd4f", + "src/textual/demo/demo_app.py": "5f2b4b16bd0a9460dd45fe14c199b3e9a57d50cfd99399c5239b291d088b5cfc", + "src/textual/demo/game.py": "f954393db46053d16f467ce2b515bc2688fb9cdb7f312a2604b0ec73bf45fc7b", + "src/textual/demo/home.py": "ddf1494385c8c3ef229481c9565afe640eb970043cf9a6fea3a2187e37abfaf1", + "src/textual/demo/page.py": "4a90b50e26445f59d9fd7bcc83d84d42197dad2a6915dc02d657a921104bd57f", + "src/textual/demo/projects.py": "4a2a7475197baba0c7dd817745c4dee3f4c46a071c5e5e9f4c0873ba79701f5f", + "src/textual/demo/widgets.py": "c4be4f6e842ebfd96c365e70284c307fdecb215800c3bb1b34071906aa4ce845", + "src/textual/design.py": "ecbbe128cec76d9fa2a71a931551ce936cc73c281eb854aafbdb8cc52d1dcece", + "src/textual/document/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "src/textual/document/_document.py": "65f87813503d34257fba768379489558870279f73fad4cc2aa0d1b840b90c474", + "src/textual/document/_document_navigator.py": "77a34f623396a073f6d397673d0f548baa43e060f4c91390c1b9fd2993d99f50", + "src/textual/document/_edit.py": "7163606a002cc374473f4ac1b04d2423f2049fd4e94628b79684924b429938d5", + "src/textual/document/_history.py": "71b8ccf2053ed1be7c18ce92d2cf147f15ce856a0876388a325b3c42895659a5", + "src/textual/document/_syntax_aware_document.py": "8a2086056b52aeb21fc0c9ce6738b4ca7c8adcfc33d03eb1b2198d3d34e4feae", + "src/textual/document/_wrapped_document.py": "66fbc6667e7bc73c2bf63dcebdfcff25eae2c185090644c264d74c8482f5fb27", + "src/textual/dom.py": "090fb793bb3de926d8dd1bea3d848b01e9660ac8907099cf117b2bbf61bf43f9", + "src/textual/driver.py": "aa027a0b9263d4ccea7e6af52d08334c54632f6209ca77cdd69f7fb8dc1c9b3e", + "src/textual/drivers/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "src/textual/drivers/_byte_stream.py": "0007c75be79580f4a42fba50db053ceb61d6c9e782d75f09a2bc890d9f887ea9", + "src/textual/drivers/_input_reader.py": "8e457772f8da89233e9f0c34356d3b36e0f2371e1a3f7a2287e8f62478559eda", + "src/textual/drivers/_input_reader_linux.py": "5e493e68579e55345b37a2368de57514556cfdc74f632ef21ce1bfb394cd518c", + "src/textual/drivers/_input_reader_windows.py": "bb7219fdd1b8366d51f34cc6fa77cff3233472cf1eb0d5816c155ce0fe88278c", + "src/textual/drivers/_writer_thread.py": "a4384cf265cba1927e00c2460aa1dabe89512dbf40265cf061c6ca77a31e0c49", + "src/textual/drivers/headless_driver.py": "7fb895d55906011d3e12588f7c27297a00dfb80eaa5a3b88ccc52222492fa332", + "src/textual/drivers/linux_driver.py": "47fbddecef3241f13af834fa046a55bea6bfedb09396a7fb6b5672214e875285", + "src/textual/drivers/linux_inline_driver.py": "39435dd82ca60142b6ee8f1f6d54d9fdd3323a98b1390cbc02692709df425908", + "src/textual/drivers/web_driver.py": "0307a379058f10ef7e89405aaa1008f69374d91a4bb95b0e2a92b32dc4452206", + "src/textual/drivers/win32.py": "0dad5f6860cea623d5ec60ab5944b13c93a5328430b84807e0974584a84577fa", + "src/textual/drivers/windows_driver.py": "023dd7aff933fb0df353ece8543dec7660e580883d4101d6a5686032b7d3c682", + "src/textual/errors.py": "40fb64cc1b505696de5ca24b9b8118c9aaa7ac5aee71b6b3b7101796bb542d84", + "src/textual/eta.py": "a39c8c4cb96e5d19274bb3351f6658b9f7405d80b1f9841bbd89b07343b24310", + "src/textual/events.py": "a0601df40a21e67a1a82e29ced131174c57414f4d639518a690d982f901351ce", + "src/textual/expand_tabs.py": "1438a75686110c83676968813cff99ffa58c155a52ee3d2f925644dffcaaba87", + "src/textual/features.py": "35d304dd87b94a87433e02b7f70904d0300d5a28c1abd04378dcf1f624ff3c9e", + "src/textual/file_monitor.py": "7457dd407a52b21796d3d9452eafce27268bda4a6db86205c67fa9779d0e873a", + "src/textual/filter.py": "d6e31d29b94d2cdd5180d1b41984ec328c9b33261a193f713ef003bd89a2a934", + "src/textual/fuzzy.py": "e981ff8af2dce83445e63ddaaadecfe9d4d7f3697ee48f1067f45aa127e66d6f", + "src/textual/geometry.py": "ac8887b05e02719f00dae3947e79fd3dc4ea97edbfc93f025c88f4afd960d1f4", + "src/textual/getters.py": "4175937bc526996f7e8de3b8dc735bc117beed72cafb1e8225d381165bd706ee", + "src/textual/highlight.py": "b494139572397233dc7adf6c5ca0539db7eba6fe038adad18e459bbe84a4c7ab", + "src/textual/keys.py": "20538e0466e8fd83b4d4f8ee4510a860a0932782fdd6cf75f94d5d7dbce3edc9", + "src/textual/layout.py": "83fd905c047ef25bde8d980f9e4b9f0ebf9cdc3a20cbb2c0afefaf53eff21f92", + "src/textual/layouts/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "src/textual/layouts/factory.py": "d0959c0808bc6754980fad77e4acecba4fce705dd33aa55c78946d75003f3871", + "src/textual/layouts/grid.py": "32c64f696bc63b6accfd3e6807e1954ff7becdc1aeb3ccaf8608bcf2e0381d73", + "src/textual/layouts/horizontal.py": "5fff91aadb37f4f98d9c8e772204883f4926d0c6ef2d834e227f3febe8b00aa6", + "src/textual/layouts/stream.py": "a295efdcde358eee1885921266d4f869678b7da4c25ba89718f7475c976a9a87", + "src/textual/layouts/vertical.py": "2d9282d6929934479cfdf809e2c69ab7d1ae9d255f423011c8a6c8e4891f7174", + "src/textual/lazy.py": "1e7286365915fb4168b35512e6282191da6b44769fbd9265045e3cea8ef33423", + "src/textual/logging.py": "612152e76546531fcff862f520aaaa9d07f96eb71b89503f8f9f879010b48b0e", + "src/textual/map_geometry.py": "5a07bdd3c8bbabe8bab7b740aebf54083cdb2a5c898410f9f25a4b2e46463053", + "src/textual/markup.py": "670ce97055b4c4ee9e427c72259b9608fd7901e080bd6b9281d5e5f4caee934f", + "src/textual/message.py": "50816fcfe00981595a196f74215c97873aa1a39702c45ccdd990ba3c97fedc62", + "src/textual/message_pump.py": "04d599547d9c3289763c470d746ee4681071cdb6e67d7f83572fb487bf67d296", + "src/textual/messages.py": "e616e9ab5eb23933bacccfc8ff39119b1578972f1f260559e2d83b7c5ffe6c05", + "src/textual/notifications.py": "3b71178c9d44930cf2856f6234db81ec2d200325c3fee2f93c55129bd840287a", + "src/textual/pad.py": "0ecca32e53b54b3554adfb7160e54342fa4cdb6abf1aae145c94d863855d52bc", + "src/textual/pilot.py": "a899c3d8f52d34f18e726506774dcaad6fe3417f87261e38aa62fb0361b484b5", + "src/textual/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "src/textual/reactive.py": "016f9104bede8302a447e3fb55a4f9d830b55e890fd2062a9e55eaea4876877d", + "src/textual/render.py": "9ed49d1adf3e19280c6d88bdcc1dad59b81cbf5a964b5ceaa80d0f121df95d29", + "src/textual/renderables/__init__.py": "2cd0c44f709c7ca418c20e7625b17a7df26bb9513ed8dd0eab8e9541f1f290bd", + "src/textual/renderables/_blend_colors.py": "29b9cacd3af65f6d931c9729f8fe9743316e7c42ccdc7ca7482f00962eb041b8", + "src/textual/renderables/background_screen.py": "a3027283568f578424adfa418c34a987abed9062083499de49e4c103440e1191", + "src/textual/renderables/bar.py": "b0fbc5bb5d5fea997feb71bcc86c8f075e03f9c36b970a66fa94f70f81dc128a", + "src/textual/renderables/blank.py": "bf8d6eb786077ce0e0dd94db4f062578eabaf213f906614194f5d3afbeaae0a7", + "src/textual/renderables/digits.py": "8c15ce9fee26e6fee285c712c1751d083158ee19282a0e2506b19e58ee83d8e8", + "src/textual/renderables/gradient.py": "d8363d9f84bdda0e55b32ff4b8f54ff16fa5f98be8d9dd4982fcdfe0430c64fc", + "src/textual/renderables/sparkline.py": "06ae2004c4a62741f9699dfd1e09e0a6f6bbfb0ec2c7717c3bc3380feba196ef", + "src/textual/renderables/styled.py": "9b2e2fb3b823d4dc8e8eb1cfe374692825890b917ad6f08fbeb26ae6351d0111", + "src/textual/renderables/text_opacity.py": "d4692df3aa91a24d05e98eb0a8ed94ba69b71e65450b905018397ce1665f8866", + "src/textual/renderables/tint.py": "5ce457d75f9b5dc6b17f5277ed31722994e249172627b528abb8be1d38c5bde7", + "src/textual/rlock.py": "fa9d60de2b6f6d3222049ddcb945f9f9effb45e74349e2a05d4e89587224ec13", + "src/textual/screen.py": "86983c42eb275dcf34c8778f4acc0bc835e26790a95f18ea62b78ee1c64bec8d", + "src/textual/scroll_view.py": "8e09dc1ac1959d4580d5c4f83675aed391750e295993a2d9e443165c92f35a5a", + "src/textual/scrollbar.py": "9ee4c48aff8e99cf082aa156ac90347f99e3ad8fd926f98d0dc6f89d23046f58", + "src/textual/selection.py": "0becb81432155f520fce0293012b30565c6ceb03451353ad4428e08b35f0933a", + "src/textual/signal.py": "0593be67da3cdb56473fe90511f9f5c2895f89ef6e1959f06326e70bf5460df3", + "src/textual/strip.py": "8dd3feb951804af8904d35209a1de7ff43d9a7a20deddd2cf199b5ec070876e6", + "src/textual/style.py": "0923247c0a98e42663c12df088ab452c313a92d1776af4c3297279c0fd68a2b5", + "src/textual/suggester.py": "9a017439060874b0cc375db6483364b0eb70e6f95caa9207150cf69093a45971", + "src/textual/suggestions.py": "6d74781c76e4b01ad69df0ae417d90d0e85bf2314f76fa73f04f6654825e9d3f", + "src/textual/system_commands.py": "a38e24215174c25c64046a7601f8ccb508fd80c3f0e7f2730da672ddadf41ee2", + "src/textual/theme.py": "e225b278093b4ead2b8f56d84ec44b0a1d8f185c4fb143f3927a57d0af00a1d3", + "src/textual/timer.py": "b4b3992f1d72bd989ee2384af9d5930bb217654fadaa66f6b2515ef237209255", + "src/textual/tree-sitter/highlights/bash.scm": "b74220d954f485b7626d2b2b61f37b522e12eb1830803e388e57dd797dc99f11", + "src/textual/tree-sitter/highlights/css.scm": "905ccc8c18334a43db74156b9d1abc77078d670e87c9dfa9cd295c07eadf4ecb", + "src/textual/tree-sitter/highlights/go.scm": "16b616d2f4f35534d14169fc7c50889bc534e5f9807609d33a17121c60e030fe", + "src/textual/tree-sitter/highlights/html.scm": "87c0832f0e6984cf026414559afd7136a5553034896920dcaa9a23df61195f12", + "src/textual/tree-sitter/highlights/java.scm": "97b4cd3bee614453e17e61515a98e8d650e66ff9bf7a18ac501f119df0937e9e", + "src/textual/tree-sitter/highlights/javascript.scm": "d3630ae6dc9b2b27b230b5f8bb92b05cd491fb12bff353dae62a0a6d780461ee", + "src/textual/tree-sitter/highlights/json.scm": "bbdfecccb805c982de8416ee814329f62db60e21fb77f509a1dadc2e65f10840", + "src/textual/tree-sitter/highlights/markdown.scm": "7906cc4cc1dd0cdc5d08030867bb9e00bc7b239e76bb257acd4203c00e24de39", + "src/textual/tree-sitter/highlights/python.scm": "09e640c2f60d5169decd6ab940c76bce6263ba5a76538372945d89fc899faded", + "src/textual/tree-sitter/highlights/regex.scm": "5930062749e18dd71cfd3fa9e662a220f1edabe0d69890a90f0ad5ed14419b7b", + "src/textual/tree-sitter/highlights/rust.scm": "1121094caebe76ff176fe567bae9de3b2bd37c88bf8ed81a62d156ff5ad747e8", + "src/textual/tree-sitter/highlights/sql.scm": "d8f3232bafbc305d88edd58a189dff8c5732d403baabf77d8fb82bd97165b79c", + "src/textual/tree-sitter/highlights/toml.scm": "38d60c6cc48afef51386f7f58a508026bfa4a3c2d02db4cbce90a7a5109ad942", + "src/textual/tree-sitter/highlights/xml.scm": "aa4a561c846928c3a4d885acbe690a60689661a45684babe16cf8b7a1509b610", + "src/textual/tree-sitter/highlights/yaml.scm": "0049b81300134ae8f3232d6a812a17397ad461a307dca7773f70699104fe1719", + "src/textual/types.py": "a93f23fbb6e5c2cdaa196e9ec23a81658070c3d9709593e079bd2b30208bed72", + "src/textual/validation.py": "cfa4e113062449deed5f7cbb3541d73f736f97357d2f7963a6535ae5d4906525", + "src/textual/visual.py": "149ae51da19de9e84384435b389387e2b0835f70d5049b7e7f35c415e36197aa", + "src/textual/walk.py": "d16bdcf452985126374a78a3ebd866e11a847612d71763644f33d9d80bc59368", + "src/textual/widget.py": "18189bc6045a7226b9af8494c5d5bf8d0de9e5cb3c22b08c9db509a97470f0c0", + "src/textual/widgets/__init__.py": "3b7530b92ce903d5902568dbc7bacf18f1be19b50b5864d9340561d3181c7310", + "src/textual/widgets/__init__.pyi": "435f9327b490197853c14ca968eabd721086566137af91fdd7931e39db8c9b08", + "src/textual/widgets/_button.py": "117398c88fa75d474f01d633ff9c3487938b8c9b77fc9a852260ad192cd5bfb2", + "src/textual/widgets/_checkbox.py": "edd71e93bdf1115a2c23c526926d1998b2eb9fe130b388695942610faf04a919", + "src/textual/widgets/_collapsible.py": "5107db5e882fd1ffcb397b2b44b57b48712d165ebff969140278e3dc06ec9ed2", + "src/textual/widgets/_content_switcher.py": "57117dd857ee199911e27a2fa527df36d923a77f084604bc0aa4a5112e014034", + "src/textual/widgets/_data_table.py": "68282160250f6591c7b220781f3695b2768c623c3b31d0c39cfffafa8ec0ba4b", + "src/textual/widgets/_digits.py": "8f8f91412e1c9b94ca4f697b2ade703bf6ddeebe9d04437b2ee7be8f65ec0a73", + "src/textual/widgets/_directory_tree.py": "c9cd35efaa8d51f27bebcd020078adcee7223bc52ca9a998609b4f277c0a29bb", + "src/textual/widgets/_footer.py": "33a31237d4ffa29ab71f01068eeae787a249fa22f4d6c4b9ecbb61c8016798fe", + "src/textual/widgets/_header.py": "ac4c654dc0f9e93617cccd5669564cf63b39e639e66550018de7b7298828ba41", + "src/textual/widgets/_help_panel.py": "b8328660b64c4b4a567fe7e015e506034dac4881ae2d28b89e2003da3aa613ca", + "src/textual/widgets/_input.py": "16554b8c128ff9f6fe96d47456eda4db8051cd1d4de776aea326659b5ea92b53", + "src/textual/widgets/_key_panel.py": "1ab98e2bd33b4a895a39416f448017c38ea53bac55abb8acaac2fce7d3bb6b6e", + "src/textual/widgets/_label.py": "64a653a23168ec1d0cf5287515a98f46ff705c839a5f0ef626e3a6fbbd127688", + "src/textual/widgets/_link.py": "05d2e42df924de65c8bee40aeb70a0dded1d44fdc84c6b30e5c2a9c95ff5b6cf", + "src/textual/widgets/_list_item.py": "e93d9e42df95eb59449010fd2d778d726d82aeb0a93ee9d0bc4f1e71c5ba63a7", + "src/textual/widgets/_list_view.py": "b93207d507dffd0dbafad5bb3e59c81eaa3b8d6ad2fc09ece2ebc9ce9b5d3e84", + "src/textual/widgets/_loading_indicator.py": "83354febee2f12cd1f4e354650d481db742cc4b35c703e9d6c8b31ab4e5912be", + "src/textual/widgets/_log.py": "383bb4f47c7170eaf1af2819bc09580f171dc87769b1a756fff76bb6e4ca2242", + "src/textual/widgets/_markdown.py": "882780dc825d3833162d9fc7f779d142c781bbee59742e92ea7095f973c98f65", + "src/textual/widgets/_markdown_viewer.py": "d2f9baf051143c05b8f2b2a094eb846641c3eb7df2ac4e3c1ac50d2f504cc3cf", + "src/textual/widgets/_masked_input.py": "8480532009bb81fed86d0ed824a86f4672de96d4cb01e4840e54adfda440a538", + "src/textual/widgets/_option_list.py": "2ffa3a05c11b7607dedd9c8fae2f1a9276b7bd320d28b9de135b75fe57f5573b", + "src/textual/widgets/_placeholder.py": "353ef60ccb201b8fe35229aa49075b70cfe3fb6a448123f9d03d262c0582a42d", + "src/textual/widgets/_pretty.py": "987ee68dcbe1f70f2e112ba1fb9538a6f2264cc360ee1a30f2109ac5a4b67943", + "src/textual/widgets/_progress_bar.py": "683810aa5d8ae55b7cd011ab93be8fdf704becb5c5cbbbbbfd52b41240c6cf34", + "src/textual/widgets/_radio_button.py": "9355e47c0c081c3bea3e94a4f0508494e4a74bf7a8124b26c0184665a32ef740", + "src/textual/widgets/_radio_set.py": "7d9f581579ffa517cb6b17d98476ad639f4c5e301ed07478a82ddff9eaba0a20", + "src/textual/widgets/_rich_log.py": "249e47fb1e891d1fb8fa358eea83f88501365967c648aa476e987d774687bcc6", + "src/textual/widgets/_rule.py": "02818d68c13ada2eac4050cf9c952d4329a880a6b55ddd8323b6f5e284d3ea77", + "src/textual/widgets/_select.py": "22328d82f5bda8b68eb25c025594a457e853d7c4b204242c096818ab90dd08a7", + "src/textual/widgets/_selection_list.py": "69b3c58ac1fca30f7411260e432bc6589acf6b25076043952d2f74a93111c823", + "src/textual/widgets/_sparkline.py": "d0e39f267c123ebb890533f15ea6e74a5bf4df7897c8157d2275c5505c330272", + "src/textual/widgets/_static.py": "60186fa330665f6a150da40ed0ea32bf0c07674d0d38c33be353d229a6c805c1", + "src/textual/widgets/_switch.py": "44059044e35af1bf8628f72ee3b4c834d7009264d69a11b304e69147f4514f92", + "src/textual/widgets/_tab.py": "89649871fd369698276e4997d7cefe4d1953fbf9f43d68474bce590048cab22d", + "src/textual/widgets/_tab_pane.py": "5fc1bb2663d8521c4f0d1bad9789d3414fdfd7968cb26c20de0ac2378f88ec22", + "src/textual/widgets/_tabbed_content.py": "8032b9a239c53bb8aac482c5edc3d75c384e61b82d5bff29ee5725b0a108c029", + "src/textual/widgets/_tabs.py": "8b66b9daa01706c8acafa979c6c162d3af651b0e9d800f2588483a1d3912302a", + "src/textual/widgets/_text_area.py": "556a1e9c88d66b662569b91286340d1c41691a30f2ef5486267b1576f4337af8", + "src/textual/widgets/_toast.py": "61df4c1debffcb10505016a57883e84053ea8d7755d11de11ab363ed063604d6", + "src/textual/widgets/_toggle_button.py": "ec04ad7f189e28729b9acb5485a2f2f9934f2ffe6dcb62200ac0c1e3ac8d91c8", + "src/textual/widgets/_tooltip.py": "74ce548ca7b6d3fee2ca185e4248574f1cdfa4ac08e56283ad10a030bf8707fb", + "src/textual/widgets/_tree.py": "693cc8442a83c85cdc24acc16c156a3ec7616dfb170f4b0b0048e422399c190d", + "src/textual/widgets/_welcome.py": "8402afe244618174fada527fcb2af4010e17fe4dda759a46b17d835c22bc78b6", + "src/textual/widgets/button.py": "5c8fe122d39e0d26063af1075cf44cd66262c7287b078813bfd7a9a93b1aae42", + "src/textual/widgets/collapsible.py": "60cf01a3ef4531cf12c5b5934bce9fa53299dd7c32a02c55a70165f1171f5fe3", + "src/textual/widgets/data_table.py": "7f2f799502f8599d7b3ca4c738fd5b922771f842579bb2c6d7fc8b9f66081d44", + "src/textual/widgets/directory_tree.py": "39ec20d6d6850aa8bfa252dd1eedc58e6c9c31d0f76e7dfecffd7dd974f4411c", + "src/textual/widgets/input.py": "7dbeee6d6b62744c151005d4d6baa3b8e578753cd2453d8b777b6d4d9a534634", + "src/textual/widgets/markdown.py": "5aa39b5cbf5b2f658f854269a3098f5a38104758c2ab61120136a78f3a90c540", + "src/textual/widgets/option_list.py": "d184c6220ff55466a1c933544ccf44c3e312a5eb9f8893fbda8113122713f711", + "src/textual/widgets/rule.py": "c460260021df26e1a6ce3bea20528abb179bb0578e7b95f72a13c6d0461e42dd", + "src/textual/widgets/select.py": "4e14e7ec6b9cad1fbe78484039a8907568243cafc42c0e9893ca99768d4321ad", + "src/textual/widgets/selection_list.py": "a929d7c5715b0cf50eaa4e356699beade750351829c4f03cd9dc2787ce197fd4", + "src/textual/widgets/tabbed_content.py": "5241d009717b6a525cf46221c490bb6ccd2b4473d3cb0cf7b68f9819071857b4", + "src/textual/widgets/text_area.py": "5fa57bd6d7885e098855273a2a51f3981df97c645dcf041085e002d64d91adbc", + "src/textual/widgets/tree.py": "1786019604fb090eb8b6685882baf36614efef3c62176e88689644b3be65f505", + "src/textual/worker.py": "f5edc8a438f8e4ba61cf6cdaff6ad6f73ef5f453c876ceabb58ecb281b7ea040", + "src/textual/worker_manager.py": "9dd0b91fbcf30354c39959f033e46c1ab266399cdb9c3883edcb31ff50cc3169" + }, + "package_build": false, + "installed_mutation": false, + "provider_call": false, + "physical_recording": false, + "owned_live_handles": [], + "remaining": "Heisenberg #430 fresh named installed App purpose: original width/style capture loss under translucent modal; delayed Unmount; tool recompose/worker/collapse/remount; no general film repeat" +} diff --git a/evidence/markdown-publication-custody-20261004/SCOPE.md b/evidence/markdown-publication-custody-20261004/SCOPE.md new file mode 100644 index 0000000000..dc74b8c6c7 --- /dev/null +++ b/evidence/markdown-publication-custody-20261004/SCOPE.md @@ -0,0 +1,73 @@ +# Markdown publication custody + +Native Markdown currently acquires its own document lock but independently chooses global App.batch_update around awaited first-batch replacement and append-block publication. The original Widget.batch context manager already owns widget locking and update batching; native recompose consumes that capability. Markdown bypasses it at both publication sites. + +Migrate both native sites to the existing widget-owned batch lifetime. No new hook, class, map, state, timer or root-screen bypass. Default Widget.batch still acquires the original reentrant lock and App batching; ordinary native Markdown retains its existing global atomic publication. Parser preparation remains outside that batch and document source ordering remains under the existing outer lock. Cancellation releases the two original contexts through their existing context managers. + +Heisenberg owns the Toad Body/Window source-resource continuation. PreparedConversationMarkdown reaches these native algorithms through its original MeasuredViewportBody + ConversationMarkdown inheritance. Local source publication must retain preceding pixels or a valid pending-body presentation through native remove/mount and width/style changes; no blind no-op override. Generic Widget.recompose is another existing batch consumer and must be covered by the rich body owner. Public Widget.batch semantics are unchanged in this native checkpoint. + +Existing refactor-audit Package parses complete native and Toad production roots. Attribute syntax is not dynamic resolution; source sites are read against actual owner/MRO and RLock task-reentrancy. BOUND-2: leaf algorithms must consume the existing batching owner rather than reach past it. This checkpoint enables that owner relationship, not a demonstrated stall/CPU improvement. No physical/performance readiness claim. + +Source reasoning and coherent combined owner implementation precede one final proportionate native/App and affected installed Toad validation. Prior Text58/55 checks and movies are not repeated. No new WT/env/native copy, package access or provider call. + +## Original Markdown checkpoint (preserved) + +Production `aebc027cb47a40459531b2bc301c811b66d97af0`: one native file 2+/2−. Markdown no longer decides root batching at either awaited publication site; both invoke original Widget.batch. Public Widget.batch, App batch counters/writer/callbacks and RLock are unchanged. Nested acquisition is by the same task under the existing Markdown document lock; unwind leaves the outer ownership intact until the original source update completes. Final batch sites cover both ordinary remove/mount and table/fence leaf update hooks. Generic recompose, Footer and App screen.batch remain the existing consumers and are not altered. + +Exact required automatic Debt37193390973 SUCCESS. Native/App and changed installed #430 qualification remain pending coherent Toad capability closure; no isolated speed/CPU/Ready claim. One final affected batch will detect parsing/streamed append order, generic root-batch/cancellation behavior, nested lock unwind and local resource publication coverage. It does not repeat Text58/55 or an unchanged film. + +Concrete shared source constraints: capture_native_paint refuses focus, active selection, held native lock and unready nested bodies. MeasuredBody.render delegates mutable live rendering, not a Loading cover. MaterializingBody owns worker custody but may lack preceding pixels; width/style invalidation can retire them after admission. Thus worker ownership alone is not paint coverage. Rich body publication must own valid paint or an original pending presentation throughout awaits, including recompose outside publication. These semantics are handed directly to the existing Toad owner, not encoded as native flags or bypasses. + +## Visible-stack terminal admission continuation + +The original modal counterexample is preserved: Screen.render creates BackgroundScreen for a translucent foreground; BackgroundScreen.__rich_console__ renders the background compositor directly, and Compositor.__rich__ calls render_strips. Neither path re-enters the background Screen._compositor_refresh. A registered MaterializingBody can lose captured coverage after width/style invalidation while its own WorkspaceScreen refresh correctly refuses; a foreground modal previously admitted only itself and could still consume that background's mutable rows. This is a source-supported counterexample, not a reproduced movie or measured stall. + +The existing Screen._compositor_refresh now acquires App's original background cohort once. A foreground terminal refresh invokes each background's existing _prepare_compositor_refresh hook and then its own before render_update/render_inline, damage consumption or writer delivery. The existing inline height query is completed before admission: it traverses original Screen content measurement and can invalidate retained width; performing it after readiness would reopen the same source-to-paint gap. Its height and clearing contracts remain unchanged. A background damage-forwarding refresh still admits its own screen; it does not render another scene. Refusal retains the requesting screen's repaint intent and compositor damage. The same acquired backdrop list supplies rendering. Existing per-screen behavior remains the hook; no new registry, readiness flag, callback queue, receipt fastpath or copied scene is introduced. Native App's existing background selection remains the authority, including its conservative visible-stack semantics. + +Both output formats now share original background admission and cleanup. Their single native format decision preserves distinct forwarding: inline forwards damage, while normal mode also retains its own repaint. This replaces two repeated negative App-mode probes with the original format branch. Inline clearing and previous-height updates and normal background repaint behavior retain their original ordering. Generic Widget.batch and App.batch_update are unchanged. BackgroundScreen continues its original Rich rendering of already-admitted backdrop geometry; adding a second gate there would be too late, after foreground rendering has begun consuming damage. No export path is modified: App.export_screenshot intentionally renders the compositor directly and remains a diagnostic observation, not evidence of admitted terminal paint. + +Before/after source evidence uses the original refactor-audit Package across all native and Toad production roots, with omitted parse paths and syntactic receiver ambiguity explicit. Source closure precedes final batched affected native/App controls and the joined #430 installed journey. The previous exact Debt success qualifies only its original Markdown head; this continuation needs its own automatic required result. No tests, App, build, package writes, film, provider or installed readiness have occurred for this continuation. + +### Preserved required ratchet negative + +Exact source44aca344 required Debt37194765254 failed: ForeignAbsenceProbe in screen.py increased by2 because the first shared forwarding form repeated `not app.is_inline` before and after parent refresh. No other positive debt delta. Raw log SHA7a0f93254d2a28a0d2a95d11d8b9a8151f7dc8329322162acda654c3db28bcea and structured original report remain under .artifacts/native59-visible-stack-source-20261004. Corrected forwarding makes the existing format branch decide those distinct ordered effects once; no policy waiver, threshold change or unrelated deletion. Generic batching and diagnostic export remain unchanged. Latest exact required result is pending publication of this correction. + +### Coherent terminal source checkpoint + +Production8f6c15a5 closes the two awaited Markdown consumers and the visible-stack terminal admission family; generic batching, BackgroundScreen rendering, App source selection and diagnostic export are unchanged. Before/after source tables retain the original negative inventory; the after table additionally includes inline geometry acquisition to make that dependency explicit (not zero by omission). Native249 and Toad288 modules parse with0 omissions. No dynamic mock, movie, native App or installed package verification has run for this new scene admission. Final controls will join the coherent #430 body/window capability: retained capture invalidated during publication with a translucent foreground must refuse the terminal frame until original body readiness; ordinary root batching and inline forwarding/clearing remain required. Existing accepted Text58/55/App/films are not repeated. + +## Native removal admission and scene retirement + +The original AwaitRemove transaction now owns prune admission as well as its independently completed removal receipt. Widget.remove, Widget.remove_children, DOMQuery.remove and App shutdown invoke AwaitRemove.prune directly. App._prune and its late first-root-screen retirement callback are deleted, including their unused imports. The transaction groups the original nodes by their own Screen, acquires/reset focus before pruning flags, invalidates existing NodeList/layout/capture projections, then invokes original Screen._forget_pruned_widgets before asynchronous teardown can run. Completed removal still owns parent layout refresh and mouse readback; cancellation and self-removal use the existing completion algorithm. No new lifetime state or alternate removal algorithm. + +Screen retirement continues to use Compositor.discard_widgets. That resource owner now derives the original cover and virtual chrome alongside logical widgets; it releases map/capture ownership and retains existing visible rectangle damage. Reparent uses this same operation before detach, unchanged. Existing child cardinality is released only on actual NodeList removal: admission invalidates projections without pretending teardown has completed. This prevents a lazy layout read from restoring pruning placements from old arrangements. + +The source counterexample is explicit: pruning makes DOM.display false before Unmount/task completion; previously the cached scene survived until the receipt and could render mutable/disposed children after the Toad Window left visible admission. This is source evidence, not a measured physical failure. Grouping reads every original owning Screen, including queries containing multiple roots; no first-screen inference or per-render pruning guard. Full Package before evidence covers native249/Toad288 with zero omissions; syntactic matches still require original receiver resolution. Final changed native/App and installed #430 controls remain pending; no package purpose, wheel, film, provider, CPU or smoothness acceptance is claimed. + +### Original required negative and projection-owner correction + +The prune source3732047f required Debt37197008364 FAILED: Compositor GodClassExcess +2, with no other positive delta. Original raw/JSON reports remain in .artifacts/native59-visible-stack-source-20261004; no waiver or ratchet change. Source review found the same three derived render projections independently invalidated at seven clear/retire/reflow/lazy-map publication sites. Compositor now owns that shared operation once through _invalidate_render_projection; all seven original consumers use it. Both before/after reflow retirements remain: geometry measurement may read projections during arrangement, so final publication must also retire the preceding map's projection. Full/visible map selection, damage and capture ownership remain distinct. This deletes duplicated projection-retirement decisions in the affected scene family rather than offsetting growth with unrelated deletion. + +The coherent pre-correction native batch passed48 real framework cases in4.53s: delayed unmount cannot retain scene placement while unrelated input proceeds; multiple original Screen roots and Loading covers retire before teardown; translucent foreground refuses rendering while its background resource is locked; generic batch/callback/cancellation/self-removal/reparent and Markdown source update contracts remain. This source-App result is not installed Toad/terminal readiness. Final correction checks will cover only the changed native scene projection family; no unchanged whole-batch or movie repeat. + +## Selection and pointer custody during retirement + +The parent source review identified a separate retained-resource gap: Screen.selections and SelectState endpoints could retain removed content within a surviving container. The original is_attached_to_dom declaration was also a method consumed as a property; its bound method did not perform the intended attachment check. SelectState now owns one endpoint-resource traversal (containers AND content), actual attachment and retired-membership queries, and the existing content-range projection moved from Screen. This deletes Screen's single-content-versus-range algorithm rather than creating another validator. + +Screen now distinguishes native geometry retirement (also used by reparent) from actual prune interaction retirement. The original Compositor operation returns its derived cover/chrome cohort; Screen uses that same result to release dirty/layout and selection resources, and App releases original capture/hover/mouse targets through its existing pointer hooks. Surviving selections and pointer targets remain. Pending selection intent referencing a retired endpoint is revoked at admission, so a delayed flush cannot republish it. Same-screen reparent uses geometry retirement only; cross-screen reparent uses original Screen selection retirement before moving existing selected ranges. Existing focus/selection transfer, callback migration and actual source custody are preserved. No per-render pruning/absence guard or new selection/paint store. + +Source evidence covers native249/Toad288 (zero omissions,93/13 selected interaction sites before migration). These are syntax sites with receiver ambiguity recorded, not proof of physical performance. The earlier native48/15 control results retain their exact source scopes; the expanded selection/pointer change requires its own final affected native batch and the same future #430 installed application purpose. No wheel/installed writes/provider/film or scopedReady claim. + +## Current native source-App qualification + +Production 64c0f1b0a113d398307ccf0ccb434836a0b92182 has exact required Debt37197836512 SUCCESS. Final changed selection/pointer/native App batch:33 passed in9.29s, including original physical Pilot Down/Up selection events, selected child retirement inside a retained container, surviving range retention, mouse capture release and same-screen/cross-screen reparent. Earlier48-case Markdown/batch/removal and15-case projection controls retain their exact earlier-source scopes; they are not summed as distinct cases or recast as final installed acceptance. Native249/Toad288 after source parses with0 omissions; one endpoint membership declaration, one content selection projection, one render-projection retirement declaration with7 original consumers. + +The initial new fixture failed before removal admission because Pilot has no mouse_move; cleanup originally held native unmount after that setup exception and reached the existing60s process bound. The fixture cleanup now covers setup too; original Pilot.mouse_up already emits Move+Up, so it supplies the same intended selection journey. An intervening fixture-edit collection SyntaxError and the original AttributeError are retained as separate failed attempts. No product source was changed to make these pass, no deadline padding or oracle relaxation. Raw paths/hashes/results are in NATIVE-APP-QUALIFIED.json. + +No wheel/install/package writes, configured provider, physical recording or owned active process. The source-App result does not qualify full #430 local batching or terminal performance. The remaining installed App purpose belongs to Heisenberg after actual holder grant; original capture/style/modal, pending unmount and ToolContent recompose/worker/collapse/remount must run through the coherent native59/Toad430 family. FullUIstalls/CPU/smoothness remain active. + +## Pending interaction acquisition + +A MouseDown authors original SelectState before an end point or selected text ranges exist. Native Screen now supplies _interaction_widgets as a borrowed iterator of focus, committed selection keys, all original pending pointer endpoints and App mouse capture. It does not force range projection, retain a copy or alter widget text rendering. Heisenberg owns all Toad body capture/row retention/tool membership migrations to this source. Native range rendering still consumes selections; original SelectState owns endpoint membership. The prior unused Screen._collect_select_widgets duplicated selection walking/slicing despite having no native, Toad, tests or tool consumers; it and its unused imports are deleted in the same selection-family change. Before source Package covers production/dependent test/tool roots with omissions and dynamic limits explicit. + +This followup source is not installed-qualified by the preceding33 controls; only the changed pending-pointer acquisition assertion is added to the existing native delayed-unmount case. No broad unchanged controls, package/wheel, holder access, movie or provider. Final coherent #430 installed App remains the shared remaining boundary. diff --git a/evidence/markdown-publication-custody-20261004/after-consumers.json b/evidence/markdown-publication-custody-20261004/after-consumers.json new file mode 100644 index 0000000000..87fa01ef61 --- /dev/null +++ b/evidence/markdown-publication-custody-20261004/after-consumers.json @@ -0,0 +1,63 @@ +{ + "head": "aebc027cb47a40459531b2bc301c811b66d97af0", + "parsed": 249, + "omissions": [], + "owned_batch_consumers": [ + { + "path": "src/textual/app.py", + "function": "recompose", + "line": 3583, + "callee": "self.screen.batch" + }, + { + "path": "src/textual/widget.py", + "function": "recompose", + "line": 1868, + "callee": "self.batch" + }, + { + "path": "src/textual/widgets/_footer.py", + "function": "_reconcile_bindings", + "line": 348, + "callee": "self.batch" + }, + { + "path": "src/textual/widgets/_markdown.py", + "function": "update", + "line": 1460, + "callee": "self.batch" + }, + { + "path": "src/textual/widgets/_markdown.py", + "function": "append", + "line": 1537, + "callee": "self.batch" + }, + { + "path": "src/textual/widgets/_markdown.py", + "function": "await_update", + "line": 1460, + "callee": "self.batch" + }, + { + "path": "src/textual/widgets/_markdown.py", + "function": "await_append", + "line": 1537, + "callee": "self.batch" + }, + { + "path": "src/textual/widgets/_markdown.py", + "function": "mount_batch", + "line": 1460, + "callee": "self.batch" + } + ], + "default_owner": "Widget.batch -> RLock + App.batch_update unchanged", + "migration": "Markdown await_update.mount_batch and await_append now use original self.batch; no direct app.batch_update call remains in Markdown", + "leaf_hooks": [ + "MarkdownBlock._update_from_block remove/mount", + "Markdown paragraph/content and fence replacements", + "Markdown table _update_rows remove/mount" + ], + "limits": "Syntax does not prove dynamic receivers. App startup screen batching, Footer recomposition, and public Widget.recompose retain generic capability. Toad owns local BodyPublication capability and nonpublication recompose risk; no blanket override or new cover inferred." +} diff --git a/evidence/markdown-publication-custody-20261004/before-consumers.json b/evidence/markdown-publication-custody-20261004/before-consumers.json new file mode 100644 index 0000000000..e86e6074dc --- /dev/null +++ b/evidence/markdown-publication-custody-20261004/before-consumers.json @@ -0,0 +1,182 @@ +[ + { + "repo": "/home/ts/wt/textual-native-subtree-strips-20261002", + "root": "src/textual", + "head": "456c27e18688e6c829c727f766561c9eb8640803", + "parsed": 249, + "omitted": [], + "call_sites": [ + { + "path": "src/textual/app.py", + "function": "_pop_to_screen", + "line": 3153, + "callee": "self.batch_update" + }, + { + "path": "src/textual/app.py", + "function": "_process_messages", + "line": 3439, + "callee": "self.batch_update" + }, + { + "path": "src/textual/app.py", + "function": "recompose", + "line": 3583, + "callee": "self.screen.batch" + }, + { + "path": "src/textual/app.py", + "function": "pop_screens", + "line": 3153, + "callee": "self.batch_update" + }, + { + "path": "src/textual/app.py", + "function": "run_process_messages", + "line": 3439, + "callee": "self.batch_update" + }, + { + "path": "src/textual/css/styles.py", + "function": "replace_rules", + "line": 941, + "callee": "prepared.batch_update" + }, + { + "path": "src/textual/css/styles.py", + "function": "replace_rules", + "line": 950, + "callee": "self.batch_update" + }, + { + "path": "src/textual/css/styles.py", + "function": "batch_update", + "line": 1578, + "callee": "self._inline_styles.batch_update" + }, + { + "path": "src/textual/css/stylesheet.py", + "function": "apply", + "line": 608, + "callee": "node._css_styles.batch_update" + }, + { + "path": "src/textual/css/stylesheet.py", + "function": "replace_rules", + "line": 832, + "callee": "base_styles.batch_update" + }, + { + "path": "src/textual/css/stylesheet.py", + "function": "update_nodes", + "line": 1023, + "callee": "node._css_styles.batch_update" + }, + { + "path": "src/textual/widget.py", + "function": "recompose", + "line": 1868, + "callee": "self.batch" + }, + { + "path": "src/textual/widget.py", + "function": "batch", + "line": 4668, + "callee": "self.app.batch_update" + }, + { + "path": "src/textual/widgets/_content_switcher.py", + "function": "_on_mount", + "line": 74, + "callee": "self.app.batch_update" + }, + { + "path": "src/textual/widgets/_content_switcher.py", + "function": "watch_current", + "line": 94, + "callee": "self.app.batch_update" + }, + { + "path": "src/textual/widgets/_content_switcher.py", + "function": "add_content", + "line": 127, + "callee": "self.app.batch_update" + }, + { + "path": "src/textual/widgets/_content_switcher.py", + "function": "_add_content", + "line": 127, + "callee": "self.app.batch_update" + }, + { + "path": "src/textual/widgets/_footer.py", + "function": "_reconcile_bindings", + "line": 348, + "callee": "self.batch" + }, + { + "path": "src/textual/widgets/_markdown.py", + "function": "update", + "line": 1460, + "callee": "self.app.batch_update" + }, + { + "path": "src/textual/widgets/_markdown.py", + "function": "append", + "line": 1537, + "callee": "self.app.batch_update" + }, + { + "path": "src/textual/widgets/_markdown.py", + "function": "await_update", + "line": 1460, + "callee": "self.app.batch_update" + }, + { + "path": "src/textual/widgets/_markdown.py", + "function": "await_append", + "line": 1537, + "callee": "self.app.batch_update" + }, + { + "path": "src/textual/widgets/_markdown.py", + "function": "mount_batch", + "line": 1460, + "callee": "self.app.batch_update" + } + ] + }, + { + "repo": "/home/ts/wt/toad-viewport-raster-cpu-continuation-20261001", + "root": "src/toad", + "head": "50d47ae2a2ce3c6d5d2c87136022b22d9ec3edde", + "parsed": 288, + "omitted": [], + "call_sites": [ + { + "path": "src/toad/mounted_message_history.py", + "function": "toggle_style", + "line": 172, + "callee": "self.app.batch_update" + }, + { + "path": "src/toad/screens/workspace.py", + "function": "_refresh_layout", + "line": 204, + "callee": "self.app.batch_update" + }, + { + "path": "src/toad/sidebar_navigation.py", + "function": "finish", + "line": 85, + "callee": "self.sidebar.app.batch_update" + }, + { + "path": "src/toad/workspace_chrome.py", + "function": "layout_sidebars", + "line": 58, + "callee": "screen.app.batch_update" + } + ] + } +] diff --git a/evidence/markdown-publication-custody-20261004/frame-admission-after.json b/evidence/markdown-publication-custody-20261004/frame-admission-after.json new file mode 100644 index 0000000000..c5b9f0550b --- /dev/null +++ b/evidence/markdown-publication-custody-20261004/frame-admission-after.json @@ -0,0 +1,256 @@ +[ + { + "repo": "/home/ts/wt/textual-native-subtree-strips-20261002", + "root": "src/textual", + "rev": "8f6c15a5ad66744069dd0b38121c3a0a1d0fb76e", + "parsed": 249, + "omitted": [], + "sites": [ + { + "path": "src/textual/_compositor.py", + "line": 1452, + "kind": "declaration", + "name": "render_update" + }, + { + "path": "src/textual/_compositor.py", + "line": 1476, + "kind": "declaration", + "name": "render_inline" + }, + { + "path": "src/textual/_compositor.py", + "line": 1545, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/_compositor.py", + "line": 1493, + "kind": "call", + "name": "self.render_strips" + }, + { + "path": "src/textual/_compositor.py", + "line": 1691, + "kind": "call", + "name": "self.render_strips" + }, + { + "path": "src/textual/_doc.py", + "line": 138, + "kind": "call", + "name": "app.export_screenshot" + }, + { + "path": "src/textual/app.py", + "line": 1689, + "kind": "declaration", + "name": "_get_inline_height" + }, + { + "path": "src/textual/app.py", + "line": 1865, + "kind": "declaration", + "name": "export_screenshot" + }, + { + "path": "src/textual/app.py", + "line": 1893, + "kind": "call", + "name": "self.screen._compositor.render_update" + }, + { + "path": "src/textual/app.py", + "line": 1923, + "kind": "call", + "name": "self.export_screenshot" + }, + { + "path": "src/textual/app.py", + "line": 1954, + "kind": "call", + "name": "self.export_screenshot" + }, + { + "path": "src/textual/app.py", + "line": 1696, + "kind": "call", + "name": "screen._get_inline_height" + }, + { + "path": "src/textual/content.py", + "line": 728, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/renderables/blank.py", + "line": 27, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/screen.py", + "line": 1198, + "kind": "declaration", + "name": "_prepare_compositor_refresh" + }, + { + "path": "src/textual/screen.py", + "line": 1208, + "kind": "declaration", + "name": "_compositor_refresh" + }, + { + "path": "src/textual/screen.py", + "line": 1498, + "kind": "declaration", + "name": "_get_inline_height" + }, + { + "path": "src/textual/screen.py", + "line": 628, + "kind": "call", + "name": "BackgroundScreen" + }, + { + "path": "src/textual/screen.py", + "line": 1222, + "kind": "call", + "name": "app._get_inline_height" + }, + { + "path": "src/textual/screen.py", + "line": 1438, + "kind": "call", + "name": "self._compositor_refresh" + }, + { + "path": "src/textual/screen.py", + "line": 1244, + "kind": "call", + "name": "self._compositor.render_update" + }, + { + "path": "src/textual/screen.py", + "line": 1282, + "kind": "call", + "name": "self._compositor_refresh" + }, + { + "path": "src/textual/screen.py", + "line": 1362, + "kind": "call", + "name": "self.app._get_inline_height" + }, + { + "path": "src/textual/screen.py", + "line": 1223, + "kind": "call", + "name": "screen._prepare_compositor_refresh" + }, + { + "path": "src/textual/screen.py", + "line": 1235, + "kind": "call", + "name": "self._compositor.render_inline" + }, + { + "path": "src/textual/visual.py", + "line": 131, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/visual.py", + "line": 324, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/visual.py", + "line": 391, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/visual.py", + "line": 227, + "kind": "call", + "name": "visual.render_strips" + }, + { + "path": "src/textual/visual.py", + "line": 411, + "kind": "call", + "name": "self._visual.render_strips" + } + ], + "limits": "Attribute syntax does not establish receiver identity. Screen/WorkspaceScreen/BackgroundScreen read against actual MRO. _get_inline_height added to the after census to expose all geometry-read consumers; not silently treated as absent from the before inventory. Export is diagnostic, not terminal admission." + }, + { + "repo": "/home/ts/wt/toad-viewport-raster-cpu-continuation-20261001", + "root": "src/toad", + "rev": "8e0900f799631d440ef62dab310f19ddb7ff2732", + "parsed": 288, + "omitted": [], + "sites": [ + { + "path": "src/toad/screens/workspace.py", + "line": 165, + "kind": "declaration", + "name": "_prepare_compositor_refresh" + }, + { + "path": "src/toad/visuals/columns.py", + "line": 44, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/toad/widgets/patch_diff.py", + "line": 245, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/toad/widgets/patch_diff.py", + "line": 339, + "kind": "call", + "name": "row.render_strips" + }, + { + "path": "src/toad/widgets/patch_diff.py", + "line": 367, + "kind": "call", + "name": "_DiffRow(self.source, y).render_strips" + }, + { + "path": "src/toad/widgets/path_search.py", + "line": 46, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/toad/widgets/prepared_markdown.py", + "line": 144, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/toad/widgets/prepared_markdown.py", + "line": 155, + "kind": "call", + "name": "self.content.render_strips" + }, + { + "path": "src/toad/widgets/throbber.py", + "line": 63, + "kind": "declaration", + "name": "render_strips" + } + ], + "limits": "Attribute syntax does not establish receiver identity. Screen/WorkspaceScreen/BackgroundScreen read against actual MRO. _get_inline_height added to the after census to expose all geometry-read consumers; not silently treated as absent from the before inventory. Export is diagnostic, not terminal admission." + } +] diff --git a/evidence/markdown-publication-custody-20261004/frame-admission-before.json b/evidence/markdown-publication-custody-20261004/frame-admission-before.json new file mode 100644 index 0000000000..0ae8258ef0 --- /dev/null +++ b/evidence/markdown-publication-custody-20261004/frame-admission-before.json @@ -0,0 +1,226 @@ +[ + { + "repo": "/home/ts/wt/textual-native-subtree-strips-20261002", + "root": "src/textual", + "rev": "9bdc8004fa75844257f92afb425bf9e951eae346", + "parsed": 249, + "omitted": [], + "sites": [ + { + "path": "src/textual/_compositor.py", + "line": 1452, + "kind": "declaration", + "name": "render_update" + }, + { + "path": "src/textual/_compositor.py", + "line": 1476, + "kind": "declaration", + "name": "render_inline" + }, + { + "path": "src/textual/_compositor.py", + "line": 1545, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/_compositor.py", + "line": 1493, + "kind": "call", + "name": "self.render_strips" + }, + { + "path": "src/textual/_compositor.py", + "line": 1691, + "kind": "call", + "name": "self.render_strips" + }, + { + "path": "src/textual/_doc.py", + "line": 138, + "kind": "call", + "name": "app.export_screenshot" + }, + { + "path": "src/textual/app.py", + "line": 1865, + "kind": "declaration", + "name": "export_screenshot" + }, + { + "path": "src/textual/app.py", + "line": 1893, + "kind": "call", + "name": "self.screen._compositor.render_update" + }, + { + "path": "src/textual/app.py", + "line": 1923, + "kind": "call", + "name": "self.export_screenshot" + }, + { + "path": "src/textual/app.py", + "line": 1954, + "kind": "call", + "name": "self.export_screenshot" + }, + { + "path": "src/textual/content.py", + "line": 728, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/renderables/blank.py", + "line": 27, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/screen.py", + "line": 1198, + "kind": "declaration", + "name": "_prepare_compositor_refresh" + }, + { + "path": "src/textual/screen.py", + "line": 1207, + "kind": "declaration", + "name": "_compositor_refresh" + }, + { + "path": "src/textual/screen.py", + "line": 628, + "kind": "call", + "name": "BackgroundScreen" + }, + { + "path": "src/textual/screen.py", + "line": 1213, + "kind": "call", + "name": "self._prepare_compositor_refresh" + }, + { + "path": "src/textual/screen.py", + "line": 1438, + "kind": "call", + "name": "self._compositor_refresh" + }, + { + "path": "src/textual/screen.py", + "line": 1245, + "kind": "call", + "name": "self._compositor.render_update" + }, + { + "path": "src/textual/screen.py", + "line": 1282, + "kind": "call", + "name": "self._compositor_refresh" + }, + { + "path": "src/textual/screen.py", + "line": 1226, + "kind": "call", + "name": "self._compositor.render_inline" + }, + { + "path": "src/textual/visual.py", + "line": 131, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/visual.py", + "line": 324, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/visual.py", + "line": 391, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/textual/visual.py", + "line": 227, + "kind": "call", + "name": "visual.render_strips" + }, + { + "path": "src/textual/visual.py", + "line": 411, + "kind": "call", + "name": "self._visual.render_strips" + } + ], + "limits": "Attribute syntax does not prove dynamic receiver identity. Screen/WorkspaceScreen overrides and BackgroundScreen rich rendering read semantically; diagnostic export is a separate observation path." + }, + { + "repo": "/home/ts/wt/toad-viewport-raster-cpu-continuation-20261001", + "root": "src/toad", + "rev": "4fca9c104620d662bfee674c33f881ca54e43c3e", + "parsed": 288, + "omitted": [], + "sites": [ + { + "path": "src/toad/screens/workspace.py", + "line": 165, + "kind": "declaration", + "name": "_prepare_compositor_refresh" + }, + { + "path": "src/toad/visuals/columns.py", + "line": 44, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/toad/widgets/patch_diff.py", + "line": 245, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/toad/widgets/patch_diff.py", + "line": 339, + "kind": "call", + "name": "row.render_strips" + }, + { + "path": "src/toad/widgets/patch_diff.py", + "line": 367, + "kind": "call", + "name": "_DiffRow(self.source, y).render_strips" + }, + { + "path": "src/toad/widgets/path_search.py", + "line": 46, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/toad/widgets/prepared_markdown.py", + "line": 144, + "kind": "declaration", + "name": "render_strips" + }, + { + "path": "src/toad/widgets/prepared_markdown.py", + "line": 155, + "kind": "call", + "name": "self.content.render_strips" + }, + { + "path": "src/toad/widgets/throbber.py", + "line": 63, + "kind": "declaration", + "name": "render_strips" + } + ], + "limits": "Attribute syntax does not prove dynamic receiver identity. Screen/WorkspaceScreen overrides and BackgroundScreen rich rendering read semantically; diagnostic export is a separate observation path." + } +] diff --git a/evidence/markdown-publication-custody-20261004/interaction-acquisition-before.json b/evidence/markdown-publication-custody-20261004/interaction-acquisition-before.json new file mode 100644 index 0000000000..6b36fac699 --- /dev/null +++ b/evidence/markdown-publication-custody-20261004/interaction-acquisition-before.json @@ -0,0 +1,49 @@ +[ + { + "repo": "/home/ts/wt/textual-native-subtree-strips-20261002", + "rev": "fac099eb63d44c0e0bcda3435c43fbc76936e51f", + "root": "src/textual", + "parsed": 249, + "omitted": [], + "sites": [ + [ + "src/textual/screen.py", + 2091, + "declaration", + "_collect_select_widgets" + ] + ] + }, + { + "repo": "/home/ts/wt/textual-native-subtree-strips-20261002", + "rev": "fac099eb63d44c0e0bcda3435c43fbc76936e51f", + "root": "tests", + "parsed": 460, + "omitted": [], + "sites": [] + }, + { + "repo": "/home/ts/wt/toad-viewport-raster-cpu-continuation-20261001", + "rev": "7c3b6101af473c4638e4476d27207bf5b5196026", + "root": "src/toad", + "parsed": 288, + "omitted": [], + "sites": [] + }, + { + "repo": "/home/ts/wt/toad-viewport-raster-cpu-continuation-20261001", + "rev": "7c3b6101af473c4638e4476d27207bf5b5196026", + "root": "tests", + "parsed": 394, + "omitted": [], + "sites": [] + }, + { + "repo": "/home/ts/wt/toad-viewport-raster-cpu-continuation-20261001", + "rev": "7c3b6101af473c4638e4476d27207bf5b5196026", + "root": "tools", + "parsed": 41, + "omitted": [], + "sites": [] + } +] diff --git a/evidence/markdown-publication-custody-20261004/interaction-after.json b/evidence/markdown-publication-custody-20261004/interaction-after.json new file mode 100644 index 0000000000..0dd9b14371 --- /dev/null +++ b/evidence/markdown-publication-custody-20261004/interaction-after.json @@ -0,0 +1,846 @@ +[ + { + "repo": "/home/ts/wt/textual-native-subtree-strips-20261002", + "head": "64c0f1b0a113d398307ccf0ccb434836a0b92182", + "parsed": 249, + "omitted": [], + "sites": [ + { + "path": "src/textual/_compositor.py", + "line": 226, + "kind": "declaration", + "name": "references_retired" + }, + { + "path": "src/textual/_compositor.py", + "line": 516, + "kind": "declaration", + "name": "_invalidate_render_projection" + }, + { + "path": "src/textual/_compositor.py", + "line": 533, + "kind": "declaration", + "name": "discard_widgets" + }, + { + "path": "src/textual/_compositor.py", + "line": 528, + "kind": "Load", + "name": "self._invalidate_render_projection" + }, + { + "path": "src/textual/_compositor.py", + "line": 621, + "kind": "Load", + "name": "self._invalidate_render_projection" + }, + { + "path": "src/textual/_compositor.py", + "line": 650, + "kind": "Load", + "name": "self._invalidate_render_projection" + }, + { + "path": "src/textual/_compositor.py", + "line": 675, + "kind": "Load", + "name": "self._invalidate_render_projection" + }, + { + "path": "src/textual/_compositor.py", + "line": 688, + "kind": "Load", + "name": "self._invalidate_render_projection" + }, + { + "path": "src/textual/_compositor.py", + "line": 544, + "kind": "Load", + "name": "resource.references_retired" + }, + { + "path": "src/textual/_compositor.py", + "line": 558, + "kind": "Load", + "name": "self._invalidate_render_projection" + }, + { + "path": "src/textual/_compositor.py", + "line": 744, + "kind": "Load", + "name": "self._invalidate_render_projection" + }, + { + "path": "src/textual/app.py", + "line": 3232, + "kind": "declaration", + "name": "_retire_pointer_widgets" + }, + { + "path": "src/textual/app.py", + "line": 652, + "kind": "Store", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 654, + "kind": "Store", + "name": "self.hover_over" + }, + { + "path": "src/textual/app.py", + "line": 656, + "kind": "Store", + "name": "self.mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3193, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/textual/app.py", + "line": 3203, + "kind": "Store", + "name": "self.hover_over" + }, + { + "path": "src/textual/app.py", + "line": 3255, + "kind": "Store", + "name": "self.mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3234, + "kind": "Load", + "name": "self.mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3251, + "kind": "Load", + "name": "self.mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3253, + "kind": "Load", + "name": "self.mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3178, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3184, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3213, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3236, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3236, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/textual/app.py", + "line": 3182, + "kind": "Store", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3191, + "kind": "Store", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3238, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3239, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/textual/app.py", + "line": 3254, + "kind": "Load", + "name": "self.mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3186, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3225, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3226, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/textual/app.py", + "line": 3238, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3239, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/textual/app.py", + "line": 3180, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3180, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3187, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3187, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/await_remove.py", + "line": 70, + "kind": "Load", + "name": "screen._forget_pruned_widgets" + }, + { + "path": "src/textual/screen.py", + "line": 1488, + "kind": "declaration", + "name": "_forget_pruned_widgets" + }, + { + "path": "src/textual/screen.py", + "line": 1494, + "kind": "declaration", + "name": "_retire_selection" + }, + { + "path": "src/textual/screen.py", + "line": 1507, + "kind": "declaration", + "name": "_forget_widget_geometry" + }, + { + "path": "src/textual/screen.py", + "line": 2166, + "kind": "declaration", + "name": "_flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 2172, + "kind": "declaration", + "name": "_apply_selection_state" + }, + { + "path": "src/textual/screen.py", + "line": 599, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/screen.py", + "line": 770, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 771, + "kind": "Store", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 780, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 1496, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 2174, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/screen.py", + "line": 2187, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 602, + "kind": "Load", + "name": "self.app.mouse_over" + }, + { + "path": "src/textual/screen.py", + "line": 983, + "kind": "Load", + "name": "self._flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 984, + "kind": "Load", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 1490, + "kind": "Load", + "name": "self._forget_widget_geometry" + }, + { + "path": "src/textual/screen.py", + "line": 1491, + "kind": "Load", + "name": "self._retire_selection" + }, + { + "path": "src/textual/screen.py", + "line": 1492, + "kind": "Load", + "name": "self.app._retire_pointer_widgets" + }, + { + "path": "src/textual/screen.py", + "line": 1505, + "kind": "Store", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 1509, + "kind": "Load", + "name": "self._compositor.discard_widgets" + }, + { + "path": "src/textual/screen.py", + "line": 1757, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 1947, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 2164, + "kind": "Load", + "name": "self._apply_selection_state" + }, + { + "path": "src/textual/screen.py", + "line": 2182, + "kind": "Load", + "name": "select_state.is_attached_to_dom" + }, + { + "path": "src/textual/screen.py", + "line": 2184, + "kind": "Store", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 2187, + "kind": "Load", + "name": "select_state.selections" + }, + { + "path": "src/textual/screen.py", + "line": 98, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/screen.py", + "line": 602, + "kind": "Load", + "name": "self.app.mouse_over" + }, + { + "path": "src/textual/screen.py", + "line": 988, + "kind": "Load", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 1264, + "kind": "Load", + "name": "self._flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 1502, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 1503, + "kind": "Load", + "name": "self._select_state.references_retired" + }, + { + "path": "src/textual/screen.py", + "line": 1758, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 1849, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 2170, + "kind": "Load", + "name": "self._apply_selection_state" + }, + { + "path": "src/textual/screen.py", + "line": 2170, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 99, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/textual/screen.py", + "line": 99, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/textual/screen.py", + "line": 1503, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 1961, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/screen.py", + "line": 1498, + "kind": "Load", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 1961, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 1980, + "kind": "Store", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 2012, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/screen.py", + "line": 2055, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2160, + "kind": "Load", + "name": "self._flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 1996, + "kind": "Load", + "name": "self._flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 2056, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 1980, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 2015, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2040, + "kind": "Store", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 2052, + "kind": "Store", + "name": "self._select_state" + }, + { + "path": "src/textual/scrollbar.py", + "line": 287, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/scrollbar.py", + "line": 332, + "kind": "Store", + "name": "self.mouse_over" + }, + { + "path": "src/textual/scrollbar.py", + "line": 336, + "kind": "Store", + "name": "self.mouse_over" + }, + { + "path": "src/textual/selection.py", + "line": 171, + "kind": "declaration", + "name": "_walk_involved_widgets" + }, + { + "path": "src/textual/selection.py", + "line": 180, + "kind": "declaration", + "name": "is_attached_to_dom" + }, + { + "path": "src/textual/selection.py", + "line": 184, + "kind": "declaration", + "name": "references_retired" + }, + { + "path": "src/textual/selection.py", + "line": 188, + "kind": "declaration", + "name": "selections" + }, + { + "path": "src/textual/selection.py", + "line": 182, + "kind": "Load", + "name": "self._walk_involved_widgets" + }, + { + "path": "src/textual/selection.py", + "line": 186, + "kind": "Load", + "name": "self._walk_involved_widgets" + }, + { + "path": "src/textual/visual.py", + "line": 241, + "kind": "Load", + "name": "widget.screen._selecting" + }, + { + "path": "src/textual/widget.py", + "line": 1763, + "kind": "declaration", + "name": "reparent" + }, + { + "path": "src/textual/widget.py", + "line": 1802, + "kind": "Load", + "name": "app.mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 1818, + "kind": "Load", + "name": "old_screen._forget_widget_geometry" + }, + { + "path": "src/textual/widget.py", + "line": 4899, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 774, + "kind": "Load", + "name": "self.screen.selections" + }, + { + "path": "src/textual/widget.py", + "line": 1810, + "kind": "Load", + "name": "old_screen._retire_selection" + }, + { + "path": "src/textual/widget.py", + "line": 1830, + "kind": "Store", + "name": "new_screen.selections" + }, + { + "path": "src/textual/widget.py", + "line": 2163, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 4332, + "kind": "Load", + "name": "self.app.mouse_over" + }, + { + "path": "src/textual/widget.py", + "line": 1830, + "kind": "Load", + "name": "new_screen.selections" + }, + { + "path": "src/textual/widget.py", + "line": 4334, + "kind": "Load", + "name": "self.app.mouse_over" + }, + { + "path": "src/textual/widget.py", + "line": 1806, + "kind": "Load", + "name": "old_screen.selections" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 454, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 790, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 795, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 807, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 796, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 674, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2001, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2010, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2017, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2018, + "kind": "Store", + "name": "self._selecting" + } + ] + }, + { + "repo": "/home/ts/wt/toad-viewport-raster-cpu-continuation-20261001", + "head": "7c3b6101af473c4638e4476d27207bf5b5196026", + "parsed": 288, + "omitted": [], + "sites": [ + { + "path": "src/toad/acp/agent_configuration.py", + "line": 101, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/toad/acp/agent_configuration.py", + "line": 118, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/toad/acp/agent_configuration.py", + "line": 140, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/toad/acp/agent_configuration.py", + "line": 122, + "kind": "Load", + "name": "self.selections" + }, + { + "path": "src/toad/acp/agent_configuration.py", + "line": 107, + "kind": "Load", + "name": "self.selections" + }, + { + "path": "src/toad/app.py", + "line": 628, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/toad/app.py", + "line": 628, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/toad/tool_output.py", + "line": 120, + "kind": "Load", + "name": "widget.screen._select_state" + }, + { + "path": "src/toad/widgets/committed_presentation.py", + "line": 189, + "kind": "Load", + "name": "view.screen.selections" + }, + { + "path": "src/toad/widgets/history_anchor.py", + "line": 256, + "kind": "Load", + "name": "self.screen.selections" + }, + { + "path": "src/toad/widgets/patch_diff.py", + "line": 327, + "kind": "Load", + "name": "code.screen._selecting" + }, + { + "path": "src/toad/widgets/viewport_body.py", + "line": 1064, + "kind": "Load", + "name": "screen.selections" + }, + { + "path": "src/toad/widgets/viewport_body.py", + "line": 642, + "kind": "Load", + "name": "self.screen.selections" + } + ] + } +] diff --git a/evidence/markdown-publication-custody-20261004/interaction-before.json b/evidence/markdown-publication-custody-20261004/interaction-before.json new file mode 100644 index 0000000000..8f63550d94 --- /dev/null +++ b/evidence/markdown-publication-custody-20261004/interaction-before.json @@ -0,0 +1,654 @@ +[ + { + "repo": "/home/ts/wt/textual-native-subtree-strips-20261002", + "head": "3ca202dbe880c05f08403d41b21124956b0a9d47", + "parsed": 249, + "omitted": [], + "sites": [ + { + "path": "src/textual/_compositor.py", + "line": 533, + "kind": "declaration", + "name": "discard_widgets" + }, + { + "path": "src/textual/app.py", + "line": 652, + "kind": "Store", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 654, + "kind": "Store", + "name": "self.hover_over" + }, + { + "path": "src/textual/app.py", + "line": 656, + "kind": "Store", + "name": "self.mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3193, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/textual/app.py", + "line": 3203, + "kind": "Store", + "name": "self.hover_over" + }, + { + "path": "src/textual/app.py", + "line": 3245, + "kind": "Store", + "name": "self.mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3241, + "kind": "Load", + "name": "self.mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3243, + "kind": "Load", + "name": "self.mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3178, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3184, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3213, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3182, + "kind": "Store", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3191, + "kind": "Store", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3244, + "kind": "Load", + "name": "self.mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3186, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3225, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3226, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/textual/app.py", + "line": 3180, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3180, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3187, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/app.py", + "line": 3187, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/await_remove.py", + "line": 70, + "kind": "Load", + "name": "screen._forget_pruned_widgets" + }, + { + "path": "src/textual/screen.py", + "line": 1488, + "kind": "declaration", + "name": "_forget_pruned_widgets" + }, + { + "path": "src/textual/screen.py", + "line": 2146, + "kind": "declaration", + "name": "_flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 2152, + "kind": "declaration", + "name": "_apply_selection_state" + }, + { + "path": "src/textual/screen.py", + "line": 599, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/screen.py", + "line": 770, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 771, + "kind": "Store", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 780, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 2190, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 602, + "kind": "Load", + "name": "self.app.mouse_over" + }, + { + "path": "src/textual/screen.py", + "line": 983, + "kind": "Load", + "name": "self._flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 984, + "kind": "Load", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 1490, + "kind": "Load", + "name": "self._compositor.discard_widgets" + }, + { + "path": "src/textual/screen.py", + "line": 1737, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 1927, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 2144, + "kind": "Load", + "name": "self._apply_selection_state" + }, + { + "path": "src/textual/screen.py", + "line": 2156, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/screen.py", + "line": 2159, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/screen.py", + "line": 2165, + "kind": "Load", + "name": "select_state.is_attached_to_dom" + }, + { + "path": "src/textual/screen.py", + "line": 2167, + "kind": "Store", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 2174, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 98, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/screen.py", + "line": 602, + "kind": "Load", + "name": "self.app.mouse_over" + }, + { + "path": "src/textual/screen.py", + "line": 988, + "kind": "Load", + "name": "self.selections" + }, + { + "path": "src/textual/screen.py", + "line": 1264, + "kind": "Load", + "name": "self._flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 1738, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 1829, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 2150, + "kind": "Load", + "name": "self._apply_selection_state" + }, + { + "path": "src/textual/screen.py", + "line": 2150, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 99, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/textual/screen.py", + "line": 99, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/textual/screen.py", + "line": 1941, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/screen.py", + "line": 1941, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 1960, + "kind": "Store", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 1992, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/screen.py", + "line": 2035, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2140, + "kind": "Load", + "name": "self._flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 1976, + "kind": "Load", + "name": "self._flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 2036, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 1960, + "kind": "Load", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 1995, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2020, + "kind": "Store", + "name": "self._select_state" + }, + { + "path": "src/textual/screen.py", + "line": 2032, + "kind": "Store", + "name": "self._select_state" + }, + { + "path": "src/textual/scrollbar.py", + "line": 287, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/textual/scrollbar.py", + "line": 332, + "kind": "Store", + "name": "self.mouse_over" + }, + { + "path": "src/textual/scrollbar.py", + "line": 336, + "kind": "Store", + "name": "self.mouse_over" + }, + { + "path": "src/textual/selection.py", + "line": 171, + "kind": "declaration", + "name": "is_attached_to_dom" + }, + { + "path": "src/textual/visual.py", + "line": 241, + "kind": "Load", + "name": "widget.screen._selecting" + }, + { + "path": "src/textual/widget.py", + "line": 1763, + "kind": "declaration", + "name": "reparent" + }, + { + "path": "src/textual/widget.py", + "line": 1802, + "kind": "Load", + "name": "app.mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 1810, + "kind": "Store", + "name": "old_screen.selections" + }, + { + "path": "src/textual/widget.py", + "line": 1819, + "kind": "Load", + "name": "old_screen._forget_pruned_widgets" + }, + { + "path": "src/textual/widget.py", + "line": 4900, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 774, + "kind": "Load", + "name": "self.screen.selections" + }, + { + "path": "src/textual/widget.py", + "line": 1831, + "kind": "Store", + "name": "new_screen.selections" + }, + { + "path": "src/textual/widget.py", + "line": 2164, + "kind": "Load", + "name": "self.app.mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 4333, + "kind": "Load", + "name": "self.app.mouse_over" + }, + { + "path": "src/textual/widget.py", + "line": 1831, + "kind": "Load", + "name": "new_screen.selections" + }, + { + "path": "src/textual/widget.py", + "line": 4335, + "kind": "Load", + "name": "self.app.mouse_over" + }, + { + "path": "src/textual/widget.py", + "line": 1806, + "kind": "Load", + "name": "old_screen.selections" + }, + { + "path": "src/textual/widget.py", + "line": 1810, + "kind": "Load", + "name": "old_screen.selections" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 454, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 790, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 795, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 807, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 796, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 674, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2001, + "kind": "Store", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2010, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2017, + "kind": "Load", + "name": "self._selecting" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2018, + "kind": "Store", + "name": "self._selecting" + } + ] + }, + { + "repo": "/home/ts/wt/toad-viewport-raster-cpu-continuation-20261001", + "head": "7c3b6101af473c4638e4476d27207bf5b5196026", + "parsed": 288, + "omitted": [], + "sites": [ + { + "path": "src/toad/acp/agent_configuration.py", + "line": 101, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/toad/acp/agent_configuration.py", + "line": 118, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/toad/acp/agent_configuration.py", + "line": 140, + "kind": "Store", + "name": "self.selections" + }, + { + "path": "src/toad/acp/agent_configuration.py", + "line": 122, + "kind": "Load", + "name": "self.selections" + }, + { + "path": "src/toad/acp/agent_configuration.py", + "line": 107, + "kind": "Load", + "name": "self.selections" + }, + { + "path": "src/toad/app.py", + "line": 628, + "kind": "Load", + "name": "self.mouse_over" + }, + { + "path": "src/toad/app.py", + "line": 628, + "kind": "Load", + "name": "self.hover_over" + }, + { + "path": "src/toad/tool_output.py", + "line": 120, + "kind": "Load", + "name": "widget.screen._select_state" + }, + { + "path": "src/toad/widgets/committed_presentation.py", + "line": 189, + "kind": "Load", + "name": "view.screen.selections" + }, + { + "path": "src/toad/widgets/history_anchor.py", + "line": 256, + "kind": "Load", + "name": "self.screen.selections" + }, + { + "path": "src/toad/widgets/patch_diff.py", + "line": 327, + "kind": "Load", + "name": "code.screen._selecting" + }, + { + "path": "src/toad/widgets/viewport_body.py", + "line": 1064, + "kind": "Load", + "name": "screen.selections" + }, + { + "path": "src/toad/widgets/viewport_body.py", + "line": 642, + "kind": "Load", + "name": "self.screen.selections" + } + ] + } +] diff --git a/evidence/markdown-publication-custody-20261004/prune-after.json b/evidence/markdown-publication-custody-20261004/prune-after.json new file mode 100644 index 0000000000..e74fe489d6 --- /dev/null +++ b/evidence/markdown-publication-custody-20261004/prune-after.json @@ -0,0 +1,720 @@ +[ + { + "repo": "/home/ts/wt/textual-native-subtree-strips-20261002", + "head": "3732047fad8d9af0e1d981ece2618d2881d8f45b", + "parsed": 249, + "omitted": [], + "sites": [ + { + "path": "src/textual/_compositor.py", + "line": 226, + "kind": "declaration", + "name": "references_retired" + }, + { + "path": "src/textual/_compositor.py", + "line": 529, + "kind": "declaration", + "name": "discard_widgets" + }, + { + "path": "src/textual/_compositor.py", + "line": 540, + "kind": "call", + "name": "resource.references_retired" + }, + { + "path": "src/textual/_compositor.py", + "line": 537, + "kind": "call", + "name": "owner._get_virtual_dom" + }, + { + "path": "src/textual/_node_list.py", + "line": 211, + "kind": "call", + "name": "self._nodes_set.remove" + }, + { + "path": "src/textual/app.py", + "line": 4556, + "kind": "call", + "name": "self.screen.query('HelpPanel').remove" + }, + { + "path": "src/textual/app.py", + "line": 2879, + "kind": "call", + "name": "screen.remove" + }, + { + "path": "src/textual/app.py", + "line": 3584, + "kind": "call", + "name": "self.screen.query('*').exclude('.-textual-system').remove" + }, + { + "path": "src/textual/app.py", + "line": 3758, + "kind": "call", + "name": "AwaitRemove.prune" + }, + { + "path": "src/textual/await_remove.py", + "line": 39, + "kind": "declaration", + "name": "prune" + }, + { + "path": "src/textual/await_remove.py", + "line": 70, + "kind": "call", + "name": "screen._forget_pruned_widgets" + }, + { + "path": "src/textual/css/_styles_builder.py", + "line": 282, + "kind": "call", + "name": "self.styles.important.remove" + }, + { + "path": "src/textual/css/query.py", + "line": 407, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/textual/css/query.py", + "line": 413, + "kind": "call", + "name": "AwaitRemove.prune" + }, + { + "path": "src/textual/css/stylesheet.py", + "line": 1006, + "kind": "call", + "name": "node._get_virtual_dom" + }, + { + "path": "src/textual/dom.py", + "line": 157, + "kind": "declaration", + "name": "_get_virtual_dom" + }, + { + "path": "src/textual/dom.py", + "line": 168, + "kind": "call", + "name": "self._get_virtual_dom" + }, + { + "path": "src/textual/driver.py", + "line": 103, + "kind": "call", + "name": "self._down_buttons.remove" + }, + { + "path": "src/textual/eta.py", + "line": 64, + "kind": "declaration", + "name": "_prune" + }, + { + "path": "src/textual/eta.py", + "line": 62, + "kind": "call", + "name": "self._prune" + }, + { + "path": "src/textual/lazy.py", + "line": 139, + "kind": "call", + "name": "self.remove" + }, + { + "path": "src/textual/lazy.py", + "line": 61, + "kind": "call", + "name": "self.remove" + }, + { + "path": "src/textual/message_pump.py", + "line": 626, + "kind": "declaration", + "name": "_message_loop_exit" + }, + { + "path": "src/textual/message_pump.py", + "line": 616, + "kind": "call", + "name": "self._message_loop_exit" + }, + { + "path": "src/textual/screen.py", + "line": 1334, + "kind": "declaration", + "name": "_message_loop_exit" + }, + { + "path": "src/textual/screen.py", + "line": 1488, + "kind": "declaration", + "name": "_forget_pruned_widgets" + }, + { + "path": "src/textual/screen.py", + "line": 1490, + "kind": "call", + "name": "self._compositor.discard_widgets" + }, + { + "path": "src/textual/screen.py", + "line": 1335, + "kind": "call", + "name": "super()._message_loop_exit" + }, + { + "path": "src/textual/widget.py", + "line": 1456, + "kind": "declaration", + "name": "_get_virtual_dom" + }, + { + "path": "src/textual/widget.py", + "line": 1763, + "kind": "declaration", + "name": "reparent" + }, + { + "path": "src/textual/widget.py", + "line": 4617, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/textual/widget.py", + "line": 4626, + "kind": "declaration", + "name": "remove_children" + }, + { + "path": "src/textual/widget.py", + "line": 4753, + "kind": "declaration", + "name": "_message_loop_exit" + }, + { + "path": "src/textual/widget.py", + "line": 1819, + "kind": "call", + "name": "old_screen._forget_pruned_widgets" + }, + { + "path": "src/textual/widget.py", + "line": 4623, + "kind": "call", + "name": "AwaitRemove.prune" + }, + { + "path": "src/textual/widget.py", + "line": 4650, + "kind": "call", + "name": "AwaitRemove.prune" + }, + { + "path": "src/textual/widget.py", + "line": 634, + "kind": "call", + "name": "siblings.remove" + }, + { + "path": "src/textual/widget.py", + "line": 879, + "kind": "call", + "name": "self._cover_widget.remove" + }, + { + "path": "src/textual/widget.py", + "line": 1800, + "kind": "call", + "name": "node._get_virtual_dom" + }, + { + "path": "src/textual/widget.py", + "line": 1869, + "kind": "call", + "name": "self.query_children('*').exclude('.-textual-system').remove" + }, + { + "path": "src/textual/widget.py", + "line": 4761, + "kind": "call", + "name": "self._get_virtual_dom" + }, + { + "path": "src/textual/widgets/_directory_tree.py", + "line": 227, + "kind": "call", + "name": "node.remove_children" + }, + { + "path": "src/textual/widgets/_directory_tree.py", + "line": 483, + "kind": "call", + "name": "node.remove_children" + }, + { + "path": "src/textual/widgets/_footer.py", + "line": 436, + "kind": "call", + "name": "parent.remove_children" + }, + { + "path": "src/textual/widgets/_list_view.py", + "line": 268, + "kind": "call", + "name": "self.query('ListView > ListItem').remove" + }, + { + "path": "src/textual/widgets/_list_view.py", + "line": 306, + "kind": "call", + "name": "item_to_remove.remove" + }, + { + "path": "src/textual/widgets/_list_view.py", + "line": 338, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 283, + "kind": "call", + "name": "self.remove" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 714, + "kind": "call", + "name": "self.query_children(f'.cell.row{self.last_row}').remove" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 1473, + "kind": "call", + "name": "markdown_block.remove" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 1461, + "kind": "call", + "name": "markdown_block.remove" + }, + { + "path": "src/textual/widgets/_tabbed_content.py", + "line": 488, + "kind": "call", + "name": "self.get_child_by_type(ContentSwitcher).remove_children" + }, + { + "path": "src/textual/widgets/_tabbed_content.py", + "line": 468, + "kind": "call", + "name": "self.get_child_by_type(ContentSwitcher).get_child_by_id(pane_id).remove" + }, + { + "path": "src/textual/widgets/_tabs.py", + "line": 523, + "kind": "call", + "name": "self.query('#tabs-list > Tab').remove" + }, + { + "path": "src/textual/widgets/_tabs.py", + "line": 567, + "kind": "call", + "name": "remove_tab.remove" + }, + { + "path": "src/textual/widgets/_toast.py", + "line": 143, + "kind": "call", + "name": "(self.parent if isinstance(self.parent, ToastHolder) else self).remove" + }, + { + "path": "src/textual/widgets/_toast.py", + "line": 187, + "kind": "call", + "name": "toast.remove" + }, + { + "path": "src/textual/widgets/_tree.py", + "line": 495, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/textual/widgets/_tree.py", + "line": 506, + "kind": "declaration", + "name": "remove_children" + } + ] + }, + { + "repo": "/home/ts/wt/toad-viewport-raster-cpu-continuation-20261001", + "head": "50051f42de9ba902fa6f07082c874c1cb6942e7b", + "parsed": 288, + "omitted": [], + "sites": [ + { + "path": "src/toad/agent_surface.py", + "line": 166, + "kind": "call", + "name": "existing.remove" + }, + { + "path": "src/toad/agent_surface.py", + "line": 172, + "kind": "call", + "name": "terminal.remove" + }, + { + "path": "src/toad/cli.py", + "line": 203, + "kind": "call", + "name": "command_args.remove" + }, + { + "path": "src/toad/conversation_submission.py", + "line": 272, + "kind": "call", + "name": "self.active.remove" + }, + { + "path": "src/toad/live_output.py", + "line": 102, + "kind": "call", + "name": "block.remove" + }, + { + "path": "src/toad/live_output.py", + "line": 118, + "kind": "call", + "name": "self.block.remove" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 143, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 173, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 227, + "kind": "call", + "name": "widget.remove" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 241, + "kind": "call", + "name": "widget.remove" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 309, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 321, + "kind": "call", + "name": "loading.remove" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 401, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/screens/agent_modal.py", + "line": 87, + "kind": "call", + "name": "launcher_agents.remove" + }, + { + "path": "src/toad/screens/comms.py", + "line": 207, + "kind": "call", + "name": "content.remove_children" + }, + { + "path": "src/toad/screens/historical_sessions.py", + "line": 100, + "kind": "call", + "name": "content.remove_children" + }, + { + "path": "src/toad/screens/main.py", + "line": 189, + "kind": "call", + "name": "self.query('#session-opening').remove" + }, + { + "path": "src/toad/screens/workspace.py", + "line": 126, + "kind": "declaration", + "name": "_message_loop_exit" + }, + { + "path": "src/toad/screens/workspace.py", + "line": 128, + "kind": "call", + "name": "super()._message_loop_exit" + }, + { + "path": "src/toad/screens/workspace.py", + "line": 323, + "kind": "call", + "name": "node._get_virtual_dom" + }, + { + "path": "src/toad/session_presentation.py", + "line": 177, + "kind": "call", + "name": "conversation.remove" + }, + { + "path": "src/toad/shell.py", + "line": 206, + "kind": "call", + "name": "self.outputs.remove" + }, + { + "path": "src/toad/sidebar_projection.py", + "line": 121, + "kind": "call", + "name": "row.query_ancestor(ChannelGroup).remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 56, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 195, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 284, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 205, + "kind": "call", + "name": "filtering.remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 287, + "kind": "call", + "name": "retired.remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 197, + "kind": "call", + "name": "self.view.remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 292, + "kind": "call", + "name": "retired.remove" + }, + { + "path": "src/toad/transcript_publication.py", + "line": 576, + "kind": "call", + "name": "loading.remove" + }, + { + "path": "src/toad/transcript_publication.py", + "line": 783, + "kind": "call", + "name": "contents.remove_children" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 890, + "kind": "call", + "name": "self._focusable_terminals.remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1064, + "kind": "call", + "name": "self.query(ThreadLoading).remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1295, + "kind": "call", + "name": "event.menu.remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 604, + "kind": "call", + "name": "self.query(ThreadLoading).remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1255, + "kind": "call", + "name": "pending_loading.remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1337, + "kind": "call", + "name": "self._mcp_live_note.remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1891, + "kind": "call", + "name": "pending_loading.remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1969, + "kind": "call", + "name": "contents.remove_children" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1987, + "kind": "call", + "name": "terminal.remove" + }, + { + "path": "src/toad/widgets/patch_diff.py", + "line": 153, + "kind": "call", + "name": "active.remove" + }, + { + "path": "src/toad/widgets/project_panel.py", + "line": 195, + "kind": "call", + "name": "self.query_one('#file-preview-loading', Static).remove" + }, + { + "path": "src/toad/widgets/retiring_sidebar.py", + "line": 39, + "kind": "call", + "name": "viewport.remove_children" + }, + { + "path": "src/toad/widgets/retiring_sidebar.py", + "line": 41, + "kind": "call", + "name": "controls.remove_children" + }, + { + "path": "src/toad/widgets/session_tabs.py", + "line": 303, + "kind": "call", + "name": "self.title_container.remove_children" + }, + { + "path": "src/toad/widgets/sidebar_tree.py", + "line": 175, + "kind": "call", + "name": "self.member_container.remove_children" + }, + { + "path": "src/toad/widgets/streaming_markdown.py", + "line": 159, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/widgets/thread_comms.py", + "line": 159, + "kind": "call", + "name": "empty.remove" + }, + { + "path": "src/toad/widgets/tool_call.py", + "line": 81, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 344, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 667, + "kind": "call", + "name": "self.filter.remove" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 821, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 823, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 952, + "kind": "call", + "name": "evicted.remove" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 377, + "kind": "call", + "name": "child.remove" + }, + { + "path": "src/toad/widgets/viewport_body.py", + "line": 687, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/workspace_sessions.py", + "line": 182, + "kind": "call", + "name": "view.remove" + } + ] + } +] diff --git a/evidence/markdown-publication-custody-20261004/prune-before.json b/evidence/markdown-publication-custody-20261004/prune-before.json new file mode 100644 index 0000000000..07fd16c0da --- /dev/null +++ b/evidence/markdown-publication-custody-20261004/prune-before.json @@ -0,0 +1,716 @@ +[ + { + "repo": "/home/ts/wt/textual-native-subtree-strips-20261002", + "head": "f66ebb1635cc40a43616eedca643071370ab6428", + "parsed": 249, + "omitted": [], + "sites": [ + { + "path": "src/textual/_compositor.py", + "line": 226, + "kind": "declaration", + "name": "references_retired" + }, + { + "path": "src/textual/_compositor.py", + "line": 529, + "kind": "declaration", + "name": "discard_widgets" + }, + { + "path": "src/textual/_compositor.py", + "line": 537, + "kind": "call", + "name": "resource.references_retired" + }, + { + "path": "src/textual/_node_list.py", + "line": 211, + "kind": "call", + "name": "self._nodes_set.remove" + }, + { + "path": "src/textual/app.py", + "line": 4404, + "kind": "declaration", + "name": "_prune" + }, + { + "path": "src/textual/app.py", + "line": 4608, + "kind": "call", + "name": "self.screen.query('HelpPanel').remove" + }, + { + "path": "src/textual/app.py", + "line": 2879, + "kind": "call", + "name": "screen.remove" + }, + { + "path": "src/textual/app.py", + "line": 4436, + "kind": "call", + "name": "pruning_screen._forget_pruned_widgets" + }, + { + "path": "src/textual/app.py", + "line": 3584, + "kind": "call", + "name": "self.screen.query('*').exclude('.-textual-system').remove" + }, + { + "path": "src/textual/app.py", + "line": 3758, + "kind": "call", + "name": "self._prune" + }, + { + "path": "src/textual/css/_styles_builder.py", + "line": 282, + "kind": "call", + "name": "self.styles.important.remove" + }, + { + "path": "src/textual/css/query.py", + "line": 408, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/textual/css/query.py", + "line": 415, + "kind": "call", + "name": "app._prune" + }, + { + "path": "src/textual/css/stylesheet.py", + "line": 1006, + "kind": "call", + "name": "node._get_virtual_dom" + }, + { + "path": "src/textual/dom.py", + "line": 157, + "kind": "declaration", + "name": "_get_virtual_dom" + }, + { + "path": "src/textual/dom.py", + "line": 168, + "kind": "call", + "name": "self._get_virtual_dom" + }, + { + "path": "src/textual/driver.py", + "line": 103, + "kind": "call", + "name": "self._down_buttons.remove" + }, + { + "path": "src/textual/eta.py", + "line": 64, + "kind": "declaration", + "name": "_prune" + }, + { + "path": "src/textual/eta.py", + "line": 62, + "kind": "call", + "name": "self._prune" + }, + { + "path": "src/textual/lazy.py", + "line": 139, + "kind": "call", + "name": "self.remove" + }, + { + "path": "src/textual/lazy.py", + "line": 61, + "kind": "call", + "name": "self.remove" + }, + { + "path": "src/textual/message_pump.py", + "line": 626, + "kind": "declaration", + "name": "_message_loop_exit" + }, + { + "path": "src/textual/message_pump.py", + "line": 616, + "kind": "call", + "name": "self._message_loop_exit" + }, + { + "path": "src/textual/screen.py", + "line": 1334, + "kind": "declaration", + "name": "_message_loop_exit" + }, + { + "path": "src/textual/screen.py", + "line": 1488, + "kind": "declaration", + "name": "_forget_pruned_widgets" + }, + { + "path": "src/textual/screen.py", + "line": 1490, + "kind": "call", + "name": "self._compositor.discard_widgets" + }, + { + "path": "src/textual/screen.py", + "line": 1335, + "kind": "call", + "name": "super()._message_loop_exit" + }, + { + "path": "src/textual/widget.py", + "line": 1456, + "kind": "declaration", + "name": "_get_virtual_dom" + }, + { + "path": "src/textual/widget.py", + "line": 1763, + "kind": "declaration", + "name": "reparent" + }, + { + "path": "src/textual/widget.py", + "line": 4617, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/textual/widget.py", + "line": 4626, + "kind": "declaration", + "name": "remove_children" + }, + { + "path": "src/textual/widget.py", + "line": 4753, + "kind": "declaration", + "name": "_message_loop_exit" + }, + { + "path": "src/textual/widget.py", + "line": 1819, + "kind": "call", + "name": "old_screen._forget_pruned_widgets" + }, + { + "path": "src/textual/widget.py", + "line": 4623, + "kind": "call", + "name": "self.app._prune" + }, + { + "path": "src/textual/widget.py", + "line": 4650, + "kind": "call", + "name": "self.app._prune" + }, + { + "path": "src/textual/widget.py", + "line": 634, + "kind": "call", + "name": "siblings.remove" + }, + { + "path": "src/textual/widget.py", + "line": 879, + "kind": "call", + "name": "self._cover_widget.remove" + }, + { + "path": "src/textual/widget.py", + "line": 1800, + "kind": "call", + "name": "node._get_virtual_dom" + }, + { + "path": "src/textual/widget.py", + "line": 1869, + "kind": "call", + "name": "self.query_children('*').exclude('.-textual-system').remove" + }, + { + "path": "src/textual/widget.py", + "line": 4761, + "kind": "call", + "name": "self._get_virtual_dom" + }, + { + "path": "src/textual/widgets/_directory_tree.py", + "line": 227, + "kind": "call", + "name": "node.remove_children" + }, + { + "path": "src/textual/widgets/_directory_tree.py", + "line": 483, + "kind": "call", + "name": "node.remove_children" + }, + { + "path": "src/textual/widgets/_footer.py", + "line": 436, + "kind": "call", + "name": "parent.remove_children" + }, + { + "path": "src/textual/widgets/_list_view.py", + "line": 268, + "kind": "call", + "name": "self.query('ListView > ListItem').remove" + }, + { + "path": "src/textual/widgets/_list_view.py", + "line": 306, + "kind": "call", + "name": "item_to_remove.remove" + }, + { + "path": "src/textual/widgets/_list_view.py", + "line": 338, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 283, + "kind": "call", + "name": "self.remove" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 714, + "kind": "call", + "name": "self.query_children(f'.cell.row{self.last_row}').remove" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 1473, + "kind": "call", + "name": "markdown_block.remove" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 1461, + "kind": "call", + "name": "markdown_block.remove" + }, + { + "path": "src/textual/widgets/_tabbed_content.py", + "line": 488, + "kind": "call", + "name": "self.get_child_by_type(ContentSwitcher).remove_children" + }, + { + "path": "src/textual/widgets/_tabbed_content.py", + "line": 468, + "kind": "call", + "name": "self.get_child_by_type(ContentSwitcher).get_child_by_id(pane_id).remove" + }, + { + "path": "src/textual/widgets/_tabs.py", + "line": 523, + "kind": "call", + "name": "self.query('#tabs-list > Tab').remove" + }, + { + "path": "src/textual/widgets/_tabs.py", + "line": 567, + "kind": "call", + "name": "remove_tab.remove" + }, + { + "path": "src/textual/widgets/_toast.py", + "line": 143, + "kind": "call", + "name": "(self.parent if isinstance(self.parent, ToastHolder) else self).remove" + }, + { + "path": "src/textual/widgets/_toast.py", + "line": 187, + "kind": "call", + "name": "toast.remove" + }, + { + "path": "src/textual/widgets/_tree.py", + "line": 495, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/textual/widgets/_tree.py", + "line": 506, + "kind": "declaration", + "name": "remove_children" + } + ], + "limits": "Syntactic remove names include unrelated domain methods. App._prune actual Widget/DOMQuery/shutdown consumers resolved semantically; Toad public removal APIs unchanged. Original virtual paint hooks are Widget._render_widget and _get_virtual_dom." + }, + { + "repo": "/home/ts/wt/toad-viewport-raster-cpu-continuation-20261001", + "head": "50051f42de9ba902fa6f07082c874c1cb6942e7b", + "parsed": 288, + "omitted": [], + "sites": [ + { + "path": "src/toad/agent_surface.py", + "line": 166, + "kind": "call", + "name": "existing.remove" + }, + { + "path": "src/toad/agent_surface.py", + "line": 172, + "kind": "call", + "name": "terminal.remove" + }, + { + "path": "src/toad/cli.py", + "line": 203, + "kind": "call", + "name": "command_args.remove" + }, + { + "path": "src/toad/conversation_submission.py", + "line": 272, + "kind": "call", + "name": "self.active.remove" + }, + { + "path": "src/toad/live_output.py", + "line": 102, + "kind": "call", + "name": "block.remove" + }, + { + "path": "src/toad/live_output.py", + "line": 118, + "kind": "call", + "name": "self.block.remove" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 143, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 173, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 227, + "kind": "call", + "name": "widget.remove" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 241, + "kind": "call", + "name": "widget.remove" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 309, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 321, + "kind": "call", + "name": "loading.remove" + }, + { + "path": "src/toad/mounted_message_history.py", + "line": 401, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/screens/agent_modal.py", + "line": 87, + "kind": "call", + "name": "launcher_agents.remove" + }, + { + "path": "src/toad/screens/comms.py", + "line": 207, + "kind": "call", + "name": "content.remove_children" + }, + { + "path": "src/toad/screens/historical_sessions.py", + "line": 100, + "kind": "call", + "name": "content.remove_children" + }, + { + "path": "src/toad/screens/main.py", + "line": 189, + "kind": "call", + "name": "self.query('#session-opening').remove" + }, + { + "path": "src/toad/screens/workspace.py", + "line": 126, + "kind": "declaration", + "name": "_message_loop_exit" + }, + { + "path": "src/toad/screens/workspace.py", + "line": 128, + "kind": "call", + "name": "super()._message_loop_exit" + }, + { + "path": "src/toad/screens/workspace.py", + "line": 323, + "kind": "call", + "name": "node._get_virtual_dom" + }, + { + "path": "src/toad/session_presentation.py", + "line": 177, + "kind": "call", + "name": "conversation.remove" + }, + { + "path": "src/toad/shell.py", + "line": 206, + "kind": "call", + "name": "self.outputs.remove" + }, + { + "path": "src/toad/sidebar_projection.py", + "line": 121, + "kind": "call", + "name": "row.query_ancestor(ChannelGroup).remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 56, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 195, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 284, + "kind": "declaration", + "name": "remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 205, + "kind": "call", + "name": "filtering.remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 287, + "kind": "call", + "name": "retired.remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 197, + "kind": "call", + "name": "self.view.remove" + }, + { + "path": "src/toad/transcript_filter.py", + "line": 292, + "kind": "call", + "name": "retired.remove" + }, + { + "path": "src/toad/transcript_publication.py", + "line": 576, + "kind": "call", + "name": "loading.remove" + }, + { + "path": "src/toad/transcript_publication.py", + "line": 783, + "kind": "call", + "name": "contents.remove_children" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 890, + "kind": "call", + "name": "self._focusable_terminals.remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1064, + "kind": "call", + "name": "self.query(ThreadLoading).remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1295, + "kind": "call", + "name": "event.menu.remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 604, + "kind": "call", + "name": "self.query(ThreadLoading).remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1255, + "kind": "call", + "name": "pending_loading.remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1337, + "kind": "call", + "name": "self._mcp_live_note.remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1891, + "kind": "call", + "name": "pending_loading.remove" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1969, + "kind": "call", + "name": "contents.remove_children" + }, + { + "path": "src/toad/widgets/conversation.py", + "line": 1987, + "kind": "call", + "name": "terminal.remove" + }, + { + "path": "src/toad/widgets/patch_diff.py", + "line": 153, + "kind": "call", + "name": "active.remove" + }, + { + "path": "src/toad/widgets/project_panel.py", + "line": 195, + "kind": "call", + "name": "self.query_one('#file-preview-loading', Static).remove" + }, + { + "path": "src/toad/widgets/retiring_sidebar.py", + "line": 39, + "kind": "call", + "name": "viewport.remove_children" + }, + { + "path": "src/toad/widgets/retiring_sidebar.py", + "line": 41, + "kind": "call", + "name": "controls.remove_children" + }, + { + "path": "src/toad/widgets/session_tabs.py", + "line": 303, + "kind": "call", + "name": "self.title_container.remove_children" + }, + { + "path": "src/toad/widgets/sidebar_tree.py", + "line": 175, + "kind": "call", + "name": "self.member_container.remove_children" + }, + { + "path": "src/toad/widgets/streaming_markdown.py", + "line": 159, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/widgets/thread_comms.py", + "line": 159, + "kind": "call", + "name": "empty.remove" + }, + { + "path": "src/toad/widgets/tool_call.py", + "line": 81, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 344, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 667, + "kind": "call", + "name": "self.filter.remove" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 821, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 823, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 952, + "kind": "call", + "name": "evicted.remove" + }, + { + "path": "src/toad/widgets/transcript_history.py", + "line": 377, + "kind": "call", + "name": "child.remove" + }, + { + "path": "src/toad/widgets/viewport_body.py", + "line": 687, + "kind": "call", + "name": "self.remove_children" + }, + { + "path": "src/toad/workspace_sessions.py", + "line": 182, + "kind": "call", + "name": "view.remove" + } + ], + "limits": "Syntactic remove names include unrelated domain methods. App._prune actual Widget/DOMQuery/shutdown consumers resolved semantically; Toad public removal APIs unchanged. Original virtual paint hooks are Widget._render_widget and _get_virtual_dom." + } +] diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 6be7df87cb..daabfb4a23 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -513,26 +513,33 @@ def max_subtree_geometry_entries(self, capacity: int) -> None: while len(self._subtree_geometry) > capacity: self._subtree_geometry.pop(next(iter(self._subtree_geometry))) + def _invalidate_render_projection(self) -> None: + """Retire layer and line projections of the original scene map.""" + self._visible_widgets = None + self._layers = None + self._cuts = None + def clear(self) -> None: """Remove all references to widgets (used when the screen closes).""" self.root = None self._full_map.clear() self._full_map_invalidated = True self._visible_map = None - self._layers = None + self._invalidate_render_projection() self.widgets.clear() - self._visible_widgets = None - self._cuts = None self._dirty_regions.clear() self._subtree_geometry.clear() - def discard_widgets(self, widgets: set[Widget]) -> None: - """Release retired scene objects while preserving their repaint damage. + def discard_widgets(self, widgets: set[Widget]) -> set[Widget]: + """Retire original widgets, their covers and chrome, retaining damage. - Geometry invalidation alone leaves the old full map and derived layer - projections owning removed widgets indefinitely on inactive screens. - Damage needs rectangles, not the retired widget trees that occupied them. + Maps and captured arrangements release paint before native teardown. """ + widgets = { + member + for owner in widgets + for member in (owner, owner._render_widget, *owner._get_virtual_dom()) + } for owner, resource in tuple(self._subtree_geometry.items()): if resource.references_retired(owner, widgets): del self._subtree_geometry[owner] @@ -541,17 +548,15 @@ def discard_widgets(self, widgets: set[Widget]) -> None: if mapping is None: continue for widget in widgets: - geometry = mapping.pop(widget, None) - if geometry is not None: + if (geometry := mapping.pop(widget, None)) is not None: changed = True - if region := geometry.region.intersection(geometry.clip): + if region := geometry.visible_region: self._dirty_regions.add(region) self.widgets.difference_update(widgets) if changed: self._full_map_invalidated = True - self._visible_widgets = None - self._layers = None - self._cuts = None + self._invalidate_render_projection() + return widgets @classmethod def _regions_to_spans( @@ -613,9 +618,7 @@ def reflow( Hidden, shown, and resized widgets. """ previous_map = self._visible_map if visible_only and self._visible_map is not None else self._full_map - self._cuts = None - self._layers = None - self._visible_widgets = None + self._invalidate_render_projection() self._visible_map = None self.root = parent self.size = size @@ -644,9 +647,7 @@ def reflow( self._full_map_invalidated = visible_only # Measuring widgets may inspect geometry and populate presentation # caches from the previous committed map. Publish only the new map. - self._visible_widgets = None - self._layers = None - self._cuts = None + self._invalidate_render_projection() self.widgets = widgets resized_widgets = self._damage_geometry(old_map, map, parent) @@ -671,9 +672,7 @@ def reflow_visible( Returns: Set of widgets that were exposed by the scroll. """ - self._cuts = None - self._layers = None - self._visible_widgets = None + self._invalidate_render_projection() self._full_map_invalidated = True self.root = parent self.size = size @@ -686,9 +685,7 @@ def reflow_visible( # Replace map and widgets self._visible_map = map - self._visible_widgets = None - self._layers = None - self._cuts = None + self._invalidate_render_projection() self.widgets = widgets exposed_widgets = map.keys() - old_map.keys() @@ -743,10 +740,8 @@ def full_map(self) -> CompositorMap: self._damage_geometry(previous, map, self.root) self._full_map = map self._full_map_invalidated = False - self._visible_widgets = None self._visible_map = None - self._layers = None - self._cuts = None + self._invalidate_render_projection() return self._full_map diff --git a/src/textual/app.py b/src/textual/app.py index 2679dd5416..50bde78d37 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -113,7 +113,7 @@ _normalize_key_list, format_key, ) -from textual.messages import CallbackType, Prune +from textual.messages import CallbackType from textual.notifications import Notification, Notifications, Notify, SeverityLevel from textual.reactive import Reactive from textual.renderables.blank import Blank @@ -3229,6 +3229,16 @@ async def check_mouse() -> None: self.call_after_refresh(check_mouse) + def _retire_pointer_widgets(self, widgets: set[Widget]) -> None: + """Release only pointer resources belonging to the retired scene cohort.""" + if self.mouse_captured in widgets: + self.capture_mouse(None) + if self.mouse_over in widgets or self.hover_over in widgets: + self._set_mouse_over( + None if self.mouse_over in widgets else self.mouse_over, + None if self.hover_over in widgets else self.hover_over, + ) + def capture_mouse(self, widget: Widget | None) -> None: """Send all mouse events to the given widget or disable mouse capture. @@ -3755,7 +3765,7 @@ async def _close_all(self) -> None: for stack in tuple(self._screen_stacks.values()): for stack_screen in reversed(stack): if stack_screen._running: - await self._prune(stack_screen) + await AwaitRemove.prune(stack_screen) stack.clear() self._installed_screens.clear() self._modes.clear() @@ -4401,58 +4411,6 @@ async def _on_app_blur(self, event: events.AppBlur) -> None: self.app_focus = False self.screen.refresh_bindings() - def _prune(self, *nodes: Widget, parent: DOMNode | None = None) -> AwaitRemove: - """Prune nodes from DOM. - - Args: - parent: Parent node. - - Returns: - Optional awaitable. - """ - if not nodes: - return AwaitRemove([]) - pruning_nodes: set[Widget] = {*nodes} - for node in nodes: - node.post_message(Prune()) - pruning_nodes.update(node.walk_children(with_self=True)) - - try: - screen = nodes[0].screen - except (ScreenStackError, NoScreen): - screen = None - else: - if screen.focused and screen.focused in pruning_nodes: - screen._reset_focus(screen.focused, list(pruning_nodes)) - pruning_screen = screen - - for node in pruning_nodes: - node._pruning = True - - def post_mount() -> None: - """Called after removing children.""" - - if pruning_screen is not None: - pruning_screen._forget_pruned_widgets(pruning_nodes) - - if parent is not None: - try: - screen = parent.screen - except (ScreenStackError, NoScreen): - pass - else: - if screen._running and screen.is_current: - self._update_mouse_over(screen) - finally: - parent.refresh(layout=True) - - await_complete = AwaitRemove( - [task for node in nodes if (task := node._task) is not None], - post_mount, - ) - await_complete.call_when_ready(self) - return await_complete - def _watch_app_focus(self, focus: bool) -> None: """Respond to changes in app focus.""" self.stylesheet.update_app_focus(self.screen) diff --git a/src/textual/await_remove.py b/src/textual/await_remove.py index 0dd91c936c..71b6efdfa5 100644 --- a/src/textual/await_remove.py +++ b/src/textual/await_remove.py @@ -15,7 +15,10 @@ from textual._types import CallbackType if TYPE_CHECKING: + from textual.dom import DOMNode from textual.message_pump import MessagePump + from textual.screen import Screen + from textual.widget import Widget @rich.repr.auto @@ -32,6 +35,58 @@ def __init__( self._finisher: Task[None] | None = None self._scheduled = False + @classmethod + def prune(cls, *nodes: Widget, parent: DOMNode | None = None) -> AwaitRemove: + """Retire native scenes before awaiting their original node teardown.""" + from textual.app import ScreenStackError + from textual.dom import NoScreen + from textual.messages import Prune + + if not nodes: + return cls([]) + app = nodes[0].app + pruning_nodes: set[Widget] = set(nodes) + for node in nodes: + node.post_message(Prune()) + pruning_nodes.update(node.walk_children(with_self=True)) + scenes: dict[Screen, set[Widget]] = {} + for node in pruning_nodes: + try: + screen = node.screen + except (ScreenStackError, NoScreen): + continue + scenes.setdefault(screen, set()).add(node) + for screen, members in scenes.items(): + if screen.focused and screen.focused in members: + screen._reset_focus(screen.focused, list(members)) + for node in pruning_nodes: + node._pruning = True + for node in nodes: + # Pruning changes display before NodeList custody is released. + # Retire its existing projections so lazy layout cannot reinsert it. + node._nodes.updated() + node._invalidate_layout() + for screen, members in scenes.items(): + screen._forget_pruned_widgets(members) + + def post_remove() -> None: + if parent is not None: + try: + screen = parent.screen + except (ScreenStackError, NoScreen): + pass + else: + if screen._running and screen.is_current: + app._update_mouse_over(screen) + finally: + parent.refresh(layout=True) + + removal = cls( + [task for node in nodes if (task := node._task) is not None], post_remove + ) + removal.call_when_ready(app) + return removal + def __rich_repr__(self) -> rich.repr.Result: yield "tasks", self._tasks yield "post_remove", self._post_remove diff --git a/src/textual/css/query.py b/src/textual/css/query.py index 660190cbf5..15a9c16c61 100644 --- a/src/textual/css/query.py +++ b/src/textual/css/query.py @@ -17,7 +17,6 @@ import rich.repr -from textual._context import active_app from textual.await_remove import AwaitRemove from textual.css.errors import DeclarationError, TokenError from textual.css.match import match @@ -411,8 +410,7 @@ def remove(self) -> AwaitRemove: Returns: An awaitable object that waits for the widgets to be removed. """ - app = active_app.get() - return app._prune(*self.nodes, parent=self._node) + return AwaitRemove.prune(*self.nodes, parent=self._node) def set_styles( self, css: str | None = None, **update_styles diff --git a/src/textual/screen.py b/src/textual/screen.py index 7e35bb8023..8ba271734d 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -52,7 +52,7 @@ from textual.css.styles import PointerShape from textual.dom import DOMNode from textual.errors import NoWidget -from textual.geometry import Offset, Region, Shape, Size +from textual.geometry import Offset, Region, Size from textual.keys import key_to_character from textual.layout import DockArrangeResult from textual.message_pump import MessagePumpClosed @@ -62,7 +62,6 @@ from textual.selection import SELECT_ALL, SelectEnd, Selection, SelectStart, SelectState from textual.signal import Signal from textual.timer import Timer -from textual.walk import walk_selectable_widgets from textual.widget import Widget from textual.widgets import Tooltip from textual.widgets._toast import ToastRack @@ -1198,9 +1197,10 @@ async def _on_idle(self, event: events.Idle) -> None: def _prepare_compositor_refresh(self) -> bool: """Prepare an admitted scene; return False while it cannot be painted. - This hook runs after batch admission, before any compositor damage is - consumed. Deferred preparation retains the existing repaint intent; - the scene owner arranges its next refresh through the update timer. + Terminal refresh calls this hook for every screen it will composite, + after batch admission and before consuming any compositor damage. + Deferred preparation retains the existing repaint intent; the scene + owner arranges its next refresh through the update timer. """ return True @@ -1210,13 +1210,21 @@ def _compositor_refresh(self) -> None: app = self.app if app._batch_count: return - if not self._prepare_compositor_refresh(): + # A translucent foreground renders its backdrops through BackgroundScreen, + # without entering those screens' refresh methods. Admit that same visible + # stack before rendering any rows, including an invalidated backdrop. + background_screens = app._background_screens + screens = (*background_screens, self) if self is app.screen else (self,) + if self is app.screen and app.is_inline: + # Height acquisition can invalidate a body's captured width. + # Complete that geometry read before asking its scene to paint. + inline_height = app._get_inline_height() + if not all(screen._prepare_compositor_refresh() for screen in screens): self._repaint_required = True return - if app.is_inline: - if self is app.screen: - inline_height = app._get_inline_height() + if self is app.screen: + if app.is_inline: clear = ( app._previous_inline_height is not None and inline_height < app._previous_inline_height @@ -1225,36 +1233,27 @@ def _compositor_refresh(self) -> None: self, self._compositor.render_inline( app.size.with_height(inline_height), - screen_stack=app._background_screens, + screen_stack=background_screens, clear=clear, ), ) app._previous_inline_height = inline_height - self._dirty_widgets.clear() - self._compositor._dirty_regions.clear() - elif ( - self in self.app._background_screens and self._compositor._dirty_regions - ): - app.screen.refresh(*self._compositor._dirty_regions) self._compositor._dirty_regions.clear() - self._dirty_widgets.clear() - - else: - if self is app.screen: - # Top screen + else: update = self._compositor.render_update( - screen_stack=app._background_screens + screen_stack=background_screens ) app._display(self, update) - self._dirty_widgets.clear() - elif ( - self in self.app._background_screens and self._compositor._dirty_regions - ): + self._dirty_widgets.clear() + elif self in background_screens and self._compositor._dirty_regions: + if app.is_inline: + app.screen.refresh(*self._compositor._dirty_regions) + else: self._set_dirty(*self._compositor._dirty_regions) app.screen.refresh(*self._compositor._dirty_regions) self._repaint_required = True - self._compositor._dirty_regions.clear() - self._dirty_widgets.clear() + self._compositor._dirty_regions.clear() + self._dirty_widgets.clear() app._update_mouse_over(self) def _on_timer_update(self) -> None: @@ -1486,14 +1485,34 @@ async def _on_update_scroll(self, message: messages.UpdateScroll) -> None: self.check_idle() def _forget_pruned_widgets(self, widgets: set[Widget]) -> None: + """Retire original scene and interaction resources before teardown.""" + widgets = self._forget_widget_geometry(widgets) + self._retire_selection(widgets) + self.app._retire_pointer_widgets(widgets) + + def _retire_selection(self, widgets: set[Widget]) -> None: + """Keep surviving ranges without retaining a removed pointer endpoint.""" + self.selections = { + widget: selection + for widget, selection in self.selections.items() + if widget not in widgets + } + if ( + self._select_state is not None + and self._select_state.references_retired(widgets) + ): + self._select_state = None + + def _forget_widget_geometry(self, widgets: set[Widget]) -> set[Widget]: """Retire scene and queued damage owners without dropping repaint regions.""" - self._compositor.discard_widgets(widgets) + widgets = self._compositor.discard_widgets(widgets) self._dirty_widgets.difference_update(widgets) for ancestor, children in tuple(self._layout_widgets.items()): if ancestor in widgets: self._layout_widgets.pop(ancestor) else: children.difference_update(widgets) + return widgets def _get_inline_height(self, size: Size) -> int: """Get the inline height (number of lines to display when running inline mode). @@ -2067,48 +2086,14 @@ def _watch__selecting(self, selecting: bool) -> None: if not selecting: self._stop_auto_scroll() - @classmethod - def _collect_select_widgets( - cls, - selection_bounds: Shape, - container: Widget, - start_widget: Widget, - end_widget: Widget, - ) -> list[Widget]: - """Get widgets between two widgets in select order. - - Args: - selection_bounds: A shape defining the selection bounds. - container: A parent widgets. - start_widget: First widget. - end_widget: Second widget. - - Returns: - Widgets between start and end, in select sort order. - """ - - widgets = list( - walk_selectable_widgets( - container, - selection_bounds, - {start_widget, end_widget}, - ) - ) - - index1: int | None = None - try: - index1 = widgets.index(start_widget) - except ValueError: - pass - - index2: int | None = None - try: - index2 = widgets.index(end_widget) + 1 - except ValueError: - pass - - results = widgets[index1:index2] - return results + def _interaction_widgets(self) -> Iterator[Widget]: + """Borrow current interaction custody without projecting selected ranges.""" + yield from self.selections + if self._select_state is not None: + yield from self._select_state._walk_involved_widgets() + for widget in (self.focused, self.app.mouse_captured): + if widget is not None: + yield widget def _watch__select_state(self, select_state: SelectState | None) -> None: """Respond to user-initiated selection change. @@ -2151,12 +2136,9 @@ def _flush_pending_selection(self) -> None: def _apply_selection_state(self, select_state: SelectState | None) -> None: """Calculate widget selections from the current pointer state.""" + self._selecting = select_state is not None if select_state is None: - # Nothing selected so nothing todo - self._selecting = False return - else: - self._selecting = True if select_state.end is None: # Pointer hasn't yet moved @@ -2167,27 +2149,7 @@ def _apply_selection_state(self, select_state: SelectState | None) -> None: self._select_state = None return - # Simple case where select starts and ends on the same widgets - if select_state.is_single_content_widget: - start_index, end_offset = select_state.content_offsets - assert select_state.start.content_widget is not None - self.selections = { - select_state.start.content_widget: Selection.from_offsets( - start_index, - end_offset + (1, 0), - ) - } - return - - # Select all the widgets - select_all = SELECT_ALL - selections = { - widget: select_all for widget in select_state._walk_selected_widgets() - } - select_state._apply_content_selections(selections) - - # Update selections - self.selections = selections + self.selections = select_state.selections() def dismiss(self, result: ScreenResultType | None = None) -> AwaitComplete: """Dismiss the screen, optionally with a result. diff --git a/src/textual/selection.py b/src/textual/selection.py index d1776fc58a..58d1e4f129 100644 --- a/src/textual/selection.py +++ b/src/textual/selection.py @@ -168,14 +168,37 @@ class SelectState(NamedTuple): end: SelectEnd | None = None """Describes the select end.""" + def _walk_involved_widgets(self) -> Iterator[Widget]: + """Original containers and content referenced by the pointer endpoints.""" + for endpoint in (self.start, self.end): + if endpoint is not None: + yield endpoint.container + if endpoint.content_widget is not None: + yield endpoint.content_widget + + @property def is_attached_to_dom(self) -> bool: """Are the widgets involved attached to the DOM?""" - # This may return False if the widgets have been removed since selection started - if not self.start.container.is_attached: - return False - if self.end is not None and not self.end.container.is_attached: - return False - return True + return all(widget.is_attached for widget in self._walk_involved_widgets()) + + def references_retired(self, widgets: set[Widget]) -> bool: + """Whether removing these resources retires the pointer intent.""" + return any(widget in widgets for widget in self._walk_involved_widgets()) + + def selections(self) -> dict[Widget, Selection]: + """Project the completed pointer intent into original content ranges.""" + assert self.end is not None + if self.is_single_content_widget: + start_offset, end_offset = self.content_offsets + assert self.start.content_widget is not None + return { + self.start.content_widget: Selection.from_offsets( + start_offset, end_offset + (1, 0) + ) + } + selections = {widget: SELECT_ALL for widget in self._walk_selected_widgets()} + self._apply_content_selections(selections) + return selections @property def is_single_content_widget(self) -> bool: diff --git a/src/textual/widget.py b/src/textual/widget.py index 9c5c3c0439..6858b57bf4 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -1807,8 +1807,7 @@ def reparent(self, parent: Widget, *, before: Widget | None = None) -> None: if old_screen is not new_screen: if focused is not None: old_screen.set_focus(None) - old_screen.selections = {node: selection for node, selection in old_screen.selections.items() - if node not in nodes} + old_screen._retire_selection(nodes) retained_callbacks = [] for callback, sender in old_screen._callbacks: if sender in nodes: @@ -1816,7 +1815,7 @@ def reparent(self, parent: Widget, *, before: Widget | None = None) -> None: else: retained_callbacks.append((callback, sender)) old_screen._callbacks[:] = retained_callbacks - old_screen._forget_pruned_widgets(nodes) + old_screen._forget_widget_geometry(nodes) previous._nodes._remove(self) self._attach(parent) if before is None: @@ -4620,7 +4619,7 @@ def remove(self) -> AwaitRemove: Returns: An awaitable object that waits for the widget to be removed. """ - await_remove = self.app._prune(self, parent=self._parent) + await_remove = AwaitRemove.prune(self, parent=self._parent) return await_remove def remove_children( @@ -4647,7 +4646,7 @@ def remove_children( ] else: children_to_remove = selector - await_remove = self.app._prune(*children_to_remove, parent=self) + await_remove = AwaitRemove.prune(*children_to_remove, parent=self) return await_remove @asynccontextmanager diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py index 3f875771d7..5deb964d4a 100644 --- a/src/textual/widgets/_markdown.py +++ b/src/textual/widgets/_markdown.py @@ -1457,7 +1457,7 @@ async def mount_batch(batch: list[MarkdownBlock]) -> None: if removed: await self.mount_all(batch) else: - with self.app.batch_update(): + async with self.batch(): await markdown_block.remove() await self.mount_all(batch) removed = True @@ -1534,7 +1534,7 @@ async def await_append() -> None: end + start_line, ) - with self.app.batch_update(): + async with self.batch(): if existing_blocks and new_blocks: last_block = existing_blocks[-1] last_block.source_range = new_blocks[0].source_range diff --git a/tests/test_batch_paint_admission.py b/tests/test_batch_paint_admission.py index 9d23fad459..3fe7fb11f0 100644 --- a/tests/test_batch_paint_admission.py +++ b/tests/test_batch_paint_admission.py @@ -4,7 +4,7 @@ from textual.app import App, ComposeResult from textual.containers import Vertical -from textual.screen import Screen +from textual.screen import ModalScreen, Screen from textual.widgets import Label @@ -95,3 +95,34 @@ def second() -> None: assert not completed.is_set() await asyncio.wait_for(completed.wait(), 1) assert order == ["first", "second"] + + +async def test_translucent_foreground_respects_background_resource_admission() -> None: + class SourceScreen(ObservedScreen): + def _prepare_compositor_refresh(self) -> bool: + return (not self.query_one("#holder").lock.is_locked + and super()._prepare_compositor_refresh()) + + class SourceApp(BatchPaintApp): + def get_default_screen(self) -> Screen: + return SourceScreen() + + app = SourceApp() + async with app.run_test(size=(50, 12)) as pilot: + holder = app.query_one("#holder", Vertical) + source = app.screen + await app.push_screen(ModalScreen()) + await pilot.pause() + assert source in app._background_screens + foreground = app.screen + async with holder.lock: + foreground._compositor._dirty_regions.add(foreground.size.region) + damage = set(foreground._compositor._dirty_regions) + app.frames.clear() + foreground._compositor_refresh() + assert not app.frames + assert foreground._compositor._dirty_regions == damage + assert foreground._repaint_required + foreground._compositor_refresh() + assert app.frames + assert not foreground._compositor._dirty_regions diff --git a/tests/test_remove_transaction.py b/tests/test_remove_transaction.py index d1cbebce7a..c1af625ab6 100644 --- a/tests/test_remove_transaction.py +++ b/tests/test_remove_transaction.py @@ -33,9 +33,15 @@ def watch_value(self, value): await app.mount(prompt, retiring) prompt.focus() await pilot.pause() + compositor = app.screen._compositor + assert retiring in compositor.visible_widgets removal = retiring.remove() try: await asyncio.wait_for(entered.wait(), 1) + # Retirement must precede delayed Unmount, including lazy map reads. + assert not retiring.display + assert retiring not in compositor.full_map + assert retiring not in compositor.visible_widgets app._driver.send_message(Key("x", "x")) await asyncio.wait_for(received.wait(), .5) assert not release.is_set() diff --git a/tests/test_removed_scene_ownership.py b/tests/test_removed_scene_ownership.py index 163ab91902..5dd17d85f9 100644 --- a/tests/test_removed_scene_ownership.py +++ b/tests/test_removed_scene_ownership.py @@ -1,10 +1,96 @@ +import asyncio + from textual.app import App +from textual.await_remove import AwaitRemove from textual.containers import VerticalGroup from textual.screen import Screen +from textual.selection import SELECT_ALL from textual.widgets import Static import pytest +async def test_pending_removal_retires_each_original_scene_and_cover(): + release = asyncio.Event() + + class SlowUnmount(VerticalGroup): + async def on_unmount(self): + await release.wait() + + app = App() + async with app.run_test() as pilot: + first, second = Screen(), Screen() + retiring = [] + scenes = [] + for scene in (first, second): + await app.push_screen(scene) + node = SlowUnmount(Static("old source")) + await scene.mount(node) + node.loading = True + await pilot.pause() + cover = node._render_widget + assert cover is not node + assert cover in scene._compositor.visible_widgets + retiring.append((node, cover)) + scenes.append(scene) + removal = AwaitRemove.prune(*(node for node, _ in retiring)) + try: + for scene, (node, cover) in zip(scenes, retiring): + assert node._pruning and not node._closed + assert cover not in scene._compositor.full_map + assert cover not in scene._compositor.visible_widgets + assert node not in scene._compositor.full_map + assert not node.display + finally: + release.set() + await removal + assert all(node._closed for node, _ in retiring) + + +async def test_pending_removal_revokes_pointer_intent_but_keeps_surviving_selection(): + entered, release = asyncio.Event(), asyncio.Event() + + class SlowText(Static): + async def on_unmount(self): + entered.set() + await release.wait() + + app = App() + async with app.run_test(size=(50, 12)) as pilot: + try: + retiring = SlowText("selected source", id="retiring") + survivor = Static("surviving source", id="survivor") + parent = VerticalGroup(retiring, survivor) + await app.mount(parent) + await pilot.pause() + assert await pilot.mouse_down(retiring, offset=(0, 0)) + assert not app.screen.selections + assert app.screen._select_state.end is None + assert retiring in app.screen._interaction_widgets() + assert await pilot.mouse_up(retiring, offset=(4, 0)) + screen = app.screen + state = screen._select_state + assert state is not None and state.start.content_widget is retiring + assert state.start.container is parent and state.is_attached_to_dom + screen.selections = {**screen.selections, survivor: SELECT_ALL} + retiring.capture_mouse() + removal = retiring.remove() + try: + assert screen._select_state is None + assert app.mouse_captured is None + assert app.mouse_over is not retiring and app.hover_over is not retiring + assert screen.selections == {survivor: SELECT_ALL} + await asyncio.wait_for(entered.wait(), 1) + screen._flush_pending_selection() + assert screen.selections == {survivor: SELECT_ALL} + finally: + release.set() + await removal + assert parent.is_attached and not state.is_attached_to_dom + assert screen.get_selected_text() == "surviving source" + finally: + release.set() + + @pytest.mark.parametrize("layout", ["vertical", "stream"]) async def test_inactive_scene_releases_removed_geometry_and_arrangements(layout): app = App() diff --git a/tests/test_reparent.py b/tests/test_reparent.py index 615ce9701b..44e036ad32 100644 --- a/tests/test_reparent.py +++ b/tests/test_reparent.py @@ -37,12 +37,14 @@ class Example(App): child = panel.query_one("#body", Static) tasks = panel._task, child._task red = child.styles.color + app.screen.selections = {child: SELECT_ALL} first.query_one("#shared") # Warm the native selector cache. panel.reparent(second) await pilot.pause() assert panel.parent is second and panel.screen is app.screen assert not first.query("#shared") and second.query_one("#shared") is panel assert panel.query_one("#body") is child and child.content == "unchanged model" + assert app.screen.selections == {child: SELECT_ALL} assert (panel._task, child._task) == tasks assert panel.mounts == 1 and panel.unmounts == 0 assert child.styles.color != red