Adds Discord DM as a notification channel beside web push, for users who linked
their Discord account and opted in. It rides the same fan-out point as push
(_dispatch_discord next to _dispatch_push in the notifier pass), reusing the
transport-agnostic title/body/deep-link, and stays best-effort and isolated: the
in-app row is already committed, and push/email remain the fallback for anyone
Discord can't reach (its DM rule requires a shared server / user install).
- discord.py: send_dm() opens the DM channel then posts an embed via the bot token,
with one capped 429 retry. Absolute deep links (a DM can't resolve a relative
path the way the service worker can).
- notify.py: _dispatch_discord() delivers only when the subscriber has a linked
discord_id and discord_dm on; no-ops entirely when no bot token is configured.
- models.py: User.discord_dm (opt-in flag) + migration 002. Linking sets it True
(an active opt-in); a new POST /discord/dm mutes it without unlinking, and unlink
clears it. Exposed on UserRead; account popover shows an on/off toggle.
Claude-Session: https://claude.ai/code/session_013dRZmX9D3JEntfMKWMTWZ8