deepspace 0.26.0 → 0.26.1

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 (52) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/{chunk-LPXWXF6R.js → chunk-YBXIUDZJ.js} +2 -2
  3. package/dist/{chunk-LPXWXF6R.js.map → chunk-YBXIUDZJ.js.map} +1 -1
  4. package/dist/cli.js +80 -45
  5. package/dist/cli.js.map +1 -1
  6. package/dist/documentation-client-core.js +1 -1
  7. package/dist/documentation-default-renderer.cjs +1 -1
  8. package/dist/documentation-react.js +1 -1
  9. package/dist/documentation-runtime.js +1 -1
  10. package/dist/documentation-server-core.js +1 -1
  11. package/dist/documentation.js +1 -1
  12. package/dist/documentation.js.map +1 -1
  13. package/dist/index.d.ts +26 -434
  14. package/dist/index.js +99 -864
  15. package/dist/index.js.map +1 -1
  16. package/dist/schema.d.ts +4 -41
  17. package/dist/schema.js +2 -242
  18. package/dist/schema.js.map +1 -1
  19. package/dist/server.d.ts +6 -162
  20. package/dist/server.js +13 -281
  21. package/dist/server.js.map +1 -1
  22. package/dist/worker.d.ts +4 -160
  23. package/dist/worker.js +15 -281
  24. package/dist/worker.js.map +1 -1
  25. package/features/messaging/feature.json +3 -43
  26. package/features/messaging/src/ChatPage.tsx +12 -16
  27. package/features/messaging/src/components/chat/ChatHeader.tsx +5 -13
  28. package/features/messaging/src/components/chat/MessageItem.tsx +7 -9
  29. package/features/messaging/src/components/chat/MessageList.tsx +57 -40
  30. package/features/messaging/src/components/chat/ThreadPanel.tsx +25 -11
  31. package/features/messaging/src/components/chat/UserProfilePopover.tsx +10 -29
  32. package/features/messaging/src/hooks/useChatChannel.ts +18 -23
  33. package/features/messaging/src/messaging-schema.ts +0 -2
  34. package/features/search-bar/feature.json +9 -20
  35. package/features/search-bar/src/InlineSearch.tsx +101 -0
  36. package/features/search-bar/src/__tests__/search-model.test.tsx +38 -0
  37. package/features/search-bar/src/search-model.ts +2 -149
  38. package/package.json +1 -1
  39. package/features/messaging/src/ChatMultiPage.tsx +0 -298
  40. package/features/messaging/src/components/chat-multi/AddMemberModal.tsx +0 -188
  41. package/features/messaging/src/components/chat-multi/BrowseGroupsModal.tsx +0 -163
  42. package/features/messaging/src/components/chat-multi/ChannelListItem.tsx +0 -147
  43. package/features/messaging/src/components/chat-multi/ChannelSettingsPanel.tsx +0 -344
  44. package/features/messaging/src/components/chat-multi/ChannelSidebar.tsx +0 -460
  45. package/features/messaging/src/components/chat-multi/CreateGroupModal.tsx +0 -190
  46. package/features/messaging/src/components/chat-multi/InvitationList.tsx +0 -149
  47. package/features/messaging/src/components/chat-multi/NewMessageModal.tsx +0 -159
  48. package/features/messaging/src/hooks/useMultiChannel.ts +0 -142
  49. package/features/search-bar/src/SearchOverlay.tsx +0 -566
  50. package/features/search-bar/src/__tests__/search-model.test.ts +0 -40
  51. package/features/sidebar/feature.json +0 -19
  52. package/features/sidebar/src/AppSidebar.tsx +0 -558
@@ -1,147 +0,0 @@
1
- /**
2
- * ChannelListItem -- Single conversation row in the channel sidebar.
3
- *
4
- * Visual treatment varies by type:
5
- * - Groups: colored icon badge with group/lock icon
6
- * - DMs: user avatar with online presence ring
7
- *
8
- * Active state uses a left accent bar + tinted background.
9
- * Unread state uses bold text + pulsing dot indicator.
10
- */
11
-
12
- import type { ReactNode } from 'react'
13
-
14
- interface ChannelListItemProps {
15
- name: string
16
- isActive: boolean
17
- hasUnread: boolean
18
- isPrivate?: boolean
19
- avatarUrl?: string
20
- avatarFallback?: string
21
- isOnline?: boolean
22
- onClick: () => void
23
- trailing?: ReactNode
24
- }
25
-
26
- const GROUP_COLORS = [
27
- 'from-primary/20 to-primary/10 text-primary',
28
- 'from-primary/15 to-primary/25 text-primary',
29
- 'from-primary/10 to-primary/20 text-primary',
30
- 'from-primary/25 to-primary/15 text-primary',
31
- 'from-primary/20 to-primary/5 text-primary',
32
- 'from-primary/5 to-primary/20 text-primary',
33
- ]
34
-
35
- function hashToIndex(str: string, max: number): number {
36
- let hash = 0
37
- for (let i = 0; i < str.length; i++) {
38
- hash = str.charCodeAt(i) + ((hash << 5) - hash)
39
- }
40
- return Math.abs(hash) % max
41
- }
42
-
43
- export function ChannelListItem({
44
- name,
45
- isActive,
46
- hasUnread,
47
- isPrivate,
48
- avatarUrl,
49
- avatarFallback,
50
- isOnline,
51
- onClick,
52
- trailing,
53
- }: ChannelListItemProps) {
54
- const showAvatar = avatarUrl !== undefined || avatarFallback !== undefined
55
- const colorIdx = hashToIndex(name, GROUP_COLORS.length)
56
-
57
- return (
58
- <button
59
- type="button"
60
- onClick={onClick}
61
- data-testid={`channel-link-${name}`}
62
- className={`
63
- group/item relative w-full flex items-center gap-3 px-3 py-2.5 rounded-xl
64
- text-left transition-all duration-200 cursor-pointer
65
- ${isActive
66
- ? 'bg-primary/8 shadow-sm shadow-primary/5'
67
- : 'hover:bg-muted/50 active:bg-muted/70'
68
- }
69
- `}
70
- >
71
- {/* Active indicator bar */}
72
- {isActive && (
73
- <span className="absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-5 rounded-r-full bg-primary" />
74
- )}
75
-
76
- {/* Avatar / icon */}
77
- {showAvatar ? (
78
- <div className="relative shrink-0">
79
- {avatarUrl ? (
80
- <img
81
- src={avatarUrl}
82
- alt=""
83
- className={`w-9 h-9 rounded-full object-cover ring-2 transition-all duration-200 ${
84
- isActive
85
- ? 'ring-primary/20'
86
- : 'ring-transparent group-hover/item:ring-muted'
87
- }`}
88
- />
89
- ) : (
90
- <div
91
- className={`w-9 h-9 rounded-full bg-gradient-to-br ${GROUP_COLORS[colorIdx]} flex items-center justify-center text-sm font-semibold ring-2 transition-all duration-200 ${
92
- isActive
93
- ? 'ring-primary/20'
94
- : 'ring-transparent group-hover/item:ring-muted'
95
- }`}
96
- >
97
- {avatarFallback?.[0]?.toUpperCase() ?? '?'}
98
- </div>
99
- )}
100
- {isOnline !== undefined && (
101
- <span
102
- className={`absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-[2.5px] border-card transition-colors duration-200 ${
103
- isOnline ? 'bg-success' : 'bg-muted-foreground/20'
104
- }`}
105
- />
106
- )}
107
- </div>
108
- ) : (
109
- <span
110
- className={`shrink-0 w-9 h-9 rounded-xl bg-gradient-to-br ${GROUP_COLORS[colorIdx]} flex items-center justify-center transition-all duration-200`}
111
- >
112
- {isPrivate ? (
113
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
114
- <path strokeLinecap="round" strokeLinejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
115
- </svg>
116
- ) : (
117
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
118
- <path strokeLinecap="round" strokeLinejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
119
- </svg>
120
- )}
121
- </span>
122
- )}
123
-
124
- {/* Name + meta */}
125
- <div className="flex-1 min-w-0">
126
- <span
127
- className={`block text-[13px] leading-tight truncate transition-colors duration-150 ${
128
- isActive
129
- ? 'font-semibold text-foreground'
130
- : hasUnread
131
- ? 'font-semibold text-foreground'
132
- : 'font-medium text-muted-foreground group-hover/item:text-foreground'
133
- }`}
134
- >
135
- {name}
136
- </span>
137
- </div>
138
-
139
- {/* Unread dot */}
140
- {hasUnread && !isActive && (
141
- <span className="shrink-0 w-2.5 h-2.5 rounded-full bg-primary animate-pulse" />
142
- )}
143
-
144
- {trailing}
145
- </button>
146
- )
147
- }
@@ -1,344 +0,0 @@
1
- /**
2
- * ChannelSettingsPanel -- View/edit group info, manage members, leave.
3
- * Renders as a slide-over panel on the right side of the chat.
4
- *
5
- * Permissions:
6
- * - Public groups: no invite button (users join via Browse Groups)
7
- * - Private groups: only the creator can invite others (invitation-based)
8
- */
9
-
10
- import { useState, useCallback, useMemo } from 'react'
11
- import { useChannels } from 'deepspace'
12
- import { useChannelMembers } from 'deepspace'
13
- import { useUser } from 'deepspace'
14
- import { useUserLookup } from 'deepspace'
15
- import { useQuery } from 'deepspace'
16
- import type { Channel, ChannelInvitation } from 'deepspace'
17
- import type { RecordData } from 'deepspace'
18
- import { AddMemberModal } from './AddMemberModal'
19
-
20
- interface ChannelSettingsPanelProps {
21
- channelId: string
22
- onClose: () => void
23
- onLeave: () => void
24
- }
25
-
26
- export function ChannelSettingsPanel({ channelId, onClose, onLeave }: ChannelSettingsPanelProps) {
27
- const { channels, update } = useChannels()
28
- const { members, leave } = useChannelMembers(channelId)
29
- const { user } = useUser()
30
- const { getUser } = useUserLookup()
31
- const [showAddMember, setShowAddMember] = useState(false)
32
- const [isEditing, setIsEditing] = useState(false)
33
- const [editName, setEditName] = useState('')
34
- const [editDescription, setEditDescription] = useState('')
35
- const [isSaving, setIsSaving] = useState(false)
36
-
37
- const channel = channels.find((c: RecordData<Channel>) => c.recordId === channelId)
38
- const isCreator = channel?.createdBy === user?.id
39
- const isPrivate = channel?.data.type === 'private'
40
- const canInvite = isPrivate && isCreator
41
-
42
- const { records: channelInvitations } = useQuery<ChannelInvitation>('channel-invitations', {
43
- where: { channelId },
44
- })
45
- const pendingInvites = useMemo(
46
- () => channelInvitations.filter(
47
- (inv: RecordData<ChannelInvitation>) => inv.data.status === 'pending'
48
- ),
49
- [channelInvitations]
50
- )
51
-
52
- const sortedMembers = useMemo(
53
- () =>
54
- [...members].sort((a, b) => {
55
- const aUser = getUser(a.data.userId)
56
- const bUser = getUser(b.data.userId)
57
- return (aUser?.name ?? '').localeCompare(bUser?.name ?? '')
58
- }),
59
- [members, getUser]
60
- )
61
-
62
- const handleLeave = useCallback(async () => {
63
- await leave()
64
- onLeave()
65
- }, [leave, onLeave])
66
-
67
- const startEditing = useCallback(() => {
68
- if (!channel) return
69
- setEditName(channel.data.name)
70
- setEditDescription(channel.data.description ?? '')
71
- setIsEditing(true)
72
- }, [channel])
73
-
74
- const cancelEditing = useCallback(() => {
75
- setIsEditing(false)
76
- }, [])
77
-
78
- const saveEditing = useCallback(async () => {
79
- if (!channel || isSaving) return
80
- const trimmedName = editName.trim().toLowerCase().replace(/\s+/g, '-')
81
- if (!trimmedName) return
82
-
83
- setIsSaving(true)
84
- try {
85
- await update(channelId, {
86
- name: trimmedName,
87
- description: editDescription.trim(),
88
- })
89
- setIsEditing(false)
90
- } finally {
91
- setIsSaving(false)
92
- }
93
- }, [channel, isSaving, editName, editDescription, update, channelId])
94
-
95
- if (!channel) return null
96
-
97
- return (
98
- <>
99
- <div
100
- data-testid="channel-settings-panel"
101
- className="w-full md:w-80 border-l border-border flex flex-col h-full bg-card shrink-0"
102
- >
103
- {/* Header */}
104
- <div className="shrink-0 h-12 border-b border-border px-4 flex items-center justify-between">
105
- <h3 className="font-semibold text-sm text-foreground">Group Info</h3>
106
- <button
107
- data-testid="close-settings-btn"
108
- onClick={onClose}
109
- className="p-1 rounded-md hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
110
- >
111
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
112
- <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
113
- </svg>
114
- </button>
115
- </div>
116
-
117
- <div className="flex-1 overflow-y-auto">
118
- {/* Channel info */}
119
- <div className="px-4 py-5">
120
- {isEditing ? (
121
- <div className="space-y-3">
122
- <div>
123
- <label className="text-xs font-medium text-muted-foreground mb-1 block">
124
- Group name
125
- </label>
126
- <input
127
- data-testid="edit-channel-name"
128
- type="text"
129
- value={editName}
130
- onChange={(e) => setEditName(e.target.value)}
131
- onKeyDown={(e) => {
132
- if (e.key === 'Enter') saveEditing()
133
- if (e.key === 'Escape') cancelEditing()
134
- }}
135
- className="w-full bg-background border border-border rounded-lg px-3 py-2 text-sm text-foreground outline-none focus:border-primary/50 focus:ring-2 focus:ring-primary/20 transition-shadow"
136
- autoFocus
137
- />
138
- </div>
139
- <div>
140
- <label className="text-xs font-medium text-muted-foreground mb-1 block">
141
- Description
142
- </label>
143
- <textarea
144
- data-testid="edit-channel-description"
145
- value={editDescription}
146
- onChange={(e) => setEditDescription(e.target.value)}
147
- onKeyDown={(e) => {
148
- if (e.key === 'Escape') cancelEditing()
149
- }}
150
- rows={2}
151
- className="w-full bg-background border border-border rounded-lg px-3 py-2 text-sm text-foreground outline-none focus:border-primary/50 focus:ring-2 focus:ring-primary/20 transition-shadow resize-none"
152
- />
153
- </div>
154
- <div className="flex gap-2">
155
- <button
156
- data-testid="save-channel-edit"
157
- onClick={saveEditing}
158
- disabled={isSaving || !editName.trim()}
159
- className="flex-1 px-3 py-1.5 text-xs font-medium bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-40 transition-colors"
160
- >
161
- {isSaving ? 'Saving...' : 'Save'}
162
- </button>
163
- <button
164
- data-testid="cancel-channel-edit"
165
- onClick={cancelEditing}
166
- disabled={isSaving}
167
- className="flex-1 px-3 py-1.5 text-xs font-medium text-muted-foreground border border-border rounded-lg hover:bg-muted transition-colors"
168
- >
169
- Cancel
170
- </button>
171
- </div>
172
- </div>
173
- ) : (
174
- <>
175
- <div className="flex items-center gap-3">
176
- <div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center shrink-0">
177
- {isPrivate ? (
178
- <svg className="w-5 h-5 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
179
- <path strokeLinecap="round" strokeLinejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
180
- </svg>
181
- ) : (
182
- <svg className="w-5 h-5 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
183
- <path strokeLinecap="round" strokeLinejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
184
- </svg>
185
- )}
186
- </div>
187
- <div className="flex-1 min-w-0">
188
- <h4 className="text-base font-semibold text-foreground truncate">
189
- {channel.data.name}
190
- </h4>
191
- <span className="text-xs text-muted-foreground capitalize">
192
- {channel.data.type} group
193
- </span>
194
- </div>
195
- {isCreator && (
196
- <button
197
- data-testid="edit-channel-btn"
198
- onClick={startEditing}
199
- className="shrink-0 p-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
200
- title="Edit group"
201
- >
202
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
203
- <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" />
204
- </svg>
205
- </button>
206
- )}
207
- </div>
208
- {channel.data.description && (
209
- <p className="mt-3 text-sm text-muted-foreground leading-relaxed">
210
- {channel.data.description}
211
- </p>
212
- )}
213
- </>
214
- )}
215
- </div>
216
-
217
- <div className="h-px bg-border mx-4" />
218
-
219
- {/* Members section */}
220
- <div className="px-4 py-4">
221
- <div className="flex items-center justify-between mb-3">
222
- <h4 className="text-sm font-semibold text-foreground">
223
- Members
224
- <span className="text-muted-foreground font-normal ml-1.5">
225
- {members.length}
226
- </span>
227
- </h4>
228
- {canInvite && (
229
- <button
230
- data-testid="add-member-btn"
231
- onClick={() => setShowAddMember(true)}
232
- className="text-xs font-medium text-primary hover:text-primary/80 transition-colors"
233
- >
234
- Invite member
235
- </button>
236
- )}
237
- </div>
238
-
239
- <div className="space-y-0.5">
240
- {sortedMembers.map((m) => {
241
- const memberUser = getUser(m.data.userId)
242
- const isCurrentUser = m.data.userId === user?.id
243
- const isMemberCreator = m.data.userId === channel.createdBy
244
-
245
- return (
246
- <div
247
- key={m.recordId}
248
- className="flex items-center gap-2.5 px-2 py-2 rounded-lg"
249
- >
250
- {memberUser?.imageUrl ? (
251
- <img src={memberUser.imageUrl} alt="" className="w-8 h-8 rounded-full object-cover" />
252
- ) : (
253
- <div className="w-8 h-8 rounded-full bg-primary/15 flex items-center justify-center text-xs font-semibold text-primary">
254
- {(memberUser?.name ?? '?')[0]?.toUpperCase()}
255
- </div>
256
- )}
257
- <div className="flex-1 min-w-0">
258
- <span className="text-sm text-foreground truncate block">
259
- {memberUser?.name ?? 'Unknown'}
260
- {isCurrentUser && (
261
- <span className="text-muted-foreground ml-1">(you)</span>
262
- )}
263
- </span>
264
- </div>
265
- {isMemberCreator && (
266
- <span className="text-[10px] font-medium text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
267
- Creator
268
- </span>
269
- )}
270
- </div>
271
- )
272
- })}
273
- </div>
274
- </div>
275
-
276
- {/* Pending invitations (private groups only) */}
277
- {isPrivate && pendingInvites.length > 0 && (
278
- <>
279
- <div className="h-px bg-border mx-4" />
280
- <div className="px-4 py-4">
281
- <h4 className="text-sm font-semibold text-foreground mb-3">
282
- Pending Invitations
283
- <span className="text-muted-foreground font-normal ml-1.5">
284
- {pendingInvites.length}
285
- </span>
286
- </h4>
287
- <div className="space-y-0.5">
288
- {pendingInvites.map((inv: RecordData<ChannelInvitation>) => {
289
- const invitedUser = getUser(inv.data.invitedUserId)
290
- return (
291
- <div
292
- key={inv.recordId}
293
- className="flex items-center gap-2.5 px-2 py-2 rounded-lg"
294
- >
295
- {invitedUser?.imageUrl ? (
296
- <img src={invitedUser.imageUrl} alt="" className="w-8 h-8 rounded-full object-cover opacity-60" />
297
- ) : (
298
- <div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center text-xs font-semibold text-muted-foreground">
299
- {(invitedUser?.name ?? '?')[0]?.toUpperCase()}
300
- </div>
301
- )}
302
- <div className="flex-1 min-w-0">
303
- <span className="text-sm text-muted-foreground truncate block">
304
- {invitedUser?.name ?? 'Unknown'}
305
- </span>
306
- </div>
307
- <span className="text-[10px] font-medium text-warning bg-warning/10 px-1.5 py-0.5 rounded">
308
- Pending
309
- </span>
310
- </div>
311
- )
312
- })}
313
- </div>
314
- </div>
315
- </>
316
- )}
317
-
318
- <div className="h-px bg-border mx-4" />
319
-
320
- {/* Leave group */}
321
- <div className="px-4 py-4">
322
- <button
323
- data-testid="leave-group-btn"
324
- onClick={handleLeave}
325
- className="w-full flex items-center gap-2.5 px-3 py-2.5 rounded-lg text-sm text-destructive hover:bg-destructive/10 transition-colors"
326
- >
327
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
328
- <path strokeLinecap="round" strokeLinejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
329
- </svg>
330
- Leave group
331
- </button>
332
- </div>
333
- </div>
334
- </div>
335
-
336
- {showAddMember && (
337
- <AddMemberModal
338
- channelId={channelId}
339
- onClose={() => setShowAddMember(false)}
340
- />
341
- )}
342
- </>
343
- )
344
- }