Files
pl_mail/assets/mail_writes.js
T
Paul LütznerandClaude Opus 5 0e996073a5 fix(mail): the unread badges follow mail that is read, not only mail acted on
Reported as the counter updating "nicht zuverlässig" when mail is read.
Unreliably rather than never, because navigating between folders re-renders
the badges server-side — so whether it looked broken depended on whether you
happened to click away afterwards.

It was never unreliable. The badges had exactly one immediate trigger,
`mail--list-toolbar:written`, and only the BULK toolbar ever dispatched it. So
they kept up with the one path almost nobody uses and with none of the paths
people actually read mail through: opening a conversation (mail--thread-read),
the envelope button on a row (mail--message-row), and the reading pane's
overflow menu (mail--message-actions). All three POST to the same status
endpoints, redraw their row from the returned Turbo Stream, and told nobody
else. Measured on the inbox after reading one mail: a full minute, no sync
event, no counts request, the badge still one too high — while /mail/sidebar/
counts had the right number the whole time. Only a reload put it right.

The announcement now belongs to everybody, in assets/mail_writes.js. A write
path that forgets to call it has the same bug, but it has it in a form somebody
can grep for.

Deliberately NOT folded into the toolbar's existing `:writing`/`:written` pair,
which is untouched. That pair is a different statement — "hold the list frame
refresh, I am part-way through a run of posts" — answered by mail--mail-pane
with a fetch of the whole frame. Firing it from mail--thread-read would re-read
the entire list on every mail opened, which is the commonest action in the app.
This event asks only for the counts, which is the thing that was stale.

thread-read announces only when the read moved a number. It marks read on every
open, including mail already read, and that is by far the commoner case once an
inbox is under control — those posts change nothing, so announcing them would
put a counts request behind every click on a mail. The sidebar's rate limiting
exists because this endpoint was once asked thirty-two times in ten seconds;
this keeps that honest, and live-counts.spec.ts pins it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:08:40 +02:00

48 lines
2.4 KiB
JavaScript

/**
* "Something about my mail just changed on the server — re-read your numbers."
*
* The sidebar badges are patched from /mail/sidebar/counts, and the only
* immediate trigger they ever had was `mail--list-toolbar:written`, dispatched
* by the bulk toolbar. That covered the one path a person almost never uses.
* The three paths they do use — opening a conversation (mail--thread-read),
* the envelope button on a row (mail--message-row) and the reading pane's
* overflow menu (mail--message-actions) — all POST to the same status
* endpoints, redraw their row from the returned Turbo Stream, and said nothing
* to anybody else. So the row went un-bold while "Inbox 4" sat beside it,
* and nothing corrected it: a mark-read publishes no Mercure sync, so the
* badges' other trigger never came either. Measured at a full minute with no
* refetch; only a reload or a navigation put the number right.
*
* Reported as the counter updating "nicht zuverlässig" rather than never,
* because navigating between folders re-renders the badges server-side — so
* whether it looked broken depended on whether you happened to click away
* after reading.
*
* Hence one announcement, in one place, that every write path makes. A new
* one that forgets to call this has the same bug, but it has it in a form
* somebody can grep for.
*
* Deliberately NOT the toolbar's existing `:writing`/`:written` pair, which
* stays exactly as it was. That pair is a different statement — "hold the list
* frame refresh, I am part-way through a run of posts" — and it is answered by
* mail--mail-pane#hold/#release with a fetch of the whole list frame. Firing
* it from mail--thread-read would re-read the entire list on every mail
* opened, which is the most common action in the app. This event asks only for
* the counts, which is the thing that was actually stale.
*/
/** The document-level event. Listened for by ui--sidebar. */
export const WRITE_EVENT = "mail:written";
/**
* Announce that a write landed.
*
* Bubbling and dispatched on `document`, because the listeners are the two
* sidebars (the mobile drawer and the desktop column) and the writers are
* inside the list frame or the reading pane — several DOM subtrees away, and
* in the frame's case a subtree that is replaced wholesale.
*/
export function announceWrite() {
document.dispatchEvent(new CustomEvent(WRITE_EVENT, { bubbles: true }));
}