Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 13 additions & 21 deletions src/toad/notification_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from __future__ import annotations

from abc import ABC, abstractmethod
from collections import Counter
from collections.abc import Iterable, Mapping
from dataclasses import dataclass
from typing import ClassVar, TYPE_CHECKING
Expand All @@ -30,22 +28,25 @@ def recipient_line(recipient: str, text: str) -> tuple[Span, ...]:
return (Span(recipient, NameRole, OpenTarget(recipient)), Span(text, MutedRole))


class NotificationStatus(ABC):
class NotificationStatus:
"""One state of the check: its one-line title and the details behind it."""

visible: ClassVar[bool] = True
working: ClassVar[bool] = False
unavailable: ClassVar[bool] = False

@property
@abstractmethod
def title(self) -> str: ...
def title(self) -> str:
return ""

@property
@abstractmethod
def details(self) -> DetailLines: ...
def details(self) -> DetailLines:
return ()

def blocks(self, shown: Shown) -> tuple[LineBlock, ...]:
"""The feedback row and its disclosure, shared by every message fragment."""
if not self.visible:
return ()
role = NoticeRole if self.unavailable else WorkingRole if self.working else MutedRole
summary = shown.summary(self.title, role)
return ((summary, Spans.from_lines(self.details, indent=" "))
Expand All @@ -54,11 +55,7 @@ def blocks(self, shown: Shown) -> tuple[LineBlock, ...]:

@dataclass(frozen=True)
class UncheckedNotifications(NotificationStatus):
title = "Notification status not checked"

@property
def details(self) -> DetailLines:
return ((Span("Not checked yet", MutedRole),),)
visible = False


@dataclass(frozen=True)
Expand All @@ -81,15 +78,10 @@ def working(self) -> bool:

@property
def title(self) -> str:
counts = Counter(item.state for item in self.ordered)
states = list(counts)
summary = " · ".join(f"{state} ({counts[state]})" for state in states[:3])
if len(states) > 3:
summary += f" · {sum(counts[state] for state in states[3:])} others"
read = sum(bool(item.displayed_to) for item in self.ordered)
if read:
summary = " · ".join(filter(None, (summary, f"Read ({read})")))
return summary or "No recorded notification result"
# Core owns receipt state, time and priority. Other recipients remain
# in the disclosure, not competing counters in the one-line caption.
ordered = self.ordered
return ordered[0].state if ordered else "No recorded notification result"

@property
def details(self) -> DetailLines:
Expand Down
12 changes: 6 additions & 6 deletions src/toad/transcript_publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ def __init__(self, owner: TranscriptPresentation):
self.owner = owner
self.pending: asyncio.Queue[TranscriptPublication] = asyncio.Queue(maxsize=1)
self.worker: Worker[None] | None = None
self.observation = ResourceRelease()

def request(self, kind: type[TranscriptPublication], *args: object) -> None:
publication = self.owner.capture(kind, *args)
Expand All @@ -344,8 +345,9 @@ def defer(self, publication: TranscriptPublication) -> None:
view = self.owner.view
if view is None:
return
view.retire_core_observations(view.app.coordination_access.events)
view.observe_core_callback(view.app.coordination_access.events, self.resume)
self.observation.close()
self.observation.callback(view.retire_core,
view.observe_core_callback(view.app.coordination_access.events, self.resume))

def resume(self, _event=None) -> bool:
"""Resume retained work only at an existing source/preparation signal."""
Expand All @@ -355,7 +357,7 @@ def resume(self, _event=None) -> bool:
view = self.owner.view
if view is None or not view.is_attached:
return False
view.retire_core_observations(view.app.coordination_access.events)
self.observation.close()
self.worker = view.run_worker(self.publish, group="transcript-source")
return True

Expand All @@ -366,9 +368,7 @@ async def publish(self) -> None:
return

def cancel(self) -> Worker[None] | None:
view = self.owner.view
if view is not None:
view.retire_core_observations(view.app.coordination_access.events)
self.observation.close()
while not self.pending.empty():
self.pending.get_nowait()
worker, self.worker = self.worker, None
Expand Down
2 changes: 2 additions & 0 deletions src/toad/widgets/message_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(self) -> None:
self.status: NotificationStatus = UncheckedNotifications()
self._details: Static | None = None
super().__init__(title=self.status.title, collapsed=True)
self.display = self.status.visible

def compose(self) -> ComposeResult:
yield self._title
Expand All @@ -60,6 +61,7 @@ def show(self, status: NotificationStatus) -> None:
if status == self.status:
return
self.status = status
self.display = status.visible
title = Content(status.title)
if not title.is_same(Content.from_text(self.title)):
self.title = title
Expand Down
Loading