[PATCH b4 v2 02/11] Add ruff checks to CI

Tamir Duberstein <[email protected]>
Newsgroups org.kernel.linux.tools
Message-ID <[email protected]>
Mark example-only variables as intentionally unused so ruff can check
the script without changing its illustrative structure.

Change `ruff.lint.select` to `ruff.lint.extend-select` to enable default
lints and fix ambiguous variable name warnings.

Enable import sorting and use `ruff check --fix` to fix existing
violations.

Configure ruff to skip submodules.

Signed-off-by: Tamir Duberstein <[email protected]>
---
 ci.sh                              |  1 +
 misc/review-ci-example.py          |  4 +-
 misc/send-receive.py               | 30 +++++++-------
 pyproject.toml                     |  6 ++-
 src/b4/__init__.py                 | 67 +++++++++++++++++-------------
 src/b4/bugs/__init__.py            |  8 ++--
 src/b4/bugs/_tui.py                |  7 ++--
 src/b4/command.py                  |  4 +-
 src/b4/diff.py                     | 16 +++----
 src/b4/dig.py                      | 13 +++---
 src/b4/ez.py                       | 33 ++++++++-------
 src/b4/kr.py                       |  2 +-
 src/b4/mbox.py                     | 28 ++++++-------
 src/b4/pr.py                       | 24 +++++------
 src/b4/review/__init__.py          | 29 ++++++++-----
 src/b4/review/_review.py           |  3 +-
 src/b4/review/checks.py            |  1 -
 src/b4/review/messages.py          |  1 -
 src/b4/review/tracking.py          |  7 ++--
 src/b4/review_tui/__init__.py      | 20 +++++----
 src/b4/review_tui/_common.py       | 85 ++++++++++++++++++++++++++------------
 src/b4/review_tui/_entry.py        |  3 +-
 src/b4/review_tui/_lite_app.py     | 19 +++++----
 src/b4/review_tui/_modals.py       | 51 ++++++++++++++---------
 src/b4/review_tui/_pw_app.py       | 28 ++++++++-----
 src/b4/review_tui/_review_app.py   | 56 ++++++++++++++++---------
 src/b4/review_tui/_tracking_app.py | 53 +++++++++++++++++-------
 src/b4/tui/_common.py              |  8 ++--
 src/b4/tui/_modals.py              | 11 +++--
 src/b4/ty.py                       | 19 ++++-----
 src/tests/conftest.py              |  7 ++--
 src/tests/test___init__.py         | 24 +++++++----
 src/tests/test_ez.py               | 11 ++---
 src/tests/test_mbox.py             | 11 ++---
 src/tests/test_patatt.py           |  3 +-
 src/tests/test_rethread.py         |  4 +-
 src/tests/test_review.py           | 12 +++---
 src/tests/test_review_checks.py    |  1 -
 src/tests/test_review_show_info.py |  5 +--
 src/tests/test_review_tracking.py  | 12 +++---
 src/tests/test_three_way_merge.py  |  7 ++--
 src/tests/test_tui_bugs.py         |  4 +-
 src/tests/test_tui_modals.py       |  6 +--
 src/tests/test_tui_review.py       |  6 +--
 src/tests/test_tui_tracking.py     | 13 +++---
 45 files changed, 436 insertions(+), 327 deletions(-)

diff --git a/ci.sh b/ci.sh
index 89a5a80..b65ae97 100755
--- a/ci.sh
+++ b/ci.sh
@@ -2,4 +2,5 @@
 
 set -eu
 
+uv run ruff check
 uv run mypy .
diff --git a/misc/review-ci-example.py b/misc/review-ci-example.py
index e5837eb..cbac2ae 100755
--- a/misc/review-ci-example.py
+++ b/misc/review-ci-example.py
@@ -43,7 +43,7 @@ import sys
 
 def main() -> None:
     msg = email.message_from_binary_file(sys.stdin.buffer)
-    subject = msg.get('subject', '(no subject)')
+    subject = msg.get('subject', '(no subject)')  # noqa: F841
     msgid = msg.get('message-id', '').strip('<> ')
 
     # Example: read tracking data for commit-based CI lookups
@@ -53,7 +53,7 @@ def main() -> None:
             tracking = json.load(fp)
         branch_tips = tracking.get('series', {}).get('branch-tips', [])
     else:
-        branch_tips = []
+        branch_tips = []  # noqa: F841
 
     # Seed the RNG with the message-id so results are stable across
     # repeated runs of the same message (simulates cached CI results).
diff --git a/misc/send-receive.py b/misc/send-receive.py
index a3dd893..35c5e99 100644
--- a/misc/send-receive.py
+++ b/misc/send-receive.py
@@ -1,29 +1,29 @@
 #!/usr/bin/env python3
 
-import falcon
-import os
-import sys
-import logging
-import logging.handlers
-import json
-import sqlalchemy as sa
-import patatt
-import smtplib
+import copy
 import email
 import email.header
 import email.policy
 import email.quoprimime
+import json
+import logging
+import logging.handlers
+import os
 import re
-import ezpi
-import copy
+import smtplib
+import sys
 import textwrap
-
 from configparser import ConfigParser, ExtendedInterpolation
+from email import charset, utils
 from string import Template
-from email import utils
-from typing import Tuple, Union, List
+from typing import List, Tuple, Union
+
+import ezpi
+import falcon
+import sqlalchemy as sa
+
+import patatt
 
-from email import charset
 charset.add_charset('utf-8', None)
 emlpolicy = email.policy.EmailPolicy(utf8=True, cte_type='8bit', max_line_length=None)
 
diff --git a/pyproject.toml b/pyproject.toml
index 867fcae..6eb2fbb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -82,13 +82,17 @@ files = [
     {filename = "src/b4/man/b4.1"},
 ]
 
+[tool.ruff]
+extend-exclude = ["ezgb", "liblore", "patatt"]
+
 [tool.ruff.lint]
-select = [
+extend-select = [
     "F",       # https://docs.astral.sh/ruff/rules/#pyflakes-f
     "B007",    # https://docs.astral.sh/ruff/rules/unused-loop-control-variable/
     "B904",    # https://docs.astral.sh/ruff/rules/raise-without-from-err/
     "DTZ",     # https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz
     "G",       # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
+    "I",       # https://docs.astral.sh/ruff/rules/#isort-i
     "PERF102", # https://docs.astral.sh/ruff/rules/incorrect-dict-iterator/
     "PGH004",  # https://docs.astral.sh/ruff/rules/blanket-noqa/
     "PIE790",  # https://docs.astral.sh/ruff/rules/unnecessary-placeholder/
diff --git a/src/b4/__init__.py b/src/b4/__init__.py
index 75238bb..1e4c91e 100644
--- a/src/b4/__init__.py
+++ b/src/b4/__init__.py
@@ -1,50 +1,61 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 # Copyright (C) 2020 by the Linux Foundation
-import subprocess
-import logging
-import hashlib
-import re
-import sys
-import os
-import fnmatch
+import argparse
+import copy
+import datetime
 import email.generator
 import email.header
 import email.parser
 import email.policy
 import email.quoprimime
 import email.utils
-import tempfile
+import fnmatch
+import hashlib
+import io
+import json
+import logging
+import mailbox
+import os
 import pathlib
-import argparse
-import smtplib
+import pwd
+import re
 import shlex
+import shutil
+import smtplib
+import subprocess
+import sys
+import tempfile
 import textwrap
-import json
-
-import urllib.parse
-import datetime
 import time
-import copy
-import shutil
-import mailbox
-import pwd
-import io
+import urllib.parse
+from contextlib import contextmanager
+from email import charset
+from email.message import EmailMessage
+from pathlib import Path
+from typing import (
+    Any,
+    BinaryIO,
+    Dict,
+    Generator,
+    Iterator,
+    List,
+    Literal,
+    Optional,
+    Sequence,
+    Set,
+    Tuple,
+    TypeVar,
+    Union,
+    overload,
+)
 
+import liblore.utils
 import requests
 
 import liblore
-import liblore.utils
-
-from pathlib import Path
-from contextlib import contextmanager
-from typing import Optional, Tuple, Set, List, BinaryIO, Union, Sequence, Literal, Iterator, Dict, \
-    TypeVar, overload, Generator, Any
 
 ConfigDictT = Dict[str, Union[str, List[str], None]]
 
-from email.message import EmailMessage
-
-from email import charset
 
 charset.add_charset('utf-8', None)
 # Policy we use for saving mail locally
diff --git a/src/b4/bugs/__init__.py b/src/b4/bugs/__init__.py
index dd28b5a..cb21611 100644
--- a/src/b4/bugs/__init__.py
+++ b/src/b4/bugs/__init__.py
@@ -4,15 +4,15 @@
 # Copyright (C) 2020 by the Linux Foundation
 """b4 bugs: manage bug reports from mailing list threads."""
 import argparse
+import json
 import logging
+import shutil
 import sys
 
-import json
-import shutil
+from ezgb._git import git_bug_cli
 
 import b4
 from ezgb import BugNotFoundError, GitBugRepo, Status
-from ezgb._git import git_bug_cli
 
 logger = logging.getLogger('b4')
 
@@ -160,7 +160,7 @@ def cmd_list(cmdargs: argparse.Namespace) -> None:
 
     for bug in bugs:
         icon = '\u25cf' if bug.status == Status.OPEN else '\u25cb'
-        labels = ' '.join(f'[{l}]' for l in sorted(bug.labels))
+        labels = ' '.join(f'[{label}]' for label in sorted(bug.labels))
         logger.info('%s %s  %s  %s',
                     icon, bug.id[:7], bug.title, labels)
 
diff --git a/src/b4/bugs/_tui.py b/src/b4/bugs/_tui.py
index 0309a0c..998e6bb 100644
--- a/src/b4/bugs/_tui.py
+++ b/src/b4/bugs/_tui.py
@@ -14,14 +14,13 @@ from typing import TYPE_CHECKING, Optional, Union
 if TYPE_CHECKING:
     from textual.events import Key
 
-from textual.events import Click, MouseScrollDown, MouseScrollUp
-
 from rich import box
 from rich.panel import Panel
 from rich.text import Text
 from textual.app import App, ComposeResult
 from textual.binding import Binding
 from textual.containers import Horizontal, Vertical
+from textual.events import Click, MouseScrollDown, MouseScrollUp
 from textual.screen import ModalScreen
 from textual.suggester import SuggestFromList
 from textual.widgets import (
@@ -34,6 +33,8 @@ from textual.widgets import (
 )
 from textual.worker import Worker, WorkerState
 
+import b4
+from b4.bugs._import import is_comment_removed, make_tombstone, parse_comment_header
 from b4.tui import (
     ActionScreen,
     ConfirmScreen,
@@ -47,8 +48,6 @@ from b4.tui import (
     resolve_styles,
     reviewer_colours,
 )
-import b4
-from b4.bugs._import import is_comment_removed, make_tombstone, parse_comment_header
 from ezgb import Bug, BugSummary, Comment, GitBugRepo, Status
 
 # Union type for items that can appear in the bug list.
diff --git a/src/b4/command.py b/src/b4/command.py
index 79ec596..7ebe79c 100644
--- a/src/b4/command.py
+++ b/src/b4/command.py
@@ -7,11 +7,11 @@ __author__ = 'Konstantin Ryabitsev <[email protected]>'
 
 import argparse
 import logging
-import b4
 import sys
-
 from typing import Any, Optional, Sequence, Union
 
+import b4
+
 logger = b4.logger
 
 
diff --git a/src/b4/diff.py b/src/b4/diff.py
index 934b9ac..8045243 100644
--- a/src/b4/diff.py
+++ b/src/b4/diff.py
@@ -5,19 +5,19 @@
 #
 __author__ = 'Konstantin Ryabitsev <[email protected]>'
 
-import os
-import sys
-import b4
-import b4.mbox
+import argparse
 import email
 import email.parser
-import shutil
+import os
 import pathlib
-import argparse
 import shlex
-
-from typing import Tuple, Optional, List
+import shutil
+import sys
 from email.message import EmailMessage
+from typing import List, Optional, Tuple
+
+import b4
+import b4.mbox
 
 logger = b4.logger
 
diff --git a/src/b4/dig.py b/src/b4/dig.py
index f13deac..b3d637d 100644
--- a/src/b4/dig.py
+++ b/src/b4/dig.py
@@ -5,19 +5,18 @@
 #
 __author__ = 'Konstantin Ryabitsev <[email protected]>'
 
-import sys
-import b4
 import argparse
+import datetime
+import email.utils
 import re
+import sys
 import urllib.parse
-import datetime
+from email.message import EmailMessage
+from typing import List, Optional, Set
 
+import b4
 import b4.mbox
 
-from email.message import EmailMessage
-import email.utils
-from typing import List, Set, Optional
-
 logger = b4.logger
 
 # Supported diff algorithms we will try to match
diff --git a/src/b4/ez.py b/src/b4/ez.py
index 94b8686..e69a106 100644
--- a/src/b4/ez.py
+++ b/src/b4/ez.py
@@ -5,31 +5,31 @@
 #
 __author__ = 'Konstantin Ryabitsev <[email protected]>'
 
-import os
-import sys
-import b4
-import re
 import argparse
-import uuid
-import time
+import base64
 import datetime
-import json
-import shlex
 import email
 import email.policy
 import email.utils
-import pathlib
-import base64
-import textwrap
 import gzip
+import hashlib
 import io
+import json
+import os
+import pathlib
+import re
+import shlex
+import sys
 import tarfile
-import hashlib
+import textwrap
+import time
 import urllib.parse
-
-from typing import Any, Optional, Tuple, List, Union, Dict, Set
-from string import Template
+import uuid
 from email.message import EmailMessage
+from string import Template
+from typing import Any, Dict, List, Optional, Set, Tuple, Union
+
+import b4
 
 try:
     import patatt
@@ -44,6 +44,7 @@ except ModuleNotFoundError:
     can_gfr = False
 
 import importlib.util
+
 can_codespell = importlib.util.find_spec('codespell_lib') is not None
 
 logger = b4.logger
@@ -216,8 +217,8 @@ def auth_new() -> None:
             sys.exit(1)
         pubkey = out.decode()
     elif algo == 'ed25519':
-        from nacl.signing import SigningKey
         from nacl.encoding import Base64Encoder
+        from nacl.signing import SigningKey
         sk = SigningKey(keydata.encode(), encoder=Base64Encoder)
         pubkey = base64.b64encode(sk.verify_key.encode()).decode()
     else:
diff --git a/src/b4/kr.py b/src/b4/kr.py
index 13f24c7..8bbfe26 100644
--- a/src/b4/kr.py
+++ b/src/b4/kr.py
@@ -7,9 +7,9 @@ __author__ = 'Konstantin Ryabitsev <[email protected]>'
 
 import argparse
 import os
-import sys
 import pathlib
 import re
+import sys
 
 import b4
 
diff --git a/src/b4/mbox.py b/src/b4/mbox.py
index 2164fcc..624a2f3 100644
--- a/src/b4/mbox.py
+++ b/src/b4/mbox.py
@@ -5,28 +5,26 @@
 #
 __author__ = 'Konstantin Ryabitsev <[email protected]>'
 
-import os
-import sys
-import mailbox
+import argparse
 import email
-import email.utils
 import email.parser
-import re
-import time
-import json
+import email.utils
 import fnmatch
-import shutil
-import pathlib
 import io
+import json
+import mailbox
+import os
+import pathlib
+import re
 import shlex
-import argparse
-
-import b4
-
-from typing import Any, Optional, Union, List, Set, Dict, Tuple
+import shutil
+import sys
+import time
+from email.message import EmailMessage
 from string import Template
+from typing import Any, Dict, List, Optional, Set, Tuple, Union
 
-from email.message import EmailMessage
+import b4
 
 logger = b4.logger
 
diff --git a/src/b4/pr.py b/src/b4/pr.py
index 5969a0d..cb2ca76 100644
--- a/src/b4/pr.py
+++ b/src/b4/pr.py
@@ -5,26 +5,24 @@
 #
 __author__ = 'Konstantin Ryabitsev <[email protected]>'
 
-import os
-import sys
-import tempfile
-
-import b4
-import re
-import json
+import argparse
 import email
 import email.message
 import email.parser
 import email.utils
-import argparse
-
+import json
+import os
+import re
+import sys
+import tempfile
 import urllib.parse
-import requests
-
 from datetime import datetime, timezone
+from email import charset, utils
+from typing import List, Optional
 
-from email import utils, charset
-from typing import Optional, List
+import requests
+
+import b4
 
 charset.add_charset('utf-8', None)
 
diff --git a/src/b4/review/__init__.py b/src/b4/review/__init__.py
index 4f64451..df1e39b 100644
--- a/src/b4/review/__init__.py
+++ b/src/b4/review/__init__.py
@@ -1,21 +1,30 @@
 # Re-export everything from the original review module
 from b4.review._review import *  # noqa: F403
 from b4.review._review import (
-    _retrieve_messages, retrieve_series_messages, _get_lore_series,
-    _collect_followups, _collect_reply_headers,
-    _get_my_review, _ensure_my_review, _cleanup_review,
-    _get_patch_state, _set_patch_state,
-    _resolve_comment_positions,
-    _render_quoted_diff_with_comments, _extract_editor_comments,
-    _clear_other_comments, _strip_subject,
-    _build_reply_from_comments, _ensure_trailers_in_body,
+    _build_reply_from_comments,
     _build_review_email,
-    _integrate_agent_reviews,
+    _cleanup_review,
+    _clear_other_comments,
+    _collect_followups,
+    _collect_reply_headers,
+    _ensure_my_review,
+    _ensure_trailers_in_body,
     _extract_comments_from_quoted_reply,
-    _integrate_sashiko_reviews,
+    _extract_editor_comments,
+    _get_lore_series,
+    _get_my_review,
+    _get_patch_state,
+    _integrate_agent_reviews,
     _integrate_followup_inline_comments,
+    _integrate_sashiko_reviews,
     _prepare_review_session,
+    _render_quoted_diff_with_comments,
+    _resolve_comment_positions,
+    _retrieve_messages,
+    _set_patch_state,
     _should_promote_waiting,
+    _strip_subject,
+    retrieve_series_messages,
 )
 
 # Tell mypy these private symbols are intentionally re-exported
diff --git a/src/b4/review/_review.py b/src/b4/review/_review.py
index 1234b0f..661369b 100644
--- a/src/b4/review/_review.py
+++ b/src/b4/review/_review.py
@@ -15,6 +15,7 @@ import re
 import shutil
 import sys
 import urllib.parse
+from typing import Any, Dict, List, Optional, Set, Tuple, Union
 
 import liblore.utils
 
@@ -22,8 +23,6 @@ import b4
 import b4.mbox
 import b4.review.tracking
 
-from typing import Dict, Any, List, Optional, Set, Tuple, Union
-
 logger = b4.logger
 
 REVIEW_MAGIC_MARKER = '--- b4-review-tracking ---'
diff --git a/src/b4/review/checks.py b/src/b4/review/checks.py
index 65ee0ca..2ea5027 100644
--- a/src/b4/review/checks.py
+++ b/src/b4/review/checks.py
@@ -12,7 +12,6 @@ import os
 import pathlib
 import shlex
 import sqlite3
-
 from email.message import EmailMessage
 from typing import Any, Dict, List, Optional, Tuple
 
diff --git a/src/b4/review/messages.py b/src/b4/review/messages.py
index 344d36b..3a0098c 100644
--- a/src/b4/review/messages.py
+++ b/src/b4/review/messages.py
@@ -8,7 +8,6 @@ __author__ = 'Konstantin Ryabitsev <[email protected]>'
 import os
 import pathlib
 import sqlite3
-
 from typing import Dict, List, Optional
 
 import b4
diff --git a/src/b4/review/tracking.py b/src/b4/review/tracking.py
index cd966ca..eb80eea 100644
--- a/src/b4/review/tracking.py
+++ b/src/b4/review/tracking.py
@@ -13,13 +13,11 @@ import os
 import pathlib
 import sqlite3
 import sys
-
-import liblore
+from typing import Any, Dict, List, Optional, Set, Tuple
 
 import b4
 import b4.mbox
-
-from typing import Any, Dict, List, Optional, Set, Tuple
+import liblore
 
 logger = b4.logger
 
@@ -1129,6 +1127,7 @@ def _store_thread_blob(topdir: str, change_id: str,
     # Local import first — avoids circular deps AND prevents UnboundLocalError
     # that would occur if `import b4.review` appeared after a `b4.xxx` call.
     import io
+
     import b4.review as _b4_review
 
     buf = io.BytesIO()
diff --git a/src/b4/review_tui/__init__.py b/src/b4/review_tui/__init__.py
index 47f3f93..68548e6 100644
--- a/src/b4/review_tui/__init__.py
+++ b/src/b4/review_tui/__init__.py
@@ -1,15 +1,21 @@
 from b4.review_tui._common import (
-    logger, PATCH_STATE_MARKERS,
-    resolve_styles, reviewer_colours,
+    PATCH_STATE_MARKERS,
+    _addrs_to_lines,
+    _lines_to_header,
+    _validate_addrs,
     gather_attestation_info,
-    _addrs_to_lines, _lines_to_header, _validate_addrs,
+    logger,
+    resolve_styles,
+    reviewer_colours,
 )
-from b4.review_tui._review_app import ReviewApp
-from b4.review_tui._tracking_app import TrackingApp
-from b4.review_tui._pw_app import PwApp
 from b4.review_tui._entry import (
-    run_branch_tui, run_pw_tui, run_tracking_tui,
+    run_branch_tui,
+    run_pw_tui,
+    run_tracking_tui,
 )
+from b4.review_tui._pw_app import PwApp
+from b4.review_tui._review_app import ReviewApp
+from b4.review_tui._tracking_app import TrackingApp
 
 __all__ = [
     'logger', 'PATCH_STATE_MARKERS',
diff --git a/src/b4/review_tui/_common.py b/src/b4/review_tui/_common.py
index 9c06b32..e819af5 100644
--- a/src/b4/review_tui/_common.py
+++ b/src/b4/review_tui/_common.py
@@ -12,22 +12,73 @@ import email.utils
 import json
 import os
 import tempfile
-
 from typing import Any, Dict, List, Optional, Set, Tuple
 
 import liblore.utils
+from rich import box
+from rich.padding import Padding
+from rich.panel import Panel
+from rich.rule import Rule
+from rich.text import Text
+from textual.widgets import RichLog
 
 import b4
 import b4.mbox
 import b4.review
 import b4.review.tracking
 
-from textual.widgets import RichLog
-from rich import box
-from rich.padding import Padding
-from rich.panel import Panel
-from rich.rule import Rule
-from rich.text import Text
+# -- Re-exported from b4.tui (canonical home for shared TUI utilities) --------
+from b4.tui._common import (
+    JKListNavMixin as JKListNavMixin,
+)
+from b4.tui._common import (
+    SeparatedFooter as SeparatedFooter,
+)
+from b4.tui._common import (
+    _addrs_to_lines as _addrs_to_lines,
+)
+from b4.tui._common import (
+    _fix_ansi_theme as _fix_ansi_theme,
+)
+from b4.tui._common import (
+    _lines_to_header as _lines_to_header,
+)
+from b4.tui._common import (
+    _quiet_worker as _quiet_worker,
+)
+from b4.tui._common import (
+    _suspend_to_shell as _suspend_to_shell,
+)
+from b4.tui._common import (
+    _to_rich_color as _to_rich_color,
+)
+from b4.tui._common import (
+    _validate_addrs as _validate_addrs,
+)
+from b4.tui._common import (
+    _wait_for_enter as _wait_for_enter,
+)
+from b4.tui._common import (
+    ci_check_styles as ci_check_styles,
+)
+from b4.tui._common import (
+    ci_markup as ci_markup,
+)
+from b4.tui._common import (
+    ci_styles as ci_styles,
+)
+from b4.tui._common import (
+    display_width as display_width,
+)
+from b4.tui._common import (
+    pad_display as pad_display,
+)
+from b4.tui._common import (
+    resolve_styles as resolve_styles,
+)
+from b4.tui._common import (
+    reviewer_colours as reviewer_colours,
+)
 
 logger = b4.logger
 
@@ -75,26 +126,6 @@ CI_CHECK_LABELS = {
 }
 
 
-# -- Re-exported from b4.tui (canonical home for shared TUI utilities) --------
-from b4.tui._common import (
-    JKListNavMixin as JKListNavMixin,
-    SeparatedFooter as SeparatedFooter,
-    _addrs_to_lines as _addrs_to_lines,
-    _fix_ansi_theme as _fix_ansi_theme,
-    _lines_to_header as _lines_to_header,
-    _quiet_worker as _quiet_worker,
-    _suspend_to_shell as _suspend_to_shell,
-    _to_rich_color as _to_rich_color,
-    _validate_addrs as _validate_addrs,
-    _wait_for_enter as _wait_for_enter,
-    ci_check_styles as ci_check_styles,
-    ci_markup as ci_markup,
-    ci_styles as ci_styles,
-    display_width as display_width,
-    pad_display as pad_display,
-    resolve_styles as resolve_styles,
-    reviewer_colours as reviewer_colours,
-)
 
 
 class CheckRunnerMixin:
diff --git a/src/b4/review_tui/_entry.py b/src/b4/review_tui/_entry.py
index 717d1eb..68a48af 100644
--- a/src/b4/review_tui/_entry.py
+++ b/src/b4/review_tui/_entry.py
@@ -10,11 +10,10 @@ from typing import Any, Dict, Optional
 import b4
 import b4.review
 import b4.review.tracking
-
 from b4.review_tui._common import logger
+from b4.review_tui._pw_app import PwApp
 from b4.review_tui._review_app import ReviewApp
 from b4.review_tui._tracking_app import TrackingApp
-from b4.review_tui._pw_app import PwApp
 
 
 def _tui_use_mouse() -> bool:
diff --git a/src/b4/review_tui/_lite_app.py b/src/b4/review_tui/_lite_app.py
index 7474927..7a37e0d 100644
--- a/src/b4/review_tui/_lite_app.py
+++ b/src/b4/review_tui/_lite_app.py
@@ -6,14 +6,10 @@
 __author__ = 'Konstantin Ryabitsev <[email protected]>'
 
 import email.utils
-
 from dataclasses import dataclass, field
 from typing import Any, Dict, List, Optional
 
-import b4
-import b4.review
-import b4.review.tracking
-
+from rich.text import Text
 from textual.app import ComposeResult
 from textual.binding import Binding
 from textual.containers import Vertical
@@ -21,11 +17,16 @@ from textual.screen import ModalScreen
 from textual.widgets import Label, ListItem, ListView, LoadingIndicator, RichLog, Static
 from textual.worker import Worker, WorkerState
 
-from rich.text import Text
-
+import b4
+import b4.review
+import b4.review.tracking
 from b4.review_tui._common import (
-    resolve_styles, _quiet_worker, _fix_ansi_theme,
-    _write_diff_line, display_width, pad_display,
+    _fix_ansi_theme,
+    _quiet_worker,
+    _write_diff_line,
+    display_width,
+    pad_display,
+    resolve_styles,
 )
 from b4.review_tui._modals import FollowupReplyPreviewScreen
 
diff --git a/src/b4/review_tui/_modals.py b/src/b4/review_tui/_modals.py
index 31b05d3..412419a 100644
--- a/src/b4/review_tui/_modals.py
+++ b/src/b4/review_tui/_modals.py
@@ -10,29 +10,50 @@ import email.utils
 import io
 import json
 import re
-
 from typing import Any, Dict, List, Optional, Tuple
 
-import b4
-
+from rich import box
+from rich.panel import Panel
+from rich.rule import Rule
+from rich.text import Text
 from textual.app import ComposeResult
 from textual.binding import Binding
 from textual.containers import Vertical
-from textual.widgets import Checkbox, Input, Label, ListItem, ListView, LoadingIndicator, ProgressBar, RichLog, Select, Static
 from textual.screen import ModalScreen
 from textual.suggester import SuggestFromList
+from textual.widgets import (
+    Checkbox,
+    Input,
+    Label,
+    ListItem,
+    ListView,
+    LoadingIndicator,
+    ProgressBar,
+    RichLog,
+    Select,
+    Static,
+)
 from textual.worker import Worker, WorkerState
-from rich import box
-from rich.panel import Panel
-from rich.rule import Rule
-from rich.text import Text
 
+import b4
 from b4.review_tui._common import (
-    CI_CHECK_LABELS, resolve_styles, ci_check_styles,
-    JKListNavMixin, logger,
-    _write_diff_line, _quiet_worker, _render_email_to_viewer,
+    CI_CHECK_LABELS,
+    JKListNavMixin,
+    _quiet_worker,
+    _render_email_to_viewer,
+    _write_diff_line,
+    ci_check_styles,
+    logger,
+    resolve_styles,
 )
 
+# Re-exported from b4.tui (canonical home for shared modals)
+from b4.tui._modals import ActionItem as ActionItem
+from b4.tui._modals import ActionScreen as ActionScreen
+from b4.tui._modals import ConfirmScreen as ConfirmScreen
+from b4.tui._modals import LimitScreen as LimitScreen
+from b4.tui._modals import ToCcScreen as ToCcScreen
+
 
 class TrailerOption(ListItem):
     """A toggleable trailer option in the trailer selection dialog."""
@@ -540,14 +561,6 @@ class FollowupReplyPreviewScreen(ModalScreen[Optional[str]]):
         self.dismiss(None)
 
 
-# Re-exported from b4.tui (canonical home for shared modals)
-from b4.tui._modals import ToCcScreen as ToCcScreen
-from b4.tui._modals import ConfirmScreen as ConfirmScreen
-from b4.tui._modals import LimitScreen as LimitScreen
-from b4.tui._modals import ActionItem as ActionItem
-from b4.tui._modals import ActionScreen as ActionScreen
-
-
 class SendScreen(ModalScreen[bool]):
     """Modal confirmation screen showing a summary of emails to send."""
 
diff --git a/src/b4/review_tui/_pw_app.py b/src/b4/review_tui/_pw_app.py
index cfc0b11..2b0c10a 100644
--- a/src/b4/review_tui/_pw_app.py
+++ b/src/b4/review_tui/_pw_app.py
@@ -7,24 +7,32 @@ __author__ = 'Konstantin Ryabitsev <[email protected]>'
 
 import json
 import pathlib
-
 from typing import Any, Dict, List, Optional, Set, Tuple
 
-import b4
-import b4.review
-import b4.review.tracking
-
+from rich.text import Text
 from textual.app import App, ComposeResult
 from textual.binding import Binding
 from textual.widgets import Footer, Label, ListItem, ListView, LoadingIndicator, Static
 from textual.worker import Worker, WorkerState
 
-from rich.text import Text
-
-from b4.review_tui._common import resolve_styles, ci_styles, logger, SeparatedFooter, _fix_ansi_theme, pad_display
+import b4
+import b4.review
+import b4.review.tracking
+from b4.review_tui._common import (
+    SeparatedFooter,
+    _fix_ansi_theme,
+    ci_styles,
+    logger,
+    pad_display,
+    resolve_styles,
+)
 from b4.review_tui._modals import (
-    CIChecksScreen, SetStateScreen, ApplyStateModal,
-    LimitScreen, HelpScreen, PW_HELP_LINES,
+    PW_HELP_LINES,
+    ApplyStateModal,
+    CIChecksScreen,
+    HelpScreen,
+    LimitScreen,
+    SetStateScreen,
 )
 
 
diff --git a/src/b4/review_tui/_review_app.py b/src/b4/review_tui/_review_app.py
index 7807064..51004de 100644
--- a/src/b4/review_tui/_review_app.py
+++ b/src/b4/review_tui/_review_app.py
@@ -10,39 +10,56 @@ import email.utils
 import os
 import re
 import subprocess
-
 from typing import Any, Dict, List, Optional, Set, Tuple
 
-import b4
-import b4.mbox
-import b4.review
-import b4.review.tracking
-
+from rich.rule import Rule
+from rich.syntax import Syntax
+from rich.text import Text
 from textual.app import App, ComposeResult
 from textual.binding import Binding
 from textual.containers import Horizontal, Vertical
 from textual.events import Click
 from textual.widgets import Label, ListItem, ListView, RichLog, Static
-from rich.rule import Rule
-from rich.syntax import Syntax
-from rich.text import Text
 
+import b4
+import b4.mbox
+import b4.review
+import b4.review.tracking
+from b4.review._review import COMMIT_MESSAGE_PATH
 from b4.review_tui._common import (
-    logger, PATCH_STATE_MARKERS,
-    resolve_styles, reviewer_colours, CheckRunnerMixin,
-    _quiet_worker, get_thread_msgs,
-    _has_review_data, _make_initials, _wait_for_enter,
-    _write_comments, _write_followup_comments,
-    _write_followup_trailers, _resolve_patch_for_followup, _chain_has_additional_patch,
-    _get_followup_depth, _render_email_to_viewer,
-    _suspend_to_shell, SeparatedFooter, _fix_ansi_theme,
+    PATCH_STATE_MARKERS,
+    CheckRunnerMixin,
+    SeparatedFooter,
+    _chain_has_additional_patch,
+    _fix_ansi_theme,
+    _get_followup_depth,
+    _has_review_data,
+    _make_initials,
+    _quiet_worker,
+    _render_email_to_viewer,
+    _resolve_patch_for_followup,
+    _suspend_to_shell,
+    _wait_for_enter,
+    _write_comments,
+    _write_followup_comments,
+    _write_followup_trailers,
+    get_thread_msgs,
+    logger,
+    resolve_styles,
+    reviewer_colours,
 )
 from b4.review_tui._modals import (
-    TrailerScreen, HelpScreen, _review_help_lines,
-    NoteScreen, PriorReviewScreen, ToCcScreen, SendScreen,
     FollowupReplyPreviewScreen,
+    HelpScreen,
+    NoteScreen,
+    PriorReviewScreen,
+    SendScreen,
+    ToCcScreen,
+    TrailerScreen,
+    _review_help_lines,
 )
 
+
 class PatchListItem(ListItem):
     """A single entry in the patch list."""
 
@@ -92,7 +109,6 @@ class FollowupItem(ListItem):
 
 
 
-from b4.review._review import COMMIT_MESSAGE_PATH
 
 
 class ReviewApp(CheckRunnerMixin, App[None]):
diff --git a/src/b4/review_tui/_tracking_app.py b/src/b4/review_tui/_tracking_app.py
index 1828bac..2493cda 100644
--- a/src/b4/review_tui/_tracking_app.py
+++ b/src/b4/review_tui/_tracking_app.py
@@ -17,15 +17,9 @@ import os
 import pathlib
 import re
 import sqlite3
-
 from string import Template
 from typing import Any, Dict, List, Literal, Optional, Tuple
 
-import b4
-import b4.mbox
-import b4.review
-import b4.review.tracking
-
 from rich.text import Text as RichText
 from textual.app import App, ComposeResult
 from textual.binding import Binding
@@ -33,19 +27,46 @@ from textual.containers import Horizontal, Vertical
 from textual.css.query import NoMatches
 from textual.widgets import Footer, Label, ListItem, ListView, Static
 from textual.worker import Worker, WorkerState
+
+import b4
+import b4.mbox
+import b4.review
+import b4.review.tracking
 from b4.review_tui._common import (
-    logger, resolve_styles, _wait_for_enter, _suspend_to_shell,
-    SeparatedFooter, _quiet_worker, CheckRunnerMixin,
-    _fix_ansi_theme, display_width, pad_display,
+    CheckRunnerMixin,
+    SeparatedFooter,
+    _fix_ansi_theme,
+    _quiet_worker,
+    _suspend_to_shell,
+    _wait_for_enter,
+    display_width,
+    logger,
+    pad_display,
+    resolve_styles,
 )
 from b4.review_tui._modals import (
-    BaseSelectionScreen, WorkerScreen, TakeScreen, TakeConfirmScreen,
-    CherryPickScreen, NewerRevisionWarningScreen,
-    RevisionChoiceScreen, RebaseScreen, TargetBranchScreen,
+    TRACKING_HELP_LINES,
     AbandonConfirmScreen,
-    ArchiveConfirmScreen, RangeDiffScreen, ThankScreen, QueueScreen, QueueDeliveryScreen,
-    LimitScreen, UpdateRevisionScreen, UpdateAllScreen,
-    ActionScreen, HelpScreen, SnoozeScreen, TRACKING_HELP_LINES,
+    ActionScreen,
+    ArchiveConfirmScreen,
+    BaseSelectionScreen,
+    CherryPickScreen,
+    HelpScreen,
+    LimitScreen,
+    NewerRevisionWarningScreen,
+    QueueDeliveryScreen,
+    QueueScreen,
+    RangeDiffScreen,
+    RebaseScreen,
+    RevisionChoiceScreen,
+    SnoozeScreen,
+    TakeConfirmScreen,
+    TakeScreen,
+    TargetBranchScreen,
+    ThankScreen,
+    UpdateAllScreen,
+    UpdateRevisionScreen,
+    WorkerScreen,
 )
 
 # Shortcut keys for the tracking-app action selector.
@@ -3725,6 +3746,7 @@ class TrackingApp(CheckRunnerMixin, App[Optional[str]]):
         """
         import tarfile
         import time
+
         import b4.ez
 
         topdir = b4.git_get_toplevel()
@@ -3823,6 +3845,7 @@ class TrackingApp(CheckRunnerMixin, App[Optional[str]]):
     def action_thank(self) -> None:
         """Compose and preview a thank-you reply for a taken series."""
         import argparse
+
         import b4.review
         import b4.ty
 
diff --git a/src/b4/tui/_common.py b/src/b4/tui/_common.py
index f03976d..8eb6c45 100644
--- a/src/b4/tui/_common.py
+++ b/src/b4/tui/_common.py
@@ -11,18 +11,16 @@ import os
 import subprocess
 import tempfile
 import unicodedata
-
-from typing import Any, Dict, List, Optional
-
-import b4
-
 from collections import defaultdict
+from typing import Any, Dict, List, Optional
 
 from textual.app import ComposeResult
 from textual.binding import Binding
 from textual.widgets import Footer, ListView
 from textual.widgets._footer import FooterKey
 
+import b4
+
 logger = b4.logger
 
 
diff --git a/src/b4/tui/_modals.py b/src/b4/tui/_modals.py
index 46025f4..15f2e3b 100644
--- a/src/b4/tui/_modals.py
+++ b/src/b4/tui/_modals.py
@@ -6,7 +6,7 @@
 """Shared modal screens for b4 Textual apps."""
 __author__ = 'Konstantin Ryabitsev <[email protected]>'
 
-from typing import Dict, List, Optional, Tuple, TYPE_CHECKING
+from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
 
 if TYPE_CHECKING:
     from textual.events import Key
@@ -14,10 +14,15 @@ if TYPE_CHECKING:
 from textual.app import ComposeResult
 from textual.binding import Binding
 from textual.containers import Vertical
-from textual.widgets import Checkbox, Input, Label, ListItem, ListView, Static, TextArea
 from textual.screen import ModalScreen
+from textual.widgets import Checkbox, Input, Label, ListItem, ListView, Static, TextArea
 
-from b4.tui._common import JKListNavMixin, _addrs_to_lines, _lines_to_header, _validate_addrs
+from b4.tui._common import (
+    JKListNavMixin,
+    _addrs_to_lines,
+    _lines_to_header,
+    _validate_addrs,
+)
 
 
 class ToCcScreen(ModalScreen[bool]):
diff --git a/src/b4/ty.py b/src/b4/ty.py
index b429566..5786222 100644
--- a/src/b4/ty.py
+++ b/src/b4/ty.py
@@ -5,23 +5,20 @@
 #
 __author__ = 'Konstantin Ryabitsev <[email protected]>'
 
-import os
-import sys
-
-import b4
-import re
+import argparse
 import email
 import email.parser
 import email.utils
 import json
-import argparse
-
-from string import Template
-from pathlib import Path
-
+import os
+import re
+import sys
 from email.message import EmailMessage
+from pathlib import Path
+from string import Template
+from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union, cast
 
-from typing import Callable, cast, Optional, Set, Tuple, Union, List, Dict, Any
+import b4
 
 ConfigDictT = b4.ConfigDictT
 JsonDictT = Dict[str, Union[str, int, List[Any], Dict[str, Any]]]
diff --git a/src/tests/conftest.py b/src/tests/conftest.py
index f42ade4..d8cd853 100644
--- a/src/tests/conftest.py
+++ b/src/tests/conftest.py
@@ -1,11 +1,12 @@
-import pytest
-import b4
 import os
 import pathlib
 import sys
-
 from typing import Generator
 
+import pytest
+
+import b4
+
 
 @pytest.fixture(scope="function", autouse=True)
 def settestdefaults(tmp_path: pathlib.Path) -> None:
diff --git a/src/tests/test___init__.py b/src/tests/test___init__.py
index 362643a..faf5c96 100644
--- a/src/tests/test___init__.py
+++ b/src/tests/test___init__.py
@@ -1,14 +1,15 @@
-import pytest
-import b4
-import os
 import email
 import email.parser
 import io
+import os
 import pathlib
 import socket
-
 from typing import Any, Dict, List, Literal, Optional, Tuple
 
+import pytest
+
+import b4
+
 
 @pytest.mark.parametrize('source,expected', [
     ('good-valid-trusted', (True, True, True, 'B6C41CE35664996C', '1623274836')),
@@ -679,8 +680,9 @@ class TestGetLoreNode:
 
     def test_uses_from_git_config(self, monkeypatch: pytest.MonkeyPatch) -> None:
         """get_lore_node() constructs via LoreNode.from_git_config()."""
-        import liblore
         from unittest.mock import MagicMock
+
+        import liblore
         mock_node = MagicMock()
         mock_from_gc = MagicMock(return_value=mock_node)
         monkeypatch.setattr(liblore.LoreNode, 'from_git_config', mock_from_gc)
@@ -690,8 +692,9 @@ class TestGetLoreNode:
 
     def test_sets_user_agent(self, monkeypatch: pytest.MonkeyPatch) -> None:
         """get_lore_node() calls set_user_agent with b4's identity."""
-        import liblore
         from unittest.mock import MagicMock
+
+        import liblore
         mock_node = MagicMock()
         monkeypatch.setattr(liblore.LoreNode, 'from_git_config', MagicMock(return_value=mock_node))
         b4.get_lore_node()
@@ -699,8 +702,9 @@ class TestGetLoreNode:
 
     def test_does_not_inject_session(self, monkeypatch: pytest.MonkeyPatch) -> None:
         """get_lore_node() lets liblore own its session."""
-        import liblore
         from unittest.mock import MagicMock
+
+        import liblore
         mock_node = MagicMock()
         monkeypatch.setattr(liblore.LoreNode, 'from_git_config', MagicMock(return_value=mock_node))
         b4.get_lore_node()
@@ -708,8 +712,9 @@ class TestGetLoreNode:
 
     def test_passes_cache_settings(self, monkeypatch: pytest.MonkeyPatch) -> None:
         """cache_dir and cache_ttl from b4 config are passed through."""
-        import liblore
         from unittest.mock import MagicMock
+
+        import liblore
         b4.MAIN_CONFIG['cache-expire'] = '5'
         mock_node = MagicMock()
         mock_from_gc = MagicMock(return_value=mock_node)
@@ -721,8 +726,9 @@ class TestGetLoreNode:
 
     def test_singleton(self, monkeypatch: pytest.MonkeyPatch) -> None:
         """Repeated calls return the same LoreNode instance."""
-        import liblore
         from unittest.mock import MagicMock
+
+        import liblore
         mock_node = MagicMock()
         mock_from_gc = MagicMock(return_value=mock_node)
         monkeypatch.setattr(liblore.LoreNode, 'from_git_config', mock_from_gc)
diff --git a/src/tests/test_ez.py b/src/tests/test_ez.py
index 7e67a0b..ef21985 100644
--- a/src/tests/test_ez.py
+++ b/src/tests/test_ez.py
@@ -1,12 +1,13 @@
-import pytest
 import os
+from typing import Any, Dict, Generator, List, Optional, Tuple
+from unittest.mock import MagicMock, patch
+
+import pytest
+
 import b4
+import b4.command
 import b4.ez
 import b4.mbox
-import b4.command
-
-from typing import Any, Dict, Generator, List, Optional, Tuple
-from unittest.mock import MagicMock, patch
 
 
 @pytest.fixture(scope="function")
diff --git a/src/tests/test_mbox.py b/src/tests/test_mbox.py
index b3c0536..b533421 100644
--- a/src/tests/test_mbox.py
+++ b/src/tests/test_mbox.py
@@ -1,13 +1,14 @@
-import pytest
 import os
-import b4
-import b4.mbox
-import b4.command
-
 from email.message import EmailMessage
 from typing import Any, Dict, List
 from unittest.mock import patch as mock_patch
 
+import pytest
+
+import b4
+import b4.command
+import b4.mbox
+
 
 @pytest.mark.parametrize('mboxf, shazamargs, compareargs, compareout, b4cfg', [
     ('shazam-git1-just-series', [],
diff --git a/src/tests/test_patatt.py b/src/tests/test_patatt.py
index 592d546..c257d41 100644
--- a/src/tests/test_patatt.py
+++ b/src/tests/test_patatt.py
@@ -10,12 +10,11 @@ from collections.abc import Generator
 from typing import Tuple, Union
 
 import pytest
+from nacl.signing import SigningKey
 
 import b4
 import patatt
 
-from nacl.signing import SigningKey
-
 
 @pytest.fixture()
 def ed25519_keypair() -> Generator[Tuple[str, str, str, str], None, None]:
diff --git a/src/tests/test_rethread.py b/src/tests/test_rethread.py
index 1cf5239..f2a0394 100644
--- a/src/tests/test_rethread.py
+++ b/src/tests/test_rethread.py
@@ -1,10 +1,10 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 # Copyright (C) 2020 by the Linux Foundation
-import b4
 import email.message
+from typing import List, Optional, Tuple
 from unittest import mock
 
-from typing import List, Optional, Tuple
+import b4
 
 
 # ---------------------------------------------------------------------------
diff --git a/src/tests/test_review.py b/src/tests/test_review.py
index 1523618..92ebe57 100644
--- a/src/tests/test_review.py
+++ b/src/tests/test_review.py
@@ -6,11 +6,9 @@ from unittest import mock
 import pytest
 
 import b4
-from b4 import review
-from b4 import review_tui
+from b4 import review, review_tui
 from b4.review._review import REVIEW_MAGIC_MARKER, check_series_attestation
 
-
 # -- Helper diffs used across tests ------------------------------------------
 
 # A minimal single-file, single-hunk diff
@@ -140,7 +138,7 @@ class TestRenderQuotedDiffWithComments:
         # First non-empty line should be an instruction
         assert lines[0].startswith('# ')
         # Instructions end before the first quoted diff line
-        instruction_lines = [l for l in lines if l.startswith('#')]
+        instruction_lines = [line for line in lines if line.startswith('#')]
         assert len(instruction_lines) >= 3
         # _extract_editor_comments should strip them
         comments = review._extract_editor_comments(result)
@@ -157,7 +155,7 @@ class TestRenderQuotedDiffWithComments:
         assert '> Second line.' in lines
         # They should come before the diff
         body_idx = lines.index('> This is the body.')
-        diff_idx = next(i for i, l in enumerate(lines) if 'diff --git' in l)
+        diff_idx = next(i for i, line in enumerate(lines) if 'diff --git' in line)
         assert body_idx < diff_idx
 
     def test_commit_msg_own_comment(self) -> None:
@@ -213,7 +211,7 @@ class TestRenderQuotedDiffWithComments:
         lines = result.splitlines()
         assert 'General note' in lines
         note_idx = lines.index('General note')
-        body_idx = next(i for i, l in enumerate(lines) if 'First body line' in l)
+        body_idx = next(i for i, line in enumerate(lines) if 'First body line' in line)
         assert note_idx < body_idx
 
 
@@ -583,7 +581,7 @@ index abc..def 100644
         assert 'General feedback.' in lines
         # Preamble should come before any quoted line
         feedback_idx = lines.index('General feedback.')
-        quoted_lines = [i for i, l in enumerate(lines) if l.startswith('>')]
+        quoted_lines = [i for i, line in enumerate(lines) if line.startswith('>')]
         if quoted_lines:
             assert feedback_idx < quoted_lines[0]
 
diff --git a/src/tests/test_review_checks.py b/src/tests/test_review_checks.py
index 7d737ad..c866082 100644
--- a/src/tests/test_review_checks.py
+++ b/src/tests/test_review_checks.py
@@ -9,7 +9,6 @@ import pytest
 
 from b4.review import checks
 
-
 # ---------------------------------------------------------------------------
 # Helpers
 # ---------------------------------------------------------------------------
diff --git a/src/tests/test_review_show_info.py b/src/tests/test_review_show_info.py
index abfbf3c..db955c4 100644
--- a/src/tests/test_review_show_info.py
+++ b/src/tests/test_review_show_info.py
@@ -5,18 +5,17 @@
 #
 """Tests for ``b4 review show-info``."""
 import json
-import pytest
 
+import pytest
 
 import b4
 import b4.review
 from b4.review._review import (
     get_review_info,
-    show_review_info,
     list_review_branches,
+    show_review_info,
 )
 
-
 # ---------------------------------------------------------------------------
 # Helpers
 # ---------------------------------------------------------------------------
diff --git a/src/tests/test_review_tracking.py b/src/tests/test_review_tracking.py
index 8cc0c70..a5fd903 100644
--- a/src/tests/test_review_tracking.py
+++ b/src/tests/test_review_tracking.py
@@ -12,8 +12,8 @@ import pytest
 import b4
 import b4.review
 from b4.review import tracking as review_tracking
-from b4.review_tui._tracking_app import _format_snooze_until, _format_attestation
 from b4.review_tui._modals import SnoozeScreen
+from b4.review_tui._tracking_app import _format_attestation, _format_snooze_until
 
 
 class TestGetReviewDataDir:
@@ -1820,14 +1820,14 @@ class TestBuildReplyFromComments:
 
     def _skip_markers(self, lines: list[str]) -> list[str]:
         """Return all skip-marker lines from the output."""
-        return [l for l in lines if l.startswith('> [ ... skip')]
+        return [line for line in lines if line.startswith('> [ ... skip')]
 
     def test_short_hunk_no_skip_marker(self) -> None:
         """Comment within 5 lines of hunk start → no skip marker of any kind."""
         lines = self._call([self._make_comment(3, 'nice')])
         assert not self._skip_markers(lines)
         # @@ header always present
-        assert any('@@ -0,0 +1,40 @@' in l for l in lines)
+        assert any('@@ -0,0 +1,40 @@' in line for line in lines)
         # All 3 added lines quoted
         assert '> +line1' in lines
         assert '> +line2' in lines
@@ -1852,7 +1852,7 @@ class TestBuildReplyFromComments:
         assert len(markers) == 1
         assert 'skip 14 lines' in markers[0]
         # @@ header present
-        assert any('@@ -0,0 +1,40 @@' in l for l in lines)
+        assert any('@@ -0,0 +1,40 @@' in line for line in lines)
         # Only lines 15-20 quoted (5 context + the commented line)
         assert '> +line15' in lines
         assert '> +line20' in lines
@@ -1903,7 +1903,7 @@ class TestBuildReplyFromComments:
     def test_hunk_header_always_present(self) -> None:
         """The @@ hunk header is always included even for a comment on line 20."""
         lines = self._call([self._make_comment(20, 'end')])
-        assert any('@@ -0,0 +1,40 @@' in l for l in lines)
+        assert any('@@ -0,0 +1,40 @@' in line for line in lines)
         assert self._skip_markers(lines)
         assert '> +line20' in lines
         assert '> +line14' not in lines
@@ -1915,7 +1915,7 @@ class TestBuildReplyFromComments:
             self._make_comment(10, 'y'),
         ]
         lines = self._call(comments)
-        quoted = [l for l in lines if l.startswith('> +')]
+        quoted = [line for line in lines if line.startswith('> +')]
         # Each quoted diff line should appear exactly once
         assert len(quoted) == len(set(quoted))
 
diff --git a/src/tests/test_three_way_merge.py b/src/tests/test_three_way_merge.py
index 83a4c77..c0127bf 100644
--- a/src/tests/test_three_way_merge.py
+++ b/src/tests/test_three_way_merge.py
@@ -1,13 +1,14 @@
 import argparse
 import json
 import os
+from typing import Any, Dict, Optional, Tuple
+from unittest.mock import patch
+
 import pytest
+
 import b4
 import b4.mbox
 
-from typing import Any, Dict, Optional, Tuple
-from unittest.mock import patch
-
 
 class TestAmConflictError:
     """Tests for the AmConflictError exception class."""
diff --git a/src/tests/test_tui_bugs.py b/src/tests/test_tui_bugs.py
index 19f073c..4cab381 100644
--- a/src/tests/test_tui_bugs.py
+++ b/src/tests/test_tui_bugs.py
@@ -12,8 +12,6 @@ from datetime import datetime, timezone
 from typing import Set
 from unittest import mock
 
-from ezgb import Bug, BugSummary, Comment, Identity, Status
-
 from b4.bugs._import import (
     format_comment,
     is_comment_removed,
@@ -29,7 +27,7 @@ from b4.bugs._tui import (
     _relative_time,
     label_color,
 )
-
+from ezgb import Bug, BugSummary, Comment, Identity, Status
 
 # ---------------------------------------------------------------------------
 # Helpers -- factory functions for real Bug and BugSummary objects
diff --git a/src/tests/test_tui_modals.py b/src/tests/test_tui_modals.py
index 7b123cc..e0e6f3f 100644
--- a/src/tests/test_tui_modals.py
+++ b/src/tests/test_tui_modals.py
@@ -9,14 +9,14 @@ Uses Textual's built-in ``App.run_test()`` / ``Pilot`` harness so the
 tests run without a real terminal.  Only lightweight, self-contained
 modals are exercised here — no database, network, or git needed.
 """
-import pytest
-
 from typing import Any, Dict, List, Optional, Tuple
 
+import pytest
 from textual.app import App, ComposeResult
 from textual.widgets import Input, Label, ListView
 
 from b4.review_tui._modals import (
+    TRACKING_HELP_LINES,
     ActionScreen,
     ConfirmScreen,
     HelpScreen,
@@ -28,10 +28,8 @@ from b4.review_tui._modals import (
     SnoozeScreen,
     TrailerScreen,
     UpdateRevisionScreen,
-    TRACKING_HELP_LINES,
 )
 
-
 # ---------------------------------------------------------------------------
 # Compat helper — Textual ≥ 1.0 (pip) uses Static.content,
 # older builds (e.g. Fedora 43 package) still use Static.renderable.
diff --git a/src/tests/test_tui_review.py b/src/tests/test_tui_review.py
index c3a2db4..3222989 100644
--- a/src/tests/test_tui_review.py
+++ b/src/tests/test_tui_review.py
@@ -8,16 +8,14 @@
 Tests the shell-return reconciliation logic that detects and handles
 cosmetic commit edits (e.g. reworded subjects via git rebase -i).
 """
-import pytest
-
 from typing import Any, Dict, List, Tuple
 
+import pytest
+
 import b4
 import b4.review
-
 from b4.review_tui._review_app import ReviewApp
 
-
 # ---------------------------------------------------------------------------
 # Helpers
 # ---------------------------------------------------------------------------
diff --git a/src/tests/test_tui_tracking.py b/src/tests/test_tui_tracking.py
index 96b160a..80004e8 100644
--- a/src/tests/test_tui_tracking.py
+++ b/src/tests/test_tui_tracking.py
@@ -11,28 +11,25 @@ core user workflows: series listing, navigation, filtering,
 status transitions, and modal interactions.
 """
 import pathlib
-import pytest
-
 from typing import Any, Dict, List, Optional
 from unittest.mock import patch
 
+import pytest
+from textual.widgets import Input, ListView, Static
+
 import b4
 import b4.review
 import b4.review.tracking as tracking
-
-from textual.widgets import Input, ListView, Static
-
-from b4.review_tui._tracking_app import TrackingApp, TrackedSeriesItem
 from b4.review_tui._modals import (
-    ActionScreen,
     ActionItem,
+    ActionScreen,
     ConfirmScreen,
     HelpScreen,
     LimitScreen,
     SnoozeScreen,
     TargetBranchScreen,
 )
-
+from b4.review_tui._tracking_app import TrackedSeriesItem, TrackingApp
 
 # ---------------------------------------------------------------------------
 # Compat helper — Textual ≥ 1.0 (pip) uses Static.content,

-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.