@goscribe/server 1.1.2 → 1.1.4

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.
Files changed (36) hide show
  1. package/dist/lib/ai-session.d.ts +13 -3
  2. package/dist/lib/ai-session.js +66 -146
  3. package/dist/lib/pusher.js +1 -1
  4. package/dist/routers/_app.d.ts +114 -7
  5. package/dist/routers/chat.js +2 -23
  6. package/dist/routers/flashcards.d.ts +25 -1
  7. package/dist/routers/flashcards.js +0 -14
  8. package/dist/routers/members.d.ts +18 -0
  9. package/dist/routers/members.js +14 -1
  10. package/dist/routers/worksheets.js +5 -4
  11. package/dist/routers/workspace.d.ts +89 -6
  12. package/dist/routers/workspace.js +389 -259
  13. package/dist/services/flashcard-progress.service.d.ts +25 -1
  14. package/dist/services/flashcard-progress.service.js +70 -31
  15. package/package.json +3 -2
  16. package/prisma/schema.prisma +196 -184
  17. package/src/lib/ai-session.ts +3 -21
  18. package/src/routers/auth.ts +50 -2
  19. package/src/routers/flashcards.ts +0 -16
  20. package/src/routers/members.ts +27 -6
  21. package/src/routers/workspace.ts +468 -439
  22. package/src/server.ts +13 -0
  23. package/ANALYSIS_PROGRESS_SPEC.md +0 -463
  24. package/PROGRESS_QUICK_REFERENCE.md +0 -239
  25. package/dist/lib/podcast-prompts.d.ts +0 -43
  26. package/dist/lib/podcast-prompts.js +0 -135
  27. package/dist/routers/ai-session.d.ts +0 -0
  28. package/dist/routers/ai-session.js +0 -1
  29. package/dist/services/flashcard.service.d.ts +0 -183
  30. package/dist/services/flashcard.service.js +0 -224
  31. package/dist/services/podcast-segment-reorder.d.ts +0 -0
  32. package/dist/services/podcast-segment-reorder.js +0 -107
  33. package/dist/services/podcast.service.d.ts +0 -0
  34. package/dist/services/podcast.service.js +0 -326
  35. package/dist/services/worksheet.service.d.ts +0 -0
  36. package/dist/services/worksheet.service.js +0 -295
package/src/server.ts CHANGED
@@ -10,6 +10,7 @@ import { appRouter } from './routers/_app.js';
10
10
  import { createContext } from './context.js';
11
11
  import { prisma } from './lib/prisma.js';
12
12
  import { logger } from './lib/logger.js';
13
+ import { supabaseClient } from './lib/storage.js';
13
14
 
14
15
  const PORT = process.env.PORT ? Number(process.env.PORT) : 3001;
15
16
 
@@ -45,6 +46,18 @@ async function main() {
45
46
  res.json({ ok: true, service: 'trpc-express', ts: Date.now() });
46
47
  });
47
48
 
49
+ app.get('/profile-picture/:objectKey', async (req, res) => {
50
+ const { objectKey } = req.params;
51
+ const signedUrl = await supabaseClient.storage
52
+ .from('media')
53
+ .createSignedUrl(objectKey, 60 * 60 * 24 * 30);
54
+ if (signedUrl.error) {
55
+ return res.status(500).json({ error: 'Failed to generate signed URL' });
56
+ }
57
+ // res.json({ url: signedUrl.data.signedUrl });
58
+ res.redirect(signedUrl.data.signedUrl);
59
+ });
60
+
48
61
  // tRPC mounted under /trpc
49
62
  app.use(
50
63
  '/trpc',
@@ -1,463 +0,0 @@
1
- # Analysis Progress Integration Spec
2
-
3
- ## Overview
4
- The analysis progress system tracks file upload and analysis status in real-time using both database storage and Pusher events.
5
-
6
- ---
7
-
8
- ## 1. Data Structure
9
-
10
- ### `Workspace.analysisProgress` (JSON field)
11
-
12
- ```typescript
13
- interface AnalysisProgress {
14
- status: AnalysisStatus;
15
- filename: string;
16
- fileType: 'image' | 'pdf';
17
- startedAt: string; // ISO 8601 timestamp
18
- completedAt?: string; // ISO 8601 timestamp (only when completed)
19
- error?: string; // Error message (only when status is 'error')
20
- steps: {
21
- fileUpload: StepStatus;
22
- fileAnalysis: StepStatus;
23
- studyGuide: StepStatus;
24
- flashcards: StepStatus;
25
- };
26
- }
27
-
28
- type AnalysisStatus =
29
- | 'starting'
30
- | 'uploading'
31
- | 'analyzing'
32
- | 'generating_artifacts'
33
- | 'generating_study_guide'
34
- | 'generating_flashcards'
35
- | 'completed'
36
- | 'error';
37
-
38
- type StepStatus =
39
- | 'pending'
40
- | 'in_progress'
41
- | 'completed'
42
- | 'skipped'
43
- | 'error';
44
- ```
45
-
46
- ---
47
-
48
- ## 2. Status Flow
49
-
50
- ### Normal Flow (All artifacts enabled)
51
- ```
52
- starting
53
- → uploading
54
- → analyzing
55
- → generating_artifacts
56
- → generating_study_guide
57
- → generating_flashcards
58
- → completed
59
- ```
60
-
61
- ### Error Flow
62
- ```
63
- Any status → error (with error message)
64
- ```
65
-
66
- ### Skipped Artifacts
67
- If user doesn't select certain artifacts, their steps will be marked as `'skipped'`:
68
-
69
- ```typescript
70
- // Example: Only flashcards enabled
71
- {
72
- status: 'generating_flashcards',
73
- steps: {
74
- fileUpload: 'completed',
75
- fileAnalysis: 'completed',
76
- studyGuide: 'skipped', // ← Not requested
77
- flashcards: 'in_progress'
78
- }
79
- }
80
- ```
81
-
82
- ---
83
-
84
- ## 3. Real-time Updates (Pusher)
85
-
86
- ### Subscribe to Progress Events
87
-
88
- ```typescript
89
- import Pusher from 'pusher-js';
90
-
91
- const pusher = new Pusher('YOUR_PUSHER_KEY', {
92
- cluster: 'us2',
93
- });
94
-
95
- const channel = pusher.subscribe(`workspace_${workspaceId}`);
96
-
97
- channel.bind('analysis_progress', (progress: AnalysisProgress) => {
98
- console.log('Progress update:', progress);
99
-
100
- // Update your UI based on progress
101
- updateProgressUI(progress);
102
- });
103
- ```
104
-
105
- ### Event Timing
106
- - Event is emitted **every time** the progress changes
107
- - Happens **before** the database write completes
108
- - Frontend receives updates in real-time as analysis progresses
109
-
110
- ---
111
-
112
- ## 4. Database Queries (tRPC)
113
-
114
- ### Get Workspace with Progress
115
-
116
- ```typescript
117
- // Query the workspace to get current progress
118
- const workspace = await trpc.workspace.get.useQuery({
119
- id: workspaceId
120
- });
121
-
122
- // Access progress
123
- const progress = workspace.analysisProgress;
124
-
125
- if (progress?.status === 'generating_flashcards') {
126
- // Show flashcards generation UI
127
- }
128
- ```
129
-
130
- ### Polling Alternative (if Pusher disconnects)
131
-
132
- ```typescript
133
- const { data: workspace, refetch } = trpc.workspace.get.useQuery(
134
- { id: workspaceId },
135
- {
136
- refetchInterval: (data) => {
137
- // Poll every 2 seconds while analyzing
138
- return data?.fileBeingAnalyzed ? 2000 : false;
139
- }
140
- }
141
- );
142
- ```
143
-
144
- ---
145
-
146
- ## 5. UI Implementation Examples
147
-
148
- ### React Component Example
149
-
150
- ```tsx
151
- import { useState, useEffect } from 'react';
152
- import Pusher from 'pusher-js';
153
- import { trpc } from '@/lib/trpc';
154
-
155
- interface Props {
156
- workspaceId: string;
157
- }
158
-
159
- export function AnalysisProgressIndicator({ workspaceId }: Props) {
160
- const [progress, setProgress] = useState<AnalysisProgress | null>(null);
161
- const { data: workspace } = trpc.workspace.get.useQuery({ id: workspaceId });
162
-
163
- useEffect(() => {
164
- // Initialize with DB state
165
- if (workspace?.analysisProgress) {
166
- setProgress(workspace.analysisProgress);
167
- }
168
-
169
- // Subscribe to real-time updates
170
- const pusher = new Pusher(process.env.NEXT_PUBLIC_PUSHER_KEY!, {
171
- cluster: 'us2',
172
- });
173
-
174
- const channel = pusher.subscribe(`workspace_${workspaceId}`);
175
-
176
- channel.bind('analysis_progress', (newProgress: AnalysisProgress) => {
177
- setProgress(newProgress);
178
- });
179
-
180
- return () => {
181
- channel.unbind_all();
182
- pusher.unsubscribe(`workspace_${workspaceId}`);
183
- };
184
- }, [workspaceId, workspace]);
185
-
186
- if (!progress) return null;
187
-
188
- return (
189
- <div className="progress-indicator">
190
- <h3>{getStatusMessage(progress.status)}</h3>
191
- <p>Analyzing: {progress.filename}</p>
192
-
193
- <div className="steps">
194
- <Step name="Upload" status={progress.steps.fileUpload} />
195
- <Step name="Analysis" status={progress.steps.fileAnalysis} />
196
- <Step name="Study Guide" status={progress.steps.studyGuide} />
197
- <Step name="Flashcards" status={progress.steps.flashcards} />
198
- </div>
199
-
200
- {progress.error && (
201
- <div className="error">{progress.error}</div>
202
- )}
203
-
204
- {progress.completedAt && (
205
- <div className="success">
206
- Completed in {calculateDuration(progress.startedAt, progress.completedAt)}
207
- </div>
208
- )}
209
- </div>
210
- );
211
- }
212
-
213
- function getStatusMessage(status: AnalysisStatus): string {
214
- const messages: Record<AnalysisStatus, string> = {
215
- starting: 'Initializing...',
216
- uploading: 'Uploading file...',
217
- analyzing: 'Analyzing content...',
218
- generating_artifacts: 'Preparing artifacts...',
219
- generating_study_guide: 'Creating study guide...',
220
- generating_flashcards: 'Generating flashcards...',
221
- completed: 'Analysis complete!',
222
- error: 'An error occurred',
223
- };
224
- return messages[status];
225
- }
226
-
227
- function Step({ name, status }: { name: string; status: StepStatus }) {
228
- const icon = {
229
- pending: '⏳',
230
- in_progress: '🔄',
231
- completed: '✅',
232
- skipped: '⏭️',
233
- error: '❌',
234
- }[status];
235
-
236
- return (
237
- <div className={`step step-${status}`}>
238
- <span>{icon}</span>
239
- <span>{name}</span>
240
- </div>
241
- );
242
- }
243
-
244
- function calculateDuration(start: string, end: string): string {
245
- const ms = new Date(end).getTime() - new Date(start).getTime();
246
- const seconds = Math.floor(ms / 1000);
247
- return `${seconds}s`;
248
- }
249
- ```
250
-
251
- ---
252
-
253
- ## 6. Progress Percentage Calculation
254
-
255
- ```typescript
256
- function calculateProgress(progress: AnalysisProgress): number {
257
- const steps = Object.values(progress.steps);
258
- const completed = steps.filter(s => s === 'completed').length;
259
- const total = steps.filter(s => s !== 'skipped').length;
260
-
261
- return total > 0 ? Math.round((completed / total) * 100) : 0;
262
- }
263
-
264
- // Usage
265
- const percentage = calculateProgress(progress);
266
- // Returns: 0-100
267
- ```
268
-
269
- ---
270
-
271
- ## 7. Backend Endpoint (Reference)
272
-
273
- ### Trigger Analysis
274
-
275
- ```typescript
276
- // Call this to start analysis
277
- const result = await trpc.workspace.uploadAndAnalyzeMedia.mutate({
278
- workspaceId: 'workspace_123',
279
- file: {
280
- filename: 'biology-notes.pdf',
281
- contentType: 'application/pdf',
282
- size: 1024000,
283
- content: base64EncodedContent,
284
- },
285
- generateStudyGuide: true,
286
- generateFlashcards: true,
287
- generateWorksheet: false, // This won't appear in progress
288
- });
289
- ```
290
-
291
- ---
292
-
293
- ## 8. Common Patterns
294
-
295
- ### Show Modal During Analysis
296
-
297
- ```tsx
298
- function AnalysisModal({ workspaceId }: { workspaceId: string }) {
299
- const { data: workspace } = trpc.workspace.get.useQuery({ id: workspaceId });
300
- const [progress, setProgress] = useState<AnalysisProgress | null>(null);
301
-
302
- // Subscribe to progress...
303
-
304
- const isAnalyzing = workspace?.fileBeingAnalyzed ||
305
- (progress && progress.status !== 'completed' && progress.status !== 'error');
306
-
307
- if (!isAnalyzing) return null;
308
-
309
- return (
310
- <Modal>
311
- <AnalysisProgressIndicator workspaceId={workspaceId} />
312
- </Modal>
313
- );
314
- }
315
- ```
316
-
317
- ### Redirect on Completion
318
-
319
- ```tsx
320
- useEffect(() => {
321
- if (progress?.status === 'completed') {
322
- setTimeout(() => {
323
- router.push(`/workspace/${workspaceId}`);
324
- }, 2000); // Show success message for 2s
325
- }
326
- }, [progress?.status]);
327
- ```
328
-
329
- ### Handle Errors
330
-
331
- ```tsx
332
- useEffect(() => {
333
- if (progress?.status === 'error') {
334
- toast.error(`Analysis failed: ${progress.error}`);
335
-
336
- // Allow user to retry
337
- setShowRetryButton(true);
338
- }
339
- }, [progress?.status]);
340
- ```
341
-
342
- ---
343
-
344
- ## 9. Testing
345
-
346
- ### Mock Progress States
347
-
348
- ```typescript
349
- const mockProgress: AnalysisProgress = {
350
- status: 'generating_flashcards',
351
- filename: 'test.pdf',
352
- fileType: 'pdf',
353
- startedAt: new Date().toISOString(),
354
- steps: {
355
- fileUpload: 'completed',
356
- fileAnalysis: 'completed',
357
- studyGuide: 'completed',
358
- flashcards: 'in_progress',
359
- },
360
- };
361
- ```
362
-
363
- ### Simulate Progress
364
-
365
- ```typescript
366
- const statuses: AnalysisStatus[] = [
367
- 'starting',
368
- 'uploading',
369
- 'analyzing',
370
- 'generating_artifacts',
371
- 'generating_study_guide',
372
- 'generating_flashcards',
373
- 'completed',
374
- ];
375
-
376
- let index = 0;
377
- const interval = setInterval(() => {
378
- setProgress(prev => ({
379
- ...prev!,
380
- status: statuses[index],
381
- }));
382
-
383
- if (++index >= statuses.length) {
384
- clearInterval(interval);
385
- }
386
- }, 2000);
387
- ```
388
-
389
- ---
390
-
391
- ## 10. Important Notes
392
-
393
- ### ⚠️ Critical Behaviors
394
-
395
- 1. **Pusher is Primary**: Database is backup. Always prioritize Pusher events.
396
- 2. **No Worksheet Tracking**: Worksheets are generated but NOT tracked in progress.
397
- 3. **Boolean Flag**: `workspace.fileBeingAnalyzed` is simpler check for "is analyzing".
398
- 4. **Progress Persistence**: Progress persists in DB even after completion (for history).
399
- 5. **Timestamps**: All timestamps are ISO 8601 strings (use `new Date(timestamp)`).
400
-
401
- ### 🎯 Best Practices
402
-
403
- 1. Show progress modal immediately on file upload
404
- 2. Use optimistic UI updates with Pusher
405
- 3. Fall back to polling if Pusher connection fails
406
- 4. Clear progress UI gracefully on completion (2-3s delay)
407
- 5. Allow users to navigate away (background processing)
408
-
409
- ---
410
-
411
- ## 11. Complete Example Flow
412
-
413
- ```typescript
414
- // 1. User uploads file
415
- const uploadResult = await trpc.workspace.uploadAndAnalyzeMedia.mutate({
416
- workspaceId,
417
- file: uploadedFile,
418
- generateStudyGuide: true,
419
- generateFlashcards: true,
420
- generateWorksheet: false,
421
- });
422
-
423
- // 2. Frontend receives Pusher events automatically:
424
- // Event 1: { status: 'starting', steps: { fileUpload: 'pending', ... } }
425
- // Event 2: { status: 'uploading', steps: { fileUpload: 'in_progress', ... } }
426
- // Event 3: { status: 'analyzing', steps: { fileUpload: 'completed', fileAnalysis: 'in_progress', ... } }
427
- // ...
428
- // Event N: { status: 'completed', steps: { all: 'completed', ... }, completedAt: '...' }
429
-
430
- // 3. UI updates automatically via state
431
- // 4. On completion, redirect to workspace
432
- ```
433
-
434
- ---
435
-
436
- ## 12. Troubleshooting
437
-
438
- ### Issue: Progress not updating
439
- - **Check**: Pusher connection status
440
- - **Check**: Correct channel name `workspace_${workspaceId}`
441
- - **Check**: Event name is `'analysis_progress'`
442
- - **Fallback**: Use polling with `refetchInterval`
443
-
444
- ### Issue: Progress shows old data
445
- - **Solution**: Clear `analysisProgress` on new upload
446
- - **Solution**: Check `fileBeingAnalyzed` flag first
447
-
448
- ### Issue: Pusher disconnects
449
- - **Solution**: Implement reconnection logic
450
- - **Solution**: Fall back to polling
451
- - **Solution**: Check Pusher key and cluster config
452
-
453
- ---
454
-
455
- ## Support
456
-
457
- For backend changes or questions, check:
458
- - `/src/routers/workspace.ts` - Main logic
459
- - `/src/lib/pusher.ts` - Pusher service
460
- - `/prisma/schema.prisma` - Database schema
461
-
462
-
463
-
@@ -1,239 +0,0 @@
1
- # Analysis Progress - Quick Reference
2
-
3
- ## 🚀 Quick Start
4
-
5
- ### 1. Subscribe to Progress (Pusher)
6
- ```typescript
7
- const channel = pusher.subscribe(`workspace_${workspaceId}`);
8
- channel.bind('analysis_progress', (progress) => {
9
- console.log(progress.status); // 'uploading', 'analyzing', etc.
10
- });
11
- ```
12
-
13
- ### 2. Query Current Progress (Database)
14
- ```typescript
15
- const workspace = await trpc.workspace.get.useQuery({ id: workspaceId });
16
- const progress = workspace.analysisProgress;
17
- ```
18
-
19
- ---
20
-
21
- ## 📊 Data Structure (TypeScript)
22
-
23
- ```typescript
24
- interface AnalysisProgress {
25
- status: 'starting' | 'uploading' | 'analyzing' |
26
- 'generating_artifacts' | 'generating_study_guide' |
27
- 'generating_flashcards' | 'completed' | 'error';
28
-
29
- filename: string;
30
- fileType: 'image' | 'pdf';
31
- startedAt: string;
32
- completedAt?: string;
33
- error?: string;
34
-
35
- steps: {
36
- fileUpload: 'pending' | 'in_progress' | 'completed' | 'skipped' | 'error';
37
- fileAnalysis: 'pending' | 'in_progress' | 'completed' | 'skipped' | 'error';
38
- studyGuide: 'pending' | 'in_progress' | 'completed' | 'skipped' | 'error';
39
- flashcards: 'pending' | 'in_progress' | 'completed' | 'skipped' | 'error';
40
- };
41
- }
42
- ```
43
-
44
- ---
45
-
46
- ## 🔄 Status Flow
47
-
48
- ```
49
- starting → uploading → analyzing → generating_artifacts
50
- → generating_study_guide → generating_flashcards → completed
51
- ```
52
-
53
- **Or jump to:** `error` (at any point)
54
-
55
- ---
56
-
57
- ## 📝 Example Progress Objects
58
-
59
- ### Starting
60
- ```json
61
- {
62
- "status": "starting",
63
- "filename": "biology.pdf",
64
- "fileType": "pdf",
65
- "startedAt": "2025-11-05T10:30:00.000Z",
66
- "steps": {
67
- "fileUpload": "pending",
68
- "fileAnalysis": "pending",
69
- "studyGuide": "pending",
70
- "flashcards": "pending"
71
- }
72
- }
73
- ```
74
-
75
- ### In Progress
76
- ```json
77
- {
78
- "status": "generating_flashcards",
79
- "steps": {
80
- "fileUpload": "completed",
81
- "fileAnalysis": "completed",
82
- "studyGuide": "completed",
83
- "flashcards": "in_progress"
84
- }
85
- }
86
- ```
87
-
88
- ### Completed
89
- ```json
90
- {
91
- "status": "completed",
92
- "startedAt": "2025-11-05T10:30:00.000Z",
93
- "completedAt": "2025-11-05T10:35:00.000Z",
94
- "steps": {
95
- "fileUpload": "completed",
96
- "fileAnalysis": "completed",
97
- "studyGuide": "completed",
98
- "flashcards": "completed"
99
- }
100
- }
101
- ```
102
-
103
- ### Error
104
- ```json
105
- {
106
- "status": "error",
107
- "error": "Failed to analyze pdf: Connection timeout",
108
- "steps": {
109
- "fileUpload": "completed",
110
- "fileAnalysis": "error",
111
- "studyGuide": "skipped",
112
- "flashcards": "skipped"
113
- }
114
- }
115
- ```
116
-
117
- ---
118
-
119
- ## 🎨 UI Helpers
120
-
121
- ### Status Messages
122
- ```typescript
123
- const messages = {
124
- starting: 'Initializing...',
125
- uploading: 'Uploading file...',
126
- analyzing: 'Analyzing content...',
127
- generating_artifacts: 'Preparing artifacts...',
128
- generating_study_guide: 'Creating study guide...',
129
- generating_flashcards: 'Generating flashcards...',
130
- completed: 'Analysis complete! ✅',
131
- error: 'An error occurred ❌',
132
- };
133
- ```
134
-
135
- ### Step Icons
136
- ```typescript
137
- const icons = {
138
- pending: '⏳',
139
- in_progress: '🔄',
140
- completed: '✅',
141
- skipped: '⏭️',
142
- error: '❌',
143
- };
144
- ```
145
-
146
- ### Progress Percentage
147
- ```typescript
148
- function getPercentage(progress: AnalysisProgress): number {
149
- const steps = Object.values(progress.steps);
150
- const completed = steps.filter(s => s === 'completed').length;
151
- const total = steps.filter(s => s !== 'skipped').length;
152
- return Math.round((completed / total) * 100);
153
- }
154
- ```
155
-
156
- ---
157
-
158
- ## 🔌 Pusher Setup
159
-
160
- ```typescript
161
- import Pusher from 'pusher-js';
162
-
163
- const pusher = new Pusher(process.env.NEXT_PUBLIC_PUSHER_KEY!, {
164
- cluster: 'us2',
165
- });
166
-
167
- const channel = pusher.subscribe(`workspace_${workspaceId}`);
168
-
169
- // Single event type for all progress updates
170
- channel.bind('analysis_progress', (progress: AnalysisProgress) => {
171
- updateUI(progress);
172
- });
173
-
174
- // Cleanup
175
- channel.unbind_all();
176
- pusher.unsubscribe(`workspace_${workspaceId}`);
177
- ```
178
-
179
- ---
180
-
181
- ## ⚡ Common Checks
182
-
183
- ### Is Analyzing?
184
- ```typescript
185
- const isAnalyzing = workspace.fileBeingAnalyzed ||
186
- (progress?.status && !['completed', 'error'].includes(progress.status));
187
- ```
188
-
189
- ### Is Complete?
190
- ```typescript
191
- const isComplete = progress?.status === 'completed';
192
- ```
193
-
194
- ### Has Error?
195
- ```typescript
196
- const hasError = progress?.status === 'error';
197
- const errorMessage = progress?.error;
198
- ```
199
-
200
- ### Duration
201
- ```typescript
202
- const duration = new Date(progress.completedAt).getTime() -
203
- new Date(progress.startedAt).getTime();
204
- const seconds = Math.floor(duration / 1000);
205
- ```
206
-
207
- ---
208
-
209
- ## 🎯 Best Practices
210
-
211
- 1. ✅ Use Pusher for real-time updates
212
- 2. ✅ Show progress modal immediately
213
- 3. ✅ Use `fileBeingAnalyzed` as simple boolean check
214
- 4. ✅ Handle disconnections with polling fallback
215
- 5. ✅ Display friendly error messages
216
- 6. ❌ Don't rely solely on polling (use Pusher!)
217
- 7. ❌ Don't block UI (allow navigation away)
218
- 8. ❌ Don't track worksheets (they're not in progress)
219
-
220
- ---
221
-
222
- ## 🐛 Troubleshooting
223
-
224
- | Issue | Solution |
225
- |-------|----------|
226
- | No updates | Check Pusher key, cluster, channel name |
227
- | Old data | Query workspace again after upload starts |
228
- | Events missed | Implement polling fallback |
229
- | Wrong channel | Format: `workspace_${workspaceId}` |
230
- | Wrong event | Event name: `'analysis_progress'` |
231
-
232
- ---
233
-
234
- ## 📚 Full Documentation
235
-
236
- See `ANALYSIS_PROGRESS_SPEC.md` for complete examples and implementation details.
237
-
238
-
239
-