Skip to content

Plan: realtime state (Soketi / Pusher protocol + TanStack Query invalidation) #97

@kingRayhan

Description

@kingRayhan

Summary

Inventory and phased rollout for realtime UI updates on TechDiary: push small signals (e.g. Pusher-compatible broker such as Soketi), then invalidate / refetch TanStack Query caches. Database and server actions remain the source of truth.

Principle

  • Realtime delivers event name + optional ids only.
  • Client invalidates existing queries (same idea as today in src/app/dashboard/notifications/page.tsx and src/components/render-props/ResourceReactionable.tsx).
flowchart LR
  subgraph writers [Writers]
    Action[Server action]
    Inngest[Inngest persistNotificationFn]
  end
  subgraph broker [Broker e.g. Soketi]
    Pub[HTTP trigger]
  end
  subgraph clients [Clients]
    PusherJS[pusher-js private channel]
    QC[TanStack Query invalidate]
  end
  Action --> Pub
  Inngest --> Pub
  Pub --> PusherJS
  PusherJS --> QC
Loading

Resource inventory

Resource Primary query keys Audience Suggested channel Phase
In-app notifications my-notifications, unread-notification-count One user private-user.{userId} 1
Comments comments, resource_id, resource_type Same article/gist page private-resource.{resourceType}.{resourceId} 2
Reactions reaction, resource_id, resource_type Same as comments Same resource channel 3
Bookmarks + dashboard list bookmark-status, dashboard-articles Per user / per resource User + optional resource channel 3–4
Author dashboard articles dashboard-articles One user private-user.{userId} 3
Gists gists, gist Owner + viewers User + gist resource channel 4
Profile article feed user-article-feed Profile visitors TBD 4
Tag feed tag-articles Tag page visitors e.g. tag.{tagId} 4
Home article feed article-feed Very wide fanout Defer (or per-user if follow graph exists) Defer
Sessions mySessions One user private-user.{userId} Optional
User card user Profile viewers Low value Optional
Static widgets top-tags, etc. Skip

Notification hook today: comment.action.ts and reaction.actions.ts emit app/notification.requested; persistNotificationFn in src/lib/inngest.ts inserts rows. Phase 1 can publish to the recipient’s user channel after successful insert in that Inngest function.

Phased rollout

  1. Phase 1 — Notifications: Subscribe to private-user.{userId}; on persist success trigger e.g. invalidate with { scope: "notifications" }; client invalidates my-notifications and unread-notification-count.
  2. Phase 2 — Comments: Subscribe from CommentSection; publish on create/update/delete; invalidate comments query.
  3. Phase 3 — Reactions / dashboard articles: Same resource channel for reactions; user channel when the signed-in user’s articles change.
  4. Defer: Global home feed broadcast; tag/profile feeds unless product requires them.

Auth and safety

  • Next.js private channel auth endpoint: verify session (getSession / authID in src/backend/services/session.actions.ts) before signing subscription.
  • Resource channels: authorize subscribe same as “allowed to read this resource”.
  • Do not treat socket payload as authoritative.

Out of scope

  • Collaborative editor (OT/CRDT).
  • Replacing Inngest for durable delivery (add broadcast as a side effect only).
  • Pushing full comment/article bodies over the socket.

Implementation checklist

  • Phase 1: private user channel + publish after notification persist (Inngest) + client invalidate
  • Phase 2: resource channel + comment mutations + invalidate in CommentSection
  • Phase 3 (optional): reactions + dashboard-articles
  • Explicitly defer global article-feed until follow-based or other narrow design exists

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions