@velt-js/mcp-installer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +373 -0
- package/bin/mcp-server.js +22 -0
- package/package.json +42 -0
- package/src/index.js +754 -0
- package/src/tools/orchestrator.js +299 -0
- package/src/tools/unified-installer.js +886 -0
- package/src/utils/cli.js +380 -0
- package/src/utils/comment-detector.js +305 -0
- package/src/utils/config.js +149 -0
- package/src/utils/framework-detection.js +262 -0
- package/src/utils/header-positioning.js +146 -0
- package/src/utils/host-app-discovery.js +1000 -0
- package/src/utils/integration.js +803 -0
- package/src/utils/plan-formatter.js +1698 -0
- package/src/utils/screenshot.js +151 -0
- package/src/utils/use-client.js +366 -0
- package/src/utils/validation.js +556 -0
- package/src/utils/velt-docs-fetcher.js +288 -0
- package/src/utils/velt-docs-urls.js +140 -0
- package/src/utils/velt-mcp-client.js +202 -0
- package/src/utils/velt-mcp.js +718 -0
|
@@ -0,0 +1,1698 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan Formatter
|
|
3
|
+
*
|
|
4
|
+
* Formats installation instructions as a sequential plan (similar to Cursor's plan mode)
|
|
5
|
+
* that the AI can follow to complete the implementation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getDocUrl, getDocMarkdownUrl } from './velt-docs-urls.js';
|
|
9
|
+
import { getSkillReferences } from './velt-docs-fetcher.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 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
|
+
*/
|
|
19
|
+
function formatSkillsSourceSection(features, options = {}) {
|
|
20
|
+
const refs = getSkillReferences(features, options);
|
|
21
|
+
|
|
22
|
+
let section = `## Implementation Sources (Priority Order)\n\n`;
|
|
23
|
+
section += `> **PREREQUISITE:** Install Velt Agent Skills via \`npx skills add velt-js/agent-skills\`\n>\n`;
|
|
24
|
+
section += `> **Source Priority:** 1) Agent Skills (primary) → 2) Docs URLs (secondary) → 3) Velt Docs MCP (user follow-up only)\n\n`;
|
|
25
|
+
|
|
26
|
+
const skillRefs = refs.filter(r => r.skillName);
|
|
27
|
+
const docsRefs = refs.filter(r => r.docsUrl);
|
|
28
|
+
|
|
29
|
+
if (skillRefs.length > 0) {
|
|
30
|
+
section += `### Primary: Agent Skills (use these first)\n\n`;
|
|
31
|
+
// Deduplicate skill names
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
for (const ref of skillRefs) {
|
|
34
|
+
if (!seen.has(ref.skillName)) {
|
|
35
|
+
seen.add(ref.skillName);
|
|
36
|
+
const features = skillRefs.filter(r => r.skillName === ref.skillName).map(r => r.feature);
|
|
37
|
+
section += `- **${ref.skillName}** — covers: ${features.join(', ')}${ref.description ? ` (${ref.description})` : ''}\n`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
section += `\n`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (docsRefs.length > 0) {
|
|
44
|
+
section += `### Secondary: Docs URLs (for features without skills)\n\n`;
|
|
45
|
+
for (const ref of docsRefs) {
|
|
46
|
+
section += `- ${ref.feature}: ${ref.docsUrl}\n`;
|
|
47
|
+
}
|
|
48
|
+
section += `\n`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
section += `### Tertiary: Velt Docs MCP\n`;
|
|
52
|
+
section += `- Only use for user follow-up questions AFTER implementation\n`;
|
|
53
|
+
section += `- Do NOT query Velt Docs MCP during initial implementation if skills cover the feature\n\n`;
|
|
54
|
+
|
|
55
|
+
return section;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
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
|
|
69
|
+
*/
|
|
70
|
+
export function formatInstallationPlan(options) {
|
|
71
|
+
const { title, steps, additionalInfo = [] } = options;
|
|
72
|
+
|
|
73
|
+
let plan = `# ${title}\n\n`;
|
|
74
|
+
|
|
75
|
+
// Add numbered steps with details
|
|
76
|
+
steps.forEach((step, index) => {
|
|
77
|
+
const stepNumber = index + 1;
|
|
78
|
+
plan += `## ${stepNumber}. ${step.title}\n`;
|
|
79
|
+
plan += `* **Details:** ${step.details}\n`;
|
|
80
|
+
|
|
81
|
+
// Add code examples if provided
|
|
82
|
+
if (step.codeExamples && step.codeExamples.length > 0) {
|
|
83
|
+
step.codeExamples.forEach((example) => {
|
|
84
|
+
plan += `\n${example.description ? `* ${example.description}:` : ''}
|
|
85
|
+
\`\`\`${example.language || 'tsx'}
|
|
86
|
+
${example.code}
|
|
87
|
+
\`\`\`\n`;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
plan += '\n';
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Add additional information sections
|
|
95
|
+
if (additionalInfo.length > 0) {
|
|
96
|
+
additionalInfo.forEach((info) => {
|
|
97
|
+
plan += `## ${info.title}\n`;
|
|
98
|
+
plan += `${info.content}\n\n`;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Add To-Do checklist
|
|
103
|
+
plan += `## To-Do List\n`;
|
|
104
|
+
steps.forEach((step, index) => {
|
|
105
|
+
const checkbox = index === 0 ? '[✓]' : '[ ]';
|
|
106
|
+
plan += `- ${checkbox} ${step.title}\n`;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
plan += '\n';
|
|
110
|
+
|
|
111
|
+
return plan;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Creates a plan for Velt Comments installation
|
|
116
|
+
*
|
|
117
|
+
* @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
|
+
* @returns {string} Formatted installation plan
|
|
126
|
+
*/
|
|
127
|
+
export function createVeltCommentsPlan(options) {
|
|
128
|
+
const {
|
|
129
|
+
commentType,
|
|
130
|
+
implementation,
|
|
131
|
+
detectedFiles = [],
|
|
132
|
+
apiKey,
|
|
133
|
+
headerPosition,
|
|
134
|
+
veltProviderLocation = 'app/page.tsx',
|
|
135
|
+
crdtEditorType,
|
|
136
|
+
} = options;
|
|
137
|
+
|
|
138
|
+
const commentTypeTitle = commentType.charAt(0).toUpperCase() + commentType.slice(1);
|
|
139
|
+
|
|
140
|
+
const steps = [];
|
|
141
|
+
|
|
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
|
+
const locationText = veltProviderLocation === 'auto-detect'
|
|
150
|
+
? 'the appropriate layout file (analyze the project structure to determine the best location)'
|
|
151
|
+
: veltProviderLocation;
|
|
152
|
+
|
|
153
|
+
steps.push({
|
|
154
|
+
title: `Import and use CLI-generated Velt components in ${locationText}`,
|
|
155
|
+
details: `The Velt CLI has generated the necessary component files in \`components/velt/\`. DO NOT create new files. Use the existing files:
|
|
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
|
+
],
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Step 3: Add TODO comments to CLI-generated auth files
|
|
282
|
+
steps.push({
|
|
283
|
+
title: `Add TODO comments to CLI-generated authentication files`,
|
|
284
|
+
details: `The Velt CLI has generated authentication files in specific locations. Add TODO comments to these existing files (DO NOT create new files):
|
|
285
|
+
|
|
286
|
+
**1. User Hook: \`app/userAuth/useAppUser.tsx\`**
|
|
287
|
+
- This file already exists from CLI
|
|
288
|
+
- Add TODO: Connect to your existing authentication system
|
|
289
|
+
- Add TODO: Replace mock user data with actual user from your auth provider
|
|
290
|
+
- Examples: Next-auth useSession(), Clerk useUser(), Auth0, etc.
|
|
291
|
+
|
|
292
|
+
**2. Auth Provider: \`components/velt/VeltInitializeUser.tsx\`**
|
|
293
|
+
- This file already exists from CLI (references useAppUser)
|
|
294
|
+
- Contains \`useVeltAuthProvider\` hook
|
|
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
|
+
],
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Step 4: Implement JWT token generation (Production Pattern)
|
|
399
|
+
steps.push({
|
|
400
|
+
title: `Implement JWT token generation via Velt API (Production Pattern)`,
|
|
401
|
+
details: `The CLI generates a placeholder JWT route. Replace it with the production pattern that calls Velt's token API.
|
|
402
|
+
|
|
403
|
+
**Production Pattern (FireHydrant Reference):**
|
|
404
|
+
Server-side API calls Velt's token endpoint to generate secure JWT tokens.
|
|
405
|
+
|
|
406
|
+
**Velt Token API:**
|
|
407
|
+
\`\`\`
|
|
408
|
+
POST https://api.velt.dev/v2/auth/token/get
|
|
409
|
+
Headers:
|
|
410
|
+
x-velt-api-key: YOUR_VELT_PUBLIC_API_KEY
|
|
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
|
+
});
|
|
471
|
+
|
|
472
|
+
if (!response.ok) {
|
|
473
|
+
console.error('[Velt] Token API error:', await response.text());
|
|
474
|
+
return NextResponse.json({ error: 'Failed to generate token' }, { status: 500 });
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const json = await response.json();
|
|
478
|
+
const token = json?.result?.data?.token;
|
|
479
|
+
if (!token) {
|
|
480
|
+
return NextResponse.json({ error: 'Invalid token response' }, { status: 500 });
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return NextResponse.json({ token });
|
|
484
|
+
} catch (error) {
|
|
485
|
+
console.error('[Velt] Token generation error:', error);
|
|
486
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
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
|
+
],
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Step 5: Replace API key placeholders
|
|
529
|
+
steps.push({
|
|
530
|
+
title: `Replace API key with actual value`,
|
|
531
|
+
details: `Update all instances of "YOUR_VELT_API_KEY" and "YOUR_VELT_AUTH_TOKEN" with your actual values: ${apiKey}. Make sure to replace in VeltProvider configuration.`,
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// Step 6: Test the installation
|
|
535
|
+
steps.push({
|
|
536
|
+
title: `Test the ${commentTypeTitle} comments functionality`,
|
|
537
|
+
details: `Start your development server and test ONLY the ${commentType} comments feature. ${getTestInstructions(commentType)} DO NOT test or implement other features.`,
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// Step 7: Check dev console for errors
|
|
541
|
+
steps.push({
|
|
542
|
+
title: `Check browser console for Velt errors/warnings`,
|
|
543
|
+
details: `Open browser DevTools Console (Press F12 or Cmd+Option+I on Mac) and look for any Velt errors or warnings. Common errors include: "Please set document id to continue", "Velt API key not found", "Failed to authenticate user". If you find any errors, use the Velt Docs MCP to query for solutions. Example query: "How do I fix 'Please set document id to continue' error in Velt?"`,
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// Additional information
|
|
547
|
+
const additionalInfo = [
|
|
548
|
+
{
|
|
549
|
+
title: '🚨 CRITICAL IMPLEMENTATION RULES',
|
|
550
|
+
content: `**WHAT TO USE FROM CLI:**
|
|
551
|
+
- ✅ Authentication setup (user identification)
|
|
552
|
+
- ✅ Document setup (document context)
|
|
553
|
+
- ✅ API key configuration
|
|
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.`,
|
|
632
|
+
},
|
|
633
|
+
];
|
|
634
|
+
|
|
635
|
+
// Generate skills source section
|
|
636
|
+
const skillsSection = formatSkillsSourceSection(['comments'], { commentType });
|
|
637
|
+
|
|
638
|
+
const basePlan = formatInstallationPlan({
|
|
639
|
+
title: `Plan for Velt ${commentTypeTitle} Comments Installation`,
|
|
640
|
+
steps,
|
|
641
|
+
additionalInfo,
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// Insert skills section right after the title line
|
|
645
|
+
const titleEnd = basePlan.indexOf('\n\n') + 2;
|
|
646
|
+
return basePlan.slice(0, titleEnd) + skillsSection + basePlan.slice(titleEnd);
|
|
647
|
+
}
|
|
648
|
+
|
|
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
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Gets test instructions for a comment type
|
|
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
|
|
682
|
+
*
|
|
683
|
+
* @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
|
+
* @returns {string} Formatted installation plan
|
|
695
|
+
*/
|
|
696
|
+
export function createMultiFeaturePlan(options) {
|
|
697
|
+
const {
|
|
698
|
+
features = [],
|
|
699
|
+
commentType,
|
|
700
|
+
crdtEditorType,
|
|
701
|
+
implementation,
|
|
702
|
+
crdtImplementation,
|
|
703
|
+
featureImplementations = {},
|
|
704
|
+
detectedFiles = [],
|
|
705
|
+
apiKey,
|
|
706
|
+
headerPosition,
|
|
707
|
+
veltProviderLocation = 'app/page.tsx',
|
|
708
|
+
} = options;
|
|
709
|
+
|
|
710
|
+
const steps = [];
|
|
711
|
+
const hasComments = features.includes('comments');
|
|
712
|
+
const hasPresence = features.includes('presence');
|
|
713
|
+
const hasCursors = features.includes('cursors');
|
|
714
|
+
const hasNotifications = features.includes('notifications');
|
|
715
|
+
const hasRecorder = features.includes('recorder');
|
|
716
|
+
const hasCRDT = features.includes('crdt');
|
|
717
|
+
|
|
718
|
+
const featureList = [];
|
|
719
|
+
if (hasComments) featureList.push(`${commentType.charAt(0).toUpperCase() + commentType.slice(1)} Comments`);
|
|
720
|
+
if (hasPresence) featureList.push('Presence');
|
|
721
|
+
if (hasCursors) featureList.push('Cursors');
|
|
722
|
+
if (hasNotifications) featureList.push('Notifications');
|
|
723
|
+
if (hasRecorder) featureList.push('Recorder');
|
|
724
|
+
if (hasCRDT) featureList.push(`CRDT (${crdtEditorType ? crdtEditorType.charAt(0).toUpperCase() + crdtEditorType.slice(1) : 'Collaborative Editing'})`);
|
|
725
|
+
|
|
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
|
+
const locationText = veltProviderLocation === 'auto-detect'
|
|
741
|
+
? 'the appropriate layout file (analyze the project structure to determine the best location)'
|
|
742
|
+
: veltProviderLocation;
|
|
743
|
+
|
|
744
|
+
steps.push({
|
|
745
|
+
title: `Import and use CLI-generated Velt components in ${locationText}`,
|
|
746
|
+
details: `The Velt CLI has generated the necessary component files in \`components/velt/\`. DO NOT create new files. Use the existing files:
|
|
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]);
|
|
976
|
+
|
|
977
|
+
// [Velt] Initialize CRDT extension with unique editorId
|
|
978
|
+
const { VeltCrdt, isLoading } = useVeltTiptapCrdtExtension({
|
|
979
|
+
editorId,
|
|
980
|
+
initialContent: veltInitialContent,
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
// [Velt] Monitor server connection state
|
|
984
|
+
const serverConnectionState = useServerConnectionStateChangeHandler();
|
|
985
|
+
|
|
986
|
+
const editor = useEditor({
|
|
987
|
+
extensions: [
|
|
988
|
+
StarterKit.configure({
|
|
989
|
+
undoRedo: false, // CRITICAL: CRDT handles undo/redo
|
|
990
|
+
}),
|
|
991
|
+
...(VeltCrdt ? [VeltCrdt] : []),
|
|
992
|
+
],
|
|
993
|
+
// content: initialContent, // CRITICAL: comment out - CRDT manages content
|
|
994
|
+
immediatelyRender: false,
|
|
995
|
+
onUpdate: ({ editor, transaction }) => {
|
|
996
|
+
// [Velt] CRITICAL: Detect remote syncs - skip saving these
|
|
997
|
+
const isRemoteSync =
|
|
998
|
+
transaction.getMeta('y-sync$') ||
|
|
999
|
+
transaction.getMeta('remote') ||
|
|
1000
|
+
transaction.getMeta('velt-sync') ||
|
|
1001
|
+
transaction.getMeta('isRemote');
|
|
1002
|
+
if (isRemoteSync) return;
|
|
1003
|
+
|
|
1004
|
+
// [Velt] Debounced auto-save (2 seconds)
|
|
1005
|
+
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current);
|
|
1006
|
+
if (transaction.docChanged && isEditorReadyRef.current) {
|
|
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;
|
|
1033
|
+
}
|
|
1034
|
+
}, [editor, isLoading, serverConnectionState, backendfallbackContent]);
|
|
1035
|
+
|
|
1036
|
+
if (isLoading) return <div>Loading...</div>;
|
|
1037
|
+
return <EditorContent editor={editor} />;
|
|
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
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Get component implementations from markdown docs above`,
|
|
1133
|
+
},
|
|
1134
|
+
],
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
// Step 3: Set up authentication and user identification with TODOs
|
|
1138
|
+
steps.push({
|
|
1139
|
+
title: `Add TODO comments to CLI-generated authentication files`,
|
|
1140
|
+
details: `The Velt CLI has generated authentication files in specific locations. Add TODO comments to these existing files (DO NOT create new files):
|
|
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
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// In useAppUser.tsx - Multiple users for testing:
|
|
1185
|
+
export function useAppUser() {
|
|
1186
|
+
// [Velt] HARDCODED USERS for testing presence/cursors
|
|
1187
|
+
// TODO: Replace with actual user from your auth provider
|
|
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
|
+
};
|
|
1211
|
+
|
|
1212
|
+
const user = users[userParam] || users['1'];
|
|
1213
|
+
|
|
1214
|
+
return { user, isUserLoggedIn: true };
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// TESTING INSTRUCTIONS:
|
|
1218
|
+
// 1. Open http://localhost:3000?user=1 in one tab
|
|
1219
|
+
// 2. Open http://localhost:3000?user=2 in another tab
|
|
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
|
+
});
|
|
1253
|
+
|
|
1254
|
+
// Step 4: Implement JWT token generation (Production Pattern)
|
|
1255
|
+
steps.push({
|
|
1256
|
+
title: `Implement JWT token generation via Velt API (Production Pattern)`,
|
|
1257
|
+
details: `The CLI generates a placeholder JWT route. Replace it with the production pattern that calls Velt's token API.
|
|
1258
|
+
|
|
1259
|
+
**Production Pattern (FireHydrant Reference):**
|
|
1260
|
+
Server-side API calls Velt's token endpoint to generate secure JWT tokens.
|
|
1261
|
+
|
|
1262
|
+
**Velt Token API:**
|
|
1263
|
+
\`\`\`
|
|
1264
|
+
POST https://api.velt.dev/v2/auth/token/get
|
|
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
|
+
\`\`\`
|
|
1273
|
+
|
|
1274
|
+
**Environment Variables to Set:**
|
|
1275
|
+
\`\`\`
|
|
1276
|
+
VELT_PUBLIC_API_KEY=your_api_key_here
|
|
1277
|
+
VELT_AUTH_TOKEN=your_auth_token_here # NEVER expose to client!
|
|
1278
|
+
\`\`\`
|
|
1279
|
+
|
|
1280
|
+
**Security Requirements:**
|
|
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
|
+
}
|
|
1306
|
+
|
|
1307
|
+
if (!VELT_API_KEY || !VELT_AUTH_TOKEN) {
|
|
1308
|
+
console.error('[Velt] Missing VELT_PUBLIC_API_KEY or VELT_AUTH_TOKEN');
|
|
1309
|
+
return NextResponse.json({ error: 'Velt credentials not configured' }, { status: 500 });
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// [Velt] Call Velt Token API
|
|
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
|
+
}),
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
if (!response.ok) {
|
|
1329
|
+
console.error('[Velt] Token API error:', await response.text());
|
|
1330
|
+
return NextResponse.json({ error: 'Failed to generate token' }, { status: 500 });
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const json = await response.json();
|
|
1334
|
+
const token = json?.result?.data?.token;
|
|
1335
|
+
if (!token) {
|
|
1336
|
+
return NextResponse.json({ error: 'Invalid token response' }, { status: 500 });
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
return NextResponse.json({ token });
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
console.error('[Velt] Token generation error:', error);
|
|
1342
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
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
|
+
],
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
// Step 5: Replace API key placeholders
|
|
1385
|
+
steps.push({
|
|
1386
|
+
title: `Replace API keys with actual values`,
|
|
1387
|
+
details: `Update all instances of "YOUR_VELT_API_KEY" and "YOUR_VELT_AUTH_TOKEN" with your actual values: ${apiKey}. Make sure to replace in VeltProvider configuration.`,
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
// Step 6: Test the installation
|
|
1391
|
+
const testInstructions = [];
|
|
1392
|
+
if (hasComments) testInstructions.push(`${commentType} comments: ${getTestInstructions(commentType)}`);
|
|
1393
|
+
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 move your mouse to see cursors');
|
|
1395
|
+
if (hasNotifications) testInstructions.push('Notifications: Check the notification bell icon appears');
|
|
1396
|
+
if (hasRecorder) testInstructions.push('Recorder: Check the recorder controls appear');
|
|
1397
|
+
|
|
1398
|
+
steps.push({
|
|
1399
|
+
title: `Test all requested features`,
|
|
1400
|
+
details: `Start your development server and test ONLY the features you requested:\n${testInstructions.map(t => `- ${t}`).join('\n')}\n\nDO NOT test or implement other features.`,
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
// Step 7: Check dev console for errors
|
|
1404
|
+
steps.push({
|
|
1405
|
+
title: `Check browser console for Velt errors/warnings`,
|
|
1406
|
+
details: `Open browser DevTools Console (Press F12 or Cmd+Option+I on Mac) and look for any Velt errors or warnings. Common errors include: "Please set document id to continue", "Velt API key not found", "Failed to authenticate user". If you find any errors, use the Velt Docs MCP to query for solutions.`,
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
// Additional information
|
|
1410
|
+
const additionalInfo = [
|
|
1411
|
+
{
|
|
1412
|
+
title: '🚨 CRITICAL IMPLEMENTATION RULES',
|
|
1413
|
+
content: `**WHAT TO USE FROM CLI:**
|
|
1414
|
+
- ✅ Authentication setup (user identification)
|
|
1415
|
+
- ✅ Document setup (document context)
|
|
1416
|
+
- ✅ API key configuration
|
|
1417
|
+
|
|
1418
|
+
**PRIMARY SOURCE: Agent Skills (installed via \`npx skills add velt-js/agent-skills\`):**
|
|
1419
|
+
- ✅ **velt-setup-best-practices** — VeltProvider setup, auth, document identity, project structure
|
|
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.`,
|
|
1443
|
+
},
|
|
1444
|
+
];
|
|
1445
|
+
|
|
1446
|
+
// Generate skills source section
|
|
1447
|
+
const skillsSection = formatSkillsSourceSection(features, { commentType, crdtEditorType });
|
|
1448
|
+
|
|
1449
|
+
const basePlan = formatInstallationPlan({
|
|
1450
|
+
title: `Plan for Velt Installation: ${featureList.join(', ')}`,
|
|
1451
|
+
steps,
|
|
1452
|
+
additionalInfo,
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
// Insert skills section right after the title line
|
|
1456
|
+
const titleEnd = basePlan.indexOf('\n\n') + 2;
|
|
1457
|
+
return basePlan.slice(0, titleEnd) + skillsSection + basePlan.slice(titleEnd);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Creates CLI-only installation report with TODO checklist
|
|
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
|
|
1473
|
+
*/
|
|
1474
|
+
export function createCliOnlyReport({ cliResult, qaResult, apiKey, cliMethod, frameworkInfo }) {
|
|
1475
|
+
const validationLines = qaResult.checks.map(c => {
|
|
1476
|
+
const icon = c.status === 'pass' ? '✅' : c.status === 'warning' ? '⚠️' : '❌';
|
|
1477
|
+
return `- ${icon} **${c.name}**: ${c.message}`;
|
|
1478
|
+
}).join('\n');
|
|
1479
|
+
|
|
1480
|
+
// CLI execution method info
|
|
1481
|
+
const cliMethodInfo = cliMethod
|
|
1482
|
+
? '**CLI Method:** npx @velt-js/add-velt'
|
|
1483
|
+
: '';
|
|
1484
|
+
|
|
1485
|
+
// Framework info
|
|
1486
|
+
const frameworkInfoSection = frameworkInfo
|
|
1487
|
+
? `**Framework:** ${frameworkInfo.projectType}${frameworkInfo.needsUseClient ? ' (with "use client" enforcement)' : ''}`
|
|
1488
|
+
: '';
|
|
1489
|
+
|
|
1490
|
+
return `# ✅ Velt CLI Installation Complete (CLI-Only Mode)
|
|
1491
|
+
|
|
1492
|
+
${cliMethodInfo}
|
|
1493
|
+
${frameworkInfoSection}
|
|
1494
|
+
|
|
1495
|
+
You chose **SKIP** - the Velt CLI scaffolding has been run without feature integration.
|
|
1496
|
+
You can now wire up the features yourself, or re-run the installer without SKIP for guided setup.
|
|
1497
|
+
|
|
1498
|
+
---
|
|
1499
|
+
|
|
1500
|
+
## Files Created by Velt CLI
|
|
1501
|
+
|
|
1502
|
+
\`\`\`
|
|
1503
|
+
components/velt/
|
|
1504
|
+
├── VeltCollaboration.tsx # Velt feature components (comments, presence, etc.)
|
|
1505
|
+
├── VeltInitializeDocument.tsx # Exports useCurrentDocument hook
|
|
1506
|
+
└── VeltInitializeUser.tsx # Exports useVeltAuthProvider hook
|
|
1507
|
+
|
|
1508
|
+
app/userAuth/
|
|
1509
|
+
├── AppUserContext.tsx # User context provider wrapper
|
|
1510
|
+
└── useAppUser.tsx # User data hook
|
|
1511
|
+
\`\`\`
|
|
1512
|
+
|
|
1513
|
+
---
|
|
1514
|
+
|
|
1515
|
+
## Validation Results
|
|
1516
|
+
|
|
1517
|
+
${validationLines}
|
|
1518
|
+
|
|
1519
|
+
**Score:** ${qaResult.score} | **Status:** ${qaResult.status}
|
|
1520
|
+
|
|
1521
|
+
---
|
|
1522
|
+
|
|
1523
|
+
## 📋 TODO Checklist (You Need To Complete)
|
|
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';
|
|
1547
|
+
|
|
1548
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
1549
|
+
return (
|
|
1550
|
+
<html lang="en">
|
|
1551
|
+
<body>
|
|
1552
|
+
<AppUserProvider>
|
|
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();
|
|
1578
|
+
|
|
1579
|
+
return (
|
|
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
|
|
1669
|
+
|
|
1670
|
+
---
|
|
1671
|
+
|
|
1672
|
+
## ⚠️ Common Issues
|
|
1673
|
+
|
|
1674
|
+
**"Velt API key not found"**
|
|
1675
|
+
- Make sure \`NEXT_PUBLIC_VELT_API_KEY\` is in your \`.env.local\`
|
|
1676
|
+
- Restart your dev server after adding environment variables
|
|
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
|
|
1685
|
+
|
|
1686
|
+
---
|
|
1687
|
+
|
|
1688
|
+
*Generated by Velt MCP Installer (CLI-Only Mode)*
|
|
1689
|
+
`;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
export default {
|
|
1693
|
+
formatInstallationPlan,
|
|
1694
|
+
formatSkillsSourceSection,
|
|
1695
|
+
createVeltCommentsPlan,
|
|
1696
|
+
createMultiFeaturePlan,
|
|
1697
|
+
createCliOnlyReport,
|
|
1698
|
+
};
|