# Unread/Read State Tracking

- **Pattern:** `ab-000772` (`community-social-engagement.notifications.read-state-management`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000772
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000772)

## Why it matters

Without a read-state column, every notification looks identical forever — the bell badge never clears, users learn to ignore it, and push/email digests repeat content the user already saw in-app. A single `read_at` timestamp unlocks unread counts, mark-all-as-read, per-channel deduplication across web and mobile, and analytics on notification effectiveness. Missing this field means every downstream notification feature is blocked until the schema is backfilled.

## Severity rationale

Low because notifications still deliver, but engagement quality and cross-channel deduplication are blocked without the field.

## Remediation

Add a nullable `readAt DateTime?` column to the notification model, expose PATCH endpoints for single-item and bulk mark-as-read, and compute the unread badge from `notifications.filter(n => !n.readAt).length`. Store the timestamp (not a boolean) so you can audit read latency later. See `prisma/schema.prisma` and `app/api/notifications/[id]/route.ts`:

```prisma
model Notification {
  id String @id
  readAt DateTime?
  userId String
}
```

## Detection

- **ID:** `read-state-management`
- **Severity:** `low`
- **What to look for:** Enumerate all relevant files and Examine the notification schema. Look for a `read_at` timestamp or `is_read` boolean field. Check for API endpoints to "Mark as read" (single or bulk). Look for UI badges showing unread notification count.
- **Pass criteria:** No more than 0 violations are acceptable. Notification model includes a read state field. Endpoints exist to mark notifications as read. UI displays unread count and visual indicators for unread notifications.
- **Fail criteria:** No read state tracked, or notifications are always read, or no way to mark as read.
- **Skip (N/A) when:** Notifications feature doesn't exist.
- **Detail on fail:** `"Notification model has no read_at field — cannot track read state"` or `"UI shows notification count but no 'Mark as read' button"`
- **Remediation:** Add read state tracking:

  ```ts
  // Prisma schema
  model Notification {
    id String @id
    userId String
    type String // 'post_liked', 'user_followed', etc.
    content String
    readAt DateTime?
    createdAt DateTime @default(now())
    user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  }

  // API route to mark as read
  export async function PATCH(req: Request, { params }) {
    const { read } = await req.json()

    const notification = await db.notification.update({
      where: { id: params.id },
      data: { readAt: read ? new Date() : null }
    })

    return Response.json(notification)
  }

  // Mark all as read
  export async function PUT(req: Request) {
    const userId = getCurrentUserId()

    await db.notification.updateMany({
      where: { userId, readAt: null },
      data: { readAt: new Date() }
    })

    return Response.json({ success: true })
  }

  // UI component
  function NotificationBell() {
    const unreadCount = notifications.filter(n => !n.readAt).length

    return (
      <button>
        🔔
        {unreadCount > 0 && <span className="badge">{unreadCount}</span>}
      </button>
    )
  }
  ```

Taxons: user-experience

HTML version: https://auditbuffet.com/patterns/ab-000772
