# Notifications

> In-app and external notifications (Planned Feature)

## 📋 Overview

Notifications keep users informed about important events such as test failures, analysis completions, and project updates through multiple channels including in-app, email, and Slack.

## 🎯 Current Status

**Status**: 📋 Planned (Phase 4)

Notification system is not yet implemented. This is planned as part of the advanced features phase.

## 🏗️ Planned Architecture

### Database Schema

**notifications table**:

```typescript
{
  id: string
  userId: string
  type: 'test_failure' | 'analysis_complete' | 'project_update' | 'mention'
  title: string
  message: string
  data: object             // Additional context
  read: boolean
  readAt: Date?
  actionUrl: string?       // Link to relevant page
  createdAt: Date
}
```

**notificationSettings table**:

```typescript
{
  id: string
  userId: string
  channel: 'in_app' | 'email' | 'slack' | 'webhook'
  events: string[]         // Which events to notify about
  enabled: boolean
  config: object           // Channel-specific config
  createdAt: Date
  updatedAt: Date
}
```

## 📬 Notification Channels (Planned)

### In-App Notifications

**Features**:

- Real-time updates via WebSocket
- Notification bell with badge count
- Toast notifications for important events
- Notification center/inbox

**Implementation**:

```typescript
// WebSocket connection
const ws = new WebSocket(`wss://api.yourdomain.com/notifications`)

ws.onmessage = (event) => {
  const notification = JSON.parse(event.data)
  
  // Show toast
  toast({
    title: notification.title,
    description: notification.message,
    action: notification.actionUrl ? (
      <Button asChild variant="outline">
        <Link to={notification.actionUrl}>View</Link>
      </Button>
    ) : undefined
  })
  
  // Update badge count
  setBadgeCount(prev => prev + 1)
}
```

### Email Notifications

**Templates**:

```html
<!-- Test Failure Email -->
<html>
  <body>
    <h1>Test Run Failed</h1>
    <p>Your test run for <strong>{{ projectName }}</strong> has failed.</p>
    
    <table>
      <tr>
        <td>Total Tests:</td>
        <td>{{ totalTests }}</td>
      </tr>
      <tr>
        <td>Failed:</td>
        <td>{{ failedTests }}</td>
      </tr>
      <tr>
        <td>Pass Rate:</td>
        <td>{{ passRate }}%</td>
      </tr>
    </table>
    
    <a href="{{ actionUrl }}">View Details</a>
  </body>
</html>
```

**Configuration**:

```bash
# .env
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASSWORD=SG.xxxxx
SMTP_FROM=notifications@yourdomain.com
```

### Slack Notifications

**Setup**:

1. Create Slack app
2. Add incoming webhook
3. Store webhook URL in user settings

**Message format**:

```json
{
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "🚨 Test Run Failed"
      }
    },
    {
      "type": "section",
      "fields": [
        {
          "type": "mrkdwn",
          "text": "*Project:*\nMy Project"
        },
        {
          "type": "mrkdwn",
          "text": "*Failed Tests:*\n5/50"
        }
      ]
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": {
            "type": "plain_text",
            "text": "View Details"
          },
          "url": "https://app.yourdomain.com/runs/123"
        }
      ]
    }
  ]
}
```

## 🔌 API Reference (Planned)

### Get Notifications

```typescript
const notifications = await trpc.notifications.list.query({
  read: false, // unread only
  limit: 20
})

// Response
{
  notifications: [
    {
      id: 'notif_123',
      type: 'test_failure',
      title: 'Test Run Failed',
      message: 'Your test run for Project X failed with 5 errors',
      data: {
        runId: 'run_456',
        projectId: 'proj_789'
      },
      actionUrl: '/dashboard/projects/proj_789/runs/run_456',
      read: false,
      createdAt: '2024-01-01T00:00:00Z'
    }
  ],
  unreadCount: 12
}
```

### Mark as Read

```typescript
await trpc.notifications.markRead.mutate({
  notificationId: 'notif_123'
})

// Or mark all as read
await trpc.notifications.markAllRead.mutate()
```

### Get Notification Settings

```typescript
const settings = await trpc.notifications.getSettings.query()

// Response
{
  settings: [
    {
      channel: 'email',
      enabled: true,
      events: ['test_failure', 'analysis_complete'],
      config: {
        email: 'user@example.com'
      }
    },
    {
      channel: 'slack',
      enabled: false,
      events: ['test_failure'],
      config: {
        webhookUrl: 'https://hooks.slack.com/...'
      }
    }
  ]
}
```

### Update Settings

```typescript
await trpc.notifications.updateSettings.mutate({
  channel: 'email',
  enabled: true,
  events: ['test_failure', 'test_success', 'analysis_complete']
})
```

## 🎨 Frontend Implementation (Planned)

### Notification Bell

```tsx
export function NotificationBell() {
  const { data } = trpc.notifications.list.useQuery({
    read: false
  })

  const unreadCount = data?.unreadCount || 0

  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button variant="ghost" size="icon" className="relative">
          <BellIcon />
          {unreadCount > 0 && (
            <span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
              {unreadCount > 9 ? '9+' : unreadCount}
            </span>
          )}
        </Button>
      </PopoverTrigger>
      
      <PopoverContent className="w-80">
        <div className="space-y-2">
          <div className="flex justify-between items-center">
            <h3 className="font-semibold">Notifications</h3>
            {unreadCount > 0 && (
              <Button
                variant="ghost"
                size="sm"
                onClick={() => markAllRead()}
              >
                Mark all read
              </Button>
            )}
          </div>
          
          <div className="space-y-2 max-h-96 overflow-y-auto">
            {data?.notifications.map((notification) => (
              <NotificationItem
                key={notification.id}
                notification={notification}
              />
            ))}
          </div>
        </div>
      </PopoverContent>
    </Popover>
  )
}
```

### Notification Item

```tsx
interface NotificationItemProps {
  notification: Notification
}

export function NotificationItem({ notification }: NotificationItemProps) {
  const markRead = trpc.notifications.markRead.useMutation()
  
  const handleClick = () => {
    if (!notification.read) {
      markRead.mutate({ notificationId: notification.id })
    }
    if (notification.actionUrl) {
      navigate(notification.actionUrl)
    }
  }

  return (
    <div
      onClick={handleClick}
      className={cn(
        'p-3 rounded-lg cursor-pointer hover:bg-muted',
        !notification.read && 'bg-blue-50'
      )}
    >
      <div className="flex items-start gap-2">
        <NotificationIcon type={notification.type} />
        <div className="flex-1 space-y-1">
          <p className="text-sm font-medium">{notification.title}</p>
          <p className="text-sm text-muted-foreground">
            {notification.message}
          </p>
          <p className="text-xs text-muted-foreground">
            {formatDistanceToNow(notification.createdAt)} ago
          </p>
        </div>
        {!notification.read && (
          <div className="w-2 h-2 bg-blue-500 rounded-full" />
        )}
      </div>
    </div>
  )
}
```

### Notification Settings Page

```tsx
export function NotificationSettings() {
  const { data } = trpc.notifications.getSettings.useQuery()
  const updateSettings = trpc.notifications.updateSettings.useMutation()

  return (
    <div className="space-y-6">
      <h1 className="text-2xl font-bold">Notification Settings</h1>
      
      <Card>
        <CardHeader>
          <CardTitle>Email Notifications</CardTitle>
        </CardHeader>
        <CardContent className="space-y-4">
          <div className="flex items-center justify-between">
            <Label>Enable Email Notifications</Label>
            <Switch
              checked={data?.email?.enabled}
              onCheckedChange={(enabled) =>
                updateSettings.mutate({
                  channel: 'email',
                  enabled
                })
              }
            />
          </div>
          
          <div className="space-y-2">
            <Label>Notify me about:</Label>
            {['test_failure', 'test_success', 'analysis_complete'].map((event) => (
              <div key={event} className="flex items-center space-x-2">
                <Checkbox
                  id={event}
                  checked={data?.email?.events.includes(event)}
                  onCheckedChange={(checked) => {
                    const events = checked
                      ? [...data.email.events, event]
                      : data.email.events.filter(e => e !== event)
                    updateSettings.mutate({
                      channel: 'email',
                      events
                    })
                  }}
                />
                <label htmlFor={event}>{formatEventName(event)}</label>
              </div>
            ))}
          </div>
        </CardContent>
      </Card>

      <Card>
        <CardHeader>
          <CardTitle>Slack Notifications</CardTitle>
        </CardHeader>
        <CardContent>
          {data?.slack?.webhookUrl ? (
            <div className="space-y-4">
              <div className="flex items-center justify-between">
                <Label>Slack Connected</Label>
                <Button variant="outline" size="sm">
                  Disconnect
                </Button>
              </div>
              {/* Event selection similar to email */}
            </div>
          ) : (
            <Button>Connect Slack</Button>
          )}
        </CardContent>
      </Card>
    </div>
  )
}
```

## 🔔 Event Types (Planned)

### Test-Related

- `test_run.started`
- `test_run.completed`
- `test_run.failed`
- `test_coverage.threshold_failed`

### Analysis-Related

- `analysis.completed`
- `analysis.failed`
- `test_candidates.found`

### Project-Related

- `project.created`
- `project.updated`
- `project.deleted`
- `project.member_added`

### Organization-Related

- `org.member_added`
- `org.member_removed`
- `org.role_changed`

## 🧪 Testing Notifications

```typescript
describe('Notifications', () => {
  it('creates notification on test failure', async () => {
    await createTestRun({
      status: 'FAILED',
      projectId: 'proj_123'
    })
    
    const notifications = await db.query.notifications.findMany({
      where: eq(notifications.type, 'test_failure')
    })
    
    expect(notifications.length).toBeGreaterThan(0)
  })
})
```

## 🔗 Related Documentation

- **Webhooks**: [Programmatic notifications](./webhooks.md)
- **Projects**: [Project settings](./projects.md)
- **Test Execution**: [Test run events](./test-execution.md)

---

*Features section complete! Continue with [Contributing](../contributing/README.md) →*
