ably-realtime

Ably real-time messaging patterns, WebSocket channel management, message validation and processing, staleness filtering, error recovery strategies, collaborative editing with drag-and-drop, optimistic updates for voting, real-time board collaboration, and Ably integration best practices for ree-board project

181 stars

Best use case

ably-realtime is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Ably real-time messaging patterns, WebSocket channel management, message validation and processing, staleness filtering, error recovery strategies, collaborative editing with drag-and-drop, optimistic updates for voting, real-time board collaboration, and Ably integration best practices for ree-board project

Teams using ably-realtime should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/ably-realtime/SKILL.md --create-dirs "https://raw.githubusercontent.com/majiayu000/claude-skill-registry/main/skills/data/ably-realtime/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/ably-realtime/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How ably-realtime Compares

Feature / Agentably-realtimeStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Ably real-time messaging patterns, WebSocket channel management, message validation and processing, staleness filtering, error recovery strategies, collaborative editing with drag-and-drop, optimistic updates for voting, real-time board collaboration, and Ably integration best practices for ree-board project

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Ably Real-time Collaboration

## When to Use This Skill

Activate this skill when working on:

- Implementing real-time features
- Setting up Ably channels
- Processing real-time messages
- Building collaborative editing features
- Implementing drag-and-drop with real-time sync
- Handling WebSocket connections
- Managing real-time state updates
- Optimizing real-time performance

## Core Patterns

### Channel Management

**Channel Naming Convention:**

```typescript
// Pattern: `board:{boardId}`
const channelName = `board:${boardId}`;
```

**Setting Up Channels:**

```typescript
"use client";

import { useChannel } from "ably/react";
import { useEffect } from "react";

export function PostChannelComponent({ boardId }: { boardId: string }) {
  const { channel } = useChannel(`board:${boardId}`, (message) => {
    processMessage(message);
  });

  return <PostList boardId={boardId} />;
}
```

### Message Processors with Validation

**Critical Pattern:** Always validate messages before processing

```typescript
// lib/realtime/messageProcessors.ts
import { z } from "zod";

// Define message schema
const PostUpdateMessageSchema = z.object({
  type: z.literal("post:update"),
  postId: z.string(),
  content: z.string().min(1).max(1000),
  userId: z.string(),
  timestamp: z.number(),
});

// Message processor
export const processPostUpdate = (rawData: unknown) => {
  try {
    // ✅ Validate message structure
    const data = PostUpdateMessageSchema.parse(rawData);

    // ✅ Check staleness (30s threshold)
    const now = Date.now();
    const age = now - data.timestamp;

    if (age > 30000) {
      console.warn("Stale message discarded", {
        type: data.type,
        age,
        postId: data.postId,
      });
      return;
    }

    // ✅ Process validated, fresh message
    updatePostContent(data.postId, data.content);
  } catch (error) {
    if (error instanceof z.ZodError) {
      console.error("Invalid message structure", {
        details: error.errors,
        rawData,
      });
    } else {
      console.error("Message processing error", error);
    }
  }
};
```

### Staleness Filtering

**30-Second Threshold:** Prevents processing old messages after reconnection

```typescript
const STALENESS_THRESHOLD_MS = 30000;

export function isMessageStale(timestamp: number): boolean {
  const age = Date.now() - timestamp;
  return age > STALENESS_THRESHOLD_MS;
}

// Usage in message handler
useChannel(`board:${boardId}`, (message) => {
  const { timestamp } = message.data;

  if (isMessageStale(timestamp)) {
    console.warn("Dropping stale message", { age: Date.now() - timestamp });
    return;
  }

  processMessage(message.data);
});
```

### Error Recovery Strategies

**Connection Error Handling:**

```typescript
import { useConnectionStateListener } from "ably/react";

export function RealtimeProvider({ children }: { children: React.ReactNode }) {
  const [connectionState, setConnectionState] = useState<string>("initialized");

  useConnectionStateListener((stateChange) => {
    setConnectionState(stateChange.current);

    switch (stateChange.current) {
      case "connected":
        console.log("✅ Connected to Ably");
        break;

      case "disconnected":
        console.warn("⚠️ Disconnected from Ably");
        break;

      case "suspended":
        console.error("❌ Connection suspended");
        // Optionally show user notification
        break;

      case "failed":
        console.error("❌ Connection failed");
        // Show error message to user
        break;
    }
  });

  return (
    <>
      {connectionState !== "connected" && (
        <ConnectionBanner state={connectionState} />
      )}
      {children}
    </>
  );
}
```

### Publishing Messages

**Always Include Timestamp:**

```typescript
import { useChannel } from "ably/react";

export function usePublishPostUpdate() {
  const { channel } = useChannel(`board:${boardId}`);

  const publishUpdate = async (postId: string, content: string) => {
    await channel.publish("post:update", {
      type: "post:update",
      postId,
      content,
      userId: currentUserId,
      timestamp: Date.now(), // ✅ Always include timestamp
    });
  };

  return publishUpdate;
}
```

### Optimistic Updates for Voting

**Pattern:** Update UI immediately, sync in background

```typescript
"use client";

import { voteSignal } from "@/lib/signal/postSignals";
import { useChannel } from "ably/react";

export function VoteButton({
  postId,
  boardId,
}: {
  postId: string;
  boardId: string;
}) {
  const { channel } = useChannel(`board:${boardId}`);

  const handleVote = async () => {
    // ✅ Optimistic update (immediate UI feedback)
    voteSignal.value = {
      ...voteSignal.value,
      [postId]: (voteSignal.value[postId] || 0) + 1,
    };

    try {
      // Persist to database
      await submitVote(postId);

      // Broadcast to other users
      await channel.publish("post:vote", {
        type: "post:vote",
        postId,
        increment: 1,
        timestamp: Date.now(),
      });
    } catch (error) {
      // ❌ Rollback on error
      voteSignal.value = {
        ...voteSignal.value,
        [postId]: voteSignal.value[postId] - 1,
      };
      console.error("Vote failed", error);
    }
  };

  return <button onClick={handleVote}>Vote</button>;
}
```

### Drag-and-Drop Integration

**Lazy-Loaded with Real-Time Sync:**

```typescript
// components/board/PostProvider.tsx
"use client";

import { useChannel } from "ably/react";
import dynamic from "next/dynamic";

// ✅ Lazy load drag-and-drop (reduces initial bundle)
const DragDropArea = dynamic(() => import("./DragDropArea"), { ssr: false });

export function PostProvider({ boardId }: { boardId: string }) {
  useChannel(`board:${boardId}`, (message) => {
    if (message.name === "post:move") {
      handlePostMove(message.data);
    }
  });

  const handleDrop = async (postId: string, newType: PostType) => {
    // Update locally
    movePostSignal(postId, newType);

    // Persist to database
    await updatePostType(postId, newType);

    // Broadcast to other users
    channel.publish("post:move", {
      type: "post:move",
      postId,
      newType,
      timestamp: Date.now(),
    });
  };

  return <DragDropArea onDrop={handleDrop} />;
}
```

## Anti-Patterns

### ❌ Not Validating Messages

**Bad:**

```typescript
useChannel(`board:${boardId}`, (message) => {
  // ❌ Trusts message data completely
  updatePost(message.data.postId, message.data.content);
});
```

**Good:**

```typescript
useChannel(`board:${boardId}`, (message) => {
  // ✅ Validates before processing
  const validated = PostUpdateSchema.safeParse(message.data);
  if (!validated.success) return;
  updatePost(validated.data.postId, validated.data.content);
});
```

### ❌ Not Checking Message Staleness

**Bad:**

```typescript
useChannel(channelName, (message) => {
  // ❌ Processes all messages, even old ones after reconnect
  processMessage(message.data);
});
```

**Good:**

```typescript
useChannel(channelName, (message) => {
  // ✅ Filters stale messages
  if (isMessageStale(message.data.timestamp)) return;
  processMessage(message.data);
});
```

### ❌ Not Handling Connection Errors

**Bad:**

```typescript
// ❌ No error handling
const { channel } = useChannel(channelName);
```

**Good:**

```typescript
// ✅ Monitor connection state
useConnectionStateListener((stateChange) => {
  if (stateChange.current === "failed") {
    showErrorNotification("Real-time connection lost");
  }
});
```

### ❌ Publishing Without Timestamp

**Bad:**

```typescript
channel.publish("update", {
  postId,
  content,
  // ❌ No timestamp for staleness check
});
```

**Good:**

```typescript
channel.publish("update", {
  postId,
  content,
  timestamp: Date.now(), // ✅ Include timestamp
});
```

### ❌ Not Handling Race Conditions

**Bad:**

```typescript
// ❌ Multiple updates could conflict
const handleVote = async () => {
  const newCount = currentCount + 1;
  await updateVoteCount(postId, newCount);
};
```

**Good:**

```typescript
// ✅ Use atomic increment
const handleVote = async () => {
  await db
    .update(postTable)
    .set({ voteCount: sql`vote_count + 1` })
    .where(eq(postTable.id, postId));
};
```

## Integration with Other Skills

- **[rbac-security](../rbac-security/SKILL.md):** Validate messages for security
- **[signal-state-management](../signal-state-management/SKILL.md):** Real-time updates to signals
- **[nextjs-app-router](../nextjs-app-router/SKILL.md):** Client components for real-time features
- **[testing-patterns](../testing-patterns/SKILL.md):** Test message processors with fake timers

## Project-Specific Context

### Key Files

- `lib/realtime/messageProcessors.ts` - Message validation and processing
- `components/board/PostProvider.tsx` - Real-time channel setup
- `components/board/PostChannelComponent.tsx` - Channel subscription
- `lib/realtime/__tests__/` - Message processor tests

### Message Types

**Current Message Types:**

```typescript
type MessageType =
  | "post:create"
  | "post:update"
  | "post:delete"
  | "post:move"
  | "post:vote"
  | "member:join"
  | "member:leave";
```

### Channel Structure

**One Channel Per Board:**

- Channel name: `board:{boardId}`
- All board events published to this channel
- Subscribers filter by message type

### Performance Optimizations

1. **Lazy Loading:** Drag-and-drop loaded on demand
2. **Staleness Filter:** Discards messages >30s old
3. **Optimistic Updates:** Immediate UI feedback
4. **Batching:** Vote counts updated atomically
5. **Connection Pooling:** Reuse Ably client instance

### Error Recovery

**Automatic Reconnection:**

- Ably SDK handles reconnection automatically
- Messages buffered during disconnection
- Staleness filter prevents processing old messages after reconnect

**Manual Recovery:**

```typescript
// Refresh data after long disconnection
if (
  stateChange.previous === "suspended" &&
  stateChange.current === "connected"
) {
  await refreshBoardData();
}
```

### Testing

**Mock Ably in Tests:**

```typescript
jest.mock("ably/react", () => ({
  useChannel: jest.fn(() => ({
    channel: {
      publish: jest.fn(),
      subscribe: jest.fn(),
    },
  })),
}));
```

---

**Last Updated:** 2026-01-10

Related Skills

action-cable-realtime

181
from majiayu000/claude-skill-registry

This skill should be used when the user asks about Action Cable, WebSockets, real-time features, channels, broadcasting, subscriptions, chat applications, live notifications, presence indicators, collaborative editing, server push, pub/sub patterns, Solid Cable, or streaming updates. Also use when discussing real-time architecture, WebSocket deployment, or alternatives like polling and Server-Sent Events. Examples:

ably

181
from majiayu000/claude-skill-registry

Implements real-time pub/sub messaging with Ably's edge infrastructure. Use when building real-time features requiring enterprise reliability, presence, message history, and global low-latency delivery.

ontopo

159
from majiayu000/claude-skill-registry

An AI agent skill to search for Israeli restaurants, check table availability, view menus, and retrieve booking links via the Ontopo platform, acting as an unofficial interface to its data.

General Utilities

grail-miner

159
from majiayu000/claude-skill-registry

This skill assists in setting up, managing, and optimizing Grail miners on Bittensor Subnet 81, handling tasks like environment configuration, R2 storage, model checkpoint management, and performance tuning.

DevOps & Infrastructure

chrome-debug

159
from majiayu000/claude-skill-registry

This skill empowers AI agents to debug web applications and inspect browser behavior using the Chrome DevTools Protocol (CDP), offering both collaborative (headful) and automated (headless) modes.

Coding & DevelopmentClaude

whisper-transcribe

159
from majiayu000/claude-skill-registry

Transcribes audio and video files to text using OpenAI's Whisper CLI, enhanced with contextual grounding from local markdown files for improved accuracy.

Media Processing

ux

159
from majiayu000/claude-skill-registry

This AI agent skill provides comprehensive guidance for creating professional and insightful User Experience (UX) designs, covering user research, information architecture, interaction design, visual guidance, and usability evaluation. It aims to produce actionable, user-centered solutions that avoid generic AI aesthetics.

UX Design & StrategyClaude

thor-skills

159
from majiayu000/claude-skill-registry

An entry point and router for AI agents to manage various THOR-related cybersecurity tasks, including running scans, analyzing logs, troubleshooting, and maintenance.

SecurityClaude

lets-go-rss

159
from majiayu000/claude-skill-registry

A lightweight, full-platform RSS subscription manager that aggregates content from YouTube, Vimeo, Behance, Twitter/X, and Chinese platforms like Bilibili, Weibo, and Douyin, featuring deduplication and AI smart classification.

Content & Documentation

tech-blog

159
from majiayu000/claude-skill-registry

Generates comprehensive technical blog posts, offering detailed explanations of system internals, architecture, and implementation, either through source code analysis or document-driven research.

Content & DocumentationClaude

vly-money

159
from majiayu000/claude-skill-registry

Generate crypto payment links for supported tokens and networks, manage access to X402 payment-protected content, and provide direct access to the vly.money wallet interface.

Fintech & CryptoClaude

modal-deployment

159
from majiayu000/claude-skill-registry

Run Python code in the cloud with serverless containers, GPUs, and autoscaling using Modal. This skill enables agents to generate code for deploying ML models, running batch jobs, serving APIs, and scaling compute-intensive workloads.

DevOps & Infrastructure