## Phase 6 – WebUI-Fundament (Status: abgeschlossen, manuell bestätigt)

- OAuth/Sessions, Settings-Framework, Layout, Modul-Toggles, Access-Rules
- Login manuell bestätigt

## Phase 7 – WebUI Modul-Seiten + Owner-Panel (Status: abgeschlossen, manuell bestätigt)

### Abgeschlossen

- Alle Modul-Dashboard-Seiten, Commands-Seite, Overview-Aktivität
- Owner-Panel (`/owner`: Übersicht, Guilds, Users, Flags, Presence, Team, Jobs, Changelog, Audit)
- Prisma `20260722190000_phase7_owner`, Shared Zod (`dashboard.ts`, `owner.ts`)
- BullMQ WebUI↔Bot, Presence-/Wartungsmodus-Anbindung
- Manuell bestätigt (User-Freigabe)

## Phase 8 – Landing, Status, Rechtsseiten (Status: abgeschlossen, manuell bestätigt)

### Abgeschlossen

- Landing `/`, Status `/status`, Rechtsseiten, Public APIs, Core-Commands, `docs/verification.md`
- Manuell bestätigt (User-Freigabe)

## Deploy-Branch (`deploy`) – Traefik + Dashboard-Domain

### Abgeschlossen (Code auf Branch `deploy`; `main` unverändert)

- Traefik-Vollstack: Compose-Netz `traefik-network` (external) + `internal`
- WebUI hinter Traefik: Host `dashboard.nexumi.de`, Entrypoint `websecure`, `certresolver=letsencrypt`
- Host-Port `3000` entfernt; nur Traefik exponiert die WebUI
- `WEBUI_URL=https://dashboard.nexumi.de` (OAuth-Callback: `/api/auth/callback`)
- Marketing-Landing archiviert unter `apps/webui/src/archived/landing-page.tsx`
- `/` zeigt Login; `/login` leitet auf `/` weiter (Query erhalten)
- **SPEC-Abweichung (bewusst):** öffentliche Landing später extern auf `nexumi.de`; Dashboard unter Subdomain
- Rechtsseiten vollständig (Impressum/Datenschutz/ToS) DE+EN; Locale per Accept-Language (Cookie hat Vorrang)
- Plesk-Landing unter `landingpage/` mit lokalen Rechtsseiten (`impressum.php`, `datenschutz.php`, `nutzungsbedingungen.php`)

### Manuell testen (Deploy)

- [ ] DNS `dashboard.nexumi.de` → Traefik-Host
- [ ] Discord OAuth Redirect: `https://dashboard.nexumi.de/api/auth/callback`
- [ ] `docker compose up -d --build` (Netz `traefik-network` muss existieren)
- [ ] TLS-Zertifikat (Resolver-Name ggf. an Traefik anpassen)
- [ ] Login `/` → Dashboard; Session-Cookie `Secure`

## Post-Phase – Premium + DSGVO (Status: implementiert, manuelle Tests offen)

### Abgeschlossen (Code)

- Premium: `PremiumTierConfig`, Guild-/User-Zuweisung, Owner-UI `/owner/premium`
- Limits enforced (Tags/Feeds/Backups) in Bot + WebUI
- DSGVO: `/privacy`, `/gdpr delete confirm:true` (Löschung/Anonymisierung + `GdprDeletionLog`)
- Log-Retention: `LoggingConfig.retentionDays` + täglicher Job `retention`/`purgeExpiredLogs`
- Snipe standardmäßig aus (`GuildSettings.snipeEnabled`, Toggle in Allgemeine Einstellungen)
- Migration `20260722200000_premium_gdpr`

### Manuell testen

- [ ] Owner `/owner/premium`: Limits speichern, Guild PREMIUM zuweisen
- [ ] Free-Limit: mehr Tags/Feeds/Backups als Free-Cap → Fehler
- [ ] `/privacy` und `/gdpr delete confirm:true`
- [ ] Logging-Retention im Dashboard setzen
- [ ] Snipe: default aus; nach Aktivierung in Einstellungen nutzbar

### Bewusst offen

- Musik- und KI-Modul (nur auf Zuruf)
- Premium-Zahlungsanbindung
- Externe Marketing-Landing auf `nexumi.de`

## Post-Phase – Dashboard-UI + Command-Palette + Landing-Polish (Status: implementiert)

### Abgeschlossen (Code)

- Guild-/Owner-Shell: Branding, Sticky-Header, mobile Sidebar-Sheet, Overview-Polish
- Globale Cmd/Ctrl+K-Suche: Seiten, Slash-Commands, Einstellungsfelder (Deep-Links)
- Settings-Feld-Registry + Form-Anchors; Commands-Seite liest `?q=`
- Plesk-Landing: Inter, Hero-Hierarchie, dezente Motion, Designsprache an WebUI

### Manuell testen

- [ ] Dashboard: Cmd/Ctrl+K → Seite, Command und Option navigieren (Hash-Scroll)
- [ ] Mobile: Sidebar-Sheet + Such-Trigger
- [ ] Overview und Owner-Panel Layout
- [ ] Landing Light/Dark (`prefers-color-scheme`) + CTAs

### Bewusst offen

- Musik- und KI-Modul (nur auf Zuruf)
- Premium-Zahlungsanbindung

## Post-Phase – Channel-Picker überall (Status: implementiert)

### Abgeschlossen (Code)

- `DiscordChannelSelect` / `DiscordChannelMultiSelect` + `useGuildChannels`
- Alle Modul-Forms: Kanal-ID-Texteingaben durch durchsuchbare Channel-Auswahl ersetzt
- Voice-/Category-Filter für TempVoice/Stats/Tickets

## Post-Phase – Role-Picker überall (Status: implementiert)

### Abgeschlossen (Code)

- `DiscordRoleSelect` / `DiscordRoleMultiSelect` + `useGuildRoles`
- Modul-Forms/Manager: Rollen-ID-Texteingaben durch durchsuchbare Role-Auswahl ersetzt (Welcome, Verification, Leveling, Logging, Birthdays, Giveaways, Tags, Tickets, Access Rules, Feeds, Scheduler, Commands)
- Selfroles-Textarea (`roleId | label | emoji`) bewusst unverändert

### Manuell testen

- [ ] Rollen in den Modul-Settings per Dropdown setzen und speichern
- [ ] Multi-Select (Autoroles, No-XP-Roles, Support-Roles, …) speichert string[] korrekt
- [ ] Access-Rules: Rolle wählen und Module zuweisen

## Post-Phase – Command-Global-Rules + Channel-Picker (Status: implementiert)

### Abgeschlossen (Code)

- Globale Command Channel-Whitelist/-Blacklist in `GuildSettings` + Commands-UI
- Searchable Multi-Select für Kanäle/Rollen (statt Raw-IDs) auf der Commands-Seite
- APIs: `/commands/globals`, `/channels`, `/roles`
- Bot erzwingt globale + per-Command Channel/Role/Cooldown-Regeln in `routeCommand`
- Migration `20260722210000_command_global_channels`

### Manuell testen

- [ ] Commands: globale Whitelist/Blacklist speichern, in Discord prüfen
- [ ] Per-Command Channel/Rollen per Dropdown setzen
- [ ] Deaktivierter Command / Cooldown antwortet ephemeral

## Post-Phase – Discord Embed-Builder (Status: implementiert)

### Abgeschlossen (Code)

- Wiederverwendbare `DiscordEmbedBuilder`-Komponente inkl. Live-Vorschau
- Basis: Titel, Beschreibung, Farbe
- Erweitert: Author (Name/Icon/URL), Thumbnail, großes Bild, Footer (Text/Icon), Titel-URL, Timestamp
- Welcome/Leave, Tags (EMBED) und Scheduler nutzen denselben Builder + `buildEmbedFromPayload`
- Platzhalter in Text-/URL-Feldern inkl. `{user.avatar}` / `{server.icon}` (Welcome/Tags)

### Manuell testen

- [ ] Welcome/Leave-Embed mit Author, Thumbnail, Banner und Footer speichern und in Discord prüfen
- [ ] Tag mit Antworttyp Embed (auch nur Bild/Author ohne Titel) erstellen/bearbeiten
- [ ] Geplante Nachricht mit erweitertem Embed anlegen
- [ ] Einfaches Embed (nur Titel/Beschreibung/Farbe) funktioniert weiter wie zuvor

## Post-Phase – Logging Multi-Event pro Kanal (Status: implementiert)

### Abgeschlossen (Code)

- Logging-UI: pro Log-Kanal mehrere Event-Typen per Multi-Select
- Bestehende 1:1-Mappings werden beim Laden nach Kanal gruppiert; Speichern expandiert wieder auf `LogChannel`-Zeilen
- Event-Typen sind exklusiv (DB `@@unique([guildId, eventType])`) – Auswahl in einer Zeile entfernt sie aus anderen

### Manuell testen

- [ ] Mehrere Events einem Kanal zuweisen und speichern
- [ ] Event von Kanal A nach Kanal B umhängen (verschwindet bei A)
- [ ] Bot schreibt Logs der gewählten Events in den richtigen Kanal

## Post-Phase – Platzhalter-Hilfe (Status: implementiert)

### Abgeschlossen (Code)

- Aufklappbare `PlaceholderHelp`-Komponente mit Kopieren pro Token
- Kontextspezifische Presets (Welcome, Tags, Birthdays, Leveling, TempVoice, Stats, Feeds, Scheduler)
- Welcome-DM nutzt jetzt dieselben Platzhalter wie Welcome-Text (nicht nur `{user}`)

### Manuell testen

- [ ] Welcome: Liste aufklappen, Platzhalter kopieren, in Embed/Text einfügen
- [ ] Tags/Birthdays/Leveling/Stats/Feeds/Scheduler: nur die jeweiligen Tokens sichtbar

## Post-Phase – Components V2 (Status: implementiert, manuelle Tests offen)

### Abgeschlossen (Code)

- Shared Zod: `MessageComponentsV2` + Actions, `COMPONENTS_V2` für Welcome/Tags, Leave-Typen, custom_id `cv2:{source}:{ref}:{actionId}`
- Prisma-Migration `20260722220000_components_v2`: `welcomeComponents`/`leaveType`/`leaveComponents`, `Tag.components`, `ScheduledMessage.components`, `ComponentMessageBinding`
- Bot: `buildComponentsV2Payload`, Anbindung Welcome/Leave/Tags/Scheduler, Interaction-Handler, Dashboard-Send-Queue `messages`/`dashboardMessageSend`
- Slash: `/embed builder` + `/embed components` (Quick-V2)
- WebUI: `DiscordComponentsV2Builder`, Mode-Switches Welcome/Tags/Scheduler, Dashboard-Seite `/messages`

### Manuell testen

- [ ] Welcome V2 mit Container + Thumbnail-Section + Link-Button + Toggle-Role-Button
- [ ] Tag V2 mit String-Select → ephemeral reply
- [ ] Scheduler V2 Ankündigung
- [ ] Dashboard Messages: Embed + Components V2 senden
- [ ] Slash `/embed components`
- [ ] Bestehende Embeds unverändert
- [ ] Migration anwenden (`prisma migrate deploy`) und Bot/WebUI neu bauen

### Bewusst offen

- Modal-only Components, File-Component, Premium-Buttons (nicht im MVP)
- Pixelgenaue Discord-Vorschau im Dashboard

## Post-Phase – Harden (Sharding / Gates / Observability) (Status: implementiert)

### Abgeschlossen (Code)

- Confirmations (Moderation + Guildbackup) in Redis mit TTL statt Prozess-`Map`
- Command-Registrierung einmal im Manager (Redis-Lock); Recurring Jobs einmal (Primary-Shard + Lock)
- Modul-Toggles + FeatureFlags + User-/Guild-Blacklists im Bot erzwungen (`routeCommand`, Events, GuildCreate leave)
- Moderation: Hierarchy-Checks, Duration vor Ban-Confirm, Timeout-Cap 28d, warn remove guild-scoped, atomare Case-Nummern, Temp-Ban Job-Dedup + UNBAN-Case, `setDefaultMemberPermissions`
- Sentry (`@sentry/node`) in Bot + WebUI (`instrumentation.ts`); echte Prometheus-Metrics (`/metrics`)
- Interaction-Registry statt if-Kette; Unit-Tests für Duration, Confirm-Store, Module-Gates, Metrics

### Manuell testen

- [ ] Ban mit Dauer: Confirm auf anderem Shard / nach Restart innerhalb TTL
- [ ] Dashboard: Modul Moderation aus → `/ban` antwortet „Modul deaktiviert“
- [ ] Owner: User-Blacklist → Interactions blockiert; Guild-Blacklist → Bot verlässt Server
- [ ] `/metrics` mit Bearer-Token: Counters + Shard-Ping
- [ ] Temp-Ban ablaufen → Unban + Case `UNBAN`

### Bewusst offen

- Musik- und KI-Modul (nur auf Zuruf)
- Premium-Zahlungsanbindung
- P4-UX (Server-Lock, Ban deleteMessageSeconds) bewusst nachgelagert

## Post-Phase – Verification Captcha-Anbieter + Alt-Erkennung (Status: implementiert)

### Abgeschlossen (Code)

- Captcha-Anbieter wählbar pro Guild: `MATH`, `RECAPTCHA_V2`, `RECAPTCHA_V3`, `HCAPTCHA`, `TURNSTILE`
- Google/hCaptcha/Turnstile Keys in `.env` (WebUI); Dashboard zeigt nur konfigurierte Anbieter
- Captcha-Seite `/verify/captcha` mit Provider-Widgets; Server-side Siteverify
- Alt-Account-Erkennung: gesalzener IP-Hash (Captcha-Flow) + Invite-Cluster; optional blockieren via `failAction`
- Prisma `VerificationRecord` + Config-Felder; Migration `20260723120000_verification_captcha_alt`
- Slash `/verify setup`: `captcha_provider`, `alt_detection`

### Manuell testen

- [ ] `.env`: `RECAPTCHA_SITE_KEY`/`SECRET` setzen → Dashboard zeigt reCAPTCHA v2/v3
- [ ] Guild Captcha-Modus + Anbieter wählen → Verify-Button → WebUI Captcha → Rolle
- [ ] Alt-Erkennung an: zweiter Account gleiche IP → Kick/Ban je nach failAction
- [ ] Migration: `prisma migrate deploy` + Bot/WebUI neu bauen
- [ ] `CAPTCHA_IP_HASH_SALT` gesetzt (min. 16 Zeichen), sonst keine IP-Alt-Signale

### Bewusst offen

- Kein Browser-Fingerprinting (DSGVO); IP nur gehasht mit Salt
- Keine manuelle „Alt freigeben“-UI (nur Flag in `VerificationRecord`)

## Post-Phase – Leveling Rollen-Belohnungen UI (Status: implementiert)

### Abgeschlossen (Code)

- Dashboard Leveling: Level→Rolle-Belohnungen bearbeiten (Liste, speichern ersetzt `LevelReward`)
- Shared Schema `rewards` + `hasUniqueRewardLevels`; i18n DE/EN
- Stapel-Toggle mit Hinweis neben dem Belohnungs-Editor

### Manuell testen

- [ ] Belohnung hinzufügen (z. B. Level 5 → Rolle), speichern, Mitglied levelt → Rolle vergeben
- [ ] Stack an/aus: stapeln vs. nur aktuelle Belohnungsrolle
- [ ] Doppeltes Level → Fehlermeldung, speichern blockiert

## Post-Phase – Giveaway Bugs (Timestamp + End-Status) (Status: implementiert)

### Abgeschlossen (Code)

- Embed: Endzeit als Discord-Timestamp in der **Description** (Footer rendert `<t:…>` nicht)
- `/giveaway end` wirft bei fehlender/bereits beendeter ID Fehler statt Fake-Erfolg
- Dashboard „Jetzt beenden“ prüft `endedAt` nach Job; Timeout/Fehler → kein Erfolgs-Toast

### Manuell testen

- [ ] Neues Giveaway: Endzeit als „in X Stunden“ sichtbar, nicht als Roh-`<t:…>`
- [ ] `/giveaway end` mit falscher ID → Fehlermeldung; mit korrekter ID → Reroll möglich
- [ ] Dashboard „Jetzt beenden“ → Status beendet, Discord-Nachricht aktualisiert

## Post-Phase – Audit Remediation (Reliability + Security) (Status: implementiert)

### Abgeschlossen (Code)

- Scheduler: Self-Remove des aktiven Jobs entfernt; safeRemove für externe Cancels
- Channel-Picker: Fehler/leere Responses nicht cachen; Error+Reload wie Rollen
- `requirePermission`: Member- vs. Bot-Permissions getrennt; Defer auf Ban/Purge/Lock/Backup/Giveaway/Media/Translate
- Giveaway: atomares End (`updateMany`), Pause cancel/reschedule, Dashboard Pause/Delete via BullMQ
- Component-/Context-Gates: Maintenance + Modul-Toggle
- Owner-Guild-Bypass ab SUPPORT; OAuth `refresh_token` + Session-Refresh
- Queue safeRemove (Temp-Ban, Reminders, Selfroles); `addJobAndAwait` rethrowt Enqueue-Fehler
- DefaultMemberPermissions für Admin-Commands; Cooldown erst nach erfolgreichem Execute
- API: BadRequest/Upstream-Errors; Discord Roles/Channels 401/403/429/502

### Manuell testen

- [ ] One-shot Schedule sendet einmal (kein Doppel-Post nach Worker-Retry)
- [ ] Channel-Dropdown bei API-Fehler: Retry statt permanent „Keine Treffer“
- [ ] Giveaway Pause im Dashboard → Button disabled; Unpause → Timer weiter; End trotz Pause
- [ ] Modul aus → Join-Button / Ticket-Panel antwortet „Modul deaktiviert“
- [ ] VIEWER-Owner ohne Manage Guild: kein Dashboard-Guild-Zugriff; SUPPORT+: Bypass
- [ ] Session nach Token-Ablauf refresht ohne erzwungenen Re-Login (mit gültigem refresh_token)

## Post-Phase – Giveaway End Fix + Role Picker (Status: implementiert)

### Abgeschlossen (Code)

- Root cause: `endGiveaway` removed its own locked BullMQ job → End-Job crashte vor DB-Update (Timer/UI/Command hingen)
- `cancelGiveawayJob` nur noch außerhalb des Workers (Command/Dashboard); locked remove wird ignoriert
- Dashboard-End nutzt eindeutige Job-IDs (`giveaway-end-now-…`); klarere 502-Fehler statt generischem 500
- Bot-Start: `recoverOverdueGiveaways` beendet überfällige aktive Giveaways
- Role-Picker: kein Cache von leeren/fehlerhaften Responses; Redis-Cache best-effort; manuelle Filter statt cmdk-Default

### Manuell testen

- [ ] Giveaway starten (kurz), warten bis Timer abläuft → Embed „Beendet“, Button weg, Gewinner gezogen
- [ ] Giveaway vorzeitig per Dashboard „Jetzt beenden“ → kein Internal Server Error
- [ ] `/giveaway list` → ID kopieren → `/giveaway end id:…` → Erfolg
- [ ] Verifizierung: Rollen-Dropdown zeigt Serverrollen (nicht nur „Keine Treffer“)
- [ ] Nach Deploy: bereits hängende Giveaways werden beim Bot-Start recovered

## Post-Phase – Starboard Verbesserungen (Status: implementiert)

### Abgeschlossen (Code)

- `/starboard setup` funktioniert trotz `enabled: false` (Gate-Ausnahme)
- Kanal-Permission-Check vor Setup; `/starboard status`
- Sync: Source-Delete/Bulk-Delete, Message-Edit, RemoveAll/RemoveEmoji
- Race-sicheres Create + Recreate wenn Starboard-Msg fehlt
- Zod/UI: Emoji bis 80 Zeichen, Threshold 1–100, Channel Pflicht bei enabled, NSFW-Hinweis
- Unit-Tests `emojiMatches` / `shouldIgnoreMessage`

### Manuell testen

- [ ] `/starboard setup` auf frischem Server ohne Dashboard-Toggle
- [ ] Stern ≥ Schwelle → Post; unter Schwelle → Post entfernt
- [ ] Original löschen/editieren → Starboard aktualisiert/entfernt
- [ ] Dashboard: aktivieren ohne Kanal → Fehler

## Post-Phase – Welcome Embed Mentions + Autorole + Preview (Status: implementiert)

### Abgeschlossen (Code)

- Embed `{user}` in Titel/Author/Footer als Displayname (Discord resolved Mentions dort nicht)
- Beschreibung/Content behalten `<@id>`-Mentions
- Autorole: Rollen nachladen, managed/@everyone filtern, `editable`-Check, Warn-Logs bei Hierarchy/Permission
- WebUI Embed-/Components-Vorschau: echte Session-User- + Guild-Daten statt Dummy `@Alex`

### Manuell testen

- [ ] Welcome-Embed Titel `Welcome {user}` → Anzeigename, nicht `<@id>`
- [ ] Beschreibung `{user}` → klickbare Mention
- [ ] User-Autorollen gesetzt, Bot-Rolle über Autorolle, Manage Roles → Rolle bei Join
- [ ] Dashboard Welcome-Embed-Vorschau zeigt eigenen Namen/Avatar und Servername/-icon

## Post-Phase – Owner Guild-Liste Meta + Invite (Status: implementiert)

### Abgeschlossen (Code)

- Owner `/owner/guilds`: Discord-Name, Icon, Member-Count, ID (Live-Meta via Bot-Token, Redis-Cache)
- Suche nach Name und ID
- Aktion „Invite erstellen“ (Systemkanal zuerst, max. 24h / 5 Uses, Clipboard + Owner-Audit)

### Manuell testen

- [ ] Serverliste zeigt Name/Logo/Mitglieder statt nur ID
- [ ] Suche nach Servername filtert
- [ ] Invite erstellen → Link im Toast/Clipboard; Bot braucht Create Instant Invite

## Post-Phase – Owner Guild-Dashboard Bypass (Status: implementiert)

### Abgeschlossen (Code)

- Owner-Team (jede Owner-Rolle) kann jedes Guild-Dashboard öffnen, in dem der Bot installiert ist — ohne Discord „Server verwalten“
- `requireGuildAccess` / Layout nutzen Owner-Bypass; Guild-Meta via Bot-API wenn nicht in OAuth-Liste
- Owner `/owner/guilds`: Button „Dashboard öffnen“; Hinweisbanner im Guild-Dashboard bei Bypass

### Manuell testen

- [ ] Owner-UI → Server → Dashboard öffnen (Server, auf dem man kein Member/Manager ist)
- [ ] Welcome/Settings speichern funktioniert; Banner „Owner-Zugriff“ sichtbar
- [ ] Nicht-Owner ohne Manage Guild → weiterhin kein Zugriff

## Post-Phase – Owner Presence WebUI Polish (Status: implementiert)

### Abgeschlossen (Code)

- PresenceForm: Live-Vorschau, i18n-Enums, Zeichenzähler, Switch+Confirm für Wartung, Hinweise
- Sofort-Apply: WebUI enqueued `presenceRefresh` nach Save
- Wartungsmodus: Discord DND + Wartungstext (keine Rotation)
- VIEWER read-only auf `/owner/presence` (Save nur ADMIN+)

### Manuell testen

- [ ] Status/Text speichern → Discord-Präsenz innerhalb weniger Sekunden
- [ ] Rotierende Zeilen → Vorschau erste Zeile; Job rotiert weiter
- [ ] Wartung an → Confirm, Discord zeigt Wartungstext, kein Rotating
- [ ] VIEWER sieht Formular, kann nicht speichern

## Post-Phase – Owner About /about (Status: implementiert)

### Abgeschlossen (Code)

- Core-Command `/about` (neben SPEC-`/info`); Help-Katalog + Locales DE/EN
- Prisma `BotAboutConfig` Singleton + Migration `20260724120000_bot_about`
- Owner `/owner/about`: TEXT / Embed / Components V2 (Builder), Redis-Cache, Audit
- Components-V2-Source `a` für Button-Interaktionen aus `/about`

### Manuell testen

- [ ] Migration deploy + Bot/WebUI neu bauen
- [ ] Owner About Embed speichern → `/about` zeigt Embed
- [ ] About deaktivieren → Deaktiviert-Hinweis
- [ ] `/info` unverändert technisch

## Post-Phase – Automod/Moderation Dashboard Ausbau (Status: implementiert)

### Abgeschlossen (Code)

- Automod: alle 10 Filterregeln im Dashboard (Aktion, Schwellen, Link-WL/BL, Wörter/Regex, Ausnahmen)
- Lockdown an/aus enqueued `activateLockdown` / `deactivateLockdown` an den Bot
- Anti-Raid-Aktion `VERIFY` (Unverified-Rolle) zusätzlich zu `LOCKDOWN`
- Moderation: Warn-Tabelle + Case edit/delete im Dashboard
- `/lock` `/unlock` Option `server` für guild-weiten Lock
- Shared: `AutomodRuleDashboardSchema`, `CaseUpdateDashboardSchema`, `AntiRaidAction` inkl. VERIFY

### Manuell testen

- [ ] Automod: WORD_FILTER Wörter setzen, EXTERNAL_LINK Whitelist, Regel speichern → Filter greift
- [ ] Lockdown-Toggle aus → Kanäle wieder beschreibbar
- [ ] Anti-Raid VERIFY: Unverified-Rolle in Verification konfiguriert, Join-Welle → Rolle
- [ ] Cases: Grund editieren / löschen; Warnings entfernen
- [ ] `/lock server:true` und `/unlock server:true`

## Post-Phase – Selfroles WebUI → Discord Sync (Status: implementiert)

### Abgeschlossen (Code)

- Root cause: Dashboard speicherte Selfrole-Panels nur in Postgres, ohne BullMQ-Job an den Bot
- Neue Queue `selfroles` mit Jobs `selfRolePanelCreate` / `Update` / `Delete`
- WebUI wartet (bounded) auf Bot-Bestätigung; Panel hat danach `messageId`
- Update zieht fehlende Discord-Nachricht nach (auch für Alt-Panels ohne `messageId`)
- Roles-JSON `{ entries }` wird im Dashboard korrekt gelesen

### Manuell testen

- [ ] WebUI: Panel erstellen → Embed/Buttons erscheinen im gewählten Channel
- [ ] Panel editieren (Titel/Rollen) → Discord-Nachricht aktualisiert sich
- [ ] Channel wechseln → alte Nachricht weg, neue im Zielkanal
- [ ] Panel löschen → Discord-Nachricht entfernt
- [ ] Alt-Panel ohne Message speichern → Nachricht wird nachgezogen

## Nächster geplanter Schritt

- Deploy auf Traefik-Server manuell verifizieren; danach ggf. `deploy` → `main` mergen (nur auf Freigabe).
