The green "finished — unread" session dot lived only in the transient
$unreadFinishedSessionIds atom, written by a live busy->idle edge the
renderer had to witness. Closing and reopening the app grayed out every
dot, and a session that finished while the app was closed could never
be flagged at all.
Add a persisted layer (session-unread.ts), ported from the webui's
proven design:
- Seen watermarks (hermes.desktop.sessionSeenCounts): the message_count
last acknowledged per session, keyed by the durable lineage id (same
rule as session colors). A row whose live count exceeds its watermark
paints unread on every list refresh - this reconstructs dots after a
restart AND surfaces sessions that finished while the app was closed.
First sight of an unknown session seeds the watermark so a fresh
install doesn't light up every row.
- Explicit finish markers (hermes.desktop.unreadFinishedSessions): the
live edge, persisted, covering the gap before the sidebar list
refreshes its counts.
Opening a session acks both; the selected session's watermark tracks
its live count so on-screen activity never reads as unread. Chat and
cron rows get full watermark treatment; messaging rows keep explicit
markers only, so inbound messages don't paint false completion dots.
Profile switches keep persisted markers (keyed by durable id) and only
wipe the transient paint layer, so a round-trip repaints them.
Covered by store unit tests and an e2e spec that boots the app three
times: dot appears on a background finish, survives a restart, clears
on open, and stays cleared after another restart.