Frontend architecture for channel connections and notification settings management.
Terminology: Notification channel configuration is a Setting (affects functionality) per ../../foundation/user-data.md.
Route Structure
/app/settings/notifications/
├── +page.svelte # Main: connections + preferences
├── +page.server.ts # Load + form actions
├── telegram/
│ └── callback/
│ ├── +page.svelte # "Connecting..." loading state
│ └── +page.server.ts # Webhook already handles, this is fallback
└── discord/
└── callback/
├── +page.svelte # "Connecting..." loading state
└── +page.server.ts # Exchange code, store tokens, redirect
Why /app/settings/notifications?
| Alternative |
Rejected Because |
/app/notifications/preferences |
"Notifications" = inbox, "Settings" = configuration |
/app/account/notifications |
Account is profile/security, not integrations |
User mental model: "I go to Settings to configure how things work."
Page Layout
┌─────────────────────────────────────────────────────────────────┐
│ Notification Settings │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Connected Channels │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ ✉️ Email Connected ││ │
│ │ │ user@example.com ││ │
│ │ │ (Cannot disconnect) ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ 📱 Telegram [Connect] ││ │
│ │ │ Not connected ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ 💬 Discord [Connect] ││ │
│ │ │ Not connected ││ │
│ │ │ ⚠️ Requires joining our server for DMs ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Notification Settings │ │
│ │ │ │
│ │ Email Telegram Discord │ │
│ │ │ │
│ │ Mentions ✓ ○ ○ │ │
│ │ Comments ✓ ○ ○ │ │
│ │ Security alerts ✓* ○ ○ │ │
│ │ System updates ✓ ○ ○ │ │
│ │ │ │
│ │ * Required - cannot be disabled │ │
│ │ ○ Connect channel to enable │ │
│ │ │ │
│ │ [Save Settings] │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Email Frequency │ │
│ │ │ │
│ │ ○ Real-time │ │
│ │ ● Daily digest │ │
│ │ ○ Weekly digest │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Channel Connection Cards
Connected State
┌─────────────────────────────────────────────────────────────────┐
│ 📱 Telegram ✓ Connected │
│ @username │
│ Last notification: 2 hours ago │
│ │
│ [Disconnect] │
└─────────────────────────────────────────────────────────────────┘
Disconnected State
┌─────────────────────────────────────────────────────────────────┐
│ 📱 Telegram │
│ Not connected │
│ │
│ Connect to receive instant notifications on Telegram. │
│ [Connect] │
└─────────────────────────────────────────────────────────────────┘
Error State
┌─────────────────────────────────────────────────────────────────┐
│ 📱 Telegram ⚠️ Disconnected │
│ @username │
│ │
│ Bot was blocked. Reconnect to resume notifications. │
│ [Reconnect] │
└─────────────────────────────────────────────────────────────────┘
Settings Matrix
Data Structure
// NotificationType config (static)
{
id: 'mention',
name: 'Mentions',
description: 'When someone @mentions you',
supportedChannels: ['email', 'telegram', 'discord'],
required: false
}
// Security type (cannot disable email)
{
id: 'security',
name: 'Security alerts',
description: 'New device logins, security events',
supportedChannels: ['email', 'telegram', 'discord'],
required: true, // Cannot disable email
requiredChannels: ['email']
}
Matrix Cell States
| State |
Visual |
Interaction |
| Enabled |
✓ (checked) |
Click to disable |
| Disabled |
☐ (unchecked) |
Click to enable |
| Unavailable |
○ (greyed out) |
Click shows "Connect {channel}" tooltip |
| Required |
✓* (locked) |
Cannot click, shows "Required" tooltip |
| Not supported |
— (dash) |
Channel doesn't support this type |
Form Schema
notification_settings:
mention:
email: boolean
telegram: boolean
discord: boolean
comment:
email: boolean
telegram: boolean
discord: boolean
security:
email: true # Forced, cannot be false
telegram: boolean
discord: boolean
system:
email: boolean
telegram: boolean
discord: boolean
Connection Flows
Telegram Connection
- User clicks "Connect Telegram"
- Form action generates verification token
- Returns deep link URL
- Client opens
t.me/Bot?start=TOKEN in new tab
- User sends
/start to bot in Telegram
- Bot webhook validates token, links account
- User returns to settings page
- Page shows polling indicator, then updates to "Connected"
Polling after click:
┌─────────────────────────────────────────────────────────────────┐
│ 📱 Telegram │
│ Waiting for Telegram... [Cancel] │
│ Open Telegram and send /start to @YourBot │
└─────────────────────────────────────────────────────────────────┘
Poll /api/notifications/telegram/status every 3 seconds until connected or cancelled.
Discord Connection
- User clicks "Connect Discord"
- Navigate to Discord OAuth URL (same window on mobile, new tab on desktop)
- User approves on Discord
- Discord redirects to
/discord/callback
- Callback exchanges code, stores tokens, redirects to settings with success param
- Settings page shows toast: "Discord connected!"
Form Actions
| Action |
Purpose |
connectTelegram |
Generate token, return deep link |
disconnectTelegram |
Set is_active = false |
disconnectDiscord |
Revoke token, set is_active = false |
updateSettings |
Save settings matrix |
Mobile Considerations
Touch Targets
| Element |
Min Size |
| Checkboxes |
44×44px (WCAG, Apple HIG) |
| Buttons |
44×44px min height |
| Card tap areas |
Full card width |
Responsive Matrix
On mobile (<768px), matrix transforms to stacked cards:
┌─────────────────────────────────────────┐
│ Mentions │
│ │
│ Email [✓] │
│ Telegram [○] Connect to enable │
│ Discord [○] Connect to enable │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Security alerts │
│ │
│ Email [✓] Required │
│ Telegram [○] Connect to enable │
│ Discord [○] Connect to enable │
└─────────────────────────────────────────┘
OAuth on Mobile
Use window.location.href instead of window.open() for Discord OAuth:
- Mobile browsers may block popups
- Returning to app is more reliable with same-window navigation
Accessibility
ARIA Labels
| Element |
Label |
| Channel card |
"Telegram: Connected as @username" |
| Matrix checkbox |
"Send mentions via Telegram" |
| Required checkbox |
"Security alerts via Email (required)" |
| Disabled checkbox |
"Connect Telegram to enable" |
Keyboard Navigation
| Key |
Action |
| Tab |
Move between cards and controls |
| Space/Enter |
Toggle checkbox, activate button |
| Escape |
Close confirmation dialogs |
Screen Reader Announcements
- After successful connection: "Telegram connected successfully"
- After settings save: "Notification settings saved"
- After disconnect: "Telegram disconnected"
Data Loading
Load Function
| Data |
Source |
channels |
Query all channel tables for user |
settings |
Query notification_settings row |
notificationTypes |
Static config (could be cached) |
discordClientId |
Environment variable |
discordRedirectUri |
Environment variable |
Optimistic Updates
- Preference toggles: Update UI immediately, revert on error
- Connection status: Poll or use SSE for real-time updates
- Disconnect: Confirm first, then update
Error Handling
| Error |
Display |
| Token generation failed |
Toast: "Failed to generate connection link" |
| OAuth cancelled |
URL param → Toast: "Discord connection cancelled" |
| OAuth error |
URL param → Toast: "Discord connection failed" |
| Settings save failed |
Toast: "Failed to save settings" + revert |
| Channel already linked |
Toast: "This Telegram account is already linked" |
Component Structure
src/lib/components/composites/notifications/
├── ChannelCard.svelte # Single channel connection card
├── PreferencesMatrix.svelte # Type × Channel grid
├── PreferencesCard.svelte # Mobile stacked version
├── ConnectionStatus.svelte # Polling indicator
└── FrequencySelector.svelte # Email digest options
Related