From 22ea7437fda0b9b66b9d535542cbf78854bcfd9a Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 6 Nov 2026 13:07:21 -0500 Subject: [PATCH 1/3] Test apps publish frames every 1 ms (TEXTUAL_FPS=1000) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd --- tests/support/roots.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/support/roots.py b/tests/support/roots.py index 333234599..170b7743b 100644 --- a/tests/support/roots.py +++ b/tests/support/roots.py @@ -108,5 +108,9 @@ def environment(self, inherited: dict[str, str]) -> dict[str, str]: "TERM": "xterm-256color", "COLORTERM": "truecolor", "FORCE_COLOR": "1", + # A test app publishes every 1 ms instead of 1/144 s. Each input still gets its + # own frame; a wheel walk that waited one 7 ms tick per event no longer does + # (the saved-history scroll test went from 36.5 s to 26.5 s). + "TEXTUAL_FPS": "1000", }) return env From 23c9174b5901f73ba1040735541a0065d669af00 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 6 Nov 2026 13:55:04 -0500 Subject: [PATCH 2/3] The test template imports every toad module before it forks Each forked app test imported the screens and widgets it mounted cold (toad.screens.comms, widgets.comms_chat, wire_history, welcome, session_tabs: 51 modules, 0.21 s cold), so every one of 594 tests paid again for imports the template could hold once. Imported before the fork, the smallest app tests in test_initial_session_title.py go from 1.43-1.98 s to 1.18-1.23 s each, and the template's own first test from 10.6 s to 6.2 s. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd --- tests/support/template_server.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/support/template_server.py b/tests/support/template_server.py index 7285dab12..68ea99e3b 100644 --- a/tests/support/template_server.py +++ b/tests/support/template_server.py @@ -8,7 +8,9 @@ from dataclasses import dataclass import gc +import importlib import json +import pkgutil import os import socket import sys @@ -17,6 +19,7 @@ import traceback import pytest +import toad import support.workspace # noqa: F401 (the app, its fixtures and the comms package) from agent_comms.storage.locked_store import RootFile @@ -85,6 +88,10 @@ def serve() -> None: """Warm what every app test pays for, then fork each test and its helper spawner.""" # Every agent_comms module once, so a forked helper imports and walks nothing. RootFile.load_declarations() + # Every toad module once: the app imports its screens and widgets as it mounts them + # (51 modules, 0.21 s cold), and every test paid for the ones it reached. + for module in pkgutil.walk_packages(toad.__path__, "toad."): + importlib.import_module(module.name) # The file-preview terminal probe: 0.15 s of every app's start. Neither this process nor # a forked test has a terminal, so both detect the same answer. FileKind.prepare_terminal() From 5f2c86fd7c42354b0572152f101033af27db6bb1 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 6 Nov 2026 13:59:33 -0500 Subject: [PATCH 3/3] Saved settings resolve each level's owner once for its file keys keys_held and kept asked saved_key for every setting, and saved_key resolved the containing record's value (DeclaredField.owner_type -> ObjectState.get_resolved_value) per field, only to learn which class owns the field's file key. Every field of one level shares that owner, so saved_keys resolves it once and maps every field to its key. Opening settings at launch goes from 48.0 ms to 13.7 ms for a populated toad.json (15.3 to 11.3 ms for an empty one); the keys held are unchanged. tests/model/test_saved_settings.py and the settings, forms and keymap tests: 36 passed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd --- src/toad/preferences.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/toad/preferences.py b/src/toad/preferences.py index 9f6dd0bd1..bb58eb564 100644 --- a/src/toad/preferences.py +++ b/src/toad/preferences.py @@ -474,26 +474,33 @@ def practice_opening(cls, files: FilesRequest, directory: Path) -> None: if path.exists(): FieldCodec.decode(ToadSettings, FieldCodec.parse(path.read_text("utf-8"))) - def saved_key(self, path: DottedFieldPath) -> str: - owner = DeclaredField(self.state, path).owner_type - return next(key for declaration, key in FieldCodec.record_fields(owner) - if declaration.name == path.field_name) + def saved_keys(self, path: str) -> list[tuple[DottedFieldPath, str]]: + """Each setting or group directly below `path`, with its key in the file. + + They share one owner record, so its class is resolved once for the level: resolving + it again per field cost every launch 0.04 s of value resolution for the keys alone. + """ + below = self.state.direct_parameter_paths(path) + if not below: + return [] + owner = DeclaredField(self.state, below[0]).owner_type + keys = {declaration.name: key for declaration, key in FieldCodec.record_fields(owner)} + return [(field, keys[field.field_name]) for field in below] def keys_held(self, path: str, raw: object) -> list[DottedFieldPath]: """The settings and groups a document read from the file has a key for.""" if not isinstance(raw, dict): return [] held = [] - for below in self.state.direct_parameter_paths(path): - if (key := self.saved_key(below)) in raw: + for below, key in self.saved_keys(path): + if key in raw: held += [below, *self.keys_held(below.value, raw[key])] return held def kept(self, path: str) -> dict[str, JsonValue]: """The part of the whole encoded tree that belongs in the file.""" document: dict[str, JsonValue] = {} - for below in self.state.direct_parameter_paths(path): - key = self.saved_key(below) + for below, key in self.saved_keys(path): if self.state.has_parameter_descendants(below.value): part = self.kept(below.value) if part or below in self.user_set: