@velt-js/mcp-installer 0.1.0 → 0.2.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/README.md +14 -0
- package/package.json +1 -1
- package/src/utils/plan-formatter.js +240 -1391
- package/src/utils/use-client.js +10 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plan Formatter
|
|
3
3
|
*
|
|
4
|
-
* Formats installation instructions as a sequential plan
|
|
5
|
-
*
|
|
4
|
+
* Formats installation instructions as a sequential plan that the AI follows.
|
|
5
|
+
*
|
|
6
|
+
* ARCHITECTURE: This formatter generates ORCHESTRATION steps only.
|
|
7
|
+
* Implementation details (code patterns, CSS, configuration) live in agent-skills.
|
|
8
|
+
* The plan tells the AI WHAT to do and WHICH skill rule to follow for HOW.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import { getDocUrl, getDocMarkdownUrl } from './velt-docs-urls.js';
|
|
@@ -10,11 +13,6 @@ import { getSkillReferences } from './velt-docs-fetcher.js';
|
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Generates a "Skills & Sources" section for the plan output.
|
|
13
|
-
* Skills-covered features get a skill reference; others get docs URLs.
|
|
14
|
-
*
|
|
15
|
-
* @param {string[]} features - Features being installed
|
|
16
|
-
* @param {Object} [options] - Options (commentType, crdtEditorType)
|
|
17
|
-
* @returns {string} Markdown section
|
|
18
16
|
*/
|
|
19
17
|
function formatSkillsSourceSection(features, options = {}) {
|
|
20
18
|
const refs = getSkillReferences(features, options);
|
|
@@ -28,13 +26,12 @@ function formatSkillsSourceSection(features, options = {}) {
|
|
|
28
26
|
|
|
29
27
|
if (skillRefs.length > 0) {
|
|
30
28
|
section += `### Primary: Agent Skills (use these first)\n\n`;
|
|
31
|
-
// Deduplicate skill names
|
|
32
29
|
const seen = new Set();
|
|
33
30
|
for (const ref of skillRefs) {
|
|
34
31
|
if (!seen.has(ref.skillName)) {
|
|
35
32
|
seen.add(ref.skillName);
|
|
36
|
-
const
|
|
37
|
-
section += `- **${ref.skillName}** — covers: ${
|
|
33
|
+
const feats = skillRefs.filter(r => r.skillName === ref.skillName).map(r => r.feature);
|
|
34
|
+
section += `- **${ref.skillName}** — covers: ${feats.join(', ')}${ref.description ? ` (${ref.description})` : ''}\n`;
|
|
38
35
|
}
|
|
39
36
|
}
|
|
40
37
|
section += `\n`;
|
|
@@ -56,29 +53,18 @@ function formatSkillsSourceSection(features, options = {}) {
|
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
/**
|
|
59
|
-
* Formats a plan with numbered steps, details, and a to-do checklist
|
|
60
|
-
*
|
|
61
|
-
* @param {Object} options - Plan options
|
|
62
|
-
* @param {string} options.title - Plan title (e.g., "Plan for Velt Freestyle Comments Installation")
|
|
63
|
-
* @param {Array} options.steps - Array of step objects
|
|
64
|
-
* @param {string} options.steps[].title - Step title
|
|
65
|
-
* @param {string} options.steps[].details - Step details/instructions
|
|
66
|
-
* @param {string[]} [options.steps[].codeExamples] - Optional code examples
|
|
67
|
-
* @param {Array} [options.additionalInfo] - Additional information sections
|
|
68
|
-
* @returns {string} Formatted plan as markdown
|
|
56
|
+
* Formats a plan with numbered steps, details, and a to-do checklist.
|
|
69
57
|
*/
|
|
70
58
|
export function formatInstallationPlan(options) {
|
|
71
59
|
const { title, steps, additionalInfo = [] } = options;
|
|
72
60
|
|
|
73
61
|
let plan = `# ${title}\n\n`;
|
|
74
62
|
|
|
75
|
-
// Add numbered steps with details
|
|
76
63
|
steps.forEach((step, index) => {
|
|
77
64
|
const stepNumber = index + 1;
|
|
78
65
|
plan += `## ${stepNumber}. ${step.title}\n`;
|
|
79
66
|
plan += `* **Details:** ${step.details}\n`;
|
|
80
67
|
|
|
81
|
-
// Add code examples if provided
|
|
82
68
|
if (step.codeExamples && step.codeExamples.length > 0) {
|
|
83
69
|
step.codeExamples.forEach((example) => {
|
|
84
70
|
plan += `\n${example.description ? `* ${example.description}:` : ''}
|
|
@@ -91,7 +77,6 @@ ${example.code}
|
|
|
91
77
|
plan += '\n';
|
|
92
78
|
});
|
|
93
79
|
|
|
94
|
-
// Add additional information sections
|
|
95
80
|
if (additionalInfo.length > 0) {
|
|
96
81
|
additionalInfo.forEach((info) => {
|
|
97
82
|
plan += `## ${info.title}\n`;
|
|
@@ -99,7 +84,6 @@ ${example.code}
|
|
|
99
84
|
});
|
|
100
85
|
}
|
|
101
86
|
|
|
102
|
-
// Add To-Do checklist
|
|
103
87
|
plan += `## To-Do List\n`;
|
|
104
88
|
steps.forEach((step, index) => {
|
|
105
89
|
const checkbox = index === 0 ? '[✓]' : '[ ]';
|
|
@@ -107,21 +91,31 @@ ${example.code}
|
|
|
107
91
|
});
|
|
108
92
|
|
|
109
93
|
plan += '\n';
|
|
110
|
-
|
|
111
94
|
return plan;
|
|
112
95
|
}
|
|
113
96
|
|
|
114
97
|
/**
|
|
115
|
-
*
|
|
98
|
+
* Gets test instructions for a comment type.
|
|
99
|
+
*/
|
|
100
|
+
function getTestInstructions(commentType) {
|
|
101
|
+
const instructions = {
|
|
102
|
+
freestyle: 'Click the Comment Tool button, then click anywhere on the page to add a comment.',
|
|
103
|
+
popover: 'Click the Comment Tool button next to an element to attach a comment to it.',
|
|
104
|
+
page: 'Open the Comments Sidebar and add a page-level comment at the bottom.',
|
|
105
|
+
stream: 'Select text to see comments appear in the stream column on the right.',
|
|
106
|
+
text: 'Highlight any text to see the Comment Tool button appear, then click it to add a comment.',
|
|
107
|
+
inline: 'Navigate to your content area and test adding inline comments.',
|
|
108
|
+
tiptap: 'Open your Tiptap editor, select text, and use the comment button in the bubble menu.',
|
|
109
|
+
lexical: 'Open your Lexical editor, select text, and add comments using the Velt comment integration.',
|
|
110
|
+
slate: 'Open your Slate.js editor, select text, and add comments using the Velt comment integration.',
|
|
111
|
+
};
|
|
112
|
+
return instructions[commentType] || 'Test adding comments in your application.';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Creates a plan for Velt Comments installation (single-feature).
|
|
116
117
|
*
|
|
117
118
|
* @param {Object} options - Installation options
|
|
118
|
-
* @param {string} options.commentType - Type of comments (freestyle, popover, page, stream, text)
|
|
119
|
-
* @param {Object} options.implementation - Implementation details from Velt Docs
|
|
120
|
-
* @param {Array} options.detectedFiles - Files detected for modification
|
|
121
|
-
* @param {string} options.apiKey - API key preview
|
|
122
|
-
* @param {string} options.headerPosition - Header position
|
|
123
|
-
* @param {string} options.veltProviderLocation - Where to install VeltProvider
|
|
124
|
-
* @param {string} options.crdtEditorType - CRDT editor type (tiptap, codemirror, blocknote)
|
|
125
119
|
* @returns {string} Formatted installation plan
|
|
126
120
|
*/
|
|
127
121
|
export function createVeltCommentsPlan(options) {
|
|
@@ -133,564 +127,113 @@ export function createVeltCommentsPlan(options) {
|
|
|
133
127
|
headerPosition,
|
|
134
128
|
veltProviderLocation = 'app/page.tsx',
|
|
135
129
|
crdtEditorType,
|
|
130
|
+
frameworkInfo,
|
|
131
|
+
wiring,
|
|
136
132
|
} = options;
|
|
137
133
|
|
|
138
134
|
const commentTypeTitle = commentType.charAt(0).toUpperCase() + commentType.slice(1);
|
|
139
|
-
|
|
140
135
|
const steps = [];
|
|
141
136
|
|
|
142
|
-
// Add warning about only implementing requested features
|
|
143
|
-
steps.push({
|
|
144
|
-
title: `⚠️ CRITICAL: Only implement ${commentTypeTitle} Comments`,
|
|
145
|
-
details: `You are ONLY installing ${commentTypeTitle} Comments. DO NOT implement: VeltNotificationsTool, VeltPresence, VeltCursor, VeltRecorder, or any other components unless the user specifically requested them. Only use authentication, user setup, and document setup from CLI. Get ${commentTypeTitle} Comments implementation from: ${implementation?.mdUrl || getDocMarkdownUrl('comments', commentType)}`,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// Step 2: Use CLI-generated Velt components
|
|
149
137
|
const locationText = veltProviderLocation === 'auto-detect'
|
|
150
|
-
? 'the appropriate layout file
|
|
138
|
+
? 'the appropriate layout file'
|
|
151
139
|
: veltProviderLocation;
|
|
152
140
|
|
|
141
|
+
// Step 1: Scope warning
|
|
153
142
|
steps.push({
|
|
154
|
-
title:
|
|
155
|
-
details: `
|
|
156
|
-
|
|
157
|
-
**CLI-Generated Files:**
|
|
158
|
-
- \`components/velt/VeltInitializeUser.tsx\` - Exports \`useVeltAuthProvider\` hook (NOT a wrapper component)
|
|
159
|
-
- \`components/velt/VeltInitializeDocument.tsx\` - Exports \`useCurrentDocument\` hook for document context
|
|
160
|
-
- \`components/velt/VeltCollaboration.tsx\` - Velt feature components (comments, presence, etc.)
|
|
161
|
-
- \`app/userAuth/AppUserContext.tsx\` - User context provider wrapper
|
|
162
|
-
- \`app/userAuth/useAppUser.tsx\` - User data hook (add TODOs here)
|
|
163
|
-
- \`app/api/velt/token/route.ts\` - Token generation API
|
|
164
|
-
|
|
165
|
-
⚠️ **CRITICAL ARCHITECTURE (Sample App Pattern):**
|
|
166
|
-
|
|
167
|
-
**1. app/layout.tsx - Wrap with AppUserProvider:**
|
|
168
|
-
\`\`\`tsx
|
|
169
|
-
import { AppUserProvider } from './userAuth/AppUserContext'
|
|
170
|
-
|
|
171
|
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
172
|
-
return (
|
|
173
|
-
<html lang="en">
|
|
174
|
-
<body>
|
|
175
|
-
<AppUserProvider>
|
|
176
|
-
{children}
|
|
177
|
-
</AppUserProvider>
|
|
178
|
-
</body>
|
|
179
|
-
</html>
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
\`\`\`
|
|
183
|
-
**Why**: AppUserProvider provides user context to all components including Velt auth.
|
|
184
|
-
|
|
185
|
-
**2. app/page.tsx (or root page) - VeltProvider with authProvider hook:**
|
|
186
|
-
\`\`\`tsx
|
|
187
|
-
"use client";
|
|
188
|
-
import { VeltProvider } from '@veltdev/react';
|
|
189
|
-
import { useVeltAuthProvider } from '@/components/velt/VeltInitializeUser';
|
|
190
|
-
import { useCurrentDocument } from '@/components/velt/VeltInitializeDocument';
|
|
191
|
-
import { VeltCollaboration } from '@/components/velt/VeltCollaboration';
|
|
192
|
-
|
|
193
|
-
const VELT_API_KEY = process.env.NEXT_PUBLIC_VELT_API_KEY!;
|
|
194
|
-
|
|
195
|
-
export default function Page() {
|
|
196
|
-
const { authProvider } = useVeltAuthProvider();
|
|
197
|
-
const { documentId } = useCurrentDocument();
|
|
198
|
-
|
|
199
|
-
return (
|
|
200
|
-
<VeltProvider apiKey={VELT_API_KEY} authProvider={authProvider}>
|
|
201
|
-
<VeltCollaboration documentId={documentId} />
|
|
202
|
-
{/* Your page content */}
|
|
203
|
-
</VeltProvider>
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
\`\`\`
|
|
207
|
-
**Why**: VeltProvider must be in page.tsx with authProvider from hook, NOT nested wrapper components.
|
|
208
|
-
|
|
209
|
-
**What to do:**
|
|
210
|
-
1. Add AppUserProvider wrapper to app/layout.tsx
|
|
211
|
-
2. Add VeltProvider to ${locationText} using useVeltAuthProvider hook
|
|
212
|
-
3. Import VeltCollaboration for feature components
|
|
213
|
-
4. Follow the markdown documentation for ${commentType} comment-specific implementation: ${implementation?.mdUrl || getDocMarkdownUrl('comments', commentType)}
|
|
214
|
-
|
|
215
|
-
**CRITICAL - For Tiptap/Lexical/Slate Comments:**
|
|
216
|
-
- ✅ **FIND EXISTING EDITOR** - Search the project for existing Tiptap/Lexical/Slate editor components
|
|
217
|
-
- ✅ **INTEGRATE INTO EXISTING EDITOR** - Add Velt comments to the existing editor, DO NOT create a new editor
|
|
218
|
-
- ✅ **USE BUBBLE MENU PATTERN** - Add comment button to bubble menu (appears on text selection), NOT a fixed toolbar
|
|
219
|
-
- ✅ Use ONLY the comment-specific package: @veltdev/tiptap-velt-comments, @veltdev/lexical-velt-comments, or @veltdev/slate-velt-comments
|
|
220
|
-
- ❌ DO NOT create a new editor component if one already exists
|
|
221
|
-
- ❌ DO NOT create a fixed toolbar with Bold/Italic/Comment buttons
|
|
222
|
-
- ❌ DO NOT use CRDT packages (@veltdev/tiptap-velt-collaboration or similar)
|
|
223
|
-
- ❌ DO NOT implement real-time collaborative editing - only comments on the editor
|
|
224
|
-
|
|
225
|
-
**Tiptap Pattern:**
|
|
226
|
-
\`\`\`tsx
|
|
227
|
-
import { BubbleMenu } from '@tiptap/react'
|
|
228
|
-
import { TiptapVeltComments, addComment, renderComments } from '@veltdev/tiptap-velt-comments'
|
|
229
|
-
import { useCommentAnnotations } from '@veltdev/react'
|
|
230
|
-
|
|
231
|
-
// Add to editor extensions: TiptapVeltComments
|
|
232
|
-
// Use BubbleMenu component with comment button
|
|
233
|
-
<BubbleMenu editor={editor}>
|
|
234
|
-
<button onClick={() => addComment({ editor })}>💬 Comment</button>
|
|
235
|
-
</BubbleMenu>
|
|
236
|
-
\`\`\`
|
|
237
|
-
|
|
238
|
-
**Lexical Pattern:**
|
|
239
|
-
- Add VeltCommentsPlugin to editor plugins
|
|
240
|
-
- Use custom bubble menu that appears on selection
|
|
241
|
-
- Trigger addComment() from bubble menu button
|
|
242
|
-
|
|
243
|
-
**Slate Pattern:**
|
|
244
|
-
- Wrap editor with withVeltComments()
|
|
245
|
-
- Implement bubble menu with position tracking on selection
|
|
246
|
-
- Trigger addComment({ editor }) from bubble menu button
|
|
247
|
-
|
|
248
|
-
**If NO existing editor found:** Provide minimal integration example with bubble menu, but recommend user add to their existing editor.
|
|
249
|
-
|
|
250
|
-
**IMPORTANT:** All Velt-related files should remain in \`components/velt/\`. Do not create new Velt files outside this folder.`,
|
|
251
|
-
codeExamples: [
|
|
252
|
-
{
|
|
253
|
-
description: `Correct pattern: VeltProvider in page.tsx with authProvider hook`,
|
|
254
|
-
language: 'tsx',
|
|
255
|
-
code: `// app/page.tsx (or your root page component)
|
|
256
|
-
"use client";
|
|
257
|
-
import { VeltProvider } from '@veltdev/react';
|
|
258
|
-
import { useVeltAuthProvider } from '@/components/velt/VeltInitializeUser';
|
|
259
|
-
import { useCurrentDocument } from '@/components/velt/VeltInitializeDocument';
|
|
260
|
-
import { VeltCollaboration } from '@/components/velt/VeltCollaboration';
|
|
261
|
-
|
|
262
|
-
const VELT_API_KEY = process.env.NEXT_PUBLIC_VELT_API_KEY!;
|
|
263
|
-
|
|
264
|
-
export default function Page() {
|
|
265
|
-
const { authProvider } = useVeltAuthProvider();
|
|
266
|
-
const { documentId } = useCurrentDocument();
|
|
267
|
-
|
|
268
|
-
return (
|
|
269
|
-
<VeltProvider apiKey={VELT_API_KEY} authProvider={authProvider}>
|
|
270
|
-
<VeltCollaboration documentId={documentId} />
|
|
271
|
-
{/* Your page content here */}
|
|
272
|
-
</VeltProvider>
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// For ${commentType} comments: Follow implementation at ${implementation?.mdUrl || getDocMarkdownUrl('comments', commentType)}`,
|
|
277
|
-
},
|
|
278
|
-
],
|
|
143
|
+
title: `⚠️ CRITICAL: Only implement ${commentTypeTitle} Comments`,
|
|
144
|
+
details: `You are ONLY installing ${commentTypeTitle} Comments. DO NOT implement other components unless requested.`,
|
|
279
145
|
});
|
|
280
146
|
|
|
281
|
-
// Step
|
|
147
|
+
// Step 2: Wire VeltProvider + CLI components
|
|
282
148
|
steps.push({
|
|
283
|
-
title: `
|
|
284
|
-
details:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
- File location may vary, look for useVeltAuthProvider hook
|
|
296
|
-
|
|
297
|
-
**3. Document Hook: Check for \`app/document/useCurrentDocument.tsx\`**
|
|
298
|
-
- May be in CLI-generated files or needs to be created
|
|
299
|
-
- Add TODO: Implement document identification logic
|
|
300
|
-
- Add TODO: Return unique document ID based on current page/route
|
|
301
|
-
- Examples: router.query.id, pathname, page slug
|
|
302
|
-
|
|
303
|
-
**4. Token API: \`app/api/velt/token/route.ts\`**
|
|
304
|
-
- This file already exists from CLI
|
|
305
|
-
- Add TODO: Connect to your backend authentication
|
|
306
|
-
- Add TODO: Validate user session before generating token
|
|
307
|
-
|
|
308
|
-
**IMPORTANT:** Only modify CLI-generated files. Do not create new files. Keep all Velt code in \`components/velt/\` and the specified locations.
|
|
309
|
-
|
|
310
|
-
**FOR TESTING PRESENCE/CURSORS:** Add logic to test with multiple users:
|
|
311
|
-
1. Hardcode a fixed document ID (e.g., "demo-document") so all tabs use the same document
|
|
312
|
-
2. Provide 2 hardcoded users (user-1 and user-2) with different names/avatars
|
|
313
|
-
3. Allow switching users via URL parameter (?user=1 or ?user=2) to test presence/cursors
|
|
314
|
-
4. Open multiple browser tabs with different user parameters to see live presence and cursors`,
|
|
315
|
-
codeExamples: [
|
|
316
|
-
{
|
|
317
|
-
description: 'Example: Hardcoded document ID and multiple users for testing',
|
|
318
|
-
language: 'typescript',
|
|
319
|
-
code: `// In useCurrentDocument.tsx - Hardcode document ID for testing:
|
|
320
|
-
export function useCurrentDocument() {
|
|
321
|
-
// [Velt] HARDCODED for testing presence/cursors
|
|
322
|
-
// TODO: Replace with dynamic document ID based on your routing
|
|
323
|
-
const documentId = "demo-document"; // Fixed ID so all tabs see same document
|
|
324
|
-
|
|
325
|
-
return { documentId, documentName: "Demo Document" };
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// In useAppUser.tsx - Multiple users for testing:
|
|
329
|
-
export function useAppUser() {
|
|
330
|
-
// [Velt] HARDCODED USERS for testing presence/cursors
|
|
331
|
-
// TODO: Replace with actual user from your auth provider
|
|
332
|
-
|
|
333
|
-
// Get user from URL parameter (?user=1 or ?user=2)
|
|
334
|
-
const searchParams = typeof window !== 'undefined'
|
|
335
|
-
? new URLSearchParams(window.location.search)
|
|
336
|
-
: null;
|
|
337
|
-
const userParam = searchParams?.get('user') || '1';
|
|
338
|
-
|
|
339
|
-
const users = {
|
|
340
|
-
'1': {
|
|
341
|
-
userId: "user-1",
|
|
342
|
-
name: "Demo User 1",
|
|
343
|
-
email: "user1@example.com",
|
|
344
|
-
photoUrl: "https://i.pravatar.cc/150?img=1",
|
|
345
|
-
organizationId: "demo-org",
|
|
346
|
-
},
|
|
347
|
-
'2': {
|
|
348
|
-
userId: "user-2",
|
|
349
|
-
name: "Demo User 2",
|
|
350
|
-
email: "user2@example.com",
|
|
351
|
-
photoUrl: "https://i.pravatar.cc/150?img=2",
|
|
352
|
-
organizationId: "demo-org",
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
const user = users[userParam] || users['1'];
|
|
357
|
-
|
|
358
|
-
return { user, isUserLoggedIn: true };
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// TESTING INSTRUCTIONS:
|
|
362
|
-
// 1. Open http://localhost:3000?user=1 in one tab
|
|
363
|
-
// 2. Open http://localhost:3000?user=2 in another tab
|
|
364
|
-
// 3. You should see 2 different avatars in presence
|
|
365
|
-
// 4. Move mouse in one tab to see cursor in the other tab`,
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
description: 'Example TODO comments to add',
|
|
369
|
-
language: 'typescript',
|
|
370
|
-
code: `// In useAppUser.tsx:
|
|
371
|
-
// TODO: Connect to your authentication system
|
|
372
|
-
// TODO: Replace this mock user data with actual user from your auth provider
|
|
373
|
-
// Example: const user = useAuth(); // Your auth hook
|
|
374
|
-
// Example: const user = useSession(); // Next-auth
|
|
375
|
-
// Example: const user = useUser(); // Clerk, Auth0, etc.
|
|
376
|
-
|
|
377
|
-
// In useCurrentDocument.tsx:
|
|
378
|
-
// TODO: Implement document identification logic
|
|
379
|
-
// TODO: Return a unique document ID based on your app's routing
|
|
380
|
-
// Example: Use route params, URL, page ID, etc.
|
|
381
|
-
// Example: const documentId = router.query.id;
|
|
382
|
-
// Example: const documentId = \`page-\${pathname}\`;
|
|
383
|
-
|
|
384
|
-
// In app/api/velt/auth/route.ts:
|
|
385
|
-
// TODO: SECURITY - Validate user session before generating token
|
|
386
|
-
// TODO: Connect to your backend authentication
|
|
387
|
-
// TODO: Verify user is authenticated and authorized
|
|
388
|
-
|
|
389
|
-
// If JWT generation is present:
|
|
390
|
-
// TODO: SECURITY - Replace 'your-secret-key' with actual secret
|
|
391
|
-
// TODO: Store secret in environment variables (process.env.JWT_SECRET)
|
|
392
|
-
// TODO: NEVER commit secrets to version control
|
|
393
|
-
// TODO: Implement token expiration (expiresIn: '24h')`,
|
|
394
|
-
},
|
|
395
|
-
],
|
|
149
|
+
title: `Wire VeltProvider and CLI-generated components in ${locationText}`,
|
|
150
|
+
details: `**Skill reference:** \`velt-setup-best-practices\` → provider-wiring rules
|
|
151
|
+
|
|
152
|
+
Use the CLI-generated files in \`components/velt/\`. Wire them following the skill patterns:
|
|
153
|
+
- Import \`useVeltAuthProvider\` from \`components/velt/VeltInitializeUser.tsx\`
|
|
154
|
+
- Import \`VeltCollaboration\` from \`components/velt/VeltCollaboration.tsx\`
|
|
155
|
+
- Wrap your page content with \`<VeltProvider apiKey={...} authProvider={authProvider}>\`
|
|
156
|
+
- Place \`<VeltCollaboration />\` inside the VeltProvider
|
|
157
|
+
- The page file MUST have \`"use client"\` directive (Next.js)
|
|
158
|
+
- VeltProvider goes in \`${locationText}\`, NOT in layout.tsx (layout.tsx exports metadata, which is server-only)
|
|
159
|
+
|
|
160
|
+
**IMPORTANT:** Do NOT create new Velt files. Use the CLI-generated files in \`components/velt/\`.`,
|
|
396
161
|
});
|
|
397
162
|
|
|
398
|
-
// Step
|
|
163
|
+
// Step 3: Configure authentication
|
|
399
164
|
steps.push({
|
|
400
|
-
title: `
|
|
401
|
-
details:
|
|
402
|
-
|
|
403
|
-
**Production Pattern (FireHydrant Reference):**
|
|
404
|
-
Server-side API calls Velt's token endpoint to generate secure JWT tokens.
|
|
165
|
+
title: `Set up authentication and JWT token generation`,
|
|
166
|
+
details: `**Skill reference:** \`velt-setup-best-practices\` → identity-jwt-generation, identity-user-object-shape rules
|
|
405
167
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
x-velt-auth-token: YOUR_VELT_AUTH_TOKEN (keep secret!)
|
|
412
|
-
Body:
|
|
413
|
-
{ "data": { "userId": "...", "userProperties": { "organizationId": "...", "email": "..." } } }
|
|
414
|
-
Response:
|
|
415
|
-
{ "result": { "data": { "token": "eyJ..." } } }
|
|
416
|
-
\`\`\`
|
|
417
|
-
|
|
418
|
-
**Environment Variables to Set:**
|
|
419
|
-
\`\`\`
|
|
420
|
-
VELT_PUBLIC_API_KEY=your_api_key_here
|
|
421
|
-
VELT_AUTH_TOKEN=your_auth_token_here # NEVER expose to client!
|
|
422
|
-
\`\`\`
|
|
423
|
-
|
|
424
|
-
**Security Requirements:**
|
|
425
|
-
- Keep VELT_AUTH_TOKEN server-side only (never expose to client)
|
|
426
|
-
- Validate user session before generating tokens
|
|
427
|
-
- The auth token should only be used in API routes, not client components`,
|
|
428
|
-
codeExamples: [
|
|
429
|
-
{
|
|
430
|
-
description: 'Production API Route: app/api/velt/token/route.ts',
|
|
431
|
-
language: 'typescript',
|
|
432
|
-
code: `import { NextRequest, NextResponse } from 'next/server';
|
|
433
|
-
|
|
434
|
-
// [Velt] JWT Token Generation - Production Pattern
|
|
435
|
-
const VELT_API_KEY = process.env.VELT_PUBLIC_API_KEY;
|
|
436
|
-
const VELT_AUTH_TOKEN = process.env.VELT_AUTH_TOKEN;
|
|
437
|
-
|
|
438
|
-
export async function POST(request: NextRequest) {
|
|
439
|
-
try {
|
|
440
|
-
// [Velt] TODO: Add user session validation here
|
|
441
|
-
// const session = await getServerSession(authOptions);
|
|
442
|
-
// if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
443
|
-
|
|
444
|
-
const body = await request.json();
|
|
445
|
-
const { userId, organizationId, email } = body;
|
|
446
|
-
|
|
447
|
-
if (!userId) {
|
|
448
|
-
return NextResponse.json({ error: 'userId is required' }, { status: 400 });
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (!VELT_API_KEY || !VELT_AUTH_TOKEN) {
|
|
452
|
-
console.error('[Velt] Missing VELT_PUBLIC_API_KEY or VELT_AUTH_TOKEN');
|
|
453
|
-
return NextResponse.json({ error: 'Velt credentials not configured' }, { status: 500 });
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// [Velt] Call Velt Token API
|
|
457
|
-
const response = await fetch('https://api.velt.dev/v2/auth/token/get', {
|
|
458
|
-
method: 'POST',
|
|
459
|
-
headers: {
|
|
460
|
-
'Content-Type': 'application/json',
|
|
461
|
-
'x-velt-api-key': VELT_API_KEY,
|
|
462
|
-
'x-velt-auth-token': VELT_AUTH_TOKEN,
|
|
463
|
-
},
|
|
464
|
-
body: JSON.stringify({
|
|
465
|
-
data: {
|
|
466
|
-
userId,
|
|
467
|
-
userProperties: { organizationId: organizationId || 'default-org', email: email || '' },
|
|
468
|
-
},
|
|
469
|
-
}),
|
|
470
|
-
});
|
|
168
|
+
Follow the skill patterns to:
|
|
169
|
+
- Configure \`app/api/velt/token/route.ts\` for server-side JWT generation
|
|
170
|
+
- Set up user identification with required fields (userId, name, email, organizationId)
|
|
171
|
+
- Ensure auth token is server-only (VELT_AUTH_TOKEN in .env.local, NOT in client bundle)`,
|
|
172
|
+
});
|
|
471
173
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
174
|
+
// Step 4: Configure .env.local
|
|
175
|
+
steps.push({
|
|
176
|
+
title: `Set up environment variables`,
|
|
177
|
+
details: `Create or update \`.env.local\` with your Velt credentials (API key: ${apiKey}).
|
|
178
|
+
Required variables: NEXT_PUBLIC_VELT_API_KEY, VELT_API_KEY, VELT_AUTH_TOKEN.`,
|
|
179
|
+
});
|
|
476
180
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
181
|
+
// Step 5: Set up two-user testing
|
|
182
|
+
steps.push({
|
|
183
|
+
title: `Set up two-user testing`,
|
|
184
|
+
details: `**Skill reference:** \`velt-setup-best-practices\` → debug-multi-user-testing rule
|
|
482
185
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
}`,
|
|
489
|
-
},
|
|
490
|
-
{
|
|
491
|
-
description: 'Client-side auth provider with backend token fetch',
|
|
492
|
-
language: 'typescript',
|
|
493
|
-
code: `// In VeltInitializeUser.tsx - useVeltAuthProvider hook
|
|
494
|
-
export function useVeltAuthProvider() {
|
|
495
|
-
const { user } = useAppUser();
|
|
496
|
-
|
|
497
|
-
// [Velt] Token generation - calls backend API
|
|
498
|
-
const generateToken = useCallback(async (): Promise<string> => {
|
|
499
|
-
const response = await fetch('/api/velt/token', {
|
|
500
|
-
method: 'POST',
|
|
501
|
-
headers: { 'Content-Type': 'application/json' },
|
|
502
|
-
body: JSON.stringify({
|
|
503
|
-
userId: user?.userId,
|
|
504
|
-
organizationId: user?.organizationId,
|
|
505
|
-
email: user?.email,
|
|
506
|
-
}),
|
|
507
|
-
});
|
|
508
|
-
if (!response.ok) throw new Error('Token fetch failed');
|
|
509
|
-
const data = await response.json();
|
|
510
|
-
return data.token;
|
|
511
|
-
}, [user]);
|
|
512
|
-
|
|
513
|
-
const authProvider: VeltAuthProvider | undefined = useMemo(() => {
|
|
514
|
-
if (!user?.userId) return undefined;
|
|
515
|
-
return {
|
|
516
|
-
user: { userId: user.userId, name: user.name, email: user.email, organizationId: user.organizationId },
|
|
517
|
-
generateToken,
|
|
518
|
-
retryConfig: { retryCount: 3, retryDelay: 1000 },
|
|
519
|
-
};
|
|
520
|
-
}, [user, generateToken]);
|
|
521
|
-
|
|
522
|
-
return { authProvider };
|
|
523
|
-
}`,
|
|
524
|
-
},
|
|
525
|
-
],
|
|
186
|
+
The app MUST support testing with two different users. Follow the skill pattern:
|
|
187
|
+
- Provide sign-in buttons for both Alice and Bob
|
|
188
|
+
- Do NOT auto-login with a default user — let the sign-in page render
|
|
189
|
+
- Support \`?user=user-1\` and \`?user=user-2\` URL params for quick switching`,
|
|
526
190
|
});
|
|
527
191
|
|
|
528
|
-
// Step
|
|
192
|
+
// Step 6: Clear .next cache
|
|
529
193
|
steps.push({
|
|
530
|
-
title: `
|
|
531
|
-
details: `
|
|
194
|
+
title: `Clear .next build cache before first run`,
|
|
195
|
+
details: `Run \`rm -rf .next\` before starting the dev server. This prevents stale cache issues after changing imports.`,
|
|
532
196
|
});
|
|
533
197
|
|
|
534
|
-
// Step
|
|
198
|
+
// Step 7: Test
|
|
535
199
|
steps.push({
|
|
536
200
|
title: `Test the ${commentTypeTitle} comments functionality`,
|
|
537
|
-
details: `Start your development server and test
|
|
201
|
+
details: `Start your development server and test. ${getTestInstructions(commentType)} DO NOT test or implement other features.`,
|
|
538
202
|
});
|
|
539
203
|
|
|
540
|
-
// Step
|
|
204
|
+
// Step 8: Check console
|
|
541
205
|
steps.push({
|
|
542
206
|
title: `Check browser console for Velt errors/warnings`,
|
|
543
|
-
details: `Open browser DevTools Console
|
|
207
|
+
details: `Open browser DevTools Console and look for Velt errors. Common: "Please set document id", "Velt API key not found", "Failed to authenticate user". If errors occur, consult \`velt-setup-best-practices\` → debug-common-issues rule.`,
|
|
544
208
|
});
|
|
545
209
|
|
|
546
|
-
// Additional
|
|
210
|
+
// Additional info
|
|
547
211
|
const additionalInfo = [
|
|
548
212
|
{
|
|
549
213
|
title: '🚨 CRITICAL IMPLEMENTATION RULES',
|
|
550
|
-
content: `**
|
|
551
|
-
- ✅
|
|
552
|
-
- ✅
|
|
553
|
-
-
|
|
554
|
-
|
|
555
|
-
**PRIMARY SOURCE: Agent Skills (installed via \`npx skills add velt-js/agent-skills\`):**
|
|
556
|
-
- ✅ **velt-setup-best-practices** — VeltProvider setup, auth, document identity, project structure
|
|
557
|
-
- ✅ **velt-comments-best-practices** — ${commentType} comments implementation patterns and best practices
|
|
558
|
-
- Use skills as your FIRST reference for implementation details
|
|
559
|
-
|
|
560
|
-
**SECONDARY SOURCE: Docs URLs (only if skills don't cover it):**
|
|
561
|
-
- ${implementation?.mdUrl || getDocMarkdownUrl('comments', commentType)}
|
|
562
|
-
|
|
563
|
-
**TERTIARY SOURCE: Velt Docs MCP:**
|
|
564
|
-
- Only use for user follow-up questions AFTER implementation
|
|
565
|
-
|
|
566
|
-
**WHAT NOT TO IMPLEMENT:**
|
|
567
|
-
- ❌ VeltTools component
|
|
568
|
-
- ❌ ui-customization folder (unless user asks)
|
|
569
|
-
- ❌ Components user didn't request
|
|
570
|
-
- ❌ DO NOT copy code from CLI-generated files in components/velt/*`,
|
|
571
|
-
},
|
|
572
|
-
{
|
|
573
|
-
title: 'Additional Features Available',
|
|
574
|
-
content: `After completing the comments installation, you can add more Velt features:
|
|
575
|
-
|
|
576
|
-
**Presence (Live Users):**
|
|
577
|
-
- Setup: ${getDocMarkdownUrl('presence', null, 'setup')}
|
|
578
|
-
- Add \`<VeltPresence />\` to show online users with avatars
|
|
579
|
-
- Customize behavior: ${getDocMarkdownUrl('presence', null, 'customizeBehavior')}
|
|
580
|
-
|
|
581
|
-
**Cursors (Real-time Tracking):**
|
|
582
|
-
- Setup: ${getDocMarkdownUrl('cursors', null, 'setup')}
|
|
583
|
-
- Add \`<VeltCursor />\` to show live cursor positions
|
|
584
|
-
- Customize behavior: ${getDocMarkdownUrl('cursors', null, 'customizeBehavior')}
|
|
585
|
-
|
|
586
|
-
**Notifications:**
|
|
587
|
-
- Setup: ${getDocMarkdownUrl('notifications', null, 'setup')}
|
|
588
|
-
- Add \`<VeltNotificationsTool />\` for notification bell
|
|
589
|
-
- Customize behavior: ${getDocMarkdownUrl('notifications', null, 'customizeBehavior')}
|
|
590
|
-
|
|
591
|
-
**Recorder:**
|
|
592
|
-
- Setup: ${getDocMarkdownUrl('recorder', null, 'setup')}
|
|
593
|
-
- Add \`<VeltRecorder />\` for screen/audio recording
|
|
594
|
-
- Customize behavior: ${getDocMarkdownUrl('recorder', null, 'customizeBehavior')}`,
|
|
595
|
-
},
|
|
596
|
-
{
|
|
597
|
-
title: 'Post-Installation: Dev Tools & Troubleshooting',
|
|
598
|
-
content: `**Check Browser Console:**
|
|
599
|
-
After installation, open your browser DevTools Console (Press F12 or Cmd+Option+I on Mac):
|
|
600
|
-
1. Look for Velt SDK messages (usually prefixed with "[Velt]")
|
|
601
|
-
2. Check for warnings about configuration issues
|
|
602
|
-
3. Verify API key is loaded correctly
|
|
603
|
-
4. Watch for authentication errors or document ID issues
|
|
604
|
-
|
|
605
|
-
**Common Console Warnings:**
|
|
606
|
-
- "Velt API key not found" - Check that YOUR_VELT_API_KEY was replaced
|
|
607
|
-
- "Failed to authenticate user" - Verify auth token and user setup
|
|
608
|
-
- "Document ID missing" - Ensure document context is initialized
|
|
609
|
-
|
|
610
|
-
**Get Help:**
|
|
611
|
-
- For questions or issues AFTER installation, use the Velt Docs MCP server
|
|
612
|
-
- Query example: "How do I fix authentication errors in Velt?"
|
|
613
|
-
- The Velt Docs MCP has up-to-date solutions for common issues`,
|
|
614
|
-
},
|
|
615
|
-
{
|
|
616
|
-
title: 'Documentation Reference',
|
|
617
|
-
content: `**Primary: Agent Skills (installed via \`npx skills add velt-js/agent-skills\`)**
|
|
618
|
-
- **velt-setup-best-practices** — VeltProvider, auth, document identity, project structure
|
|
619
|
-
- **velt-comments-best-practices** — ${commentType} comments implementation patterns
|
|
620
|
-
|
|
621
|
-
**Secondary: Docs URLs**
|
|
622
|
-
- This feature: ${implementation?.mdUrl || getDocMarkdownUrl('comments', commentType)}
|
|
623
|
-
- Pattern: https://docs.velt.dev/[feature]/[page].md
|
|
624
|
-
|
|
625
|
-
**Tertiary: Velt Docs MCP**
|
|
626
|
-
After installation, query the Velt Docs MCP server for:
|
|
627
|
-
- Feature customization
|
|
628
|
-
- Troubleshooting
|
|
629
|
-
- Advanced configuration
|
|
630
|
-
- Integration patterns
|
|
631
|
-
Do NOT query during initial implementation — use Agent Skills first.`,
|
|
214
|
+
content: `**Source priority:** Agent Skills > Embedded Rules > Docs URLs
|
|
215
|
+
- ✅ **velt-setup-best-practices** — VeltProvider, auth, document identity, project structure
|
|
216
|
+
- ✅ **velt-comments-best-practices** — ${commentType} comments implementation patterns
|
|
217
|
+
- Do NOT reimplement patterns from scratch — follow the skill rules exactly`,
|
|
632
218
|
},
|
|
633
219
|
];
|
|
634
220
|
|
|
635
|
-
//
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
const basePlan = formatInstallationPlan({
|
|
221
|
+
// Format and add skills section
|
|
222
|
+
const plan = formatInstallationPlan({
|
|
639
223
|
title: `Plan for Velt ${commentTypeTitle} Comments Installation`,
|
|
640
224
|
steps,
|
|
641
225
|
additionalInfo,
|
|
642
226
|
});
|
|
643
227
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
return basePlan.slice(0, titleEnd) + skillsSection + basePlan.slice(titleEnd);
|
|
228
|
+
const skillsSection = formatSkillsSourceSection(['comments'], { commentType });
|
|
229
|
+
return plan + '\n' + skillsSection;
|
|
647
230
|
}
|
|
648
231
|
|
|
649
|
-
/**
|
|
650
|
-
* Gets CSS position styles for header positioning
|
|
651
|
-
*/
|
|
652
|
-
function getPositionStyles(position) {
|
|
653
|
-
const styles = {
|
|
654
|
-
'top-left': 'top: \'20px\',\n left: \'20px\',',
|
|
655
|
-
'top-right': 'top: \'20px\',\n right: \'20px\',',
|
|
656
|
-
'bottom-left': 'bottom: \'20px\',\n left: \'20px\',',
|
|
657
|
-
'bottom-right': 'bottom: \'20px\',\n right: \'20px\',',
|
|
658
|
-
};
|
|
659
|
-
return styles[position] || styles['top-right'];
|
|
660
|
-
}
|
|
661
232
|
|
|
662
233
|
/**
|
|
663
|
-
*
|
|
664
|
-
*/
|
|
665
|
-
function getTestInstructions(commentType) {
|
|
666
|
-
const instructions = {
|
|
667
|
-
freestyle: 'Click the Comment Tool button, then click anywhere on the page to add a comment.',
|
|
668
|
-
popover: 'Click the Comment Tool button next to an element to attach a comment to it.',
|
|
669
|
-
page: 'Open the Comments Sidebar and add a page-level comment at the bottom.',
|
|
670
|
-
stream: 'Select text to see comments appear in the stream column on the right.',
|
|
671
|
-
text: 'Highlight any text to see the Comment Tool button appear, then click it to add a comment.',
|
|
672
|
-
inline: 'Navigate to your content area and test adding inline comments.',
|
|
673
|
-
tiptap: 'Open your Tiptap editor, select text, and add comments using the Velt comment integration.',
|
|
674
|
-
lexical: 'Open your Lexical editor, select text, and add comments using the Velt comment integration.',
|
|
675
|
-
slate: 'Open your Slate.js editor, select text, and add comments using the Velt comment integration.',
|
|
676
|
-
};
|
|
677
|
-
return instructions[commentType] || 'Test adding comments in your application.';
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* Creates a comprehensive plan for multiple Velt features
|
|
234
|
+
* Creates a comprehensive plan for multiple Velt features.
|
|
682
235
|
*
|
|
683
236
|
* @param {Object} options - Installation options
|
|
684
|
-
* @param {string[]} options.features - Features to install (comments, presence, cursors, notifications, recorder, crdt)
|
|
685
|
-
* @param {string} options.commentType - Type of comments (if comments feature is included)
|
|
686
|
-
* @param {string} options.crdtEditorType - CRDT editor type (tiptap, codemirror, blocknote)
|
|
687
|
-
* @param {Object} options.implementation - Comment implementation details from Velt Docs
|
|
688
|
-
* @param {Object} options.crdtImplementation - CRDT implementation details from Velt Docs
|
|
689
|
-
* @param {Object} options.featureImplementations - Other feature implementations from Velt Docs
|
|
690
|
-
* @param {Array} options.detectedFiles - Files detected for modification
|
|
691
|
-
* @param {string} options.apiKey - API key preview
|
|
692
|
-
* @param {string} options.headerPosition - Header position
|
|
693
|
-
* @param {string} options.veltProviderLocation - Where to install VeltProvider
|
|
694
237
|
* @returns {string} Formatted installation plan
|
|
695
238
|
*/
|
|
696
239
|
export function createMultiFeaturePlan(options) {
|
|
@@ -705,6 +248,8 @@ export function createMultiFeaturePlan(options) {
|
|
|
705
248
|
apiKey,
|
|
706
249
|
headerPosition,
|
|
707
250
|
veltProviderLocation = 'app/page.tsx',
|
|
251
|
+
frameworkInfo,
|
|
252
|
+
wiring,
|
|
708
253
|
} = options;
|
|
709
254
|
|
|
710
255
|
const steps = [];
|
|
@@ -723,753 +268,205 @@ export function createMultiFeaturePlan(options) {
|
|
|
723
268
|
if (hasRecorder) featureList.push('Recorder');
|
|
724
269
|
if (hasCRDT) featureList.push(`CRDT (${crdtEditorType ? crdtEditorType.charAt(0).toUpperCase() + crdtEditorType.slice(1) : 'Collaborative Editing'})`);
|
|
725
270
|
|
|
726
|
-
// Add warning about only implementing requested features
|
|
727
|
-
steps.push({
|
|
728
|
-
title: `⚠️ CRITICAL: Only implement ${featureList.join(', ')}`,
|
|
729
|
-
details: `You are ONLY installing: ${featureList.join(', ')}. DO NOT implement any other components unless the user specifically requested them. Only use authentication, user setup, and document setup from CLI. Get implementation details from markdown URLs at docs.velt.dev.`,
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
// Step 2: Add VeltProvider
|
|
733
|
-
const componentsToAdd = [];
|
|
734
|
-
if (hasComments) componentsToAdd.push('VeltComments');
|
|
735
|
-
if (hasPresence) componentsToAdd.push('VeltPresence');
|
|
736
|
-
if (hasCursors) componentsToAdd.push('VeltCursor');
|
|
737
|
-
if (hasNotifications) componentsToAdd.push('VeltNotificationsTool');
|
|
738
|
-
if (hasRecorder) componentsToAdd.push('VeltRecorder');
|
|
739
|
-
|
|
740
271
|
const locationText = veltProviderLocation === 'auto-detect'
|
|
741
|
-
? 'the appropriate
|
|
272
|
+
? 'the appropriate page file'
|
|
742
273
|
: veltProviderLocation;
|
|
743
274
|
|
|
275
|
+
// Step 1: Scope warning
|
|
744
276
|
steps.push({
|
|
745
|
-
title:
|
|
746
|
-
details: `
|
|
747
|
-
|
|
748
|
-
**CLI-Generated Files:**
|
|
749
|
-
- \`components/velt/VeltInitializeUser.tsx\` - Exports \`useVeltAuthProvider\` hook (NOT a wrapper component)
|
|
750
|
-
- \`components/velt/VeltInitializeDocument.tsx\` - Exports \`useCurrentDocument\` hook for document context
|
|
751
|
-
- \`components/velt/VeltCollaboration.tsx\` - Velt feature components (comments, presence, etc.)
|
|
752
|
-
- \`app/userAuth/AppUserContext.tsx\` - User context provider wrapper
|
|
753
|
-
- \`app/userAuth/useAppUser.tsx\` - User data hook (add TODOs here)
|
|
754
|
-
- \`app/api/velt/token/route.ts\` - Token generation API
|
|
755
|
-
|
|
756
|
-
⚠️ **CRITICAL ARCHITECTURE (Sample App Pattern):**
|
|
757
|
-
|
|
758
|
-
**1. app/layout.tsx - Wrap with AppUserProvider:**
|
|
759
|
-
\`\`\`tsx
|
|
760
|
-
import { AppUserProvider } from './userAuth/AppUserContext'
|
|
761
|
-
|
|
762
|
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
763
|
-
return (
|
|
764
|
-
<html lang="en">
|
|
765
|
-
<body>
|
|
766
|
-
<AppUserProvider>
|
|
767
|
-
{children}
|
|
768
|
-
</AppUserProvider>
|
|
769
|
-
</body>
|
|
770
|
-
</html>
|
|
771
|
-
)
|
|
772
|
-
}
|
|
773
|
-
\`\`\`
|
|
774
|
-
**Why**: AppUserProvider provides user context to all components including Velt auth.
|
|
775
|
-
|
|
776
|
-
**2. app/page.tsx (or root page) - VeltProvider with authProvider hook:**
|
|
777
|
-
\`\`\`tsx
|
|
778
|
-
"use client";
|
|
779
|
-
import { VeltProvider } from '@veltdev/react';
|
|
780
|
-
import { useVeltAuthProvider } from '@/components/velt/VeltInitializeUser';
|
|
781
|
-
import { useCurrentDocument } from '@/components/velt/VeltInitializeDocument';
|
|
782
|
-
import { VeltCollaboration } from '@/components/velt/VeltCollaboration';
|
|
783
|
-
|
|
784
|
-
const VELT_API_KEY = process.env.NEXT_PUBLIC_VELT_API_KEY!;
|
|
785
|
-
|
|
786
|
-
export default function Page() {
|
|
787
|
-
const { authProvider } = useVeltAuthProvider();
|
|
788
|
-
const { documentId } = useCurrentDocument();
|
|
789
|
-
|
|
790
|
-
return (
|
|
791
|
-
<VeltProvider apiKey={VELT_API_KEY} authProvider={authProvider}>
|
|
792
|
-
<VeltCollaboration documentId={documentId} />
|
|
793
|
-
{/* Your page content */}
|
|
794
|
-
</VeltProvider>
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
\`\`\`
|
|
798
|
-
**Why**: VeltProvider must be in page.tsx with authProvider from hook, NOT nested wrapper components.
|
|
799
|
-
|
|
800
|
-
**What to do:**
|
|
801
|
-
1. Add AppUserProvider wrapper to app/layout.tsx
|
|
802
|
-
2. Add VeltProvider to ${locationText} using useVeltAuthProvider hook
|
|
803
|
-
3. Import VeltCollaboration for feature components
|
|
804
|
-
4. Follow the markdown documentation for feature-specific implementation
|
|
805
|
-
|
|
806
|
-
⚠️ **CRITICAL: Position ALL Velt Components in User's Chosen Corner:**
|
|
807
|
-
All Velt feature components (presence, notifications, comments sidebar, etc.) should be placed in the SAME corner that the user specified. This creates a consistent, grouped UI.
|
|
808
|
-
|
|
809
|
-
**Position based on user's chosen corner:**
|
|
810
|
-
- **top-left**: \`fixed top-4 left-4\`
|
|
811
|
-
- **top-right**: \`fixed top-4 right-4\`
|
|
812
|
-
- **bottom-left**: \`fixed bottom-4 left-4\`
|
|
813
|
-
- **bottom-right**: \`fixed bottom-4 right-4\`
|
|
814
|
-
|
|
815
|
-
\`\`\`tsx
|
|
816
|
-
// VeltCollaboration.tsx - Place ALL Velt components in the chosen corner
|
|
817
|
-
export function VeltCollaboration({ documentId }: { documentId: string }) {
|
|
818
|
-
return (
|
|
819
|
-
<>
|
|
820
|
-
{/* [Velt] ALL features grouped in user's chosen corner */}
|
|
821
|
-
<div className="fixed [POSITION] z-50 flex flex-col gap-2">
|
|
822
|
-
{/* Presence avatars */}
|
|
823
|
-
<VeltPresence flockMode={false} maxUsers={5} />
|
|
824
|
-
|
|
825
|
-
{/* Notifications bell */}
|
|
826
|
-
<VeltNotificationsTool />
|
|
827
|
-
|
|
828
|
-
{/* Comments sidebar trigger (if using comments) */}
|
|
829
|
-
<VeltCommentsSidebar />
|
|
830
|
-
</div>
|
|
831
|
-
|
|
832
|
-
{/* Cursors render across the whole page */}
|
|
833
|
-
<VeltCursor />
|
|
834
|
-
|
|
835
|
-
{/* Comments tool for freestyle comments */}
|
|
836
|
-
<VeltCommentTool />
|
|
837
|
-
</>
|
|
838
|
-
);
|
|
839
|
-
}
|
|
840
|
-
\`\`\`
|
|
841
|
-
|
|
842
|
-
**Replace [POSITION] with user's choice:**
|
|
843
|
-
- top-left → \`top-4 left-4\`
|
|
844
|
-
- top-right → \`top-4 right-4\`
|
|
845
|
-
- bottom-left → \`bottom-4 left-4\`
|
|
846
|
-
- bottom-right → \`bottom-4 right-4\`
|
|
847
|
-
|
|
848
|
-
**Why**: Grouping all Velt features in one corner creates a clean, consistent UI. Without positioned containers, Velt components create a white bar at the top of the page.
|
|
849
|
-
|
|
850
|
-
Also add to globals.css or VeltCustomization.css:
|
|
851
|
-
\`\`\`css
|
|
852
|
-
/* Remove default white background from Velt components */
|
|
853
|
-
velt-presence-container,
|
|
854
|
-
velt-presence-container *,
|
|
855
|
-
velt-notifications-tool-container,
|
|
856
|
-
velt-notifications-tool-container *,
|
|
857
|
-
velt-comments-sidebar-container,
|
|
858
|
-
velt-comments-sidebar-container * {
|
|
859
|
-
background: transparent !important;
|
|
860
|
-
}
|
|
861
|
-
\`\`\`
|
|
862
|
-
|
|
863
|
-
**CRITICAL - For Tiptap/Lexical/Slate Comments:**
|
|
864
|
-
- ✅ **FIND EXISTING EDITOR** - Search the project for existing Tiptap/Lexical/Slate editor components
|
|
865
|
-
- ✅ **INTEGRATE INTO EXISTING EDITOR** - Add Velt comments to the existing editor, DO NOT create a new editor
|
|
866
|
-
- ✅ **USE BUBBLE MENU PATTERN** - Add comment button to bubble menu (appears on text selection), NOT a fixed toolbar
|
|
867
|
-
- ✅ Use ONLY the comment-specific package: @veltdev/tiptap-velt-comments, @veltdev/lexical-velt-comments, or @veltdev/slate-velt-comments
|
|
868
|
-
- ❌ DO NOT create a new editor component if one already exists
|
|
869
|
-
- ❌ DO NOT create a fixed toolbar with Bold/Italic/Comment buttons
|
|
870
|
-
- ❌ DO NOT use CRDT packages (@veltdev/tiptap-velt-collaboration or similar)
|
|
871
|
-
- ❌ DO NOT implement real-time collaborative editing - only comments on the editor
|
|
872
|
-
|
|
873
|
-
**Tiptap Pattern:**
|
|
874
|
-
\`\`\`tsx
|
|
875
|
-
import { BubbleMenu } from '@tiptap/react'
|
|
876
|
-
import { TiptapVeltComments, addComment, renderComments } from '@veltdev/tiptap-velt-comments'
|
|
877
|
-
import { useCommentAnnotations } from '@veltdev/react'
|
|
878
|
-
|
|
879
|
-
// Add to editor extensions: TiptapVeltComments
|
|
880
|
-
// Use BubbleMenu component with comment button
|
|
881
|
-
<BubbleMenu editor={editor}>
|
|
882
|
-
<button onClick={() => addComment({ editor })}>💬 Comment</button>
|
|
883
|
-
</BubbleMenu>
|
|
884
|
-
\`\`\`
|
|
885
|
-
|
|
886
|
-
**Lexical Pattern:**
|
|
887
|
-
- Add VeltCommentsPlugin to editor plugins
|
|
888
|
-
- Use custom bubble menu that appears on selection
|
|
889
|
-
- Trigger addComment() from bubble menu button
|
|
890
|
-
|
|
891
|
-
**Slate Pattern:**
|
|
892
|
-
- Wrap editor with withVeltComments()
|
|
893
|
-
- Implement bubble menu with position tracking on selection
|
|
894
|
-
- Trigger addComment({ editor }) from bubble menu button
|
|
895
|
-
|
|
896
|
-
**If NO existing editor found:** Provide minimal integration example with bubble menu, but recommend user add to their existing editor.
|
|
897
|
-
|
|
898
|
-
**IMPORTANT:** All Velt-related files should remain in \`components/velt/\`. Do not create new Velt files outside this folder.
|
|
899
|
-
|
|
900
|
-
${hasCRDT && crdtEditorType ? `
|
|
901
|
-
**CRITICAL - For ${crdtEditorType.charAt(0).toUpperCase() + crdtEditorType.slice(1)} CRDT (Collaborative Real-Time Document Editing):**
|
|
902
|
-
${crdtEditorType === 'tiptap' ? `- ✅ **Required Packages:**
|
|
903
|
-
- @veltdev/tiptap-crdt-react (exact version: 4.5.8)
|
|
904
|
-
- @veltdev/tiptap-crdt (exact version: 4.5.8)
|
|
905
|
-
- @tiptap/y-tiptap (for Yjs integration)
|
|
906
|
-
- yjs (CRDT framework)
|
|
907
|
-
- y-prosemirror (ProseMirror bindings for Yjs)
|
|
908
|
-
|
|
909
|
-
⚠️ **CRITICAL: ID MAPPING PATTERN (FireHydrant Pattern):**
|
|
910
|
-
- **documentId**: Set via VeltInitializeDocument - one per page/resource (e.g., \`retrospective-123\`)
|
|
911
|
-
- **editorId**: Unique per editor instance - use format \`\${documentId}/\${fieldId}\` (e.g., \`retrospective-123/question-456\`)
|
|
912
|
-
- **Why**: This allows multiple editors per document, each with independent CRDT state
|
|
913
|
-
|
|
914
|
-
⚠️ **CRITICAL STARTERKIT CONFIGURATION:**
|
|
915
|
-
- ❌ **WRONG**: \`StarterKit.configure({ history: false })\` - DO NOT USE "history"
|
|
916
|
-
- ✅ **CORRECT**: \`StarterKit.configure({ undoRedo: false })\`
|
|
917
|
-
- **Why**: StarterKit doesn't have a "history" option. Use "undoRedo" instead. CRDT handles undo/redo.
|
|
918
|
-
|
|
919
|
-
⚠️ **CRITICAL INITIAL CONTENT:**
|
|
920
|
-
- ❌ **WRONG**: \`content: initialContent\` in useEditor
|
|
921
|
-
- ✅ **CORRECT**: \`// content: initialContent\` (comment it out)
|
|
922
|
-
- **Why**: Let CRDT handle initial content loading. Seed from backend only when CRDT doc is empty.
|
|
923
|
-
|
|
924
|
-
⚠️ **CRITICAL AUTO-SAVE PATTERN (FireHydrant Pattern):**
|
|
925
|
-
- ✅ Use 2-second debounce to avoid excessive backend saves
|
|
926
|
-
- ✅ Detect remote syncs: check \`transaction.getMeta('y-sync$')\`, \`transaction.getMeta('remote')\`, \`transaction.getMeta('velt-sync')\`, \`transaction.getMeta('isRemote')\`
|
|
927
|
-
- ✅ Skip saving for remote syncs (these are changes from other users)
|
|
928
|
-
- ✅ Only save local changes to backend
|
|
929
|
-
|
|
930
|
-
⚠️ **CRITICAL BACKEND CONTENT SEEDING:**
|
|
931
|
-
- ✅ When CRDT doc is empty AND backend has content, seed once
|
|
932
|
-
- ✅ Use \`useServerConnectionStateChangeHandler()\` to check connection is 'online' before seeding
|
|
933
|
-
- ✅ Track seeding state with ref to avoid double-seeding
|
|
934
|
-
|
|
935
|
-
⚠️ **CRITICAL COMMENTS EXTENSION (if adding comments to CRDT editor):**
|
|
936
|
-
- ❌ **WRONG**: \`TiptapVeltComments.configure({ editorId, HTMLAttributes })\`
|
|
937
|
-
- ✅ **CORRECT**: \`TiptapVeltComments\` (no .configure())
|
|
938
|
-
- **Why**: The extension works without configuration
|
|
939
|
-
|
|
940
|
-
**Tiptap CRDT Pattern (PRODUCTION CODE - FireHydrant Pattern):**
|
|
941
|
-
\`\`\`tsx
|
|
942
|
-
import { useEffect, useRef, useMemo } from 'react';
|
|
943
|
-
import { useEditor, EditorContent } from '@tiptap/react';
|
|
944
|
-
import StarterKit from '@tiptap/starter-kit';
|
|
945
|
-
import { useVeltTiptapCrdtExtension } from '@veltdev/tiptap-crdt-react';
|
|
946
|
-
import { useServerConnectionStateChangeHandler } from '@veltdev/react';
|
|
947
|
-
|
|
948
|
-
interface TipTapCollabEditorProps {
|
|
949
|
-
documentId: string; // From VeltInitializeDocument context
|
|
950
|
-
fieldId: string; // Unique field ID within document
|
|
951
|
-
backendfallbackContent?: any; // Backend content for seeding
|
|
952
|
-
onUpdate?: (params: { fieldId: string; value: any }) => void;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
export function TipTapCollabEditor({
|
|
956
|
-
documentId,
|
|
957
|
-
fieldId,
|
|
958
|
-
backendfallbackContent,
|
|
959
|
-
onUpdate,
|
|
960
|
-
}: TipTapCollabEditorProps) {
|
|
961
|
-
const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
962
|
-
const hasSeededContentRef = useRef(false);
|
|
963
|
-
const isEditorReadyRef = useRef(false);
|
|
964
|
-
|
|
965
|
-
// [Velt] CRITICAL: Combine documentId and fieldId for unique editorId
|
|
966
|
-
const editorId = \`\${documentId}/\${fieldId}\`;
|
|
967
|
-
|
|
968
|
-
// [Velt] Format initial content for CRDT
|
|
969
|
-
const veltInitialContent = useMemo(() => {
|
|
970
|
-
if (!backendfallbackContent) return undefined;
|
|
971
|
-
if (Array.isArray(backendfallbackContent)) {
|
|
972
|
-
return { type: 'doc', content: backendfallbackContent };
|
|
973
|
-
}
|
|
974
|
-
return backendfallbackContent;
|
|
975
|
-
}, [backendfallbackContent]);
|
|
277
|
+
title: `⚠️ CRITICAL: Only implement ${featureList.join(', ')}`,
|
|
278
|
+
details: `You are ONLY installing: ${featureList.join(', ')}. DO NOT implement any other components unless the user specifically requested them.`,
|
|
279
|
+
});
|
|
976
280
|
|
|
977
|
-
//
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
281
|
+
// Step 2: Wire VeltProvider + CLI components
|
|
282
|
+
steps.push({
|
|
283
|
+
title: `Wire VeltProvider and CLI-generated components in ${locationText}`,
|
|
284
|
+
details: `**Skill reference:** \`velt-setup-best-practices\` → provider-wiring, identity rules
|
|
285
|
+
|
|
286
|
+
Use the CLI-generated files in \`components/velt/\`. Wire them following the skill patterns:
|
|
287
|
+
- Import \`useVeltAuthProvider\` from \`components/velt/VeltInitializeUser.tsx\`
|
|
288
|
+
- Import \`VeltCollaboration\` from \`components/velt/VeltCollaboration.tsx\`
|
|
289
|
+
- Wrap your page content with \`<VeltProvider apiKey={...} authProvider={authProvider}>\`
|
|
290
|
+
- Place \`<VeltCollaboration />\` inside the VeltProvider
|
|
291
|
+
- The page file MUST have \`"use client"\` directive (Next.js)
|
|
292
|
+
- VeltProvider goes in \`${locationText}\`, NOT in layout.tsx (layout.tsx exports metadata)
|
|
293
|
+
- Position Velt UI components (presence, notifications, sidebar) in the ${headerPosition} corner
|
|
294
|
+
|
|
295
|
+
**IMPORTANT:** Do NOT create new Velt files. Use the CLI-generated files in \`components/velt/\`.`,
|
|
981
296
|
});
|
|
982
297
|
|
|
983
|
-
//
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
saveTimeoutRef.current = setTimeout(() => {
|
|
1008
|
-
const content = editor.getJSON()?.content || [];
|
|
1009
|
-
onUpdate?.({ fieldId, value: content });
|
|
1010
|
-
}, 2000);
|
|
1011
|
-
}
|
|
1012
|
-
},
|
|
1013
|
-
}, [VeltCrdt]);
|
|
1014
|
-
|
|
1015
|
-
// [Velt] Seed from backend when CRDT doc is empty
|
|
1016
|
-
useEffect(() => {
|
|
1017
|
-
if (
|
|
1018
|
-
editor && !isLoading &&
|
|
1019
|
-
serverConnectionState === 'online' &&
|
|
1020
|
-
!hasSeededContentRef.current &&
|
|
1021
|
-
isEditorEmpty(editor) &&
|
|
1022
|
-
hasBackendContent(backendfallbackContent)
|
|
1023
|
-
) {
|
|
1024
|
-
hasSeededContentRef.current = true;
|
|
1025
|
-
setTimeout(() => {
|
|
1026
|
-
if (editor && !editor.isDestroyed) {
|
|
1027
|
-
editor.commands.setContent(backendfallbackContent);
|
|
1028
|
-
isEditorReadyRef.current = true;
|
|
1029
|
-
}
|
|
1030
|
-
}, 100);
|
|
1031
|
-
} else if (editor && !isLoading && serverConnectionState === 'online') {
|
|
1032
|
-
isEditorReadyRef.current = true;
|
|
298
|
+
// Step 3: CRDT editor setup (conditional)
|
|
299
|
+
if (hasCRDT && crdtEditorType) {
|
|
300
|
+
const editorName = crdtEditorType.charAt(0).toUpperCase() + crdtEditorType.slice(1);
|
|
301
|
+
|
|
302
|
+
let skillRules = '';
|
|
303
|
+
let requirements = '';
|
|
304
|
+
|
|
305
|
+
if (crdtEditorType === 'tiptap') {
|
|
306
|
+
skillRules = 'tiptap-setup-react, tiptap-editor-id, tiptap-disable-history, tiptap-initial-content';
|
|
307
|
+
requirements = `- Use \`useVeltTiptapCrdtExtension\` hook with editorId = \`\${documentId}/\${fieldId}\`
|
|
308
|
+
- Disable \`undoRedo\` in StarterKit (NOT \`history\` — StarterKit has no "history" option)
|
|
309
|
+
- Use HTML strings for initialContent, NOT JSON objects (see tiptap-initial-content rule)
|
|
310
|
+
- Set \`immediatelyRender: false\` in useEditor options
|
|
311
|
+
- CRDT extension should be LAST in the extensions array`;
|
|
312
|
+
} else if (crdtEditorType === 'codemirror') {
|
|
313
|
+
skillRules = 'codemirror-setup-react, codemirror-ycollab, codemirror-editor-id';
|
|
314
|
+
requirements = `- Use \`useVeltCodeMirrorCrdtExtension\` hook
|
|
315
|
+
- Wire yCollab extension with store.getYText() and store.getAwareness()
|
|
316
|
+
- Use store.getUndoManager() for undo/redo`;
|
|
317
|
+
} else if (crdtEditorType === 'blocknote') {
|
|
318
|
+
skillRules = 'blocknote-setup-react, blocknote-editor-id';
|
|
319
|
+
requirements = `- Use \`useVeltBlockNoteCrdtExtension\` hook
|
|
320
|
+
- Pass collaborationConfig to useCreateBlockNote
|
|
321
|
+
- BlockNote handles CRDT automatically with the config`;
|
|
1033
322
|
}
|
|
1034
|
-
}, [editor, isLoading, serverConnectionState, backendfallbackContent]);
|
|
1035
323
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
// Helper functions
|
|
1041
|
-
const isEditorEmpty = (editor) => {
|
|
1042
|
-
const json = editor?.getJSON();
|
|
1043
|
-
if (!json?.content?.length) return true;
|
|
1044
|
-
if (json.content.length === 1 && json.content[0].type === 'paragraph' && !json.content[0].content?.length) return true;
|
|
1045
|
-
return false;
|
|
1046
|
-
};
|
|
1047
|
-
const hasBackendContent = (content) => content && !(Array.isArray(content) && content.length === 0);
|
|
1048
|
-
\`\`\`
|
|
1049
|
-
` : ''}${crdtEditorType === 'codemirror' ? `- ✅ Package: @veltdev/codemirror-crdt-react
|
|
1050
|
-
- ✅ Hook: useVeltCodeMirrorCrdtExtension({ editorId, initialContent })
|
|
1051
|
-
- ✅ Returns: { store, isLoading }
|
|
1052
|
-
- ✅ Use store.getYText(), store.getAwareness(), store.getUndoManager()
|
|
1053
|
-
- ✅ Requires y-codemirror.next package for yCollab
|
|
1054
|
-
|
|
1055
|
-
**CodeMirror CRDT Pattern:**
|
|
1056
|
-
\`\`\`tsx
|
|
1057
|
-
import { useVeltCodeMirrorCrdtExtension } from '@veltdev/codemirror-crdt-react'
|
|
1058
|
-
import { yCollab } from 'y-codemirror.next'
|
|
1059
|
-
import { EditorState } from '@codemirror/state'
|
|
1060
|
-
import { EditorView } from 'codemirror'
|
|
1061
|
-
|
|
1062
|
-
const { store, isLoading } = useVeltCodeMirrorCrdtExtension({
|
|
1063
|
-
editorId: 'codemirror-editor-1',
|
|
1064
|
-
initialContent: yourInitialContent
|
|
1065
|
-
})
|
|
1066
|
-
|
|
1067
|
-
// In useEffect:
|
|
1068
|
-
const startState = EditorState.create({
|
|
1069
|
-
doc: store.getYText()?.toString() ?? '',
|
|
1070
|
-
extensions: [
|
|
1071
|
-
// ... other extensions
|
|
1072
|
-
yCollab(store.getYText()!, store.getAwareness(), {
|
|
1073
|
-
undoManager: store.getUndoManager()
|
|
1074
|
-
}),
|
|
1075
|
-
],
|
|
1076
|
-
})
|
|
1077
|
-
|
|
1078
|
-
const view = new EditorView({ state: startState, parent: editorRef.current })
|
|
1079
|
-
\`\`\`
|
|
1080
|
-
` : ''}${crdtEditorType === 'blocknote' ? `- ✅ Package: @veltdev/blocknote-crdt-react
|
|
1081
|
-
- ✅ Hook: useVeltBlockNoteCrdtExtension({ editorId, initialContent })
|
|
1082
|
-
- ✅ Returns: { collaborationConfig, isLoading }
|
|
1083
|
-
- ✅ Pass collaborationConfig to useCreateBlockNote
|
|
1084
|
-
- ✅ BlockNote handles CRDT automatically with the config
|
|
1085
|
-
|
|
1086
|
-
**BlockNote CRDT Pattern:**
|
|
1087
|
-
\`\`\`tsx
|
|
1088
|
-
import { useVeltBlockNoteCrdtExtension } from '@veltdev/blocknote-crdt-react'
|
|
1089
|
-
import { useCreateBlockNote } from '@blocknote/react'
|
|
1090
|
-
import { BlockNoteView } from '@blocknote/mantine'
|
|
1091
|
-
|
|
1092
|
-
const { collaborationConfig, isLoading } = useVeltBlockNoteCrdtExtension({
|
|
1093
|
-
editorId: 'blocknote-editor-1',
|
|
1094
|
-
initialContent: JSON.stringify([{ type: "paragraph", content: "" }])
|
|
1095
|
-
})
|
|
1096
|
-
|
|
1097
|
-
const editor = useCreateBlockNote({
|
|
1098
|
-
collaboration: collaborationConfig,
|
|
1099
|
-
}, [collaborationConfig])
|
|
1100
|
-
|
|
1101
|
-
return <BlockNoteView editor={editor} />
|
|
1102
|
-
\`\`\`
|
|
1103
|
-
` : ''}
|
|
1104
|
-
` : ''}
|
|
1105
|
-
**Get implementation details from markdown docs:**
|
|
1106
|
-
${hasComments ? `- Comments (${commentType}): ${implementation?.mdUrl || getDocMarkdownUrl('comments', commentType)}${implementation?.source ? ` (fetched from ${implementation.source})` : ''}\n` : ''}${hasPresence ? `- Presence: ${featureImplementations.presence?.mdUrl || getDocMarkdownUrl('presence')}${featureImplementations.presence?.source ? ` (fetched from ${featureImplementations.presence.source})` : ''}\n` : ''}${hasCursors ? `- Cursors: ${featureImplementations.cursors?.mdUrl || getDocMarkdownUrl('cursors')}${featureImplementations.cursors?.source ? ` (fetched from ${featureImplementations.cursors.source})` : ''}\n` : ''}${hasNotifications ? `- Notifications: ${featureImplementations.notifications?.mdUrl || getDocMarkdownUrl('notifications')}${featureImplementations.notifications?.source ? ` (fetched from ${featureImplementations.notifications.source})` : ''}\n` : ''}${hasRecorder ? `- Recorder: ${featureImplementations.recorder?.mdUrl || getDocMarkdownUrl('recorder')}${featureImplementations.recorder?.source ? ` (fetched from ${featureImplementations.recorder.source})` : ''}\n` : ''}${hasCRDT && crdtEditorType ? `- CRDT (${crdtEditorType}): ${crdtImplementation?.mdUrl || getDocMarkdownUrl('crdt', crdtEditorType)}${crdtImplementation?.source ? ` (fetched from ${crdtImplementation.source})` : ''}\n` : ''}`,
|
|
1107
|
-
codeExamples: [
|
|
1108
|
-
{
|
|
1109
|
-
description: `Correct pattern: VeltProvider in page.tsx with authProvider hook`,
|
|
1110
|
-
language: 'tsx',
|
|
1111
|
-
code: `// app/page.tsx (or your root page component)
|
|
1112
|
-
"use client";
|
|
1113
|
-
import { VeltProvider } from '@veltdev/react';
|
|
1114
|
-
import { useVeltAuthProvider } from '@/components/velt/VeltInitializeUser';
|
|
1115
|
-
import { useCurrentDocument } from '@/components/velt/VeltInitializeDocument';
|
|
1116
|
-
import { VeltCollaboration } from '@/components/velt/VeltCollaboration';
|
|
1117
|
-
|
|
1118
|
-
const VELT_API_KEY = process.env.NEXT_PUBLIC_VELT_API_KEY!;
|
|
1119
|
-
|
|
1120
|
-
export default function Page() {
|
|
1121
|
-
const { authProvider } = useVeltAuthProvider();
|
|
1122
|
-
const { documentId } = useCurrentDocument();
|
|
1123
|
-
|
|
1124
|
-
return (
|
|
1125
|
-
<VeltProvider apiKey={VELT_API_KEY} authProvider={authProvider}>
|
|
1126
|
-
<VeltCollaboration documentId={documentId} />
|
|
1127
|
-
{/* Your page content here */}
|
|
1128
|
-
</VeltProvider>
|
|
1129
|
-
);
|
|
1130
|
-
}
|
|
324
|
+
steps.push({
|
|
325
|
+
title: `Create ${editorName} CRDT editor component`,
|
|
326
|
+
details: `**Skill reference:** \`velt-crdt-best-practices\` → ${skillRules}
|
|
1131
327
|
|
|
1132
|
-
|
|
1133
|
-
},
|
|
1134
|
-
],
|
|
1135
|
-
});
|
|
328
|
+
Create \`components/velt/${editorName}CollabEditor.tsx\` following the skill patterns.
|
|
1136
329
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
**1. User Hook: \`app/userAuth/useAppUser.tsx\`**
|
|
1143
|
-
- This file already exists from CLI
|
|
1144
|
-
- Add TODO: Connect to your existing authentication system
|
|
1145
|
-
- Add TODO: Replace mock user data with actual user from your auth provider
|
|
1146
|
-
- Examples: Next-auth useSession(), Clerk useUser(), Auth0, etc.
|
|
1147
|
-
|
|
1148
|
-
**2. Auth Provider: \`components/velt/VeltInitializeUser.tsx\`**
|
|
1149
|
-
- This file already exists from CLI (references useAppUser)
|
|
1150
|
-
- Contains \`useVeltAuthProvider\` hook
|
|
1151
|
-
- File location may vary, look for useVeltAuthProvider hook
|
|
1152
|
-
|
|
1153
|
-
**3. Document Hook: Check for \`app/document/useCurrentDocument.tsx\`**
|
|
1154
|
-
- May be in CLI-generated files or needs to be created
|
|
1155
|
-
- Add TODO: Implement document identification logic
|
|
1156
|
-
- Add TODO: Return unique document ID based on current page/route
|
|
1157
|
-
- Examples: router.query.id, pathname, page slug
|
|
1158
|
-
|
|
1159
|
-
**4. Token API: \`app/api/velt/token/route.ts\`**
|
|
1160
|
-
- This file already exists from CLI
|
|
1161
|
-
- Add TODO: Connect to your backend authentication
|
|
1162
|
-
- Add TODO: Validate user session before generating token
|
|
1163
|
-
|
|
1164
|
-
**IMPORTANT:** Only modify CLI-generated files. Do not create new files. Keep all Velt code in \`components/velt/\` and the specified locations.
|
|
1165
|
-
|
|
1166
|
-
**FOR TESTING PRESENCE/CURSORS:** Add logic to test with multiple users:
|
|
1167
|
-
1. Hardcode a fixed document ID (e.g., "demo-document") so all tabs use the same document
|
|
1168
|
-
2. Provide 2 hardcoded users (user-1 and user-2) with different names/avatars
|
|
1169
|
-
3. Allow switching users via URL parameter (?user=1 or ?user=2) to test presence/cursors
|
|
1170
|
-
4. Open multiple browser tabs with different user parameters to see live presence and cursors`,
|
|
1171
|
-
codeExamples: [
|
|
1172
|
-
{
|
|
1173
|
-
description: 'Example: Hardcoded document ID and multiple users for testing',
|
|
1174
|
-
language: 'typescript',
|
|
1175
|
-
code: `// In useCurrentDocument.tsx - Hardcode document ID for testing:
|
|
1176
|
-
export function useCurrentDocument() {
|
|
1177
|
-
// [Velt] HARDCODED for testing presence/cursors
|
|
1178
|
-
// TODO: Replace with dynamic document ID based on your routing
|
|
1179
|
-
const documentId = "demo-document"; // Fixed ID so all tabs see same document
|
|
1180
|
-
|
|
1181
|
-
return { documentId, documentName: "Demo Document" };
|
|
1182
|
-
}
|
|
330
|
+
Requirements:
|
|
331
|
+
${requirements}`,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
1183
334
|
|
|
1184
|
-
//
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
// Get user from URL parameter (?user=1 or ?user=2)
|
|
1190
|
-
const searchParams = typeof window !== 'undefined'
|
|
1191
|
-
? new URLSearchParams(window.location.search)
|
|
1192
|
-
: null;
|
|
1193
|
-
const userParam = searchParams?.get('user') || '1';
|
|
1194
|
-
|
|
1195
|
-
const users = {
|
|
1196
|
-
'1': {
|
|
1197
|
-
userId: "user-1",
|
|
1198
|
-
name: "Demo User 1",
|
|
1199
|
-
email: "user1@example.com",
|
|
1200
|
-
photoUrl: "https://i.pravatar.cc/150?img=1",
|
|
1201
|
-
organizationId: "demo-org",
|
|
1202
|
-
},
|
|
1203
|
-
'2': {
|
|
1204
|
-
userId: "user-2",
|
|
1205
|
-
name: "Demo User 2",
|
|
1206
|
-
email: "user2@example.com",
|
|
1207
|
-
photoUrl: "https://i.pravatar.cc/150?img=2",
|
|
1208
|
-
organizationId: "demo-org",
|
|
1209
|
-
}
|
|
1210
|
-
};
|
|
335
|
+
// Step 4: Comments integration with editor (conditional)
|
|
336
|
+
if (hasComments && hasCRDT && crdtEditorType) {
|
|
337
|
+
steps.push({
|
|
338
|
+
title: `MANDATORY: Integrate TiptapVeltComments extension in editor`,
|
|
339
|
+
details: `**Skill reference:** \`velt-crdt-best-practices\` → tiptap-comments-integration rule
|
|
1211
340
|
|
|
1212
|
-
|
|
341
|
+
⚠️ WITHOUT THIS, THE APP WILL FREEZE WHEN COMMENTS ARE TRIGGERED.
|
|
1213
342
|
|
|
1214
|
-
|
|
1215
|
-
|
|
343
|
+
The global \`<VeltComments>\` component is necessary but NOT sufficient for editor comments.
|
|
344
|
+
You MUST also:
|
|
345
|
+
- Add \`TiptapVeltComments\` to the editor's extensions array (BEFORE the CRDT extension)
|
|
346
|
+
- Import and call \`highlightComments(editor, commentAnnotations)\` in a useEffect (v4 API)
|
|
347
|
+
- Import and use \`triggerAddComment(editor)\` for the comment button (v4 API)
|
|
348
|
+
- Import \`useCommentAnnotations\` from \`@veltdev/react\` to subscribe to comment data
|
|
1216
349
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
// 3. You should see 2 different avatars in presence
|
|
1221
|
-
// 4. Move mouse in one tab to see cursor in the other tab`,
|
|
1222
|
-
},
|
|
1223
|
-
{
|
|
1224
|
-
description: 'Example TODO comments to add',
|
|
1225
|
-
language: 'typescript',
|
|
1226
|
-
code: `// In useAppUser.tsx:
|
|
1227
|
-
// TODO: Connect to your authentication system
|
|
1228
|
-
// TODO: Replace this mock user data with actual user from your auth provider
|
|
1229
|
-
// Example: const user = useAuth(); // Your auth hook
|
|
1230
|
-
// Example: const user = useSession(); // Next-auth
|
|
1231
|
-
// Example: const user = useUser(); // Clerk, Auth0, etc.
|
|
1232
|
-
|
|
1233
|
-
// In useCurrentDocument.tsx:
|
|
1234
|
-
// TODO: Implement document identification logic
|
|
1235
|
-
// TODO: Return a unique document ID based on your app's routing
|
|
1236
|
-
// Example: Use route params, URL, page ID, etc.
|
|
1237
|
-
// Example: const documentId = router.query.id;
|
|
1238
|
-
// Example: const documentId = \`page-\${pathname}\`;
|
|
1239
|
-
|
|
1240
|
-
// In app/api/velt/auth/route.ts:
|
|
1241
|
-
// TODO: SECURITY - Validate user session before generating token
|
|
1242
|
-
// TODO: Connect to your backend authentication
|
|
1243
|
-
// TODO: Verify user is authenticated and authorized
|
|
1244
|
-
|
|
1245
|
-
// If JWT generation is present:
|
|
1246
|
-
// TODO: SECURITY - Replace 'your-secret-key' with actual secret
|
|
1247
|
-
// TODO: Store secret in environment variables (process.env.JWT_SECRET)
|
|
1248
|
-
// TODO: NEVER commit secrets to version control
|
|
1249
|
-
// TODO: Implement token expiration (expiresIn: '24h')`,
|
|
1250
|
-
},
|
|
1251
|
-
],
|
|
1252
|
-
});
|
|
350
|
+
Check your installed package version — v4 uses \`triggerAddComment\`/\`highlightComments\`, v5 uses \`addComment\`/\`renderComments\`.`,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
1253
353
|
|
|
1254
|
-
// Step
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
354
|
+
// Step 5: SSR safety (conditional, Next.js + Tiptap)
|
|
355
|
+
if (hasCRDT && crdtEditorType === 'tiptap') {
|
|
356
|
+
steps.push({
|
|
357
|
+
title: `MANDATORY: Load editor with next/dynamic (SSR safety)`,
|
|
358
|
+
details: `**Skill reference:** \`velt-crdt-best-practices\` → tiptap-nextjs-ssr rule
|
|
1258
359
|
|
|
1259
|
-
|
|
1260
|
-
Server-side API calls Velt's token endpoint to generate secure JWT tokens.
|
|
360
|
+
Tiptap and @veltdev/tiptap-velt-comments use browser-only APIs. In Next.js, the editor component MUST be loaded with \`next/dynamic\` and \`ssr: false\` in the page that renders it. Without this, the app will crash with a \`g.catch is not a function\` error.
|
|
1261
361
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
Headers:
|
|
1266
|
-
x-velt-api-key: YOUR_VELT_PUBLIC_API_KEY
|
|
1267
|
-
x-velt-auth-token: YOUR_VELT_AUTH_TOKEN (keep secret!)
|
|
1268
|
-
Body:
|
|
1269
|
-
{ "data": { "userId": "...", "userProperties": { "organizationId": "...", "email": "..." } } }
|
|
1270
|
-
Response:
|
|
1271
|
-
{ "result": { "data": { "token": "eyJ..." } } }
|
|
1272
|
-
\`\`\`
|
|
362
|
+
In your page file, use \`dynamic(() => import(...), { ssr: false })\` — do NOT import the editor component directly.`,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
1273
365
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
366
|
+
// Step 6: Cursor CSS (conditional)
|
|
367
|
+
if (hasCRDT && crdtEditorType === 'tiptap') {
|
|
368
|
+
steps.push({
|
|
369
|
+
title: `MANDATORY: Add collaboration cursor CSS to globals.css`,
|
|
370
|
+
details: `**Skill reference:** \`velt-crdt-best-practices\` → tiptap-cursor-css rule
|
|
1279
371
|
|
|
1280
|
-
|
|
1281
|
-
- Keep VELT_AUTH_TOKEN server-side only (never expose to client)
|
|
1282
|
-
- Validate user session before generating tokens
|
|
1283
|
-
- The auth token should only be used in API routes, not client components`,
|
|
1284
|
-
codeExamples: [
|
|
1285
|
-
{
|
|
1286
|
-
description: 'Production API Route: app/api/velt/token/route.ts',
|
|
1287
|
-
language: 'typescript',
|
|
1288
|
-
code: `import { NextRequest, NextResponse } from 'next/server';
|
|
1289
|
-
|
|
1290
|
-
// [Velt] JWT Token Generation - Production Pattern
|
|
1291
|
-
const VELT_API_KEY = process.env.VELT_PUBLIC_API_KEY;
|
|
1292
|
-
const VELT_AUTH_TOKEN = process.env.VELT_AUTH_TOKEN;
|
|
1293
|
-
|
|
1294
|
-
export async function POST(request: NextRequest) {
|
|
1295
|
-
try {
|
|
1296
|
-
// [Velt] TODO: Add user session validation here
|
|
1297
|
-
// const session = await getServerSession(authOptions);
|
|
1298
|
-
// if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
1299
|
-
|
|
1300
|
-
const body = await request.json();
|
|
1301
|
-
const { userId, organizationId, email } = body;
|
|
1302
|
-
|
|
1303
|
-
if (!userId) {
|
|
1304
|
-
return NextResponse.json({ error: 'userId is required' }, { status: 400 });
|
|
1305
|
-
}
|
|
372
|
+
Without this CSS, remote user cursors appear as thick full-width blocks instead of thin carets.
|
|
1306
373
|
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
}
|
|
374
|
+
Follow the skill rule exactly — it targets BOTH:
|
|
375
|
+
- \`.ProseMirror-yjs-cursor\` classes (from y-prosemirror, used by Velt CRDT)
|
|
376
|
+
- \`.collaboration-cursor__caret\` classes (from @tiptap/extension-collaboration-cursor)
|
|
1311
377
|
|
|
1312
|
-
|
|
1313
|
-
const response = await fetch('https://api.velt.dev/v2/auth/token/get', {
|
|
1314
|
-
method: 'POST',
|
|
1315
|
-
headers: {
|
|
1316
|
-
'Content-Type': 'application/json',
|
|
1317
|
-
'x-velt-api-key': VELT_API_KEY,
|
|
1318
|
-
'x-velt-auth-token': VELT_AUTH_TOKEN,
|
|
1319
|
-
},
|
|
1320
|
-
body: JSON.stringify({
|
|
1321
|
-
data: {
|
|
1322
|
-
userId,
|
|
1323
|
-
userProperties: { organizationId: organizationId || 'default-org', email: email || '' },
|
|
1324
|
-
},
|
|
1325
|
-
}),
|
|
378
|
+
Critical: the \`> span { display: inline !important }\` rule is required to prevent block-level rendering.`,
|
|
1326
379
|
});
|
|
380
|
+
}
|
|
1327
381
|
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
382
|
+
// Step 7: Authentication setup
|
|
383
|
+
steps.push({
|
|
384
|
+
title: `Set up authentication and JWT token generation`,
|
|
385
|
+
details: `**Skill reference:** \`velt-setup-best-practices\` → identity-jwt-generation, identity-user-object-shape rules
|
|
1332
386
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
387
|
+
Follow the skill patterns to:
|
|
388
|
+
- Configure \`app/api/velt/token/route.ts\` for server-side JWT generation
|
|
389
|
+
- Set up user identification with required fields (userId, name, email, organizationId)
|
|
390
|
+
- Ensure auth token is server-only (VELT_AUTH_TOKEN in .env.local, NOT in client bundle)`,
|
|
391
|
+
});
|
|
1338
392
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
}`,
|
|
1345
|
-
},
|
|
1346
|
-
{
|
|
1347
|
-
description: 'Client-side auth provider with backend token fetch',
|
|
1348
|
-
language: 'typescript',
|
|
1349
|
-
code: `// In VeltInitializeUser.tsx - useVeltAuthProvider hook
|
|
1350
|
-
export function useVeltAuthProvider() {
|
|
1351
|
-
const { user } = useAppUser();
|
|
1352
|
-
|
|
1353
|
-
// [Velt] Token generation - calls backend API
|
|
1354
|
-
const generateToken = useCallback(async (): Promise<string> => {
|
|
1355
|
-
const response = await fetch('/api/velt/token', {
|
|
1356
|
-
method: 'POST',
|
|
1357
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1358
|
-
body: JSON.stringify({
|
|
1359
|
-
userId: user?.userId,
|
|
1360
|
-
organizationId: user?.organizationId,
|
|
1361
|
-
email: user?.email,
|
|
1362
|
-
}),
|
|
1363
|
-
});
|
|
1364
|
-
if (!response.ok) throw new Error('Token fetch failed');
|
|
1365
|
-
const data = await response.json();
|
|
1366
|
-
return data.token;
|
|
1367
|
-
}, [user]);
|
|
1368
|
-
|
|
1369
|
-
const authProvider: VeltAuthProvider | undefined = useMemo(() => {
|
|
1370
|
-
if (!user?.userId) return undefined;
|
|
1371
|
-
return {
|
|
1372
|
-
user: { userId: user.userId, name: user.name, email: user.email, organizationId: user.organizationId },
|
|
1373
|
-
generateToken,
|
|
1374
|
-
retryConfig: { retryCount: 3, retryDelay: 1000 },
|
|
1375
|
-
};
|
|
1376
|
-
}, [user, generateToken]);
|
|
1377
|
-
|
|
1378
|
-
return { authProvider };
|
|
1379
|
-
}`,
|
|
1380
|
-
},
|
|
1381
|
-
],
|
|
393
|
+
// Step 8: Environment variables
|
|
394
|
+
steps.push({
|
|
395
|
+
title: `Set up environment variables`,
|
|
396
|
+
details: `Create or update \`.env.local\` with your Velt credentials (API key: ${apiKey}).
|
|
397
|
+
Required variables: NEXT_PUBLIC_VELT_API_KEY, VELT_API_KEY, VELT_AUTH_TOKEN.`,
|
|
1382
398
|
});
|
|
1383
399
|
|
|
1384
|
-
// Step
|
|
400
|
+
// Step 9: Two-user testing
|
|
1385
401
|
steps.push({
|
|
1386
|
-
title: `
|
|
1387
|
-
details:
|
|
402
|
+
title: `Set up two-user testing`,
|
|
403
|
+
details: `**Skill reference:** \`velt-setup-best-practices\` → debug-multi-user-testing rule
|
|
404
|
+
|
|
405
|
+
The app MUST support testing with two different users. Follow the skill pattern:
|
|
406
|
+
- Provide sign-in buttons for both Alice and Bob
|
|
407
|
+
- Do NOT auto-login with a default user — if the scaffolding has \`params.get("user") || "user-1"\`, remove the \`|| "user-1"\` default
|
|
408
|
+
- Support \`?user=user-1\` and \`?user=user-2\` URL params for quick switching`,
|
|
1388
409
|
});
|
|
1389
410
|
|
|
1390
|
-
// Step
|
|
411
|
+
// Step 10: Clear .next cache
|
|
412
|
+
steps.push({
|
|
413
|
+
title: `Clear .next build cache before first run`,
|
|
414
|
+
details: `Run \`rm -rf .next\` before starting the dev server. This prevents stale cache issues, especially after changing SSR patterns (adding \`next/dynamic\`).`,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Step 11: Test
|
|
1391
418
|
const testInstructions = [];
|
|
1392
419
|
if (hasComments) testInstructions.push(`${commentType} comments: ${getTestInstructions(commentType)}`);
|
|
1393
420
|
if (hasPresence) testInstructions.push('Presence: Check that user avatars appear in the presence component');
|
|
1394
|
-
if (hasCursors) testInstructions.push('Cursors: Open in two browser windows and
|
|
421
|
+
if (hasCursors) testInstructions.push('Cursors: Open in two browser windows and verify thin caret cursors with name labels');
|
|
1395
422
|
if (hasNotifications) testInstructions.push('Notifications: Check the notification bell icon appears');
|
|
1396
423
|
if (hasRecorder) testInstructions.push('Recorder: Check the recorder controls appear');
|
|
1397
424
|
|
|
1398
425
|
steps.push({
|
|
1399
426
|
title: `Test all requested features`,
|
|
1400
|
-
details: `Start your development server and test
|
|
427
|
+
details: `Start your development server and test:\n${testInstructions.map(t => `- ${t}`).join('\n')}\n\nDO NOT test or implement other features.`,
|
|
1401
428
|
});
|
|
1402
429
|
|
|
1403
|
-
// Step
|
|
430
|
+
// Step 12: Check console
|
|
1404
431
|
steps.push({
|
|
1405
432
|
title: `Check browser console for Velt errors/warnings`,
|
|
1406
|
-
details: `Open browser DevTools Console
|
|
433
|
+
details: `Open browser DevTools Console and look for Velt errors. Common: "Please set document id", "Velt API key not found", "Failed to authenticate user". If errors occur, consult \`velt-setup-best-practices\` → debug-common-issues rule.`,
|
|
1407
434
|
});
|
|
1408
435
|
|
|
1409
|
-
// Additional
|
|
436
|
+
// Additional info
|
|
437
|
+
const skillsList = [];
|
|
438
|
+
skillsList.push('- ✅ **velt-setup-best-practices** — VeltProvider, auth, document identity, project structure');
|
|
439
|
+
if (hasComments) skillsList.push(`- ✅ **velt-comments-best-practices** — ${commentType} comments implementation patterns`);
|
|
440
|
+
if (hasCRDT) skillsList.push(`- ✅ **velt-crdt-best-practices** — ${crdtEditorType || 'collaborative editing'} CRDT patterns`);
|
|
441
|
+
if (hasNotifications) skillsList.push('- ✅ **velt-notifications-best-practices** — notifications setup and customization');
|
|
442
|
+
|
|
1410
443
|
const additionalInfo = [
|
|
1411
444
|
{
|
|
1412
445
|
title: '🚨 CRITICAL IMPLEMENTATION RULES',
|
|
1413
|
-
content: `**
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
-
|
|
1420
|
-
${hasComments ? `- ✅ **velt-comments-best-practices** — ${commentType} comments implementation patterns\n` : ''}${hasCRDT ? `- ✅ **velt-crdt-best-practices** — ${crdtEditorType || 'collaborative editing'} CRDT patterns\n` : ''}${hasNotifications ? `- ✅ **velt-notifications-best-practices** — notifications setup and customization\n` : ''}- Use skills as your FIRST reference for implementation details
|
|
1421
|
-
|
|
1422
|
-
**SECONDARY SOURCE: Docs URLs (for features without skills coverage):**
|
|
1423
|
-
${hasPresence ? `- Presence: ${featureImplementations.presence?.mdUrl || getDocMarkdownUrl('presence')}\n` : ''}${hasCursors ? `- Cursors: ${featureImplementations.cursors?.mdUrl || getDocMarkdownUrl('cursors')}\n` : ''}${hasRecorder ? `- Recorder: ${featureImplementations.recorder?.mdUrl || getDocMarkdownUrl('recorder')}\n` : ''}${!hasPresence && !hasCursors && !hasRecorder ? `- All selected features are covered by Agent Skills\n` : ''}
|
|
1424
|
-
**TERTIARY SOURCE: Velt Docs MCP:**
|
|
1425
|
-
- Only use for user follow-up questions AFTER implementation
|
|
1426
|
-
|
|
1427
|
-
**WHAT NOT TO IMPLEMENT:**
|
|
1428
|
-
- ❌ VeltTools component (unless explicitly requested)
|
|
1429
|
-
- ❌ ui-customization folder (unless user asks)
|
|
1430
|
-
- ❌ Components user didn't request
|
|
1431
|
-
- ❌ DO NOT copy code from CLI-generated files in components/velt/*`,
|
|
1432
|
-
},
|
|
1433
|
-
{
|
|
1434
|
-
title: 'Documentation References',
|
|
1435
|
-
content: `**Primary: Agent Skills (installed via \`npx skills add velt-js/agent-skills\`)**
|
|
1436
|
-
- velt-setup-best-practices — setup, provider, auth, document
|
|
1437
|
-
${hasComments ? `- velt-comments-best-practices — ${commentType} comments\n` : ''}${hasCRDT ? `- velt-crdt-best-practices — ${crdtEditorType || 'collaborative editing'}\n` : ''}${hasNotifications ? `- velt-notifications-best-practices — notifications\n` : ''}
|
|
1438
|
-
**Secondary: Docs URLs (for features without skills)**
|
|
1439
|
-
${hasPresence ? `- Presence: ${featureImplementations.presence?.mdUrl || getDocMarkdownUrl('presence')}\n` : ''}${hasCursors ? `- Cursors: ${featureImplementations.cursors?.mdUrl || getDocMarkdownUrl('cursors')}\n` : ''}${hasRecorder ? `- Recorder: ${featureImplementations.recorder?.mdUrl || getDocMarkdownUrl('recorder')}\n` : ''}All Velt docs available as markdown at: https://docs.velt.dev/[feature]/[page].md
|
|
1440
|
-
${hasCRDT && crdtImplementation?.data?.markdown ? `\n**CRDT Implementation Details (from ${crdtImplementation.source}):**\n${crdtImplementation.data.markdown.substring(0, 2000)}${crdtImplementation.data.markdown.length > 2000 ? '...\n\n[See full documentation at: ' + crdtImplementation.mdUrl + ']' : ''}\n` : ''}
|
|
1441
|
-
**Tertiary: Velt Docs MCP**
|
|
1442
|
-
After installation, query the Velt Docs MCP server for customization, troubleshooting, and advanced configuration. Do NOT use during initial implementation if skills cover the feature.`,
|
|
446
|
+
content: `**Source priority:** Agent Skills > Embedded Rules > Docs URLs
|
|
447
|
+
|
|
448
|
+
${skillsList.join('\n')}
|
|
449
|
+
|
|
450
|
+
- Do NOT reimplement patterns from scratch — follow the skill rules exactly
|
|
451
|
+
- If a skill rule and this plan conflict, the skill rule is correct
|
|
452
|
+
- Do NOT create files outside \`components/velt/\` unless necessary for app-specific wiring`,
|
|
1443
453
|
},
|
|
1444
454
|
];
|
|
1445
455
|
|
|
1446
|
-
//
|
|
1447
|
-
const
|
|
1448
|
-
|
|
1449
|
-
const basePlan = formatInstallationPlan({
|
|
1450
|
-
title: `Plan for Velt Installation: ${featureList.join(', ')}`,
|
|
456
|
+
// Format and add skills section
|
|
457
|
+
const plan = formatInstallationPlan({
|
|
458
|
+
title: `Plan for Velt ${featureList.join(' + ')} Installation`,
|
|
1451
459
|
steps,
|
|
1452
460
|
additionalInfo,
|
|
1453
461
|
});
|
|
1454
462
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
return basePlan.slice(0, titleEnd) + skillsSection + basePlan.slice(titleEnd);
|
|
463
|
+
const skillsSection = formatSkillsSourceSection(features, { commentType, crdtEditorType });
|
|
464
|
+
return plan + '\n' + skillsSection;
|
|
1458
465
|
}
|
|
1459
466
|
|
|
467
|
+
|
|
1460
468
|
/**
|
|
1461
|
-
* Creates CLI-only installation report
|
|
1462
|
-
*
|
|
1463
|
-
* Used when user types SKIP at feature selection.
|
|
1464
|
-
* Returns a checklist of what was created and what user needs to do.
|
|
1465
|
-
*
|
|
1466
|
-
* @param {Object} options
|
|
1467
|
-
* @param {Object} options.cliResult - Result from Velt CLI execution
|
|
1468
|
-
* @param {Object} options.qaResult - Basic QA validation results
|
|
1469
|
-
* @param {string} options.apiKey - API key (masked)
|
|
1470
|
-
* @param {string} [options.cliMethod] - CLI execution method ('linked' or 'direct')
|
|
1471
|
-
* @param {Object} [options.frameworkInfo] - Framework detection info
|
|
1472
|
-
* @returns {string} Markdown report
|
|
469
|
+
* Creates a CLI-only installation report (SKIP mode).
|
|
1473
470
|
*/
|
|
1474
471
|
export function createCliOnlyReport({ cliResult, qaResult, apiKey, cliMethod, frameworkInfo }) {
|
|
1475
472
|
const validationLines = qaResult.checks.map(c => {
|
|
@@ -1477,12 +474,8 @@ export function createCliOnlyReport({ cliResult, qaResult, apiKey, cliMethod, fr
|
|
|
1477
474
|
return `- ${icon} **${c.name}**: ${c.message}`;
|
|
1478
475
|
}).join('\n');
|
|
1479
476
|
|
|
1480
|
-
|
|
1481
|
-
const cliMethodInfo = cliMethod
|
|
1482
|
-
? '**CLI Method:** npx @velt-js/add-velt'
|
|
1483
|
-
: '';
|
|
477
|
+
const cliMethodInfo = cliMethod ? '**CLI Method:** npx @velt-js/add-velt' : '';
|
|
1484
478
|
|
|
1485
|
-
// Framework info
|
|
1486
479
|
const frameworkInfoSection = frameworkInfo
|
|
1487
480
|
? `**Framework:** ${frameworkInfo.projectType}${frameworkInfo.needsUseClient ? ' (with "use client" enforcement)' : ''}`
|
|
1488
481
|
: '';
|
|
@@ -1520,168 +513,24 @@ ${validationLines}
|
|
|
1520
513
|
|
|
1521
514
|
---
|
|
1522
515
|
|
|
1523
|
-
## 📋
|
|
1524
|
-
|
|
1525
|
-
### 1. Verify @veltdev/react is installed
|
|
1526
|
-
|
|
1527
|
-
The CLI should have installed \`@veltdev/react\`. If not, run:
|
|
1528
|
-
\`\`\`bash
|
|
1529
|
-
npm install @veltdev/react
|
|
1530
|
-
\`\`\`
|
|
1531
|
-
|
|
1532
|
-
### 2. Configure environment variables
|
|
1533
|
-
|
|
1534
|
-
Create or update \`.env.local\`:
|
|
1535
|
-
|
|
1536
|
-
\`\`\`env
|
|
1537
|
-
NEXT_PUBLIC_VELT_API_KEY=${apiKey}
|
|
1538
|
-
\`\`\`
|
|
1539
|
-
|
|
1540
|
-
### 3. Set up app/layout.tsx with AppUserProvider
|
|
1541
|
-
|
|
1542
|
-
Wrap your app with AppUserProvider for user context:
|
|
1543
|
-
|
|
1544
|
-
\`\`\`tsx
|
|
1545
|
-
// app/layout.tsx
|
|
1546
|
-
import { AppUserProvider } from './userAuth/AppUserContext';
|
|
516
|
+
## 📋 Next Steps
|
|
1547
517
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
{children}
|
|
1554
|
-
</AppUserProvider>
|
|
1555
|
-
</body>
|
|
1556
|
-
</html>
|
|
1557
|
-
);
|
|
1558
|
-
}
|
|
1559
|
-
\`\`\`
|
|
1560
|
-
|
|
1561
|
-
### 4. Set up app/page.tsx with VeltProvider
|
|
1562
|
-
|
|
1563
|
-
Add VeltProvider to your root page using the authProvider hook:
|
|
1564
|
-
|
|
1565
|
-
\`\`\`tsx
|
|
1566
|
-
// app/page.tsx
|
|
1567
|
-
"use client";
|
|
1568
|
-
import { VeltProvider } from '@veltdev/react';
|
|
1569
|
-
import { useVeltAuthProvider } from '@/components/velt/VeltInitializeUser';
|
|
1570
|
-
import { useCurrentDocument } from '@/components/velt/VeltInitializeDocument';
|
|
1571
|
-
import { VeltCollaboration } from '@/components/velt/VeltCollaboration';
|
|
1572
|
-
|
|
1573
|
-
const VELT_API_KEY = process.env.NEXT_PUBLIC_VELT_API_KEY!;
|
|
1574
|
-
|
|
1575
|
-
export default function Page() {
|
|
1576
|
-
const { authProvider } = useVeltAuthProvider();
|
|
1577
|
-
const { documentId } = useCurrentDocument();
|
|
518
|
+
**Skill reference:** Install agent-skills via \`npx skills add velt-js/agent-skills\`, then follow:
|
|
519
|
+
- \`velt-setup-best-practices\` — for VeltProvider wiring, auth, document setup
|
|
520
|
+
- \`velt-comments-best-practices\` — for comments integration
|
|
521
|
+
- \`velt-crdt-best-practices\` — for CRDT/collaborative editing
|
|
522
|
+
- \`velt-notifications-best-practices\` — for notifications
|
|
1578
523
|
|
|
1579
|
-
|
|
1580
|
-
<VeltProvider apiKey={VELT_API_KEY} authProvider={authProvider}>
|
|
1581
|
-
<VeltCollaboration documentId={documentId} />
|
|
1582
|
-
{/* Your page content */}
|
|
1583
|
-
</VeltProvider>
|
|
1584
|
-
);
|
|
1585
|
-
}
|
|
1586
|
-
\`\`\`
|
|
1587
|
-
|
|
1588
|
-
### 5. Configure user authentication
|
|
1589
|
-
|
|
1590
|
-
Edit \`app/userAuth/useAppUser.tsx\` to connect your auth:
|
|
1591
|
-
|
|
1592
|
-
\`\`\`tsx
|
|
1593
|
-
// TODO: Replace hardcoded user with your auth provider
|
|
1594
|
-
// Examples:
|
|
1595
|
-
// - Next-Auth: const { data: session } = useSession();
|
|
1596
|
-
// - Clerk: const { user } = useUser();
|
|
1597
|
-
// - Auth0: const { user } = useAuth0();
|
|
1598
|
-
|
|
1599
|
-
const user = {
|
|
1600
|
-
userId: "your-user-id", // Required: unique user ID
|
|
1601
|
-
name: "User Name", // Required: display name
|
|
1602
|
-
email: "user@example.com", // Required: email
|
|
1603
|
-
photoUrl: "https://...", // Optional: avatar URL
|
|
1604
|
-
organizationId: "your-org", // Optional: for multi-tenant apps
|
|
1605
|
-
};
|
|
1606
|
-
\`\`\`
|
|
1607
|
-
|
|
1608
|
-
### 6. Configure document identification
|
|
1609
|
-
|
|
1610
|
-
Edit \`components/velt/VeltInitializeDocument.tsx\` to set document ID in the useCurrentDocument hook:
|
|
1611
|
-
|
|
1612
|
-
\`\`\`tsx
|
|
1613
|
-
// TODO: Replace with your document ID logic
|
|
1614
|
-
// The document ID determines which users see each other's comments/cursors
|
|
1615
|
-
// Examples:
|
|
1616
|
-
// - Page-based: const documentId = pathname;
|
|
1617
|
-
// - Route param: const documentId = params.id;
|
|
1618
|
-
// - Custom: const documentId = getCurrentProjectId();
|
|
1619
|
-
|
|
1620
|
-
const documentId = "your-document-id";
|
|
1621
|
-
\`\`\`
|
|
1622
|
-
|
|
1623
|
-
### 6. Add Velt feature components
|
|
1624
|
-
|
|
1625
|
-
Add specific features where needed in your app:
|
|
1626
|
-
|
|
1627
|
-
\`\`\`tsx
|
|
1628
|
-
import { VeltComments, VeltPresence, VeltCursor } from '@veltdev/react';
|
|
1629
|
-
|
|
1630
|
-
// Comments - add where you want commenting
|
|
1631
|
-
<VeltComments />
|
|
1632
|
-
|
|
1633
|
-
// Presence - shows online users
|
|
1634
|
-
<VeltPresence />
|
|
1635
|
-
|
|
1636
|
-
// Cursors - shows live cursor positions
|
|
1637
|
-
<VeltCursor />
|
|
1638
|
-
\`\`\`
|
|
1639
|
-
|
|
1640
|
-
---
|
|
1641
|
-
|
|
1642
|
-
## 🔗 Documentation
|
|
1643
|
-
|
|
1644
|
-
- **Quick Start:** https://docs.velt.dev/get-started/quickstart
|
|
1645
|
-
- **Authentication:** https://docs.velt.dev/get-started/quickstart#step-5-authenticate-users
|
|
1646
|
-
- **Document Setup:** https://docs.velt.dev/get-started/quickstart#step-6-initialize-document
|
|
1647
|
-
- **Comments:** https://docs.velt.dev/async-collaboration/comments/setup
|
|
1648
|
-
- **Presence:** https://docs.velt.dev/realtime-collaboration/presence/setup
|
|
1649
|
-
- **Cursors:** https://docs.velt.dev/realtime-collaboration/cursors/setup
|
|
1650
|
-
|
|
1651
|
-
---
|
|
1652
|
-
|
|
1653
|
-
## 🚀 Next Steps
|
|
1654
|
-
|
|
1655
|
-
**Option A: Manual Setup**
|
|
1656
|
-
Follow the TODO checklist above to wire up Velt features yourself.
|
|
1657
|
-
|
|
1658
|
-
**Option B: Guided Setup**
|
|
1659
|
-
Re-run the installer and select specific features (don't type SKIP):
|
|
1660
|
-
\`\`\`
|
|
1661
|
-
@velt-installer install
|
|
1662
|
-
\`\`\`
|
|
1663
|
-
|
|
1664
|
-
The guided mode will:
|
|
1665
|
-
- Generate a detailed implementation plan for your selected features
|
|
1666
|
-
- Detect your project structure and recommend file placements
|
|
1667
|
-
- Provide feature-specific code examples from Velt docs
|
|
1668
|
-
- Apply changes only after your approval
|
|
524
|
+
Or re-run the installer with specific features (don't type SKIP) for guided setup.
|
|
1669
525
|
|
|
1670
526
|
---
|
|
1671
527
|
|
|
1672
528
|
## ⚠️ Common Issues
|
|
1673
529
|
|
|
1674
|
-
|
|
1675
|
-
-
|
|
1676
|
-
-
|
|
1677
|
-
|
|
1678
|
-
**"Please set document id to continue"**
|
|
1679
|
-
- Ensure \`VeltInitializeDocument\` is properly configured with a document ID
|
|
1680
|
-
- The document ID should be unique per collaborative context
|
|
1681
|
-
|
|
1682
|
-
**"Failed to authenticate user"**
|
|
1683
|
-
- Check that your user data includes required fields: \`userId\`, \`name\`, \`email\`
|
|
1684
|
-
- Verify the auth token is correct in your environment variables
|
|
530
|
+
Consult \`velt-setup-best-practices\` → debug-common-issues rule for:
|
|
531
|
+
- "Velt API key not found" — check .env.local
|
|
532
|
+
- "Please set document id" — check VeltInitializeDocument
|
|
533
|
+
- "Failed to authenticate user" — check user object fields
|
|
1685
534
|
|
|
1686
535
|
---
|
|
1687
536
|
|