import threading import time from dataclasses import dataclass, field @dataclass class MessageState: _text: str | None = field(default=None) _expires_at: float | None = field(default=None) # monotonic timestamp _lock: threading.Lock = field(default_factory=threading.Lock) def set(self, text: str, duration: float | None) -> None: """Set message. duration=None means persistent (no expiry).""" with self._lock: self._text = text self._expires_at = time.monotonic() + duration if duration else None def clear(self) -> None: with self._lock: self._text = None self._expires_at = None def get(self) -> tuple[str | None, float | None]: """Return (text, remaining_seconds). Both None if no active message.""" with self._lock: if self._text is None: return None, None if self._expires_at is not None and time.monotonic() >= self._expires_at: self._text = None self._expires_at = None return None, None remaining: float | None = None if self._expires_at is not None: remaining = max(0.0, self._expires_at - time.monotonic()) return self._text, remaining def to_dict(self) -> dict: text, remaining = self.get() return { "active": text is not None, "text": text, "remaining_seconds": round(remaining, 1) if remaining is not None else None, "persistent": text is not None and remaining is None, }