~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
98 lines · 4.3 KB Raw History

Notifications Design

Overview

Add a notifications system to Grain, matching grain-next's approach: computed queries over existing records (favorites, comments, follows, mentions) with read/unread tracking via server-side timestamp.

Notification Types

  1. gallery-favorite — someone favorited your gallery
  2. gallery-comment — someone commented on your gallery
  3. gallery-comment-mention — someone mentioned you in a comment
  4. gallery-mention — someone mentioned you in a gallery description
  5. reply — someone replied to your comment
  6. follow — someone followed you

Data Layer

No new database tables. Notifications are computed by querying existing records where the target matches the viewer's DID:

  • social.grain.favorite — subject is a gallery URI owned by viewer
  • social.grain.comment — subject is a gallery URI owned by viewer, or facets contain a mention of viewer's DID
  • social.grain.comment (reply) — reply_to references a comment by viewer
  • social.grain.graph.follow — subject is viewer's DID
  • social.grain.gallery — facets in description mention viewer's DID

Read/Unread Tracking

Store a lastSeenNotifications ISO timestamp in _preferences via the existing putPreference endpoint. Notifications with created_at after this timestamp are "unseen."

API

New XRPC Query: social.grain.unspecced.getNotifications

  • Input: viewer DID, cursor (optional), limit (default 20)
  • Output: paginated edges with:
    • reason: one of the 6 notification types
    • author: hydrated profile (did, handle, displayName, avatar)
    • subject: gallery URI or comment URI (context-dependent)
    • gallery: hydrated gallery info (title, thumbnail) when applicable
    • commentText: comment text when applicable
    • createdAt: timestamp
    • cursor: for pagination
    • unseenCount: count of notifications newer than lastSeenNotifications

The query unions results from favorites, comments, follows, and galleries, sorted by created_at desc.

UI

Bell Icon

  • Desktop: Left sidebar, between Home and Settings icons. Shows red badge with unseen count when > 0.
  • Mobile: Bottom bar, between Create and Profile. Same badge behavior.

Notifications Page (/notifications)

  • Infinite scroll list, 20 items per page
  • Each row: author avatar, action text, relative timestamp, context (gallery thumbnail, comment text)
  • On mount: updates lastSeenNotifications to now, clears badge

Notification Row Rendering by Type

TypeAction TextContext
gallery-favorite"favorited your gallery"Gallery title + thumbnail
gallery-comment"commented on your gallery"Comment text + gallery thumbnail
gallery-comment-mention"mentioned you in a comment"Comment text + gallery thumbnail
gallery-mention"mentioned you in a gallery"Gallery description excerpt + thumbnail
reply"replied to your comment"Reply text + original comment + thumbnail
follow"followed you"(none)

State Management

TanStack Query Keys

  • ["notifications", did] — paginated notification list (60s stale time)
  • ["unseenNotificationCount", did] — badge count (60s stale time)

Mark-as-Read Flow

  1. User navigates to /notifications
  2. onMount calls putPreference({ key: "lastSeenNotifications", value: now })
  3. Invalidates ["unseenNotificationCount"] — badge clears
  4. Optimistic update: set count to 0 immediately

File Changes

New Files

  • server/xrpc/getNotifications.ts — XRPC query handler
  • app/routes/notifications/+page.svelte — notifications page
  • app/lib/components/atoms/NotificationItem.svelte — notification row component

Modified Files

  • hatk.config.ts — register new XRPC endpoint
  • app/lib/queries.ts — notification list + unseen count queries
  • app/lib/components/organisms/Sidebar.svelte — bell icon with badge
  • app/lib/components/molecules/MobileBottomBar.svelte — bell icon with badge