mail UI: dedupe broadcast fan-out and drop in-game admin compose
Two issues surfaced once the long-lived dev environment finally
reached the diplomail view:
1. `/sent` returns one row per recipient for broadcast and admin
fan-outs (so the admin tooling can render the materialised
audience). The list pane fed all rows into the stand-alone
bucket, so the `{#each entries as e (entryKey(e))}` key in
`thread-list.svelte` collapsed to the same `standalone:${id}`
for every recipient and Svelte 5 aborted the render with
`each_key_duplicate`. Dedupe stand-alones by `message_id` in
`buildEntries`.
2. The compose dialog exposed an `admin` kind toggle gated on
"owner of game". That was a Phase 28 plan decision, but admin
compose is an operator tool (server admin), not an in-game
action — every game owner should not be able to broadcast
admin notifications. Drop the admin option, the audience
sub-toggles, and the admin path through `submit`. The
`MailStore.composeAdmin` wrapper and the backend RPC stay so
the future admin UI can call them.
Vitest covers the fan-out dedup with three rows sharing one
`message_id` collapsing to a single stand-alone entry.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -317,9 +317,24 @@ function buildEntries(inbox: MailMessage[], sent: MailMessage[]): MailListEntry[
|
||||
thread.latestAt = last.createdAt;
|
||||
}
|
||||
|
||||
// Broadcast and admin fan-outs return one row per recipient from
|
||||
// the `/sent` endpoint (so the admin UI sees the materialised
|
||||
// audience). The in-game list pane collapses them by `message_id`
|
||||
// — without this dedupe the {#each} key in `thread-list.svelte`
|
||||
// repeats and Svelte 5 aborts the render with `each_key_duplicate`.
|
||||
const seen = new Set<string>();
|
||||
const dedupedStandalones: MailStandalone[] = [];
|
||||
for (const s of standalones) {
|
||||
if (seen.has(s.message.messageId)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(s.message.messageId);
|
||||
dedupedStandalones.push(s);
|
||||
}
|
||||
|
||||
const entries: MailListEntry[] = [
|
||||
...Array.from(threadsByRace.values()),
|
||||
...standalones,
|
||||
...dedupedStandalones,
|
||||
];
|
||||
entries.sort((a, b) => b.latestAt.getTime() - a.latestAt.getTime());
|
||||
return entries;
|
||||
|
||||
Reference in New Issue
Block a user