90944948f5
TUI for orchestrating AI coding harness sessions (Claude Code, Codex, Kiro, etc.) via tmux. Click CLI bootstraps a Textual TUI over ProjectService/SessionService backed by SQLite, spawning harness sessions as tmux windows through TmuxManager. Includes recent fixes: - Visible Tab focus highlight on dialog OK/Cancel buttons - Auto-select first project on launch - Auto-select first session + per-project session-selection memory - tmux new-window targets an explicit free index, fixing "index N in use" failures (broken spawn/attach in attached sessions) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
43 lines
1.5 KiB
Python
43 lines
1.5 KiB
Python
import logging
|
|
import sys
|
|
|
|
import hqt.logging as hqt_logging
|
|
from hqt.config import Settings
|
|
|
|
|
|
def test_setup_logging_does_not_write_to_terminal(tmp_path, monkeypatch):
|
|
"""setup_logging must NOT attach a StreamHandler that writes to the real
|
|
terminal stderr/stdout.
|
|
|
|
The TUI is a Textual app that paints to sys.__stderr__. A root-logger
|
|
StreamHandler bound to that same fd writes raw log lines straight onto the
|
|
pane Textual owns, corrupting tmux's grid — visible as rendering artifacts
|
|
when switching back to the hqt window. Logs must go to file only.
|
|
"""
|
|
settings = Settings(db_path=tmp_path / "hqt.db")
|
|
monkeypatch.setattr(hqt_logging, "get_settings", lambda: settings)
|
|
|
|
root = logging.getLogger()
|
|
saved_handlers = root.handlers[:]
|
|
saved_level = root.level
|
|
try:
|
|
root.handlers.clear()
|
|
hqt_logging.setup_logging()
|
|
|
|
terminal_streams = {sys.stderr, sys.__stderr__, sys.stdout, sys.__stdout__}
|
|
offending = [
|
|
h
|
|
for h in root.handlers
|
|
# FileHandler subclasses StreamHandler but writes to a file, not the terminal.
|
|
if type(h) is logging.StreamHandler
|
|
and getattr(h, "stream", None) in terminal_streams
|
|
]
|
|
assert not offending, (
|
|
f"setup_logging attached a terminal StreamHandler: {offending}"
|
|
)
|
|
# Logs are still captured to a file.
|
|
assert any(isinstance(h, logging.FileHandler) for h in root.handlers)
|
|
finally:
|
|
root.handlers[:] = saved_handlers
|
|
root.setLevel(saved_level)
|