deepspace 0.16.0 → 0.18.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.
@@ -216,16 +216,14 @@ export function LibrarySidebar({
216
216
  const onCommitFolderRenameRef = useRef(onCommitRenameFolder)
217
217
  onCommitFolderRenameRef.current = onCommitRenameFolder
218
218
 
219
- useEffect(() => {
220
- return () => {
221
- // Intentionally read the ref's LATEST value at unmount to clear whatever
222
- // blur timer is pending; capturing it at effect-setup time (null) would be
223
- // wrong here since this is a timer ref, not a DOM node.
224
- // eslint-disable-next-line react-hooks/exhaustive-deps -- must clear the latest pending timer at unmount, not the setup-time value
225
- if (folderRenameBlurTimerRef.current) clearTimeout(folderRenameBlurTimerRef.current)
226
- }
219
+ const clearPendingFolderRename = useCallback(() => {
220
+ if (folderRenameBlurTimerRef.current) clearTimeout(folderRenameBlurTimerRef.current)
227
221
  }, [])
228
222
 
223
+ useEffect(() => {
224
+ return clearPendingFolderRename
225
+ }, [clearPendingFolderRename])
226
+
229
227
  const submitNewFolder = useCallback(async () => {
230
228
  const name = newFolderName.trim()
231
229
  if (!name) {
@@ -14,7 +14,7 @@
14
14
  */
15
15
 
16
16
  import { useEffect, useMemo, useRef } from 'react'
17
- import { useEditor, type Editor } from '@tiptap/react'
17
+ import { useEditor, type AnyExtension, type Editor } from '@tiptap/react'
18
18
  import StarterKit from '@tiptap/starter-kit'
19
19
  import Collaboration from '@tiptap/extension-collaboration'
20
20
  import { CollaborationCaret } from '@tiptap/extension-collaboration-caret'
@@ -127,7 +127,7 @@ export function useDocEditor({
127
127
  const provider = useMemo(() => (awareness ? { awareness } : null), [awareness])
128
128
 
129
129
  const extensions = useMemo(() => {
130
- const exts: unknown[] = [
130
+ const exts: AnyExtension[] = [
131
131
  StarterKit.configure({
132
132
  // Yjs has its own undo/redo history via the Collaboration extension; keeping
133
133
  // both enabled corrupts the shared document on undo.
@@ -175,12 +175,7 @@ export function useDocEditor({
175
175
  // in dev (the editor view is destroyed and re-created, and any subscriber
176
176
  // that runs in between throws on `editor.view`). Defer to a useEffect.
177
177
  immediatelyRender: false,
178
- // Bare directive on purpose: naming @typescript-eslint/no-explicit-any
179
- // here is a hard "unknown rule" error under the scaffolded app's
180
- // minimal eslint config (which never registers that plugin), while the
181
- // SDK monorepo's config does enable the rule and needs the suppression.
182
- // eslint-disable-next-line
183
- extensions: extensions as any,
178
+ extensions,
184
179
  editorProps: {
185
180
  transformPastedHTML: stripCollaborationArtifactsFromHTML,
186
181
  attributes: {
@@ -59,7 +59,7 @@
59
59
  "framer-motion": "^12.38.0"
60
60
  },
61
61
  "instructions": [
62
- "Configure APP_NAME, HERO_HEADLINE, HERO_SUBHEADLINE, FAQ_ITEMS etc. in LandingPage.tsx"
62
+ "Configure APP_NAME, HERO_HEADLINE, HERO_SUBHEADLINE, FAQ_ITEMS, etc. in src/pages/(app)/landing.tsx"
63
63
  ],
64
64
  "patterns": []
65
65
  }
@@ -14,7 +14,14 @@ import type { GroupedReaction } from 'deepspace'
14
14
  import { UserProfilePopover } from './UserProfilePopover'
15
15
  import { useLongPress } from '../hooks/useLongPress'
16
16
 
17
- const QUICK_EMOJIS = ['\u{1F44D}', '\u2764\uFE0F', '\u{1F602}', '\u{1F389}', '\u{1F525}', '\u{1F440}']
17
+ const QUICK_EMOJIS = [
18
+ '\u{1F44D}',
19
+ '\u2764\uFE0F',
20
+ '\u{1F602}',
21
+ '\u{1F389}',
22
+ '\u{1F525}',
23
+ '\u{1F440}',
24
+ ]
18
25
 
19
26
  export interface MessageRect {
20
27
  top: number
@@ -61,6 +68,8 @@ export function MessageItem({
61
68
  const [editContent, setEditContent] = useState(message.data.content)
62
69
  const editContainerRef = useRef<HTMLDivElement>(null)
63
70
  const contentRef = useRef<HTMLDivElement>(null)
71
+ const latestMessageContentRef = useRef(message.data.content)
72
+ latestMessageContentRef.current = message.data.content
64
73
 
65
74
  const authorId = message.data.authorId || message.createdBy
66
75
  const author = getUser(authorId)
@@ -78,27 +87,25 @@ export function MessageItem({
78
87
  height: rect.height,
79
88
  })
80
89
  },
81
- { delay: 500, threshold: 10 }
90
+ { delay: 500, threshold: 10 },
82
91
  )
83
92
 
84
93
  useEffect(() => {
85
- if (forceEdit && !isEditing) {
86
- setIsEditing(true)
87
- setEditContent(message.data.content)
88
- }
89
- // Deps intentionally limited to [forceEdit]: this reacts only to the
90
- // forceEdit trigger. Including isEditing/message.data.content would re-fire
91
- // on every keystroke and clobber the in-progress edit buffer.
92
- // eslint-disable-next-line react-hooks/exhaustive-deps -- one-shot on forceEdit; adding isEditing/message.data.content would re-fire on keystrokes and clobber the edit buffer
93
- }, [forceEdit])
94
+ if (!forceEdit || isEditing) return
95
+ setEditContent(latestMessageContentRef.current)
96
+ setIsEditing(true)
97
+ }, [forceEdit, isEditing])
94
98
 
95
99
  const timestamp = new Date(message.createdAt)
96
100
  const timeStr = timestamp.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })
97
- const dateTimeStr = timestamp.toLocaleDateString(undefined, {
98
- month: '2-digit',
99
- day: '2-digit',
100
- year: '2-digit',
101
- }) + ', ' + timeStr
101
+ const dateTimeStr =
102
+ timestamp.toLocaleDateString(undefined, {
103
+ month: '2-digit',
104
+ day: '2-digit',
105
+ year: '2-digit',
106
+ }) +
107
+ ', ' +
108
+ timeStr
102
109
 
103
110
  const exitEdit = useCallback(() => {
104
111
  setIsEditing(false)
@@ -173,10 +180,7 @@ export function MessageItem({
173
180
  }`}
174
181
  >
175
182
  <div className="shrink-0 w-10" />
176
- <p
177
- data-testid="deleted-tombstone"
178
- className="text-sm italic text-muted-foreground/70"
179
- >
183
+ <p data-testid="deleted-tombstone" className="text-sm italic text-muted-foreground/70">
180
184
  This message was deleted.
181
185
  </p>
182
186
  </div>
@@ -205,10 +209,7 @@ export function MessageItem({
205
209
  </div>
206
210
 
207
211
  {/* Content column */}
208
- <div
209
- ref={contentRef}
210
- className={`flex-1 min-w-0 ${isHighlighted ? 'invisible' : ''}`}
211
- >
212
+ <div ref={contentRef} className={`flex-1 min-w-0 ${isHighlighted ? 'invisible' : ''}`}>
212
213
  {isFirstInGroup && (
213
214
  <div className="flex items-baseline gap-2 mb-0.5">
214
215
  <UserProfilePopover userId={authorId} onStartDM={onStartDM}>
@@ -239,7 +240,13 @@ export function MessageItem({
239
240
  className="p-1.5 rounded-md bg-primary/10 text-primary hover:bg-primary/20 transition-colors"
240
241
  title="Save"
241
242
  >
242
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
243
+ <svg
244
+ className="w-4 h-4"
245
+ fill="none"
246
+ viewBox="0 0 24 24"
247
+ stroke="currentColor"
248
+ strokeWidth={2}
249
+ >
243
250
  <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
244
251
  </svg>
245
252
  </button>
@@ -249,7 +256,13 @@ export function MessageItem({
249
256
  className="p-1.5 rounded-md bg-muted text-muted-foreground hover:bg-destructive/10 hover:text-destructive transition-colors"
250
257
  title="Cancel"
251
258
  >
252
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
259
+ <svg
260
+ className="w-4 h-4"
261
+ fill="none"
262
+ viewBox="0 0 24 24"
263
+ stroke="currentColor"
264
+ strokeWidth={2}
265
+ >
253
266
  <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
254
267
  </svg>
255
268
  </button>
@@ -257,7 +270,10 @@ export function MessageItem({
257
270
  </div>
258
271
  </div>
259
272
  ) : (
260
- <p className="text-sm text-foreground/90 whitespace-pre-wrap break-words leading-relaxed" data-testid="message-content">
273
+ <p
274
+ className="text-sm text-foreground/90 whitespace-pre-wrap break-words leading-relaxed"
275
+ data-testid="message-content"
276
+ >
261
277
  {message.data.content}
262
278
  {message.data.edited ? (
263
279
  <span className="text-[10px] ml-1.5 text-muted-foreground italic">(edited)</span>
@@ -300,7 +316,10 @@ export function MessageItem({
300
316
 
301
317
  {/* Desktop hover toolbar */}
302
318
  {!isEditing && (
303
- <div data-testid={`hover-toolbar-${message.recordId}`} className="absolute -top-3 right-5 flex items-center gap-0.5 bg-card border border-border/60 rounded-lg shadow-sm p-0.5 transition-all duration-100 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto">
319
+ <div
320
+ data-testid={`hover-toolbar-${message.recordId}`}
321
+ className="absolute -top-3 right-5 flex items-center gap-0.5 bg-card border border-border/60 rounded-lg shadow-sm p-0.5 transition-all duration-100 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto"
322
+ >
304
323
  {QUICK_EMOJIS.map((emoji) => (
305
324
  <button
306
325
  key={emoji}
@@ -318,8 +337,18 @@ export function MessageItem({
318
337
  className="p-1.5 rounded-md hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
319
338
  title="Reply in thread"
320
339
  >
321
- <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
322
- <path strokeLinecap="round" strokeLinejoin="round" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
340
+ <svg
341
+ className="w-3.5 h-3.5"
342
+ fill="none"
343
+ viewBox="0 0 24 24"
344
+ stroke="currentColor"
345
+ strokeWidth={1.5}
346
+ >
347
+ <path
348
+ strokeLinecap="round"
349
+ strokeLinejoin="round"
350
+ d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"
351
+ />
323
352
  </svg>
324
353
  </button>
325
354
  {isOwn && (
@@ -333,8 +362,18 @@ export function MessageItem({
333
362
  className="p-1.5 rounded-md hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
334
363
  title="Edit message"
335
364
  >
336
- <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
337
- <path strokeLinecap="round" strokeLinejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
365
+ <svg
366
+ className="w-3.5 h-3.5"
367
+ fill="none"
368
+ viewBox="0 0 24 24"
369
+ stroke="currentColor"
370
+ strokeWidth={1.5}
371
+ >
372
+ <path
373
+ strokeLinecap="round"
374
+ strokeLinejoin="round"
375
+ d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
376
+ />
338
377
  </svg>
339
378
  </button>
340
379
  <button
@@ -343,8 +382,18 @@ export function MessageItem({
343
382
  className="p-1.5 rounded-md hover:bg-destructive/10 text-muted-foreground hover:text-destructive transition-colors"
344
383
  title="Delete message"
345
384
  >
346
- <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
347
- <path strokeLinecap="round" strokeLinejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
385
+ <svg
386
+ className="w-3.5 h-3.5"
387
+ fill="none"
388
+ viewBox="0 0 24 24"
389
+ stroke="currentColor"
390
+ strokeWidth={1.5}
391
+ >
392
+ <path
393
+ strokeLinecap="round"
394
+ strokeLinejoin="round"
395
+ d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
396
+ />
348
397
  </svg>
349
398
  </button>
350
399
  </>
@@ -36,9 +36,7 @@ export function useChatChannel(channelName: string = DEFAULT_CHANNEL_NAME) {
36
36
  const defaultChannel = channels
37
37
  .filter(
38
38
  (c: RecordData<Channel>) =>
39
- c.data.name === channelName &&
40
- c.data.type === 'public' &&
41
- !c.data.archived,
39
+ c.data.name === channelName && c.data.type === 'public' && !c.data.archived,
42
40
  )
43
41
  .sort(
44
42
  (a: RecordData<Channel>, b: RecordData<Channel>) =>
@@ -59,11 +57,7 @@ export function useChatChannel(channelName: string = DEFAULT_CHANNEL_NAME) {
59
57
  description: 'Default chat channel',
60
58
  })
61
59
  }
62
- // Deps intentionally limited to [user, status]: this is a one-shot init
63
- // guarded by hasInitialized. Including defaultChannel/create/channelName
64
- // would re-run and defeat the create-once semantics.
65
- // eslint-disable-next-line react-hooks/exhaustive-deps -- one-shot init guarded by hasInitialized; adding defaultChannel/create/channelName would defeat create-once
66
- }, [user, status])
60
+ }, [user, status, defaultChannel, create, channelName])
67
61
 
68
62
  // Wait for the members query to be ready before auto-joining.
69
63
  useEffect(() => {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "deepspace",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "engines": {
5
- "node": ">=22.15.0"
5
+ "node": ">=22.15.0 <23 || >=24 <25 || >=26 <27"
6
6
  },
7
7
  "description": "DeepSpace SDK — build real-time collaborative apps on Cloudflare Workers",
8
8
  "type": "module",