From db81bd8e08d27fad7dde32f48acc4eb3b252fbbc Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Fri, 15 May 2026 22:46:00 +0200 Subject: [PATCH] Phase 28 (Steps 7+8): header unread badge + push/init wiring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 7 — header view-menu badge. `view-menu.svelte` reads `mailStore.unreadCount` and renders an inline pill next to the "diplomatic mail" entry whenever the counter is non-zero. The badge styling matches the per-row dot in `thread-list.svelte` so the two surfaces feel consistent. Step 8 — push event handler + MailStore init in the in-game layout. `routes/games/[id]/+layout.svelte`: - registers a `diplomail.message.received` handler alongside the existing `game.turn.ready` / `game.paused` ones, parses the signed payload, calls `mailStore.applyPushEvent` to refresh the inbox for the matching game, and raises a toast with a "view" deep-link that navigates to `/games/:id/mail`; - adds `mailStore.init({ client, cache, gameId })` to the boot `Promise.all` so the inbox + sent lists are warm by the time the view mounts, and the badge counter is populated before any user interaction; - disposes the new subscription in the `onDestroy` block so a game switch does not leak handlers across navigations. Co-Authored-By: Claude Opus 4.7 (1M context) --- ui/frontend/src/lib/header/view-menu.svelte | 26 +++++++++- .../src/routes/games/[id]/+layout.svelte | 52 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/ui/frontend/src/lib/header/view-menu.svelte b/ui/frontend/src/lib/header/view-menu.svelte index cbe5bd4..30f6c99 100644 --- a/ui/frontend/src/lib/header/view-menu.svelte +++ b/ui/frontend/src/lib/header/view-menu.svelte @@ -15,10 +15,13 @@ polishes microcopy. import { onMount } from "svelte"; import { goto } from "$app/navigation"; import { i18n, type TranslationKey } from "$lib/i18n/index.svelte"; + import { mailStore } from "$lib/mail-store.svelte"; type Props = { gameId: string }; let { gameId }: Props = $props(); + const mailUnread = $derived(mailStore.unreadCount); + let open = $state(false); let rootEl: HTMLDivElement | null = $state(null); @@ -122,9 +125,15 @@ polishes microcopy. type="button" role="menuitem" data-testid="view-menu-item-mail" + class="with-badge" onclick={() => go(`/games/${gameId}/mail`)} > - {i18n.t("game.view.mail")} + {i18n.t("game.view.mail")} + {#if mailUnread > 0} + + {i18n.t("game.view.mail.badge", { count: String(mailUnread) })} + + {/if}