diff --git a/src/components/announcements/DiscordIcon.tsx b/src/components/announcements/DiscordIcon.tsx
new file mode 100644
index 0000000..c4946a4
--- /dev/null
+++ b/src/components/announcements/DiscordIcon.tsx
@@ -0,0 +1,42 @@
+import { forwardRef } from 'react';
+import type { LucideProps } from 'lucide-react';
+
+/**
+ * Discord's brand mark, drawn here because lucide-react carries no brand
+ * glyphs at all (they were split out of the icon set upstream) and an
+ * announcement that arrived from a Discord channel has nothing else that
+ * says so at a glance.
+ *
+ * Deliberately shaped to lucide's own component contract — same props, same
+ * ref target, `currentColor` by default — so it can sit in
+ * `ANNOUNCEMENT_ICONS` beside real lucide icons and be rendered by call
+ * sites that know nothing about it (``).
+ *
+ * Filled rather than stroked, unlike every glyph around it: a brand mark
+ * redrawn as outlines stops reading as that brand. `fill={color}` keeps it
+ * inheriting colour exactly the way the stroked icons do, so it still
+ * follows `--text-muted` / `--accent` with no special casing.
+ */
+export const DiscordIcon = forwardRef(function DiscordIcon(
+ { size = 24, color = 'currentColor', className, style, ...rest },
+ ref,
+) {
+ return (
+
+ );
+});
diff --git a/src/components/announcements/__tests__/icons.test.ts b/src/components/announcements/__tests__/icons.test.ts
index 0ef7485..1feedd1 100644
--- a/src/components/announcements/__tests__/icons.test.ts
+++ b/src/components/announcements/__tests__/icons.test.ts
@@ -1,5 +1,6 @@
import { describe, it, expect } from 'vitest';
import { ANNOUNCEMENT_ICON_KEYS, ANNOUNCEMENT_ICONS, iconForAnnouncement } from '../icons.js';
+import { DiscordIcon } from '../DiscordIcon.js';
import type { AnnouncementType } from '../types.js';
// types.ts exports `AnnouncementType` as a union only, no runtime array — this
@@ -12,9 +13,18 @@ describe('ANNOUNCEMENT_ICON_KEYS / ANNOUNCEMENT_ICONS', () => {
expect([...ANNOUNCEMENT_ICON_KEYS]).toEqual([
'release', 'update', 'event', 'maintenance', 'security',
'megaphone', 'gift', 'sparkles', 'alert-triangle', 'info', 'key', 'coins', 'party',
+ 'discord',
]);
});
+ // The only key whose glyph is not a lucide icon — it is drawn in this
+ // package (lucide ships no brand marks). Pinned so a refactor that loses
+ // the local component and silently leaves the key mapped to nothing gets
+ // caught here rather than as an empty square in three portals.
+ it('maps the discord key to the locally drawn brand mark', () => {
+ expect(ANNOUNCEMENT_ICONS.discord).toBe(DiscordIcon);
+ });
+
it('has exactly one glyph per key, with no gaps', () => {
for (const key of ANNOUNCEMENT_ICON_KEYS) {
expect(ANNOUNCEMENT_ICONS[key]).toBeTruthy();
diff --git a/src/components/announcements/icons.ts b/src/components/announcements/icons.ts
index dbf3844..f44d53d 100644
--- a/src/components/announcements/icons.ts
+++ b/src/components/announcements/icons.ts
@@ -3,6 +3,7 @@ import {
Megaphone, Gift, Sparkles, AlertTriangle, Info, Key, Coins, PartyPopper,
} from 'lucide-react';
import type { AnnouncementType } from './types.js';
+import { DiscordIcon } from './DiscordIcon.js';
/**
* The closed set of icon keys an announcement can be tagged with. A stored
@@ -25,14 +26,19 @@ import type { AnnouncementType } from './types.js';
export const ANNOUNCEMENT_ICON_KEYS = [
'release', 'update', 'event', 'maintenance', 'security',
'megaphone', 'gift', 'sparkles', 'alert-triangle', 'info', 'key', 'coins', 'party',
+ 'discord',
] as const;
export type AnnouncementIconKey = typeof ANNOUNCEMENT_ICON_KEYS[number];
/**
- * One lucide-react component per key. Every consumer — this row, and
+ * One icon component per key. Every consumer — this row, and
* sphere-backoffice's icon picker — reads a glyph from this single map, so
* there is exactly one place that decides what each key looks like.
+ *
+ * All lucide-react except `discord`: lucide carries no brand marks, so that
+ * one is drawn locally against the same component contract (see
+ * DiscordIcon.tsx) and is otherwise indistinguishable to every call site.
*/
export const ANNOUNCEMENT_ICONS: Record = {
release: Rocket,
@@ -48,6 +54,7 @@ export const ANNOUNCEMENT_ICONS: Record = {
key: Key,
coins: Coins,
party: PartyPopper,
+ discord: DiscordIcon,
};
function isAnnouncementIconKey(value: string): value is AnnouncementIconKey {