@cloudsignal/collaborate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROMPT.md +128 -0
- package/README.md +192 -0
- package/dist/index.cjs +1182 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +409 -0
- package/dist/index.d.ts +409 -0
- package/dist/index.js +1153 -0
- package/dist/index.js.map +1 -0
- package/llms.txt +45 -0
- package/package.json +60 -0
package/PROMPT.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# CloudSignal Collaborate — AI Integration Guide
|
|
2
|
+
|
|
3
|
+
Use this guide when generating code that adds real-time collaboration features.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @cloudsignal/collaborate @cloudsignal/mqtt-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Core Pattern
|
|
12
|
+
|
|
13
|
+
Wrap your app (or a page) with `<Space>`, then use hooks and components inside it:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { Space, AvatarStack, CursorOverlay, TypingIndicator } from "@cloudsignal/collaborate";
|
|
17
|
+
|
|
18
|
+
function CollaborativePage() {
|
|
19
|
+
return (
|
|
20
|
+
<Space
|
|
21
|
+
id="my-room"
|
|
22
|
+
connection={{
|
|
23
|
+
host: "wss://connect.cloudsignal.app:18885/",
|
|
24
|
+
username: "user@org_abc123",
|
|
25
|
+
password: "user-password",
|
|
26
|
+
}}
|
|
27
|
+
userName="Alice"
|
|
28
|
+
>
|
|
29
|
+
<AvatarStack className="absolute top-4 right-4" />
|
|
30
|
+
<CursorOverlay>
|
|
31
|
+
<div style={{ width: "100%", height: "400px" }}>
|
|
32
|
+
Your content here
|
|
33
|
+
</div>
|
|
34
|
+
</CursorOverlay>
|
|
35
|
+
<TypingIndicator />
|
|
36
|
+
</Space>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Available Primitives
|
|
42
|
+
|
|
43
|
+
### Provider
|
|
44
|
+
- `<Space id="..." connection={...} userName="...">` — wraps everything, manages connection
|
|
45
|
+
|
|
46
|
+
### Hooks (use inside `<Space>`)
|
|
47
|
+
- `useSpace()` — access connection state, self user
|
|
48
|
+
- `usePresence()` — who's online: `{ members, count, onJoin, onLeave }`
|
|
49
|
+
- `useCursors()` — live cursors: `{ cursors, publishCursor }`
|
|
50
|
+
- `useLock(componentId)` — component locking: `{ isLocked, lockedBy, lock, unlock }`
|
|
51
|
+
- `useTypingIndicator(inputId?)` — typing state: `{ typingUsers, startTyping, stopTyping }`
|
|
52
|
+
- `useReactions()` — emoji reactions: `{ reactions, sendReaction }`
|
|
53
|
+
- `useBroadcast<T>(event?)` — custom events: `{ broadcast, onMessage }`
|
|
54
|
+
- `useSharedState<T>(key, initial)` — synced KV state: `[value, setValue]`
|
|
55
|
+
|
|
56
|
+
### Components (drop-in UI)
|
|
57
|
+
- `<AvatarStack max={5} size={32} />` — online user avatars
|
|
58
|
+
- `<CursorOverlay>` — wraps content, renders live cursors
|
|
59
|
+
- `<TypingIndicator inputId="..." />` — "Alice is typing..."
|
|
60
|
+
- `<LockIndicator componentId="...">` — lock status badge + border
|
|
61
|
+
- `<ReactionBar emojis={['👍','❤️','🎉']} />` — emoji bar + floating animations
|
|
62
|
+
- `<PresenceBorder componentId="...">` — auto-locks on focus, shows who's editing
|
|
63
|
+
|
|
64
|
+
## Connection Options
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// Direct credentials
|
|
68
|
+
connection={{ host: "wss://...", username: "user@org_id", password: "pass" }}
|
|
69
|
+
|
|
70
|
+
// Token-based (secret key)
|
|
71
|
+
connection={{ host: "wss://...", organizationId: "org-uuid", secretKey: "sk_..." }}
|
|
72
|
+
|
|
73
|
+
// External IdP (Supabase, Clerk, etc.)
|
|
74
|
+
connection={{ host: "wss://...", organizationId: "org-uuid", externalToken: "jwt...", tokenServiceUrl: "https://auth.cloudsignal.app" }}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Common Patterns
|
|
78
|
+
|
|
79
|
+
### Collaborative Form
|
|
80
|
+
```tsx
|
|
81
|
+
<Space id="form-123" connection={conn} userName="Alice">
|
|
82
|
+
<AvatarStack />
|
|
83
|
+
<PresenceBorder componentId="title">
|
|
84
|
+
<input onChange={() => startTyping()} placeholder="Title" />
|
|
85
|
+
</PresenceBorder>
|
|
86
|
+
<PresenceBorder componentId="body">
|
|
87
|
+
<textarea onChange={() => startTyping()} placeholder="Body" />
|
|
88
|
+
</PresenceBorder>
|
|
89
|
+
<TypingIndicator />
|
|
90
|
+
</Space>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Shared Counter
|
|
94
|
+
```tsx
|
|
95
|
+
function Counter() {
|
|
96
|
+
const [count, setCount] = useSharedState('counter', 0);
|
|
97
|
+
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Custom Chat Events
|
|
102
|
+
```tsx
|
|
103
|
+
function Chat() {
|
|
104
|
+
const { broadcast, onMessage } = useBroadcast<{ text: string }>('chat');
|
|
105
|
+
const [messages, setMessages] = useState([]);
|
|
106
|
+
|
|
107
|
+
useEffect(() => onMessage(msg => setMessages(prev => [...prev, msg])), []);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<>
|
|
111
|
+
{messages.map(m => <p>{m.text}</p>)}
|
|
112
|
+
<input onKeyDown={e => {
|
|
113
|
+
if (e.key === 'Enter') broadcast({ text: e.currentTarget.value });
|
|
114
|
+
}} />
|
|
115
|
+
</>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Rules for AI Code Generation
|
|
121
|
+
|
|
122
|
+
1. Always wrap collaborative areas with `<Space>` before using any hooks/components
|
|
123
|
+
2. `connection` prop is required — never omit it
|
|
124
|
+
3. All hooks must be called inside a `<Space>` provider
|
|
125
|
+
4. `<CursorOverlay>` needs a child with defined dimensions (width/height)
|
|
126
|
+
5. Use `<PresenceBorder>` for form fields — it auto-locks on focus
|
|
127
|
+
6. `useSharedState` is last-write-wins — don't use for conflict-sensitive data
|
|
128
|
+
7. Components accept `className` for Tailwind/CSS styling
|
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# @cloudsignal/collaborate
|
|
2
|
+
|
|
3
|
+
Real-time collaboration primitives for React — powered by [CloudSignal](https://cloudsignal.io) MQTT.
|
|
4
|
+
|
|
5
|
+
Drop-in components for **cursors**, **presence**, **component locking**, **typing indicators**, **emoji reactions**, **shared state**, and **custom broadcast events**.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @cloudsignal/collaborate @cloudsignal/mqtt-client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { Space, AvatarStack, CursorOverlay, TypingIndicator } from "@cloudsignal/collaborate";
|
|
17
|
+
|
|
18
|
+
export default function App() {
|
|
19
|
+
return (
|
|
20
|
+
<Space
|
|
21
|
+
id="my-room"
|
|
22
|
+
connection={{
|
|
23
|
+
host: "wss://connect.cloudsignal.app:18885/",
|
|
24
|
+
username: "alice@org_k7xm4pqr2n5t",
|
|
25
|
+
password: "alice-password",
|
|
26
|
+
}}
|
|
27
|
+
userName="Alice"
|
|
28
|
+
>
|
|
29
|
+
<AvatarStack />
|
|
30
|
+
|
|
31
|
+
<CursorOverlay>
|
|
32
|
+
<div style={{ width: "100%", height: "500px", background: "#fafafa" }}>
|
|
33
|
+
Move your mouse here
|
|
34
|
+
</div>
|
|
35
|
+
</CursorOverlay>
|
|
36
|
+
|
|
37
|
+
<TypingIndicator />
|
|
38
|
+
</Space>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Primitives
|
|
44
|
+
|
|
45
|
+
### Provider
|
|
46
|
+
|
|
47
|
+
| Component | Description |
|
|
48
|
+
|-----------|-------------|
|
|
49
|
+
| `<Space>` | Wraps your app. Manages MQTT connection, presence heartbeats, and topic routing. |
|
|
50
|
+
|
|
51
|
+
### Hooks
|
|
52
|
+
|
|
53
|
+
| Hook | Returns | Description |
|
|
54
|
+
|------|---------|-------------|
|
|
55
|
+
| `useSpace()` | `{ spaceId, self, isConnected, error }` | Access space context |
|
|
56
|
+
| `usePresence()` | `{ members, count, onJoin, onLeave }` | Who's online |
|
|
57
|
+
| `useCursors()` | `{ cursors, publishCursor }` | Live cursor positions |
|
|
58
|
+
| `useLock(id)` | `{ isLocked, lockedBy, lock, unlock }` | Component locking |
|
|
59
|
+
| `useTypingIndicator()` | `{ typingUsers, startTyping, stopTyping }` | Typing state |
|
|
60
|
+
| `useReactions()` | `{ reactions, sendReaction }` | Emoji reactions |
|
|
61
|
+
| `useBroadcast(event?)` | `{ broadcast, lastMessage, onMessage }` | Custom pub/sub |
|
|
62
|
+
| `useSharedState(key, init)` | `[value, setValue]` | Synced key-value state |
|
|
63
|
+
|
|
64
|
+
### Components
|
|
65
|
+
|
|
66
|
+
| Component | Description |
|
|
67
|
+
|-----------|-------------|
|
|
68
|
+
| `<AvatarStack>` | Overlapping user avatars with "+N" overflow |
|
|
69
|
+
| `<CursorOverlay>` | Wraps content, renders live cursor SVGs |
|
|
70
|
+
| `<TypingIndicator>` | "Alice is typing..." with animated dots |
|
|
71
|
+
| `<LockIndicator>` | Lock badge showing who's editing |
|
|
72
|
+
| `<ReactionBar>` | Emoji buttons with floating animations |
|
|
73
|
+
| `<PresenceBorder>` | Auto-locks on focus, colored border per user |
|
|
74
|
+
|
|
75
|
+
## Connection Methods
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// Direct credentials
|
|
79
|
+
<Space connection={{ host: "wss://...", username: "user@org_id", password: "pass" }} />
|
|
80
|
+
|
|
81
|
+
// Token auth (secret key)
|
|
82
|
+
<Space connection={{ host: "wss://...", organizationId: "uuid", secretKey: "sk_..." }} />
|
|
83
|
+
|
|
84
|
+
// External IdP (Supabase, Clerk, Firebase, Auth0)
|
|
85
|
+
<Space connection={{
|
|
86
|
+
host: "wss://...",
|
|
87
|
+
organizationId: "uuid",
|
|
88
|
+
externalToken: jwt,
|
|
89
|
+
tokenServiceUrl: "https://auth.cloudsignal.app"
|
|
90
|
+
}} />
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Examples
|
|
94
|
+
|
|
95
|
+
### Collaborative Form
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { Space, AvatarStack, PresenceBorder, TypingIndicator, useTypingIndicator } from "@cloudsignal/collaborate";
|
|
99
|
+
|
|
100
|
+
function FormFields() {
|
|
101
|
+
const { startTyping } = useTypingIndicator();
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<>
|
|
105
|
+
<PresenceBorder componentId="title">
|
|
106
|
+
<input onChange={() => startTyping()} placeholder="Title" />
|
|
107
|
+
</PresenceBorder>
|
|
108
|
+
<PresenceBorder componentId="body">
|
|
109
|
+
<textarea onChange={() => startTyping()} placeholder="Description" />
|
|
110
|
+
</PresenceBorder>
|
|
111
|
+
<TypingIndicator />
|
|
112
|
+
</>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default function Page() {
|
|
117
|
+
return (
|
|
118
|
+
<Space id="form-123" connection={conn} userName="Alice">
|
|
119
|
+
<AvatarStack />
|
|
120
|
+
<FormFields />
|
|
121
|
+
</Space>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Shared Counter
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { useSharedState } from "@cloudsignal/collaborate";
|
|
130
|
+
|
|
131
|
+
function Counter() {
|
|
132
|
+
const [count, setCount] = useSharedState("counter", 0);
|
|
133
|
+
return <button onClick={() => setCount(count + 1)}>Clicks: {count}</button>;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Real-time Chat via Broadcast
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { useBroadcast } from "@cloudsignal/collaborate";
|
|
141
|
+
|
|
142
|
+
function Chat() {
|
|
143
|
+
const { broadcast, onMessage } = useBroadcast<{ text: string; from: string }>("chat");
|
|
144
|
+
const [messages, setMessages] = useState<{ text: string; from: string }[]>([]);
|
|
145
|
+
|
|
146
|
+
useEffect(() => onMessage(msg => setMessages(prev => [...prev, msg])), []);
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div>
|
|
150
|
+
{messages.map((m, i) => <p key={i}><b>{m.from}:</b> {m.text}</p>)}
|
|
151
|
+
<input onKeyDown={e => {
|
|
152
|
+
if (e.key === "Enter") {
|
|
153
|
+
broadcast({ text: e.currentTarget.value, from: "Alice" });
|
|
154
|
+
e.currentTarget.value = "";
|
|
155
|
+
}
|
|
156
|
+
}} />
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Architecture
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
@cloudsignal/collaborate (this package)
|
|
166
|
+
└── @cloudsignal/mqtt-client (peer dependency — MQTT transport)
|
|
167
|
+
└── VerneMQ broker (CloudSignal infrastructure)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
All collaboration data flows over MQTT topics under `$spaces/{spaceId}/`:
|
|
171
|
+
|
|
172
|
+
| Topic | QoS | Retain | Purpose |
|
|
173
|
+
|-------|-----|--------|---------|
|
|
174
|
+
| `$spaces/{id}/presence` | 0 | No | Heartbeats, join/leave |
|
|
175
|
+
| `$spaces/{id}/cursors` | 0 | No | Cursor positions |
|
|
176
|
+
| `$spaces/{id}/locks` | 1 | No | Lock acquire/release |
|
|
177
|
+
| `$spaces/{id}/typing` | 0 | No | Typing indicators |
|
|
178
|
+
| `$spaces/{id}/reactions` | 0 | No | Emoji reactions |
|
|
179
|
+
| `$spaces/{id}/broadcast/{event}` | 0 | No | Custom events |
|
|
180
|
+
| `$spaces/{id}/state/{key}` | 1 | Yes | Shared state (LWW) |
|
|
181
|
+
|
|
182
|
+
## Performance
|
|
183
|
+
|
|
184
|
+
- **Cursors** use ref-based storage + imperative DOM updates — zero React re-renders per message
|
|
185
|
+
- **Presence** and **typing** use `useState` since they update at human speed (<1Hz)
|
|
186
|
+
- **Single wildcard subscription** per space — `$spaces/{id}/#`
|
|
187
|
+
- **Throttled publishing** — cursors at ~33Hz, typing at max 1 publish/2s
|
|
188
|
+
- **Stale cleanup** — cursors fade after 3s, typing clears after 4s, presence after 30s
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT
|