Live updates

View on GitHub

Live updates

Changes other people make, and new notifications, show up on open pages without a reload: statuses, assignees and comments on an issue, the board, the list, the calendar, the dashboard counters, the notification bell, and who else is looking at an issue. It runs on Laravel Reverb (a WebSocket server) with Laravel Echo in the browser. Without Reverb the app keeps working and refreshes itself on a timer instead.

Architecture in one paragraph

A write calls LiveUpdates (directly, or through the BroadcastsLiveChanges model trait). LiveUpdates collects and de-duplicates the changes of a request or job and, once it is done, dispatches one ProjectChanged event per project (and AccessChanged / NotificationsChanged per user) on the live queue. Reverb delivers it to the private channels project.{id} and user.{id}. The event carries ids and kinds only, never row contents. In the browser useLiveChannel receives it, changeProps.ts maps it to the Inertia props that went stale, and requestLiveReload re-reads just those props through the normal authorised routes (coalesced, paused while the tab is hidden or a card is being dragged). If the socket is off or down, useLiveFallback does the same re-read on a timer. Presence ("who is viewing this issue", "someone is editing the description") uses a presence channel and client-to-client whispers; it needs the socket and has no fallback.

Guides, in the order you'd actually need them

  1. Set up ReverbLive updatesSet up ReverbFile: docker-compose.yml — the reverb Docker service, every environment variable (Doppler and .env), generating keys, development vs production behind a TLS proxy, scaling.
  2. Announce a changeLive updatesAnnounce a changeLive clients only hear about a write if the write says so. There are three ways, from most to least automatic. The worked example adds live updates for a h… — make a new model or write path tell live clients: BroadcastsLiveChanges, LiveUpdates, the events and the rule that payloads carry no data.
  3. React to a change in the UILive updatesReact to a change in the UIThe worked example makes a hypothetical project Milestones page refresh when a milestones change arrives (the type announced in Announce a change). — keep a page in step: useLiveChannel, changeProps.ts, closures for lazy props, pausing reloads.
  4. Presence and edit hintsLive updatesPresence and edit hintsPresence answers "who else has this open?" and carries small client-to-client hints ("Ada is editing the description"). It uses a Reverb presence channel a… — show who is on a page and warn about concurrent edits.
  5. Fallback, security and troubleshootingLive updatesFallback, security and troubleshootingIf Reverb is not configured, or the socket is down, an open page re-reads the props it shows on a timer, so the app behaves like it did before live updates… — what happens without Reverb, the security model, and what to check when nothing arrives.

What is live today

WhereEventThe page re-reads
Notification bell and popup, browser notificationsnotifications.changed on user.{id}notifications, notificationUnreadCount
The user's abilities and project listaccess.changed on user.{id}the whole page
Project list, board, calendar, activityproject.changed on project.{id}the props of the visible view
Issue page (fields, comments, activity)project.changedissue, ancestors, linkedPullRequests, activity while open
Dashboard counters, trend, feedproject.changed on each of the user's projectsissueStats, productivity_trend, projects, activityLogs
Settings: members, roles, labels, priorities, issue typesproject.changedthe whole page
Who is viewing an issue, description edit warningpresence channel issue.{id}no reload (client state)

Environment variables at a glance

VariablePurpose
BROADCAST_CONNECTIONreverb turns live updates on; anything else (or missing keys) keeps the app on polling
REVERB_APP_ID, REVERB_APP_KEY, REVERB_APP_SECRETthe credentials; make reverb-keys prints a fresh set
REVERB_PUBLIC_HOST, REVERB_PUBLIC_PORT, REVERB_PUBLIC_SCHEMEwhat the browser connects to
REVERB_ALLOWED_ORIGINShosts allowed to open a socket (default: the APP_URL host)
REVERB_SCALING_ENABLEDshare events between several Reverb instances through Redis
LIVE_POLL_INTERVALfallback refresh in seconds (default 30, minimum 10, 0 = off)