/* ── what the machine says ───────────────────────────────────────── */ /* A tab's own name: the section, and when the dot is up, what the number counts. "You, 3 transfers" — not a stray 3 read out before the word it belongs to. */ function tabCount(id) { if (id === "you") return activeTransfers().length; if (id === "community") return unreadTotal(); return 0; } function tabName(id, label) { const n = tabCount(id); if (!n) return label; return label + ", " + n + (id === "you" ? " transfer" + (n === 1 ? "" : "s") : " unread"); } /* The one live region. It is mounted for the life of the app on purpose: a region that appears with its words already inside it is announced reliably by neither TalkBack nor VoiceOver, so an announcement here is a TEXT CHANGE in a node that was always there. The desktop does the same (FollowToast's always-mounted .layer, PreviewOverlay's .sr-announce). Never drawn — shell.css clips it to a pixel. */ const announce = $derived([player.error || lib.error, notice.text, notice.action?.label].filter(Boolean).join(". "));