All checks were successful
Build and Release / build-and-release (push) Successful in 14m19s
6.2 KiB
6.2 KiB
Update this file when making significant changes.
See also: CONVEX_RULES.md | CONVEX_EXAMPLES.md
Architecture
- Backend: Convex (reactive database + serverless functions)
- Frontend: React + Vite (Electron app)
- Auth: Zero-knowledge custom auth via Convex mutations (getSalt, verifyUser, createUserWithProfile)
- Real-time: Convex reactive queries (
useQueryauto-updates all connected clients) - Voice/Video: LiveKit (token generation via Convex Node action)
- E2E Encryption: Client-side via Electron IPC (
window.cryptoAPI) - File Storage: Convex built-in storage (
generateUploadUrl+getUrl)
Key Convex Files (convex/)
schema.ts- Full schema: userProfiles (with avatarStorageId, aboutMe, customStatus), categories (name, position), channels (with categoryId, topic, position), messages, messageReactions, channelKeys, roles, userRoles, invites, dmParticipants, typingIndicators, voiceStates, channelReadState, serverSettings (afkChannelId, afkTimeout)auth.ts- getSalt, verifyUser, createUserWithProfile, getPublicKeys (includes avatarUrl, aboutMe, customStatus), updateProfile, updateStatuscategories.ts- list, create, rename, remove, reorderchannels.ts- list, get, create (with categoryId/topic/position), rename, remove (cascade), updateTopic, moveChannel, reorderChannelsmembers.ts- getChannelMembers (includes isHoist on roles, avatarUrl, aboutMe, customStatus)channelKeys.ts- uploadKeys, getKeysForUsermessages.ts- list (with reactions + username), send, edit, pin, listPinned, remove (with manage_messages permission check)reactions.ts- add, removeserverSettings.ts- get, update (manage_channels permission), clearAfkChannel (internal)typing.ts- startTyping, stopTyping, getTyping, cleanExpired (scheduled)dms.ts- openDM, listDMsinvites.ts- create, use, revokeroles.ts- list, create, update, remove, listMembers, assign, unassign, getMyPermissionsvoiceState.ts- join, leave, updateState, getAll, afkMove (self-move to AFK channel)voice.ts- getToken (Node action, livekit-server-sdk)files.ts- generateUploadUrl, getFileUrlgifs.ts- search, categories (Node actions, Tenor API)readState.ts- getReadState, markRead, getAllReadStates, getLatestMessageTimestamps (unread tracking)
Frontend Structure (Frontend/Electron/src/)
main.jsx- ConvexProvider + VoiceProvider + HashRouterpages/Login.jsx- Convex auth (getSalt + verifyUser)pages/Register.jsx- Convex auth (createUserWithProfile + invite flow)pages/Chat.jsx- useQuery for channels, categories, channelKeys, DMscomponents/ChatArea.jsx- Messages, typing, reactions via Convex queries/mutationscomponents/Sidebar.jsx- Channel/category creation, key distribution, invites, drag-and-drop reordering via @dnd-kitcontexts/VoiceContext.jsx- Voice state via Convex + LiveKit room managementcomponents/ChannelSettingsModal.jsx- Channel rename/delete via Convex mutationscomponents/ServerSettingsModal.jsx- Role management via Convex queries/mutationscomponents/MessageItem.jsx- Individual message rendering with unread divider supportcomponents/Avatar.jsx- Reusable avatar component (image or colored-initial fallback)components/FriendsView.jsx- User list via Convex querycomponents/DMList.jsx- DM user picker via Convex querycomponents/GifPicker.jsx- GIF search via Convex actioncomponents/VoiceRoom.jsx- LiveKit token via Convex action
Important Patterns
- Channel IDs use Convex
_id(notid) - all references usechannel._id - Auth: client hashes DAK -> HAK before sending, server does string comparison
- First user bootstrap: createUserWithProfile creates Owner + @everyone roles
- Vite config uses
envDir: '../../'to pick up root.env.local socket.io-clientfully removed, all socket refs replaced with Convex- No Express backend needed -
Backend/directory is legacy and can be deleted - Convex queries are reactive - no need for manual refresh or socket listeners
- File uploads use Convex storage:
generateUploadUrl-> POST blob ->getFileUrl - Typing indicators use scheduled functions for TTL cleanup
- CSS uses Discord dark theme colors via
:rootvariables (--bg-primary: #313338,--bg-secondary: #2b2d31,--bg-tertiary: #1e1f22) - Sidebar width is 312px (72px server strip + 240px channel panel)
- Channels are grouped by
categoryId(referencescategoriestable) with collapsible headers and drag-and-drop reordering (@dnd-kit) - Categories are first-class entities with position-based ordering; uncategorized channels show under "Channels" group
- Members list groups by hoisted roles (isHoist) then Online/Offline
- Avatar component supports both image URLs and colored-initial fallback
- Title bar has back/forward navigation arrows
- Chat header includes thread, pin, members, notification icons + channel topic
- Voice connected panel includes elapsed time timer
- Keyboard shortcuts: Ctrl+K (quick switcher), Ctrl+Shift+M (mute toggle)
- Unread tracking:
channelReadStatetable stores last-read timestamp per user/channel. ChatArea shows red "NEW" divider, Sidebar shows white dot on unread channels - AFK voice channel:
serverSettingssingleton table storesafkChannelId+afkTimeout. VoiceContext pollsidleAPI.getSystemIdleTime()every 15s; auto-moves idle users to AFK channel viavoiceState.afkMove. Users in AFK channel are force-muted and can't unmute. Sidebar shows "(AFK)" label. Server Settings Overview tab has AFK config UI.
Environment Variables
In .env.local at project root:
CONVEX_DEPLOYMENT- Convex deployment URL (set bynpx convex dev)VITE_CONVEX_URL- Convex URL for frontend (set bynpx convex dev)VITE_LIVEKIT_URL- LiveKit server URLLIVEKIT_API_KEY- LiveKit API key (used in Convex Node action)LIVEKIT_API_SECRET- LiveKit API secret (used in Convex Node action)TENOR_API_KEY- Tenor GIF API key (used in Convex Node action)
Running the App
npm install && npm run install:frontendnpx convex dev(starts Convex backend, creates.env.local)- In another terminal:
cd Frontend/Electron && npm run dev(ornpm run electron:dev)