From 1c6c72b646cdeb9a29864b93430feadb16dae098 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 8 Oct 2026 10:34:04 -0400 Subject: [PATCH 1/3] Record installed native-tail traversal and build continuation --- .../dm-initial-publication-20261008/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/evidence/dm-initial-publication-20261008/README.md b/evidence/dm-initial-publication-20261008/README.md index f5429be52..b99f6dbf2 100644 --- a/evidence/dm-initial-publication-20261008/README.md +++ b/evidence/dm-initial-publication-20261008/README.md @@ -980,3 +980,22 @@ not a performance pass. Current wheel is published for new launches through the reviewed original publication owner; existing windows retain loaded code. Exact checks/publication live under visible-retirement-delivery-20261008 and retirement-installed.log/retirement-sidebar-drag.log in existing scratch. + +## Tail demand uses reverse native traversal + +Toad580 ac60b1cf: source_tail stops the existing body-root traversal at the +first reverse-order native boundary instead of acquiring and reversing every +body root. Default forward traversal remains unchanged. requires_body first +rejects unregistered bodies and admits actual visible bodies before acquiring +interaction ancestry. No order catalog or protection cache was added. + +Native source and exact installed Apps passed tail/reverse order after actual +native reorder and removal.705 source/test modules parsed without omissions. +No broad performance measurement or frame-time improvement is claimed. +The builder finished wheels then refused a relative output URI before prefix +creation; original negative retained. Installation/verification continued with +the original completed wheel and absolute path, without rebuilding. The same +builder now resolves its output path before acquisition.954 assets/full69 and +native package verification passed. Reviewed cohort published for new launches; +backend and existing loaded windows preserved. Raw tail-source.log, +tail-installed.log and tail-build.log remain in existing scratch. From ecbc533e0a52687c89710bcfc8b65fc75835128b Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 8 Oct 2026 10:37:27 -0400 Subject: [PATCH 2/3] Retain current installed frame-time failure and runtime cleanup --- .../dm-initial-publication-20261008/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/evidence/dm-initial-publication-20261008/README.md b/evidence/dm-initial-publication-20261008/README.md index b99f6dbf2..aebdc483b 100644 --- a/evidence/dm-initial-publication-20261008/README.md +++ b/evidence/dm-initial-publication-20261008/README.md @@ -999,3 +999,22 @@ builder now resolves its output path before acquisition.954 assets/full69 and native package verification passed. Reviewed cohort published for new launches; backend and existing loaded windows preserved. Raw tail-source.log, tail-installed.log and tail-build.log remain in existing scratch. + +## Current installed pacing remains unqualified + +Current document-tail cohort160-wheel/20incoming private native-driver burst +completed ordered/no App error/provider0. Wheel median84.4/p95345.7ms; +frame median13.0/p95114.5/max291.6ms. Single run under actual host conditions; +no causal regression/improvement claim. UI-thread CPU spans: full reflow25 +calls/425.2ms/worst50.6; visible62/231.2/worst32.1; paint160/266.5/worst6.1. +WorkerStatic preparation122/7.6ms total and row render69/2.2ms total do not +explain this layout cost. Direct thread-time records retained in tail-wheel; +cProfile is not used for these measurements. Native compositional geometry +migration remains open, including held publication loans and explicit reader +demand. Matched installed application comparison follows its final checkpoint. + +Fresh privileged283-process census without gaps or borrowers and executable +symlink checks permitted removal of two superseded runtime directories, +110389417bytes. Current document-tail, visible-retirement rollback and backend +remain. Source/wheels/proofs/raw are preserved; original removal evidence in +Parent-tail-superseded-runtime-retired-20261008.json. From 2b8ca45181d875457f38f482465370745a41a935 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 8 Oct 2026 11:33:12 -0400 Subject: [PATCH 3/3] Move optional startup imports through lifecycle preparation ApplicationLifetime owns process naming; startup and all three CLI server consumers use its original behavior. Delete the CLI function and startup's reverse import of the command module. VersionMonitor owns its installed-version preparation and complete query. Move cold httpx, packaging and TOML imports plus installed-version parsing through the supplied existing preparation runner. App supplies its bounded PreparationRuntime.run_thread; standalone execution uses asyncio.to_thread. Client construction uses the same runner. Preserve async HTTP on the original loop, async-with closure, cancellation, failure handling and upgrade notices. Opt-in UsageReports also acquires its cold HTTP module through preparation; its policy, request and error boundary remain unchanged. NRA SourceModule/module_syntax_index before/after traced 862 production files across Toad, current Core and native Textual roots, zero parse omissions under Python 3.14. Process-title definition moves cli.py to ApplicationLifetime; startup and run --serve, acp --serve and serve callers all migrate. Version query definition moves module scope to VersionMonitor; check and standalone __main__ migrate. No former imports/calls remain. Dynamically imported outside consumers were not proved by AST; no compatibility alias retained. Verification: actual PreparationRuntime + httpx/local HTTP response verified cold imports off UI thread, upgrade notice substitution, malformed response retaining previous status, cancellation propagation and client closure. Lifecycle title invocation does not import toad.cli. Source CLI --help passes. No App/provider launch; integration owner carries installed App qualification. --- src/toad/application_lifetime.py | 18 +++-- src/toad/cli.py | 21 ++---- src/toad/version.py | 113 ++++++++++++++++++------------- 3 files changed, 84 insertions(+), 68 deletions(-) diff --git a/src/toad/application_lifetime.py b/src/toad/application_lifetime.py index 348ec6ca0..72683d122 100644 --- a/src/toad/application_lifetime.py +++ b/src/toad/application_lifetime.py @@ -3,6 +3,7 @@ from datetime import datetime, timezone from functools import partial +from importlib import import_module import platform from time import monotonic from typing import TYPE_CHECKING @@ -27,7 +28,7 @@ async def send(self, event_name: str, properties: dict[str, object]) -> None: async def deliver(self, event_name: str, properties: dict[str, object]) -> None: app = self.app - import httpx + httpx = await app.preparation.run_thread(import_module, "httpx") from toad import get_version width, height = app.size @@ -52,6 +53,16 @@ async def deliver(self, event_name: str, properties: dict[str, object]) -> None: class ApplicationLifetime: """A confirmed quit and explicit quit share the same save-after-blur path.""" + @staticmethod + def set_process_title(title: str) -> None: + """Name the terminal application or its CLI server process.""" + try: + import setproctitle + + setproctitle.setproctitle(title) + except Exception: + pass + def __init__(self, app: ToadApp, initial_mode: str | None) -> None: self.app = app self.initial_mode = initial_mode @@ -70,12 +81,11 @@ async def start(self) -> None: await app.session_navigation.new(app.session_navigation.default_source) app.terminal_attention.attach() app.set_timer(1, self.check_version) - from toad.cli import set_process_title - app.run_worker(partial(set_process_title, "toad"), thread=True, exit_on_error=False) + app.run_worker(partial(self.set_process_title, "toad"), thread=True, exit_on_error=False) app.update_show_sessions() def check_version(self) -> None: - self.app.run_worker(self.version.check, exit_on_error=False) + self.app.run_worker(partial(self.version.check, self.app.preparation.run_thread), exit_on_error=False) def confirm_quit(self) -> None: now = monotonic() diff --git a/src/toad/cli.py b/src/toad/cli.py index 6b341f16f..c1ff930d6 100644 --- a/src/toad/cli.py +++ b/src/toad/cli.py @@ -3,6 +3,7 @@ import click from toad.app import ToadApp +from toad.application_lifetime import ApplicationLifetime from toad.agent_schema import AgentDefinition from toad.render_backend import Renderer from toad.render_choices import RendererChoice @@ -47,20 +48,6 @@ def renderer_from_cli(backend: str | None) -> Renderer | None: raise click.ClickException(str(error)) from error -def set_process_title(title: str) -> None: - """Set the process title. - - Args: - title: Desired title. - """ - try: - import setproctitle - - setproctitle.setproctitle(title) - except Exception: - pass - - def check_directory(path: str) -> None: """Check a path is directory, or exit the app. @@ -212,7 +199,7 @@ def run( title=serve_command, public_url=public_url, ) - set_process_title("toad --serve") + ApplicationLifetime.set_process_title("toad --serve") server.serve() else: app = ToadApp( @@ -316,7 +303,7 @@ def acp( port=port, title=serve_command, ) - set_process_title("toad acp --serve") + ApplicationLifetime.set_process_title("toad acp --serve") server.serve() else: @@ -381,7 +368,7 @@ def serve(port: int, host: str, public_url: str | None = None) -> None: server = ToadWebServer( sys.argv[0], host=host, port=port, title="Toad", public_url=public_url ) - set_process_title("toad serve") + ApplicationLifetime.set_process_title("toad serve") server.serve() diff --git a/src/toad/version.py b/src/toad/version.py index f96d235e8..4ee96f9d4 100644 --- a/src/toad/version.py +++ b/src/toad/version.py @@ -1,8 +1,14 @@ +from __future__ import annotations + import asyncio -from typing import NamedTuple +from collections.abc import Awaitable, Callable +from typing import Any, NamedTuple, TYPE_CHECKING from abc import ABC, abstractmethod from dataclasses import dataclass +if TYPE_CHECKING: + from packaging.version import Version + VERSION_TOML_URL = "https://www.batrachian.ai/toad.toml" @@ -46,67 +52,80 @@ class VersionMonitor: def __init__(self) -> None: self.status: VersionStatus = CurrentVersion() - async def check(self) -> None: + async def check( + self, run_thread: Callable[..., Awaitable[Any]] = asyncio.to_thread, + ) -> None: try: - self.status = await check_version() + self.status = await self.check_version(run_thread) except VersionCheckFailed: return + @staticmethod + def prepare_version() -> Version: + """Acquire cold dependencies and installed metadata outside UI work.""" + import httpx # noqa: F401 - acquired before the UI consumes the module + import packaging.version + import tomllib # noqa: F401 - acquired before the UI decodes the response -async def check_version() -> VersionStatus: - """Check for a new version of Toad. - - The outcome owns whether an upgrade notice exists. - """ - import httpx - import packaging.version - import tomllib - - from toad import get_version - - try: - current_version = packaging.version.parse(get_version()) - except packaging.version.InvalidVersion as error: - raise VersionCheckFailed(f"Invalid version;{error}") + from toad import get_version - try: - client = await asyncio.to_thread(httpx.AsyncClient) - async with client: - response = await client.get(VERSION_TOML_URL) - version_toml_bytes = await response.aread() - except Exception as error: - raise VersionCheckFailed(f"Failed to retrieve version;{error}") - - try: - version_toml = version_toml_bytes.decode("utf-8", "replace") - version_meta = tomllib.loads(version_toml) - except Exception as error: - raise VersionCheckFailed(f"Failed to decode version TOML;{error}") + try: + return packaging.version.parse(get_version()) + except packaging.version.InvalidVersion as error: + raise VersionCheckFailed(f"Invalid version;{error}") + + @classmethod + async def check_version( + cls, run_thread: Callable[..., Awaitable[Any]] = asyncio.to_thread, + ) -> VersionStatus: + """Check for a new version of Toad. + + The outcome owns whether an upgrade notice exists. + """ + current_version = await run_thread(cls.prepare_version) + # These modules were acquired by the preparation worker above. + import httpx + import packaging.version + import tomllib - if not isinstance(version_meta, dict): - raise VersionCheckFailed("Response isn't TOML") + try: + client = await run_thread(httpx.AsyncClient) + async with client: + response = await client.get(VERSION_TOML_URL) + version_toml_bytes = await response.aread() + except Exception as error: + raise VersionCheckFailed(f"Failed to retrieve version;{error}") - toad_version = str(version_meta.get("version", "0")) - version_message = str(version_meta.get("upgrade_message", "")) - version_message = version_message.replace("$VERSION", toad_version) - verison_meta = VersionMeta( - version=toad_version, - upgrade_message=version_message, - visit_url=str(version_meta.get("visit_url", "")), - ) + try: + version_toml = version_toml_bytes.decode("utf-8", "replace") + version_meta = tomllib.loads(version_toml) + except Exception as error: + raise VersionCheckFailed(f"Failed to decode version TOML;{error}") + + if not isinstance(version_meta, dict): + raise VersionCheckFailed("Response isn't TOML") + + toad_version = str(version_meta.get("version", "0")) + version_message = str(version_meta.get("upgrade_message", "")) + version_message = version_message.replace("$VERSION", toad_version) + verison_meta = VersionMeta( + version=toad_version, + upgrade_message=version_message, + visit_url=str(version_meta.get("visit_url", "")), + ) - try: - new_version = packaging.version.parse(verison_meta.version) - except packaging.version.InvalidVersion as error: - raise VersionCheckFailed(f"Invalid remote version;{error}") + try: + new_version = packaging.version.parse(verison_meta.version) + except packaging.version.InvalidVersion as error: + raise VersionCheckFailed(f"Invalid remote version;{error}") - return AvailableVersion(verison_meta) if new_version > current_version else CurrentVersion() + return AvailableVersion(verison_meta) if new_version > current_version else CurrentVersion() if __name__ == "__main__": async def run() -> None: - result = await check_version() + result = await VersionMonitor.check_version() from rich import print print(result)