vibesuite 1.3.3 → 2.0.2
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/README.md +75 -6
- package/assets/.agent/skills/avoid-feature-creep/SKILL.md +307 -0
- package/assets/.agent/skills/avoid-feature-creep/agents/openai.yaml +3 -0
- package/assets/.agent/skills/avoid-feature-creep/assets/large-logo.png +0 -0
- package/assets/.agent/skills/avoid-feature-creep/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex/SKILL.md +62 -0
- package/assets/.agent/skills/convex/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-agents/SKILL.md +516 -0
- package/assets/.agent/skills/convex-agents/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-agents/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-agents/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-best-practices/SKILL.md +369 -0
- package/assets/.agent/skills/convex-best-practices/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-best-practices/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-best-practices/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-component-authoring/SKILL.md +457 -0
- package/assets/.agent/skills/convex-component-authoring/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-component-authoring/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-component-authoring/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-cron-jobs/SKILL.md +604 -0
- package/assets/.agent/skills/convex-cron-jobs/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-cron-jobs/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-cron-jobs/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-file-storage/SKILL.md +467 -0
- package/assets/.agent/skills/convex-file-storage/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-file-storage/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-file-storage/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-functions/SKILL.md +458 -0
- package/assets/.agent/skills/convex-functions/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-functions/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-functions/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-http-actions/SKILL.md +733 -0
- package/assets/.agent/skills/convex-http-actions/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-http-actions/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-http-actions/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-migrations/SKILL.md +712 -0
- package/assets/.agent/skills/convex-migrations/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-migrations/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-migrations/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-realtime/SKILL.md +443 -0
- package/assets/.agent/skills/convex-realtime/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-realtime/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-realtime/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-schema-validator/SKILL.md +400 -0
- package/assets/.agent/skills/convex-schema-validator/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-schema-validator/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-schema-validator/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-security-audit/SKILL.md +539 -0
- package/assets/.agent/skills/convex-security-audit/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-security-audit/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-security-audit/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/convex-security-check/SKILL.md +378 -0
- package/assets/.agent/skills/convex-security-check/agents/openai.yaml +3 -0
- package/assets/.agent/skills/convex-security-check/assets/large-logo.png +0 -0
- package/assets/.agent/skills/convex-security-check/assets/small-logo.svg +17 -0
- package/assets/.agent/skills/github-ops/SKILL.md +4 -4
- package/assets/.agent/skills/google-trends/SKILL.md +7 -7
- package/assets/.agent/skills/optimize-agent-context/SKILL.md +97 -0
- package/assets/.agent/skills/youtube-pipeline/SKILL.md +10 -10
- package/assets/.agent/workflows/LEGACY/init_smart_ops.md +2 -2
- package/assets/.agent/workflows/agent_reset.md +4 -6
- package/assets/.agent/workflows/mode-orchestrator.md +17 -22
- package/assets/.agent/workflows/mode-visionary.md +3 -10
- package/assets/.agent/workflows/optimize-agent-context.md +54 -0
- package/assets/.agent/workflows/remotion-build.md +17 -17
- package/assets/.agent/workflows/stitch.md +4 -4
- package/assets/VibeCode-Agents/vibe-orchestrator.yaml +14 -33
- package/assets/VibeCode-Agents/vibe-visionary.yaml +3 -13
- package/package.json +1 -1
- package/src/cli.js +416 -20
- package/src/harness.js +281 -0
- package/src/store.js +239 -0
- package/assets/VibeCode-Agents/custom_modes.yaml +0 -979
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_3_23)">
|
|
3
|
+
<g clip-path="url(#clip1_3_23)">
|
|
4
|
+
<path d="M10.0643 12.5735C12.3769 12.3166 14.5572 11.0843 15.7577 9.02756C15.1892 14.1148 9.62646 17.3302 5.08583 15.356C4.66743 15.1746 4.30728 14.8728 4.06013 14.4848C3.03973 12.8825 2.7043 10.8437 3.18626 8.99344C4.56327 11.37 7.3632 12.8267 10.0643 12.5735Z" fill="#F3B01C"/>
|
|
5
|
+
<path d="M3.1018 7.50072C2.16436 9.66714 2.12376 12.2034 3.27303 14.2907C-0.771507 11.2479 -0.72737 4.7362 3.2236 1.72378C3.58904 1.44535 4.02333 1.2801 4.47881 1.25494C6.3519 1.15614 8.25501 1.88006 9.58963 3.22909C6.87799 3.25604 4.23695 4.99308 3.1018 7.50072Z" fill="#8D2676"/>
|
|
6
|
+
<path d="M10.8974 3.89562C9.52924 1.98794 7.38779 0.68921 5.04156 0.649695C9.57686 -1.40888 15.1555 1.92867 15.7629 6.86314C15.8194 7.32119 15.7452 7.78824 15.5421 8.20138C14.6948 9.92223 13.1236 11.2569 11.2876 11.7508C12.6328 9.25579 12.4668 6.20748 10.8974 3.89562Z" fill="#EE342F"/>
|
|
7
|
+
</g>
|
|
8
|
+
</g>
|
|
9
|
+
<defs>
|
|
10
|
+
<clipPath id="clip0_3_23">
|
|
11
|
+
<rect width="16" height="16" fill="white"/>
|
|
12
|
+
</clipPath>
|
|
13
|
+
<clipPath id="clip1_3_23">
|
|
14
|
+
<rect width="16" height="16" fill="white"/>
|
|
15
|
+
</clipPath>
|
|
16
|
+
</defs>
|
|
17
|
+
</svg>
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: convex-realtime
|
|
3
|
+
displayName: Convex Realtime
|
|
4
|
+
description: Patterns for building reactive apps including subscription management, optimistic updates, cache behavior, and paginated queries with cursor-based loading
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
author: Convex
|
|
7
|
+
tags: [convex, realtime, subscriptions, optimistic-updates, pagination]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Convex Realtime
|
|
11
|
+
|
|
12
|
+
Build reactive applications with Convex's real-time subscriptions, optimistic updates, intelligent caching, and cursor-based pagination.
|
|
13
|
+
|
|
14
|
+
## Documentation Sources
|
|
15
|
+
|
|
16
|
+
Before implementing, do not assume; fetch the latest documentation:
|
|
17
|
+
|
|
18
|
+
- Primary: https://docs.convex.dev/client/react
|
|
19
|
+
- Optimistic Updates: https://docs.convex.dev/client/react/optimistic-updates
|
|
20
|
+
- Pagination: https://docs.convex.dev/database/pagination
|
|
21
|
+
- For broader context: https://docs.convex.dev/llms.txt
|
|
22
|
+
|
|
23
|
+
## Instructions
|
|
24
|
+
|
|
25
|
+
### How Convex Realtime Works
|
|
26
|
+
|
|
27
|
+
1. **Automatic Subscriptions** - useQuery creates a subscription that updates automatically
|
|
28
|
+
2. **Smart Caching** - Query results are cached and shared across components
|
|
29
|
+
3. **Consistency** - All subscriptions see a consistent view of the database
|
|
30
|
+
4. **Efficient Updates** - Only re-renders when relevant data changes
|
|
31
|
+
|
|
32
|
+
### Basic Subscriptions
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// React component with real-time data
|
|
36
|
+
import { useQuery } from "convex/react";
|
|
37
|
+
import { api } from "../convex/_generated/api";
|
|
38
|
+
|
|
39
|
+
function TaskList({ userId }: { userId: Id<"users"> }) {
|
|
40
|
+
// Automatically subscribes and updates in real-time
|
|
41
|
+
const tasks = useQuery(api.tasks.list, { userId });
|
|
42
|
+
|
|
43
|
+
if (tasks === undefined) {
|
|
44
|
+
return <div>Loading...</div>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<ul>
|
|
49
|
+
{tasks.map((task) => (
|
|
50
|
+
<li key={task._id}>{task.title}</li>
|
|
51
|
+
))}
|
|
52
|
+
</ul>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Conditional Queries
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { useQuery } from "convex/react";
|
|
61
|
+
import { api } from "../convex/_generated/api";
|
|
62
|
+
|
|
63
|
+
function UserProfile({ userId }: { userId: Id<"users"> | null }) {
|
|
64
|
+
// Skip query when userId is null
|
|
65
|
+
const user = useQuery(
|
|
66
|
+
api.users.get,
|
|
67
|
+
userId ? { userId } : "skip"
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (userId === null) {
|
|
71
|
+
return <div>Select a user</div>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (user === undefined) {
|
|
75
|
+
return <div>Loading...</div>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return <div>{user.name}</div>;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Mutations with Real-time Updates
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { useMutation, useQuery } from "convex/react";
|
|
86
|
+
import { api } from "../convex/_generated/api";
|
|
87
|
+
|
|
88
|
+
function TaskManager({ userId }: { userId: Id<"users"> }) {
|
|
89
|
+
const tasks = useQuery(api.tasks.list, { userId });
|
|
90
|
+
const createTask = useMutation(api.tasks.create);
|
|
91
|
+
const toggleTask = useMutation(api.tasks.toggle);
|
|
92
|
+
|
|
93
|
+
const handleCreate = async (title: string) => {
|
|
94
|
+
// Mutation triggers automatic re-render when data changes
|
|
95
|
+
await createTask({ title, userId });
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleToggle = async (taskId: Id<"tasks">) => {
|
|
99
|
+
await toggleTask({ taskId });
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div>
|
|
104
|
+
<button onClick={() => handleCreate("New Task")}>Add Task</button>
|
|
105
|
+
<ul>
|
|
106
|
+
{tasks?.map((task) => (
|
|
107
|
+
<li key={task._id} onClick={() => handleToggle(task._id)}>
|
|
108
|
+
{task.completed ? "✓" : "○"} {task.title}
|
|
109
|
+
</li>
|
|
110
|
+
))}
|
|
111
|
+
</ul>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Optimistic Updates
|
|
118
|
+
|
|
119
|
+
Show changes immediately before server confirmation:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { useMutation, useQuery } from "convex/react";
|
|
123
|
+
import { api } from "../convex/_generated/api";
|
|
124
|
+
import { Id } from "../convex/_generated/dataModel";
|
|
125
|
+
|
|
126
|
+
function TaskItem({ task }: { task: Task }) {
|
|
127
|
+
const toggleTask = useMutation(api.tasks.toggle).withOptimisticUpdate(
|
|
128
|
+
(localStore, args) => {
|
|
129
|
+
const { taskId } = args;
|
|
130
|
+
const currentValue = localStore.getQuery(api.tasks.get, { taskId });
|
|
131
|
+
|
|
132
|
+
if (currentValue !== undefined) {
|
|
133
|
+
localStore.setQuery(api.tasks.get, { taskId }, {
|
|
134
|
+
...currentValue,
|
|
135
|
+
completed: !currentValue.completed,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<div onClick={() => toggleTask({ taskId: task._id })}>
|
|
143
|
+
{task.completed ? "✓" : "○"} {task.title}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Optimistic Updates for Lists
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { useMutation } from "convex/react";
|
|
153
|
+
import { api } from "../convex/_generated/api";
|
|
154
|
+
|
|
155
|
+
function useCreateTask(userId: Id<"users">) {
|
|
156
|
+
return useMutation(api.tasks.create).withOptimisticUpdate(
|
|
157
|
+
(localStore, args) => {
|
|
158
|
+
const { title, userId } = args;
|
|
159
|
+
const currentTasks = localStore.getQuery(api.tasks.list, { userId });
|
|
160
|
+
|
|
161
|
+
if (currentTasks !== undefined) {
|
|
162
|
+
// Add optimistic task to the list
|
|
163
|
+
const optimisticTask = {
|
|
164
|
+
_id: crypto.randomUUID() as Id<"tasks">,
|
|
165
|
+
_creationTime: Date.now(),
|
|
166
|
+
title,
|
|
167
|
+
userId,
|
|
168
|
+
completed: false,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
localStore.setQuery(api.tasks.list, { userId }, [
|
|
172
|
+
optimisticTask,
|
|
173
|
+
...currentTasks,
|
|
174
|
+
]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Cursor-Based Pagination
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// convex/messages.ts
|
|
185
|
+
import { query } from "./_generated/server";
|
|
186
|
+
import { v } from "convex/values";
|
|
187
|
+
import { paginationOptsValidator } from "convex/server";
|
|
188
|
+
|
|
189
|
+
export const listPaginated = query({
|
|
190
|
+
args: {
|
|
191
|
+
channelId: v.id("channels"),
|
|
192
|
+
paginationOpts: paginationOptsValidator,
|
|
193
|
+
},
|
|
194
|
+
handler: async (ctx, args) => {
|
|
195
|
+
return await ctx.db
|
|
196
|
+
.query("messages")
|
|
197
|
+
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
|
|
198
|
+
.order("desc")
|
|
199
|
+
.paginate(args.paginationOpts);
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// React component with pagination
|
|
206
|
+
import { usePaginatedQuery } from "convex/react";
|
|
207
|
+
import { api } from "../convex/_generated/api";
|
|
208
|
+
|
|
209
|
+
function MessageList({ channelId }: { channelId: Id<"channels"> }) {
|
|
210
|
+
const { results, status, loadMore } = usePaginatedQuery(
|
|
211
|
+
api.messages.listPaginated,
|
|
212
|
+
{ channelId },
|
|
213
|
+
{ initialNumItems: 20 }
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<div>
|
|
218
|
+
{results.map((message) => (
|
|
219
|
+
<div key={message._id}>{message.content}</div>
|
|
220
|
+
))}
|
|
221
|
+
|
|
222
|
+
{status === "CanLoadMore" && (
|
|
223
|
+
<button onClick={() => loadMore(20)}>Load More</button>
|
|
224
|
+
)}
|
|
225
|
+
|
|
226
|
+
{status === "LoadingMore" && <div>Loading...</div>}
|
|
227
|
+
|
|
228
|
+
{status === "Exhausted" && <div>No more messages</div>}
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Infinite Scroll Pattern
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { usePaginatedQuery } from "convex/react";
|
|
238
|
+
import { useEffect, useRef } from "react";
|
|
239
|
+
import { api } from "../convex/_generated/api";
|
|
240
|
+
|
|
241
|
+
function InfiniteMessageList({ channelId }: { channelId: Id<"channels"> }) {
|
|
242
|
+
const { results, status, loadMore } = usePaginatedQuery(
|
|
243
|
+
api.messages.listPaginated,
|
|
244
|
+
{ channelId },
|
|
245
|
+
{ initialNumItems: 20 }
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const observerRef = useRef<IntersectionObserver>();
|
|
249
|
+
const loadMoreRef = useRef<HTMLDivElement>(null);
|
|
250
|
+
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
if (observerRef.current) {
|
|
253
|
+
observerRef.current.disconnect();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
observerRef.current = new IntersectionObserver((entries) => {
|
|
257
|
+
if (entries[0].isIntersecting && status === "CanLoadMore") {
|
|
258
|
+
loadMore(20);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
if (loadMoreRef.current) {
|
|
263
|
+
observerRef.current.observe(loadMoreRef.current);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return () => observerRef.current?.disconnect();
|
|
267
|
+
}, [status, loadMore]);
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<div>
|
|
271
|
+
{results.map((message) => (
|
|
272
|
+
<div key={message._id}>{message.content}</div>
|
|
273
|
+
))}
|
|
274
|
+
<div ref={loadMoreRef} style={{ height: 1 }} />
|
|
275
|
+
{status === "LoadingMore" && <div>Loading...</div>}
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Multiple Subscriptions
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
import { useQuery } from "convex/react";
|
|
285
|
+
import { api } from "../convex/_generated/api";
|
|
286
|
+
|
|
287
|
+
function Dashboard({ userId }: { userId: Id<"users"> }) {
|
|
288
|
+
// Multiple subscriptions update independently
|
|
289
|
+
const user = useQuery(api.users.get, { userId });
|
|
290
|
+
const tasks = useQuery(api.tasks.list, { userId });
|
|
291
|
+
const notifications = useQuery(api.notifications.unread, { userId });
|
|
292
|
+
|
|
293
|
+
const isLoading = user === undefined ||
|
|
294
|
+
tasks === undefined ||
|
|
295
|
+
notifications === undefined;
|
|
296
|
+
|
|
297
|
+
if (isLoading) {
|
|
298
|
+
return <div>Loading...</div>;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<div>
|
|
303
|
+
<h1>Welcome, {user.name}</h1>
|
|
304
|
+
<p>You have {tasks.length} tasks</p>
|
|
305
|
+
<p>{notifications.length} unread notifications</p>
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Examples
|
|
312
|
+
|
|
313
|
+
### Real-time Chat Application
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// convex/messages.ts
|
|
317
|
+
import { query, mutation } from "./_generated/server";
|
|
318
|
+
import { v } from "convex/values";
|
|
319
|
+
|
|
320
|
+
export const list = query({
|
|
321
|
+
args: { channelId: v.id("channels") },
|
|
322
|
+
returns: v.array(v.object({
|
|
323
|
+
_id: v.id("messages"),
|
|
324
|
+
_creationTime: v.number(),
|
|
325
|
+
content: v.string(),
|
|
326
|
+
authorId: v.id("users"),
|
|
327
|
+
authorName: v.string(),
|
|
328
|
+
})),
|
|
329
|
+
handler: async (ctx, args) => {
|
|
330
|
+
const messages = await ctx.db
|
|
331
|
+
.query("messages")
|
|
332
|
+
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
|
|
333
|
+
.order("desc")
|
|
334
|
+
.take(100);
|
|
335
|
+
|
|
336
|
+
// Enrich with author names
|
|
337
|
+
return Promise.all(
|
|
338
|
+
messages.map(async (msg) => {
|
|
339
|
+
const author = await ctx.db.get(msg.authorId);
|
|
340
|
+
return {
|
|
341
|
+
...msg,
|
|
342
|
+
authorName: author?.name ?? "Unknown",
|
|
343
|
+
};
|
|
344
|
+
})
|
|
345
|
+
);
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
export const send = mutation({
|
|
350
|
+
args: {
|
|
351
|
+
channelId: v.id("channels"),
|
|
352
|
+
authorId: v.id("users"),
|
|
353
|
+
content: v.string(),
|
|
354
|
+
},
|
|
355
|
+
returns: v.id("messages"),
|
|
356
|
+
handler: async (ctx, args) => {
|
|
357
|
+
return await ctx.db.insert("messages", {
|
|
358
|
+
channelId: args.channelId,
|
|
359
|
+
authorId: args.authorId,
|
|
360
|
+
content: args.content,
|
|
361
|
+
});
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// ChatRoom.tsx
|
|
368
|
+
import { useQuery, useMutation } from "convex/react";
|
|
369
|
+
import { api } from "../convex/_generated/api";
|
|
370
|
+
import { useState, useRef, useEffect } from "react";
|
|
371
|
+
|
|
372
|
+
function ChatRoom({ channelId, userId }: Props) {
|
|
373
|
+
const messages = useQuery(api.messages.list, { channelId });
|
|
374
|
+
const sendMessage = useMutation(api.messages.send);
|
|
375
|
+
const [input, setInput] = useState("");
|
|
376
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
377
|
+
|
|
378
|
+
// Auto-scroll to bottom on new messages
|
|
379
|
+
useEffect(() => {
|
|
380
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
381
|
+
}, [messages]);
|
|
382
|
+
|
|
383
|
+
const handleSend = async (e: React.FormEvent) => {
|
|
384
|
+
e.preventDefault();
|
|
385
|
+
if (!input.trim()) return;
|
|
386
|
+
|
|
387
|
+
await sendMessage({
|
|
388
|
+
channelId,
|
|
389
|
+
authorId: userId,
|
|
390
|
+
content: input.trim(),
|
|
391
|
+
});
|
|
392
|
+
setInput("");
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<div className="chat-room">
|
|
397
|
+
<div className="messages">
|
|
398
|
+
{messages?.map((msg) => (
|
|
399
|
+
<div key={msg._id} className="message">
|
|
400
|
+
<strong>{msg.authorName}:</strong> {msg.content}
|
|
401
|
+
</div>
|
|
402
|
+
))}
|
|
403
|
+
<div ref={messagesEndRef} />
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
<form onSubmit={handleSend}>
|
|
407
|
+
<input
|
|
408
|
+
value={input}
|
|
409
|
+
onChange={(e) => setInput(e.target.value)}
|
|
410
|
+
placeholder="Type a message..."
|
|
411
|
+
/>
|
|
412
|
+
<button type="submit">Send</button>
|
|
413
|
+
</form>
|
|
414
|
+
</div>
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Best Practices
|
|
420
|
+
|
|
421
|
+
- Never run `npx convex deploy` unless explicitly instructed
|
|
422
|
+
- Never run any git commands unless explicitly instructed
|
|
423
|
+
- Use "skip" for conditional queries instead of conditionally calling hooks
|
|
424
|
+
- Implement optimistic updates for better perceived performance
|
|
425
|
+
- Use usePaginatedQuery for large datasets
|
|
426
|
+
- Handle undefined state (loading) explicitly
|
|
427
|
+
- Avoid unnecessary re-renders by memoizing derived data
|
|
428
|
+
|
|
429
|
+
## Common Pitfalls
|
|
430
|
+
|
|
431
|
+
1. **Conditional hook calls** - Use "skip" instead of if statements
|
|
432
|
+
2. **Not handling loading state** - Always check for undefined
|
|
433
|
+
3. **Missing optimistic update rollback** - Optimistic updates auto-rollback on error
|
|
434
|
+
4. **Over-fetching with pagination** - Use appropriate page sizes
|
|
435
|
+
5. **Ignoring subscription cleanup** - React handles this automatically
|
|
436
|
+
|
|
437
|
+
## References
|
|
438
|
+
|
|
439
|
+
- Convex Documentation: https://docs.convex.dev/
|
|
440
|
+
- Convex LLMs.txt: https://docs.convex.dev/llms.txt
|
|
441
|
+
- React Client: https://docs.convex.dev/client/react
|
|
442
|
+
- Optimistic Updates: https://docs.convex.dev/client/react/optimistic-updates
|
|
443
|
+
- Pagination: https://docs.convex.dev/database/pagination
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_3_23)">
|
|
3
|
+
<g clip-path="url(#clip1_3_23)">
|
|
4
|
+
<path d="M10.0643 12.5735C12.3769 12.3166 14.5572 11.0843 15.7577 9.02756C15.1892 14.1148 9.62646 17.3302 5.08583 15.356C4.66743 15.1746 4.30728 14.8728 4.06013 14.4848C3.03973 12.8825 2.7043 10.8437 3.18626 8.99344C4.56327 11.37 7.3632 12.8267 10.0643 12.5735Z" fill="#F3B01C"/>
|
|
5
|
+
<path d="M3.1018 7.50072C2.16436 9.66714 2.12376 12.2034 3.27303 14.2907C-0.771507 11.2479 -0.72737 4.7362 3.2236 1.72378C3.58904 1.44535 4.02333 1.2801 4.47881 1.25494C6.3519 1.15614 8.25501 1.88006 9.58963 3.22909C6.87799 3.25604 4.23695 4.99308 3.1018 7.50072Z" fill="#8D2676"/>
|
|
6
|
+
<path d="M10.8974 3.89562C9.52924 1.98794 7.38779 0.68921 5.04156 0.649695C9.57686 -1.40888 15.1555 1.92867 15.7629 6.86314C15.8194 7.32119 15.7452 7.78824 15.5421 8.20138C14.6948 9.92223 13.1236 11.2569 11.2876 11.7508C12.6328 9.25579 12.4668 6.20748 10.8974 3.89562Z" fill="#EE342F"/>
|
|
7
|
+
</g>
|
|
8
|
+
</g>
|
|
9
|
+
<defs>
|
|
10
|
+
<clipPath id="clip0_3_23">
|
|
11
|
+
<rect width="16" height="16" fill="white"/>
|
|
12
|
+
</clipPath>
|
|
13
|
+
<clipPath id="clip1_3_23">
|
|
14
|
+
<rect width="16" height="16" fill="white"/>
|
|
15
|
+
</clipPath>
|
|
16
|
+
</defs>
|
|
17
|
+
</svg>
|