fix(notifications): Mark all read waits for the fresh list, is not offered where it cannot act, and a row says it is unread
The mask over a stale read lifted when the write settled, before the refetch landed, and the band took the rows back. A band holding only what asks by its kind (an access-requests nudge) left the button enabled with nothing to do. The unread dot was only drawn: the spoken label now says it, apart from the name Back finds the row by, and follows the row while it holds the focus.
This commit is contained in:
@@ -43,7 +43,7 @@ import {
|
||||
type VideoWatchedMeta,
|
||||
} from "../../lib/notifications";
|
||||
import { channelYouTubeUrl, formatDate } from "../../lib/format";
|
||||
import { entryKey, keyOf, orderInbox, type InboxEntry } from "../../lib/inboxOrder";
|
||||
import { clientAsks, entryKey, keyOf, orderInbox, type InboxEntry } from "../../lib/inboxOrder";
|
||||
import { Button } from "../../components/ui/Button";
|
||||
import { IconButton } from "../../components/ui/IconButton";
|
||||
import { Pressable } from "../../components/ui/Pressable";
|
||||
@@ -128,7 +128,13 @@ export default function NotificationsPanel() {
|
||||
mutationFn: api.markAllNotificationsRead,
|
||||
// Settled, not only succeeded: a refused write brings the rows back unread at once, rather than
|
||||
// leaving them read until the next poll moves them under the reader.
|
||||
onSettled: refresh,
|
||||
// And it settles once the read after it has landed: until then the cache holds what a read
|
||||
// before the commit said, and the band would take those rows back the moment the mask below lifts.
|
||||
onSettled: () =>
|
||||
Promise.all([
|
||||
qc.invalidateQueries({ queryKey: qk.notifications() }),
|
||||
qc.invalidateQueries({ queryKey: qk.notifUnread() }),
|
||||
]),
|
||||
meta: { failTitle: "errors.writeFailed.notifReadAll" },
|
||||
});
|
||||
const dismissMut = useMutation({
|
||||
@@ -154,8 +160,12 @@ export default function NotificationsPanel() {
|
||||
|
||||
const serverUnread = serverItems.some((n) => !n.read);
|
||||
const hasAny = serverItems.length > 0 || clientItems.length > 0;
|
||||
// A band of rows already read still offers the gesture that empties it.
|
||||
const canMarkAll = serverUnread || clientUnread > 0 || order.needsYou.length > 0;
|
||||
// A band of rows already read still offers the gesture that empties it — but not one that only
|
||||
// holds what asks by its kind (an access-requests nudge): pressing it would change nothing.
|
||||
const canMarkAll =
|
||||
serverUnread ||
|
||||
clientUnread > 0 ||
|
||||
order.needsYou.some((e) => e.src === "server" || !clientAsks(e.n));
|
||||
|
||||
function markAll() {
|
||||
markAllClientRead();
|
||||
@@ -688,11 +698,15 @@ function NotificationRow({
|
||||
title = t(DONE_TITLE[d.kind ?? ""] ?? "inbox.downloadDone.title");
|
||||
body = d.title || n.body;
|
||||
}
|
||||
// Named by what it is about, not its kind alone: every "Download ready" read the same to a screen
|
||||
// reader, and Back could not tell the row it was opened from by its name.
|
||||
const name = body ? `${title}: ${body}` : title;
|
||||
return (
|
||||
<div
|
||||
// Named by what it is about, not its kind alone: every "Download ready" read the same to a
|
||||
// screen reader, and Back could not tell the row it was opened from by its name.
|
||||
{...navItemAttrs(navIndex, body ? `${title}: ${body}` : title)}
|
||||
// The unread state is spoken too (the dot is only drawn), apart from the name Back finds it by.
|
||||
{...navItemAttrs(navIndex, name, {
|
||||
label: n.read ? undefined : t("inbox.unreadName", { name }),
|
||||
})}
|
||||
{...rowProps}
|
||||
className={`${SURFACE.card} group p-3.5 flex items-start gap-3 transition ${
|
||||
n.read ? "opacity-70" : ""
|
||||
|
||||
Reference in New Issue
Block a user