deepspace 0.6.2 → 0.8.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +22 -8
  3. package/dist/cli.js +10348 -5025
  4. package/dist/cli.js.map +1 -1
  5. package/dist/feature-installer.js +638 -0
  6. package/dist/feature-installer.js.map +1 -0
  7. package/dist/index.d.ts +229 -116
  8. package/dist/index.js +767 -788
  9. package/dist/index.js.map +1 -1
  10. package/dist/server.d.ts +403 -167
  11. package/dist/server.js +498 -367
  12. package/dist/server.js.map +1 -1
  13. package/dist/testing.d.ts +1 -1
  14. package/dist/testing.js +3 -3
  15. package/dist/testing.js.map +1 -1
  16. package/dist/worker.d.ts +541 -159
  17. package/dist/worker.js +611 -367
  18. package/dist/worker.js.map +1 -1
  19. package/features/ai-chat/feature.json +2 -0
  20. package/features/ai-chat/src/AiChatPage.tsx +2 -3
  21. package/features/ai-chat/src/ChatPanel.messages.tsx +344 -0
  22. package/features/ai-chat/src/ChatPanel.stream.ts +397 -0
  23. package/features/ai-chat/src/ChatPanel.tsx +159 -825
  24. package/features/ai-chat/src/__tests__/ChatPanel.stream.test.ts +163 -0
  25. package/features/canvas/src/CanvasListPage.tsx +1 -1
  26. package/features/docs/feature.json +4 -8
  27. package/features/docs/src/DocsEditorPage.tsx +33 -326
  28. package/features/docs/src/DocsListPage.tsx +33 -29
  29. package/features/docs/src/InviteDialog.tsx +11 -40
  30. package/features/docs/src/__tests__/docs-presence-access.test.ts +71 -0
  31. package/features/docs/src/docs-library-types.ts +28 -3
  32. package/features/docs/src/docs-ui.css +1 -4
  33. package/features/docs/src/use-docs-presence-access.ts +330 -0
  34. package/features/file-attachments/feature.json +2 -2
  35. package/features/file-attachments/src/AttachmentPreview.tsx +428 -0
  36. package/features/file-attachments/src/AttachmentsPage.tsx +100 -462
  37. package/features/file-attachments/src/useFilePreview.ts +54 -68
  38. package/features/file-manager/src/FileManagerPage.tsx +1 -1
  39. package/features/integration-test/src/integration.spec.ts +1 -1
  40. package/features/items/feature.json +1 -5
  41. package/features/items/src/ItemsPage.tsx +207 -135
  42. package/features/landing/feature.json +3 -0
  43. package/features/landing/src/LandingPage.tsx +4 -18
  44. package/features/leaderboard/feature.json +1 -5
  45. package/features/leaderboard/src/LeaderboardPage.tsx +333 -198
  46. package/features/messaging/feature.json +0 -4
  47. package/features/messaging/src/ChatMultiPage.tsx +1 -3
  48. package/features/messaging/src/ChatPage.tsx +1 -3
  49. package/features/messaging/src/components/chat/MessageActionSheet.tsx +40 -16
  50. package/features/search-bar/feature.json +8 -3
  51. package/features/search-bar/src/SearchOverlay.tsx +108 -252
  52. package/features/search-bar/src/__tests__/search-model.test.ts +40 -0
  53. package/features/search-bar/src/search-model.ts +223 -0
  54. package/features/tasks/src/TasksPage.tsx +3 -3
  55. package/features/topbar/src/Topbar.tsx +0 -17
  56. package/package.json +1 -2
  57. package/features/docs/src/docs-favorites.ts +0 -18
  58. package/features/docs/src/use-yjs-room-with-awareness.ts +0 -289
  59. package/features/file-attachments/src/attachments-constants.ts +0 -9
  60. package/features/items/src/items-constants.ts +0 -10
  61. package/features/leaderboard/src/leaderboard-constants.ts +0 -21
  62. package/features/messaging/src/components/chat/ReactionPicker.tsx +0 -2
  63. package/scripts/add-feature.cjs +0 -807
@@ -1,24 +1,18 @@
1
1
  /**
2
2
  * useFilePreview — Centralized file preview hook with dynamic imports.
3
3
  *
4
- * Mirrors the main app's pattern:
5
- * - Image, video, audio, PDF → native browser elements (no extra deps)
6
- * - Code/text → CodeMirror (dynamically imported)
7
- * - CSV → PapaParse (dynamically imported)
8
- * - DOCX → docx-preview (dynamically imported)
9
- * - XLSX → ExcelJS (dynamically imported)
4
+ * Native image/video/audio need no loader. PDF, code/text, CSV, DOCX, and
5
+ * XLSX load their parser or renderer only when selected.
10
6
  *
11
7
  * Heavy libraries are only loaded when needed, keeping the base bundle small.
12
8
  *
13
9
  * @example
14
10
  * ```tsx
15
- * const { PreviewComponent, isLoading, error } = useFilePreview({
11
+ * const preview = useFilePreview({
16
12
  * url: 'https://...',
17
13
  * fileName: 'report.csv',
18
14
  * mimeType: 'text/csv',
19
15
  * })
20
- *
21
- * return <PreviewComponent />
22
16
  * ```
23
17
  */
24
18
 
@@ -80,7 +74,6 @@ export interface XlsxSheet {
80
74
 
81
75
  export interface XlsxData {
82
76
  sheets: XlsxSheet[]
83
- activeSheet: number
84
77
  }
85
78
 
86
79
  // ============================================================================
@@ -128,56 +121,56 @@ export function useFilePreview({ url, fileName, mimeType }: FilePreviewInput): F
128
121
  const [docxModule, setDocxModule] = useState<typeof import('docx-preview') | null>(null)
129
122
  const [pdfPages, setPdfPages] = useState<HTMLCanvasElement[] | null>(null)
130
123
 
131
- // Load preview content based on category
132
124
  useEffect(() => {
133
- if (!previewable || !url) return
134
-
135
- // Native browser previews (image, video, audio) don't need fetching
136
- // PDF uses PDF.js now (rendered to canvas) to avoid iframe sandbox issues
137
- if (category === 'image' || category === 'video' || category === 'audio') {
138
- return
139
- }
140
-
141
125
  const controller = new AbortController()
142
- let cancelled = false
143
-
144
- setIsLoading(true)
126
+ const { signal } = controller
127
+ const needsContent = previewable
128
+ && !!url
129
+ && category !== 'image'
130
+ && category !== 'video'
131
+ && category !== 'audio'
132
+
133
+ // Clear every result before deciding whether this category needs an
134
+ // asynchronous loader. Without this reset, switching from a failed or
135
+ // loading document to a native image/video/audio preview retains the old
136
+ // error/loading state and hides the native preview indefinitely.
137
+ setIsLoading(needsContent)
145
138
  setError(null)
146
139
  setTextContent(null)
147
140
  setCsvData(null)
141
+ setCodeEditorKit(null)
148
142
  setXlsxData(null)
149
143
  setDocxData(null)
150
144
  setDocxModule(null)
151
145
  setPdfPages(null)
152
146
 
147
+ if (!needsContent) return () => controller.abort()
148
+
153
149
  async function loadPreview() {
154
150
  try {
155
151
  if (category === 'pdf') {
156
- await loadPdfPreview(url, controller.signal, cancelled, setPdfPages, setError)
152
+ await loadPdfPreview(url, signal, setPdfPages)
157
153
  } else if (category === 'code' || category === 'text') {
158
- await loadCodePreview(url, fileName, controller.signal, cancelled, setTextContent, setCodeEditorKit)
154
+ await loadCodePreview(url, fileName, signal, setTextContent, setCodeEditorKit)
159
155
  } else if (extension === 'csv' || extension === 'tsv') {
160
- await loadCsvPreview(url, controller.signal, cancelled, setCsvData)
156
+ await loadCsvPreview(url, signal, setCsvData)
161
157
  } else if (extension === 'docx') {
162
- await loadDocxPreview(url, controller.signal, cancelled, setDocxData, setDocxModule)
158
+ await loadDocxPreview(url, signal, setDocxData, setDocxModule)
163
159
  } else if (extension === 'xlsx' || extension === 'xls') {
164
- await loadXlsxPreview(url, controller.signal, cancelled, setXlsxData)
160
+ await loadXlsxPreview(url, signal, setXlsxData)
165
161
  }
166
162
  } catch (err) {
167
- if (!cancelled && !(err instanceof DOMException && err.name === 'AbortError')) {
163
+ if (!signal.aborted && !(err instanceof DOMException && err.name === 'AbortError')) {
168
164
  setError(err instanceof Error ? err.message : 'Failed to load preview')
169
165
  }
170
166
  } finally {
171
- if (!cancelled) setIsLoading(false)
167
+ if (!signal.aborted) setIsLoading(false)
172
168
  }
173
169
  }
174
170
 
175
- loadPreview()
171
+ void loadPreview()
176
172
 
177
- return () => {
178
- cancelled = true
179
- controller.abort()
180
- }
173
+ return () => controller.abort()
181
174
  }, [url, fileName, mimeType, category, extension, previewable])
182
175
 
183
176
  return {
@@ -209,7 +202,6 @@ async function loadCodePreview(
209
202
  url: string,
210
203
  fileName: string,
211
204
  signal: AbortSignal,
212
- cancelled: boolean,
213
205
  setTextContent: (v: string) => void,
214
206
  setCodeEditorKit: (v: CodeEditorKit) => void,
215
207
  ) {
@@ -220,11 +212,11 @@ async function loadCodePreview(
220
212
  import('@codemirror/theme-one-dark').catch(() => null),
221
213
  ])
222
214
 
223
- if (cancelled) return
215
+ if (signal.aborted) return
224
216
  if (!response.ok) throw new Error(`HTTP ${response.status}`)
225
217
 
226
218
  const text = await response.text()
227
- if (cancelled) return
219
+ if (signal.aborted) return
228
220
 
229
221
  // Pretty-print minified JSON before handing it to CodeMirror. Files like
230
222
  // `package-lock.json` or build manifests are often shipped as one long
@@ -266,7 +258,6 @@ async function loadCodePreview(
266
258
  async function loadCsvPreview(
267
259
  url: string,
268
260
  signal: AbortSignal,
269
- cancelled: boolean,
270
261
  setCsvData: (v: FilePreviewResult['csvData']) => void,
271
262
  ) {
272
263
  const [response, Papa] = await Promise.all([
@@ -274,11 +265,11 @@ async function loadCsvPreview(
274
265
  import('papaparse'),
275
266
  ])
276
267
 
277
- if (cancelled) return
268
+ if (signal.aborted) return
278
269
  if (!response.ok) throw new Error(`HTTP ${response.status}`)
279
270
 
280
271
  const text = await response.text()
281
- if (cancelled) return
272
+ if (signal.aborted) return
282
273
 
283
274
  const result = Papa.default.parse<string[]>(text, { skipEmptyLines: true })
284
275
  const allRows = result.data
@@ -298,7 +289,6 @@ async function loadCsvPreview(
298
289
  async function loadDocxPreview(
299
290
  url: string,
300
291
  signal: AbortSignal,
301
- cancelled: boolean,
302
292
  setDocxData: (v: ArrayBuffer) => void,
303
293
  setDocxModule: (v: typeof import('docx-preview')) => void,
304
294
  ) {
@@ -307,11 +297,11 @@ async function loadDocxPreview(
307
297
  import('docx-preview'),
308
298
  ])
309
299
 
310
- if (cancelled) return
300
+ if (signal.aborted) return
311
301
  if (!response.ok) throw new Error(`HTTP ${response.status}`)
312
302
 
313
303
  const arrayBuffer = await response.arrayBuffer()
314
- if (cancelled) return
304
+ if (signal.aborted) return
315
305
 
316
306
  setDocxData(arrayBuffer)
317
307
  setDocxModule(docxPreviewModule)
@@ -320,7 +310,6 @@ async function loadDocxPreview(
320
310
  async function loadXlsxPreview(
321
311
  url: string,
322
312
  signal: AbortSignal,
323
- cancelled: boolean,
324
313
  setXlsxData: (v: XlsxData) => void,
325
314
  ) {
326
315
  const [response, ExcelJSModule] = await Promise.all([
@@ -328,16 +317,16 @@ async function loadXlsxPreview(
328
317
  import('exceljs'),
329
318
  ])
330
319
 
331
- if (cancelled) return
320
+ if (signal.aborted) return
332
321
  if (!response.ok) throw new Error(`HTTP ${response.status}`)
333
322
 
334
323
  const arrayBuffer = await response.arrayBuffer()
335
- if (cancelled) return
324
+ if (signal.aborted) return
336
325
 
337
326
  const ExcelJS = (ExcelJSModule as { default?: typeof import('exceljs') }).default ?? ExcelJSModule
338
327
  const workbook = new ExcelJS.Workbook()
339
328
  await workbook.xlsx.load(arrayBuffer)
340
- if (cancelled) return
329
+ if (signal.aborted) return
341
330
 
342
331
  // Bounded recursion: a malformed XLSX could in theory produce
343
332
  // `{result: {result: {result: ...}}}` chains that blow the stack.
@@ -384,7 +373,7 @@ async function loadXlsxPreview(
384
373
  return { name: ws.name, headers, rows, truncated }
385
374
  })
386
375
 
387
- setXlsxData({ sheets, activeSheet: 0 })
376
+ setXlsxData({ sheets })
388
377
  }
389
378
 
390
379
  // ============================================================================
@@ -429,45 +418,42 @@ async function loadPdfJs(): Promise<typeof import('pdfjs-dist')> {
429
418
  async function loadPdfPreview(
430
419
  url: string,
431
420
  signal: AbortSignal,
432
- cancelled: boolean,
433
421
  setPdfPages: (v: HTMLCanvasElement[]) => void,
434
- setError: (v: string) => void,
435
422
  ) {
436
- try {
437
- const pdfjsLib = await loadPdfJs()
438
- if (cancelled) return
439
-
440
- const response = await fetch(url, { signal })
441
- if (cancelled) return
442
- if (!response.ok) throw new Error(`HTTP ${response.status}`)
423
+ const pdfjsLib = await loadPdfJs()
424
+ if (signal.aborted) return
443
425
 
444
- const arrayBuffer = await response.arrayBuffer()
445
- if (cancelled) return
426
+ const response = await fetch(url, { signal })
427
+ if (signal.aborted) return
428
+ if (!response.ok) throw new Error(`HTTP ${response.status}`)
446
429
 
447
- const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise
448
- if (cancelled) return
430
+ const arrayBuffer = await response.arrayBuffer()
431
+ if (signal.aborted) return
449
432
 
433
+ const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise
434
+ try {
450
435
  const canvases: HTMLCanvasElement[] = []
451
436
  const SCALE = 1.5
452
437
 
453
438
  for (let i = 1; i <= pdf.numPages; i++) {
454
- if (cancelled) return
439
+ if (signal.aborted) return
455
440
  const page = await pdf.getPage(i)
441
+ if (signal.aborted) return
456
442
  const viewport = page.getViewport({ scale: SCALE })
457
443
 
458
444
  const canvas = document.createElement('canvas')
459
445
  canvas.width = viewport.width
460
446
  canvas.height = viewport.height
461
447
 
462
- const ctx = canvas.getContext('2d')!
448
+ const ctx = canvas.getContext('2d')
449
+ if (!ctx) throw new Error('Canvas rendering is not available')
463
450
  await page.render({ canvasContext: ctx, viewport }).promise
451
+ if (signal.aborted) return
464
452
  canvases.push(canvas)
465
453
  }
466
454
 
467
- if (!cancelled) setPdfPages(canvases)
468
- } catch (err) {
469
- if (!cancelled && !(err instanceof DOMException && err.name === 'AbortError')) {
470
- setError(err instanceof Error ? err.message : 'Failed to load PDF')
471
- }
455
+ setPdfPages(canvases)
456
+ } finally {
457
+ await pdf.destroy()
472
458
  }
473
459
  }
@@ -209,7 +209,7 @@ function PreviewModal({ file, imageUrl, onClose }: PreviewModalProps) {
209
209
 
210
210
  return (
211
211
  <Modal open={!!file} onClose={onClose} size="lg">
212
- <Modal.Header onClose={onClose}>
212
+ <Modal.Header>
213
213
  <Modal.Title>{name}</Modal.Title>
214
214
  </Modal.Header>
215
215
  <Modal.Body>
@@ -6,7 +6,7 @@
6
6
  * signs in as a pre-created test account via the `users` fixture (from
7
7
  * deepspace/testing), which handles sign-in caching, context creation,
8
8
  * and cleanup. Create at least one test account first:
9
- * npx deepspace test-accounts create --email integ@deepspace.test --password TestPass123! --name "Integration Tester"
9
+ * npx deepspace test accounts create --email integ@deepspace.test --password TestPass123! --name "Integration Tester"
10
10
  */
11
11
  import { test, expect } from 'deepspace/testing'
12
12
 
@@ -3,7 +3,7 @@
3
3
  "name": "Items",
4
4
  "category": "data",
5
5
  "description": "CRUD items with ownership, status toggling, and role-based access",
6
- "details": "Reference implementation for basic CRUD with useQuery and useMutations. Items have owner-based permissions (own items editable, others read-only), active/archived status toggle, and role-based create access.",
6
+ "details": "Reference implementation for basic CRUD with useQuery and confirmed mutations. Items have owner-based permissions (own items editable, others read-only), active/archived status toggling, and role-based create access.",
7
7
  "files": [
8
8
  {
9
9
  "src": "src/ItemsPage.tsx",
@@ -12,10 +12,6 @@
12
12
  {
13
13
  "src": "src/items-schema.ts",
14
14
  "dest": "src/schemas/items-schema.ts"
15
- },
16
- {
17
- "src": "src/items-constants.ts",
18
- "dest": "src/components/items/items-constants.ts"
19
15
  }
20
16
  ],
21
17
  "schema": {