# Real-Time Updates with Turbo Streams

This guide explains how Rails Error Dashboard uses Turbo Streams to provide real-time updates for error tracking without manual page refreshes.

## Table of Contents

- [Overview](#overview)
- [How It Works](#how-it-works)
- [Features](#features)
- [Technical Implementation](#technical-implementation)
- [Configuration](#configuration)
- [Browser Requirements](#browser-requirements)
- [Performance Considerations](#performance-considerations)
- [Troubleshooting](#troubleshooting)

---

## Overview

Rails Error Dashboard now includes **real-time updates** powered by Turbo Streams. When errors occur in your application, the dashboard automatically updates without requiring a manual page refresh.

### What Gets Updated in Real-Time:

1. **Error List** - New errors appear instantly at the top of the list
2. **Dashboard Stats** - Error counts update automatically (Today, This Week, Unresolved, Resolved)
3. **Visual Indicators** - New errors are highlighted with animations
4. **Live Status** - A pulsing "Live" indicator shows the connection is active

---

## How It Works

### Turbo Streams Technology

Turbo Streams is part of the Hotwire framework and provides efficient, real-time updates over WebSockets or SSE (Server-Sent Events).

**Flow:**

```text
Error Occurs → ErrorLog Created → Turbo Stream Broadcast → Dashboard Updates
```

**Diagram:**

```text
┌─────────────────┐
│  Rails App      │
│  Error occurs   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  ErrorLog.create│
│  after_commit   │
└────────┬────────┘
         │
         ▼
┌──────────────────────┐
│ Turbo::StreamsChannel│
│ broadcast_prepend_to │
└────────┬─────────────┘
         │
         ▼
┌──────────────────────┐
│  WebSocket/SSE       │
│  to all subscribers  │
└────────┬─────────────┘
         │
         ▼
┌──────────────────────┐
│  Browser Dashboard   │
│  Auto-updates UI     │
└──────────────────────┘
```

---

## Features

### 1. Live Error List Updates

When a new error is logged:
- Appears instantly at the top of the error list
- Yellow highlight animation (3 seconds)
- Smooth slide-in transition
- No page reload needed

**Example:**

```ruby
# When this happens in your app:
raise ArgumentError, "Invalid user input"

# Dashboard users see the error appear immediately with:
# - Yellow flash animation
# - Error details (type, message, platform, etc.)
# - Auto-scrolls to top of list
```

### 2. Real-Time Stats Refresh

Dashboard statistics update automatically:
- **Today** - Errors in last 24 hours
- **This Week** - Errors in last 7 days
- **Unresolved** - Open errors
- **Resolved** - Fixed errors

All stat cards pulse briefly when updated to draw attention.

### 3. Visual Feedback

**Live Indicator:**
- Green "Live" badge pulses to show active connection
- Located in header next to timestamp

**New Error Animation:**
```css
/* Yellow highlight fades over 3 seconds */
@keyframes slideInFade {
  0% { background: #FEF3C7; transform: translateY(-20px); }
  10% { background: #FEF3C7; transform: translateY(0); }
  100% { background: transparent; }
}
```

**Stat Card Pulse:**
```css
/* Brief scale animation when stats update */
@keyframes statPulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
}
```

### 4. Error Updates (Recurrences)

When an existing error recurs:
- Occurrence count updates in place
- Last seen timestamp updates
- Stats refresh automatically
- Row briefly highlights to show the update

---

## Technical Implementation

### 1. Turbo Rails Integration

**Gemspec Addition:**

```ruby
# rails_error_dashboard.gemspec
spec.add_dependency "turbo-rails", "~> 2.0"
```

**Layout Script:**

```html
<!-- app/views/layouts/rails_error_dashboard.html.erb -->
<script type="module">
  import * as Turbo from 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.12/+esm'
</script>
```

### 2. Error Model Broadcasting

**Model Callbacks:**

```ruby
# app/models/rails_error_dashboard/error_log.rb
after_create_commit :broadcast_new_error
after_update_commit :broadcast_error_update

def broadcast_new_error
  return unless defined?(Turbo)

  platforms = ErrorLog.distinct.pluck(:platform).compact
  show_platform = platforms.size > 1

  Turbo::StreamsChannel.broadcast_prepend_to(
    "error_list",
    target: "error_list",
    partial: "rails_error_dashboard/errors/error_row",
    locals: { error: self, show_platform: show_platform }
  )
  broadcast_replace_stats
rescue => e
  Rails.logger.error("Failed to broadcast new error: #{e.message}")
end

def broadcast_error_update
  return unless defined?(Turbo)

  Turbo::StreamsChannel.broadcast_replace_to(
    "error_list",
    target: "error_#{id}",
    partial: "rails_error_dashboard/errors/error_row",
    locals: { error: self, show_platform: show_platform }
  )
  broadcast_replace_stats
end

def broadcast_replace_stats
  return unless defined?(Turbo)

  stats = Queries::DashboardStats.call
  Turbo::StreamsChannel.broadcast_replace_to(
    "error_list",
    target: "dashboard_stats",
    partial: "rails_error_dashboard/errors/stats",
    locals: { stats: stats }
  )
end
```

### 3. View Setup

**Subscribe to Turbo Stream:**

```erb
<!-- app/views/rails_error_dashboard/errors/index.html.erb -->
<%= turbo_stream_from "error_list" %>

<!-- Stats container with ID for targeting -->
<div id="dashboard_stats" class="mb-4">
  <%= render "stats", stats: @stats %>
</div>

<!-- Error list tbody with ID for targeting -->
<tbody id="error_list">
  <% @errors.each do |error| %>
    <%= render "error_row", error: error, show_platform: @platforms.size > 1 %>
  <% end %>
</tbody>
```

**Partials:**

```erb
<!-- app/views/rails_error_dashboard/errors/_error_row.html.erb -->
<tr id="error_<%= error.id %>">
  <!-- Error details... -->
</tr>

<!-- app/views/rails_error_dashboard/errors/_stats.html.erb -->
<div class="row g-4">
  <!-- Stat cards... -->
</div>
```

### 4. JavaScript Animations

```javascript
// Highlight new errors when prepended
document.addEventListener('turbo:before-stream-render', (event) => {
  const { target, action } = event.detail.newStream;

  if (action === 'prepend' && target === 'error_list') {
    setTimeout(() => {
      const firstRow = document.querySelector('#error_list tr:first-child');
      if (firstRow) {
        firstRow.classList.add('new-error');
        setTimeout(() => firstRow.classList.remove('new-error'), 3000);
      }
    }, 10);
  }

  // Pulse stats cards when updated
  if (action === 'replace' && target === 'dashboard_stats') {
    setTimeout(() => {
      document.querySelectorAll('.stat-card').forEach(card => {
        card.classList.add('updated');
        setTimeout(() => card.classList.remove('updated'), 500);
      });
    }, 10);
  }
});
```

---

## Configuration

### Enable/Disable Real-Time Updates

Real-time updates work out-of-the-box. No configuration needed!

**To disable** (if needed for debugging):

```ruby
# config/initializers/rails_error_dashboard.rb

# Option 1: Remove turbo-rails from your Gemfile
# gem 'turbo-rails' # commented out

# Option 2: Add guard in model
# app/models/rails_error_dashboard/error_log.rb
def broadcast_new_error
  return if Rails.env.test? # Skip in tests
  return unless RailsErrorDashboard.configuration.enable_realtime_updates # Custom config
  # ... rest of code
end
```

### Custom Configuration (Advanced)

Add custom settings to your configuration:

```ruby
# config/initializers/rails_error_dashboard.rb
RailsErrorDashboard.configure do |config|
  # Disable real-time updates in certain environments
  config.enable_realtime_updates = !Rails.env.development?

  # Custom broadcast channel name
  config.turbo_stream_channel = "custom_error_channel"

  # Throttle broadcasts (prevent spam)
  config.broadcast_throttle = 1.second # Max 1 broadcast per second
end
```

**Implementation:**

```ruby
# lib/rails_error_dashboard/configuration.rb
attr_accessor :enable_realtime_updates
attr_accessor :turbo_stream_channel
attr_accessor :broadcast_throttle

def initialize
  @enable_realtime_updates = true
  @turbo_stream_channel = "error_list"
  @broadcast_throttle = 0.seconds
  # ... other defaults
end
```

---

## Browser Requirements

### Supported Browsers

Real-time updates work in all modern browsers:

- ✅ Chrome 90+
- ✅ Firefox 88+
- ✅ Safari 14+
- ✅ Edge 90+
- ✅ Opera 76+

### Fallback Behavior

If Turbo Streams are not supported:
- Dashboard works normally
- Users can manually refresh the page
- No errors or broken functionality

### WebSocket vs SSE

Turbo automatically chooses the best transport:
- **WebSocket** (preferred) - Full duplex, lowest latency
- **SSE** (fallback) - Server-Sent Events, works through most proxies

---

## Performance Considerations

### Scalability

**Single User:**
- Minimal overhead (~1-2 KB per update)
- Instant updates (< 50ms latency)

**Multiple Users (10-100):**
- Each user subscribes to the same channel
- Server broadcasts once, all users receive
- Network overhead: ~1-2 KB × number of connected users

**High-Volume Apps (1000s of errors/minute):**
- Consider throttling broadcasts
- Use sampling (see [ERROR_SAMPLING_AND_FILTERING.md](/rails_error_dashboard/docs/guides/error-sampling-and-filtering/))
- Monitor WebSocket connections

### Database Impact

Broadcasts happen in `after_commit` callbacks:
- **No database overhead** - Uses existing error creation
- **No N+1 queries** - Stats query is optimized
- **Async-safe** - Works with Sidekiq/Solid Queue

### Network Bandwidth

**Per Error Broadcast:**
- Error row HTML: ~500 bytes
- Stats update HTML: ~300 bytes
- **Total: ~800 bytes per error**

**Calculation:**

```text
1,000 errors/hour = 800 KB/hour per user
10 concurrent users = 8 MB/hour total bandwidth
```

**Optimization Tips:**

1. **Use Error Sampling** - Reduce broadcast frequency
2. **Compress HTML** - Enable gzip/brotli
3. **Throttle Updates** - Limit broadcasts to 1 per second

---

## Troubleshooting

### Problem: Updates Not Appearing

**Check 1: Turbo Loaded?**

Open browser console and run:
```javascript
typeof Turbo !== 'undefined'
// Should return: true
```

**Check 2: WebSocket Connected?**

Look for console messages:
```text
Turbo Streams connected
```

If you see connection errors, check:
- Firewall/proxy settings
- SSL certificate (WebSockets require HTTPS in production)
- Rails server running

**Check 3: Broadcasts Happening?**

Add logging:
```ruby
def broadcast_new_error
  Rails.logger.info "📡 Broadcasting new error ##{id}"
  # ... rest of code
end
```

### Problem: Duplicate Errors Appearing

**Cause:** Multiple browser tabs subscribed to same channel

**Solution:** This is expected behavior. Each tab shows the same updates.

To prevent duplicates in the database, ensure deduplication is working:
```ruby
# Check error_hash generation
error = RailsErrorDashboard::ErrorLog.last
error.error_hash # Should be consistent for same error
```

### Problem: Slow Updates (> 1 second delay)

**Check 1: Stats Query Performance**

```ruby
# Time the stats query
Benchmark.ms do
  RailsErrorDashboard::Queries::DashboardStats.call
end
# Should be < 100ms
```

If slow, check:
- Database indexes (see [DATABASE_OPTIMIZATION.md](/rails_error_dashboard/docs/guides/database-optimization/))
- Number of errors in database

**Check 2: Partial Rendering**

```ruby
# Time partial rendering
Benchmark.ms do
  ApplicationController.render(
    partial: "rails_error_dashboard/errors/error_row",
    locals: { error: error, show_platform: true }
  )
end
# Should be < 50ms
```

### Problem: Memory Leak (WebSocket Connections)

**Symptoms:**
- Server memory increases over time
- Too many WebSocket connections

**Check Active Connections:**

```ruby
# Add to routes.rb for debugging
namespace :admin do
  get 'turbo_stats' => proc {
    [200, {}, ["Active Turbo connections: #{ActionCable.server.connections.size}"]]
  }
end
```

**Solution:** Set connection timeout:

```ruby
# config/cable.yml
production:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  channel_prefix: error_dashboard_production
  timeout: 30 # Disconnect idle connections after 30 seconds
```

### Problem: Updates Stop After Error

**Check Rails Logs:**

```text
Failed to broadcast new error: [error message]
```

**Common Causes:**

1. **Turbo Not Loaded** - Add guard: `return unless defined?(Turbo)`
2. **Partial Error** - Check partial syntax
3. **Database Transaction** - Ensure `after_commit` not `after_create`

**Debug Mode:**

```ruby
def broadcast_new_error
  return unless defined?(Turbo)

  begin
    # ... broadcast code
  rescue => e
    Rails.logger.error("Broadcast error: #{e.class} - #{e.message}")
    Rails.logger.error(e.backtrace.first(5).join("\n"))
    raise # Re-raise in development
  end
end
```

---

## Advanced Features

### Custom Broadcast Events

Broadcast custom updates:

```ruby
# In your code
RailsErrorDashboard::ErrorLog.broadcast_custom_alert(
  message: "System degraded - high error rate",
  severity: :critical
)

# In model
def self.broadcast_custom_alert(message:, severity:)
  Turbo::StreamsChannel.broadcast_append_to(
    "error_list",
    target: "alert_container",
    partial: "rails_error_dashboard/errors/alert",
    locals: { message: message, severity: severity }
  )
end
```

### Targeted Updates (Per-User)

Send updates to specific users:

```ruby
# Broadcast only to admins
def broadcast_new_error
  return unless defined?(Turbo)

  User.admins.find_each do |admin|
    Turbo::StreamsChannel.broadcast_prepend_to(
      "error_list_user_#{admin.id}",
      target: "error_list",
      partial: "rails_error_dashboard/errors/error_row",
      locals: { error: self }
    )
  end
end
```

**View:**

```erb
<%= turbo_stream_from "error_list_user_#{current_user.id}" %>
```

### Audio Notifications

Play sound when critical error occurs:

```javascript
document.addEventListener('turbo:before-stream-render', (event) => {
  const { target, action } = event.detail.newStream;

  if (action === 'prepend' && target === 'error_list') {
    const firstRow = document.querySelector('#error_list tr:first-child');
    const isCritical = firstRow?.querySelector('.badge-danger')?.textContent === 'CRITICAL';

    if (isCritical) {
      const audio = new Audio('/sounds/critical-error.mp3');
      audio.play().catch(() => console.log('Audio blocked'));
    }
  }
});
```

---

## Testing Real-Time Updates

### Manual Testing

**Terminal 1: Rails Server**
```bash
rails server
```

**Terminal 2: Trigger Errors**
```bash
rails runner "raise ArgumentError, 'Test error'"
```

**Browser:**
Open dashboard at `http://localhost:3000/red`
- Error should appear instantly
- Stats should update
- Yellow highlight animation should play

### Automated Testing

Real-time updates are tested in the RSpec suite:

```ruby
# spec/models/rails_error_dashboard/error_log_spec.rb
describe "real-time broadcasts" do
  it "broadcasts new error via Turbo Stream" do
    allow(Turbo::StreamsChannel).to receive(:broadcast_prepend_to)

    error = create(:error_log)

    expect(Turbo::StreamsChannel).to have_received(:broadcast_prepend_to).with(
      "error_list",
      hash_including(target: "error_list")
    )
  end
end
```

**All 545 tests pass**, including real-time update functionality!

---

## Migration Notes

### Upgrading from Pre-Turbo Version

No migration needed! Real-time updates work automatically.

**What Changed:**

1. Added `turbo-rails` dependency
2. Added Turbo CDN script to layout
3. Added `after_commit` callbacks to ErrorLog model
4. Refactored views to use partials

**Backward Compatible:**

- Old dashboards continue to work
- No database changes required
- Manual refresh still works

### Downgrading (Removing Real-Time Updates)

If you need to remove real-time updates:

1. **Remove gem:**
   ```ruby
   # rails_error_dashboard.gemspec
   # spec.add_dependency "turbo-rails", "~> 2.0"  # Comment out
   ```

2. **Remove callbacks:**
   ```ruby
   # app/models/rails_error_dashboard/error_log.rb
   # after_create_commit :broadcast_new_error  # Comment out
   # after_update_commit :broadcast_error_update  # Comment out
   ```

3. **Remove Turbo script:**
   ```erb
   <!-- app/views/layouts/rails_error_dashboard.html.erb -->
   <!-- Remove Turbo script -->
   ```

Dashboard continues to function normally with manual refresh.

---

## Additional Resources

- [Turbo Handbook](https://turbo.hotwired.dev/)
- [Turbo Streams Reference](https://turbo.hotwired.dev/handbook/streams)
- [Error Sampling & Filtering](/rails_error_dashboard/docs/guides/error-sampling-and-filtering/)
- [Database Optimization](/rails_error_dashboard/docs/guides/database-optimization/)
- [Main README](https://github.com/AnjanJ/rails_error_dashboard/blob/main/README.md)

---

## Summary

✅ **Real-time error list updates** - New errors appear instantly
✅ **Live stats refresh** - Counts update automatically
✅ **Visual feedback** - Animations highlight changes
✅ **Zero configuration** - Works out of the box
✅ **High performance** - Minimal overhead (~800 bytes per error)
✅ **Browser compatible** - All modern browsers supported
✅ **Production ready** - All 545 tests passing

**Related Features:**
- [Error Trend Visualizations](/rails_error_dashboard/docs/guides/error-trend-visualizations/) - Charts for 7-day trends
- [Baseline Monitoring](/rails_error_dashboard/docs/features/baseline-monitoring/) - Spike detection and alerts
- [Analytics](/rails_error_dashboard/docs/features/#analytics--insights) - Complete analytics features
