The unread markers were a Set in the view model's UI state and nowhere
else. Kill the app with unread conversations and every badge was gone at
the next launch, whether or not anything had been read. The same fault as
the story reactions were: a mark somebody has not yet dealt with is
theirs until they deal with it, not until the process ends.
The vault keeps the set now. It is seeded from there when the signed-in
state is built, and written from `update()`, the one chokepoint every
state change passes through, whenever the set differs from before. That
is one write for the four sites that mutate it today and for any fifth
one, rather than four writes that a fifth could forget. Synchronous on
purpose: it is a short list of ids, and dispatching it would let two quick
updates land out of order.
Coverage, honestly: the vault round trip is tested in both directions,
including that a cleared set stays cleared. The view model chokepoint is
covered by compile and the full suite; there is no view model harness
beyond a screenshot fixture, so the seed and the write are verified by
reading rather than by a failing test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>