deepspace 0.18.0 → 0.19.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/CHANGELOG.md +24 -0
- package/README.md +21 -0
- package/dist/cli.js +416 -83
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +93 -25
- package/dist/index.js.map +1 -1
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +40 -9
- package/dist/server.js +151 -44
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +28 -25
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +61 -9
- package/dist/worker.js +190 -44
- package/dist/worker.js.map +1 -1
- package/features/documentation/feature.json +2 -1
- package/features/documents/feature.json +1 -1
- package/features/documents/src/DocumentsEditorPage.tsx +16 -52
- package/features/documents/src/editor/useDocEditor.ts +1 -1
- package/features/documents/src/use-documents-presence-access.ts +1 -3
- package/features/tasks/src/TasksPage.tsx +6 -3
- package/package.json +1 -1
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"The feature is served at /docs on the app's existing hostname.",
|
|
60
60
|
"A hostname listed in documentation.json domains serves the same release at that hostname's root after you explicitly attach it to the app.",
|
|
61
61
|
"Documentation builds only when documentation.json exists; apps without this feature are unaffected.",
|
|
62
|
-
"MDX may import app components directly. documentation.tsx is the single optional shell customization point."
|
|
62
|
+
"MDX may import app components directly. documentation.tsx is the single optional shell customization point.",
|
|
63
|
+
"Agent discovery is available at /docs/llms.txt, /docs/llms-full.txt, /docs/skill.md, /docs/.well-known/mcp, and /docs/mcp."
|
|
63
64
|
],
|
|
64
65
|
"agentNotes": [
|
|
65
66
|
"Treat documentation.json, documentation/**/*.md, documentation/**/*.mdx, and optional documentation.tsx as one app-owned documentation feature.",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Documents",
|
|
4
4
|
"category": "data",
|
|
5
5
|
"description": "Real-time collaborative document editing with Tiptap + Yjs",
|
|
6
|
-
"details": "Document library with folders & favorites. Collaborative editor built on Tiptap (ProseMirror) with Y.XmlFragment via @tiptap/extension-collaboration and live remote cursors via @tiptap/extension-collaboration-caret — buttery typing, no innerHTML rebuilds, no contenteditable quirks. Invite-only documents with viewer/editor roles: owner shares via an in-app dialog that resolves an email to a DeepSpace user. RBAC is enforced by the documents schema and the starter template's built-in Yjs route.
|
|
6
|
+
"details": "Document library with folders & favorites. Collaborative editor built on Tiptap (ProseMirror) with Y.XmlFragment via @tiptap/extension-collaboration and live remote cursors via @tiptap/extension-collaboration-caret — buttery typing, no innerHTML rebuilds, no contenteditable quirks. Invite-only documents with viewer/editor roles: owner shares via an in-app dialog that resolves an email to a DeepSpace user. RBAC is enforced by the documents schema and the starter template's built-in Yjs route.",
|
|
7
7
|
"files": [
|
|
8
8
|
{
|
|
9
9
|
"src": "src/DocumentsListPage.tsx",
|
|
@@ -6,11 +6,6 @@
|
|
|
6
6
|
* applied as granular PM transactions (no `innerHTML` swap), the local
|
|
7
7
|
* caret is migrated through concurrent edits by PM's position mapping,
|
|
8
8
|
* and remote cursors are painted by `@tiptap/extension-collaboration-caret`.
|
|
9
|
-
*
|
|
10
|
-
* Legacy documents created with the old editor stored HTML in Y.Text('content').
|
|
11
|
-
* On first open by an editor/owner we migrate that into the new XmlFragment
|
|
12
|
-
* via `editor.commands.setContent(html)` and clear the legacy field. The
|
|
13
|
-
* migration is a single Yjs transaction so all peers converge.
|
|
14
9
|
*/
|
|
15
10
|
|
|
16
11
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
@@ -286,20 +281,12 @@ export default function DocumentsEditorPage() {
|
|
|
286
281
|
|
|
287
282
|
const { put } = useMutations<DocumentsDocumentFields>('documents')
|
|
288
283
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const {
|
|
296
|
-
doc,
|
|
297
|
-
text: legacyText,
|
|
298
|
-
synced,
|
|
299
|
-
canWrite,
|
|
300
|
-
writeAuthResolved,
|
|
301
|
-
awareness,
|
|
302
|
-
} = useYjsRoom(docId ?? 'noop', 'content')
|
|
284
|
+
// The text helper remains available to consumers of useYjsRoom; this editor
|
|
285
|
+
// binds Tiptap directly to Y.XmlFragment('default') on the returned document.
|
|
286
|
+
const { doc, synced, connected, canWrite, writeAuthResolved, awareness } = useYjsRoom(
|
|
287
|
+
docId ?? 'noop',
|
|
288
|
+
'content',
|
|
289
|
+
)
|
|
303
290
|
|
|
304
291
|
const presenceAccess = useDocumentsPresenceAccess({
|
|
305
292
|
docId,
|
|
@@ -388,37 +375,6 @@ export default function DocumentsEditorPage() {
|
|
|
388
375
|
placeholder: showReadOnlyDocUx ? 'View only' : 'Start typing — toolbar above for formatting…',
|
|
389
376
|
})
|
|
390
377
|
|
|
391
|
-
// Legacy migration: if doc.getXmlFragment('default') is empty but the
|
|
392
|
-
// legacy Y.Text('content') field has HTML, parse it once into Tiptap.
|
|
393
|
-
// Only the first writer to open does this; viewers never see partial state
|
|
394
|
-
// because both the wipe and the parse run inside a single Yjs transaction.
|
|
395
|
-
const migratedRef = useRef(false)
|
|
396
|
-
useEffect(() => {
|
|
397
|
-
if (migratedRef.current) return
|
|
398
|
-
if (!editor || !synced || !effectiveCanWrite) return
|
|
399
|
-
const fragment = doc.getXmlFragment('default')
|
|
400
|
-
if (fragment.length > 0) {
|
|
401
|
-
migratedRef.current = true
|
|
402
|
-
return
|
|
403
|
-
}
|
|
404
|
-
const yText = doc.getText('content')
|
|
405
|
-
const legacy = (legacyText ?? yText.toString()).trim()
|
|
406
|
-
if (!legacy) {
|
|
407
|
-
migratedRef.current = true
|
|
408
|
-
return
|
|
409
|
-
}
|
|
410
|
-
migratedRef.current = true
|
|
411
|
-
/**
|
|
412
|
-
* Tiptap parses the HTML against its schema, then writes the resulting
|
|
413
|
-
* nodes into the bound Y.XmlFragment. We then clear the legacy field
|
|
414
|
-
* inside the same transaction so peers see one atomic step.
|
|
415
|
-
*/
|
|
416
|
-
doc.transact(() => {
|
|
417
|
-
editor.commands.setContent(legacy, { emitUpdate: true })
|
|
418
|
-
if (yText.length > 0) yText.delete(0, yText.length)
|
|
419
|
-
})
|
|
420
|
-
}, [editor, synced, effectiveCanWrite, doc, legacyText])
|
|
421
|
-
|
|
422
378
|
const { participants: presenceParticipants, typingNames } = useDocumentsEditorPresence({
|
|
423
379
|
editor,
|
|
424
380
|
document: selectedDoc,
|
|
@@ -522,7 +478,7 @@ export default function DocumentsEditorPage() {
|
|
|
522
478
|
{effectiveRole === 'editor' ? 'Shared editor' : 'Shared viewer'}
|
|
523
479
|
</span>
|
|
524
480
|
) : null}
|
|
525
|
-
{synced ? (
|
|
481
|
+
{connected && synced ? (
|
|
526
482
|
<span
|
|
527
483
|
className="flex items-center gap-1.5 text-xs tabular-nums"
|
|
528
484
|
style={{ color: 'var(--documents-el-muted)' }}
|
|
@@ -530,7 +486,7 @@ export default function DocumentsEditorPage() {
|
|
|
530
486
|
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" aria-hidden />
|
|
531
487
|
Synced
|
|
532
488
|
</span>
|
|
533
|
-
) : (
|
|
489
|
+
) : connected ? (
|
|
534
490
|
<span
|
|
535
491
|
className="flex items-center gap-1.5 text-xs tabular-nums"
|
|
536
492
|
style={{ color: 'var(--documents-el-muted)' }}
|
|
@@ -538,6 +494,14 @@ export default function DocumentsEditorPage() {
|
|
|
538
494
|
<span className="h-1.5 w-1.5 animate-pulse rounded-full bg-amber-500" aria-hidden />
|
|
539
495
|
Connecting…
|
|
540
496
|
</span>
|
|
497
|
+
) : (
|
|
498
|
+
<span
|
|
499
|
+
className="flex items-center gap-1.5 text-xs tabular-nums"
|
|
500
|
+
style={{ color: 'var(--documents-el-muted)' }}
|
|
501
|
+
>
|
|
502
|
+
<span className="h-1.5 w-1.5 rounded-full bg-slate-400" aria-hidden />
|
|
503
|
+
Offline
|
|
504
|
+
</span>
|
|
541
505
|
)}
|
|
542
506
|
{effectiveRole === 'owner' ? (
|
|
543
507
|
<Badge variant="success">Owner</Badge>
|
|
@@ -120,7 +120,7 @@ export function useDocEditor({
|
|
|
120
120
|
canWrite,
|
|
121
121
|
placeholder = 'Start typing — toolbar above for formatting…',
|
|
122
122
|
}: UseDocEditorOptions): Editor | null {
|
|
123
|
-
/** Structured CRDT field that ProseMirror binds to
|
|
123
|
+
/** Structured CRDT field that ProseMirror binds to. */
|
|
124
124
|
const fragment = useMemo(() => doc.getXmlFragment('default'), [doc])
|
|
125
125
|
|
|
126
126
|
/** CollaborationCaret expects a provider-shaped `{ awareness }` object. */
|
|
@@ -96,13 +96,11 @@ function peerToDocumentsParticipant(peer: PresencePeerClient): DocumentsPresence
|
|
|
96
96
|
const participant: DocumentsPresenceParticipant = {
|
|
97
97
|
clientId: 0,
|
|
98
98
|
userId: peer.userId,
|
|
99
|
-
name: peer.userName?.trim() ||
|
|
99
|
+
name: peer.userName?.trim() || 'Guest',
|
|
100
100
|
mode,
|
|
101
101
|
typing: state.typing === true,
|
|
102
102
|
isSelf: false,
|
|
103
103
|
}
|
|
104
|
-
if (peer.userEmail) participant.email = peer.userEmail
|
|
105
|
-
if (peer.userImageUrl) participant.imageUrl = peer.userImageUrl
|
|
106
104
|
if (lastTypedAt != null) participant.lastTypedAt = lastTypedAt
|
|
107
105
|
return participant
|
|
108
106
|
}
|
|
@@ -460,7 +460,10 @@ export default function TasksPage({ className }: TasksPageProps) {
|
|
|
460
460
|
const handleUnclaim = async (challenge: { recordId: string; data: Challenge }) => {
|
|
461
461
|
await put(challenge.recordId, {
|
|
462
462
|
...challenge.data,
|
|
463
|
-
|
|
463
|
+
// Empty strings are normalized to SQL NULL by the record writer. The
|
|
464
|
+
// read model omits null database fields, so this also keeps Challenge's
|
|
465
|
+
// optional-string shape honest.
|
|
466
|
+
claimedById: '',
|
|
464
467
|
})
|
|
465
468
|
}
|
|
466
469
|
|
|
@@ -469,8 +472,8 @@ export default function TasksPage({ className }: TasksPageProps) {
|
|
|
469
472
|
await put(submitChallenge.recordId, {
|
|
470
473
|
...submitChallenge.data,
|
|
471
474
|
submitted: true,
|
|
472
|
-
submissionUrl: url
|
|
473
|
-
submissionNotes: notes
|
|
475
|
+
submissionUrl: url,
|
|
476
|
+
submissionNotes: notes,
|
|
474
477
|
})
|
|
475
478
|
setSubmitChallenge(null)
|
|
476
479
|
}
|