**Update this file when making significant changes.** See also: [CONVEX_RULES.md](./CONVEX_RULES.md) | [CONVEX_EXAMPLES.md](./CONVEX_EXAMPLES.md) ## Architecture - **Monorepo**: npm workspaces (`packages/*`, `apps/*`) - **Backend**: Convex (reactive database + serverless functions) - **Frontend**: React + Vite, shared codebase in `packages/shared/` - **Platforms**: Electron (`apps/electron/`), Web (`apps/web/`), Android via Capacitor (`apps/android/`) - **Platform Abstraction**: `usePlatform()` hook provides crypto, session, settings, idle, links, screenCapture, windowControls, updates APIs - **Auth**: Zero-knowledge custom auth via Convex mutations (getSalt, verifyUser, createUserWithProfile) - **Real-time**: Convex reactive queries (`useQuery` auto-updates all connected clients) - **Voice/Video**: LiveKit (token generation via Convex Node action) - **E2E Encryption**: Platform-specific crypto (Electron: Node crypto via IPC, Web: Web Crypto API) - **File Storage**: Convex built-in storage (`generateUploadUrl` + `getUrl`) ## Project Structure ``` Discord Clone/ ├── convex/ # Backend (Convex functions + schema) ├── packages/ │ ├── shared/ # Shared React app (all components, pages, contexts, styles) │ │ └── src/ │ │ ├── components/ # All UI components │ │ ├── pages/ # Login, Register, Chat │ │ ├── contexts/ # VoiceContext, ThemeContext, PresenceContext │ │ ├── platform/ # PlatformProvider + usePlatform hook │ │ ├── styles/ # themes.css │ │ ├── utils/ # userPreferences.js, streamUtils.jsx │ │ ├── assets/ # sounds, icons, emojis, fonts │ │ ├── App.jsx # Router + AuthGuard │ │ └── index.css # Global styles │ └── platform-web/ # Web/Capacitor platform implementations │ └── src/ │ ├── crypto.js # Web Crypto API (RSA-OAEP, Ed25519, AES-256-GCM, scrypt) │ ├── session.js # localStorage session persistence │ ├── settings.js # localStorage settings │ ├── idle.js # Page Visibility API idle detection │ └── index.js # Bundles all web platform APIs ├── apps/ │ ├── electron/ # Electron desktop app │ │ ├── main.cjs # Electron main process │ │ ├── preload.cjs # IPC bridge (window.* APIs) │ │ ├── updater.cjs # electron-updater integration │ │ ├── splash.html # Update splash screen │ │ └── src/ │ │ ├── main.jsx # Entry: PlatformProvider + HashRouter │ │ └── platform/index.js # Electron platform (delegates to window.* APIs) │ ├── web/ # Web browser app │ │ └── src/ │ │ └── main.jsx # Entry: PlatformProvider + BrowserRouter │ └── android/ # Capacitor Android wrapper │ └── capacitor.config.ts # Points webDir at ../web/dist ├── package.json # Root workspace config ├── .env.local # Convex + LiveKit + Tenor keys └── CLAUDE.md ``` ## Key Convex Files (convex/) - `schema.ts` - Full schema: userProfiles (with avatarStorageId, aboutMe, customStatus, joinSoundStorageId), categories (name, position), channels (with categoryId, topic, position), messages, messageReactions, channelKeys, roles, userRoles, invites, dmParticipants, typingIndicators, voiceStates, channelReadState, serverSettings (serverName, afkChannelId, afkTimeout, iconStorageId) - `auth.ts` - getSalt, verifyUser, createUserWithProfile, getPublicKeys (includes avatarUrl, aboutMe, customStatus, joinSoundUrl), updateProfile (includes joinSoundStorageId, removeJoinSound), updateStatus, getMyJoinSoundUrl - `categories.ts` - list, create, rename, remove, reorder - `channels.ts` - list, get, create (with categoryId/topic/position), rename, remove (cascade), updateTopic, moveChannel, reorderChannels - `members.ts` - getChannelMembers (includes isHoist on roles, avatarUrl, aboutMe, customStatus) - `channelKeys.ts` - uploadKeys, getKeysForUser - `messages.ts` - list (with reactions + username), send, edit, pin, listPinned, remove (with manage_messages permission check) - `reactions.ts` - add, remove - `serverSettings.ts` - get (resolves iconUrl), update (manage_channels permission), updateName (manage_channels permission), updateIcon (manage_channels permission), clearAfkChannel (internal) - `typing.ts` - startTyping, stopTyping, getTyping, cleanExpired (scheduled) - `dms.ts` - openDM, listDMs - `invites.ts` - create, use, revoke - `roles.ts` - list, create, update, remove, listMembers, assign, unassign, getMyPermissions - `voiceState.ts` - join, leave, updateState, getAll (includes joinSoundUrl per user), afkMove (self-move to AFK channel) - `voice.ts` - getToken (Node action, livekit-server-sdk) - `files.ts` - generateUploadUrl, getFileUrl - `gifs.ts` - search, categories (Node actions, Tenor API) - `readState.ts` - getReadState, markRead, getAllReadStates, getLatestMessageTimestamps (unread tracking) ## Shared Frontend (packages/shared/src/) - `App.jsx` - Router + AuthGuard (uses `usePlatform().session`) - `pages/Login.jsx` - Convex auth (uses `usePlatform().crypto` for key derivation) - `pages/Register.jsx` - Convex auth (uses `usePlatform().crypto` for key generation) - `pages/Chat.jsx` - useQuery for channels, categories, channelKeys, DMs - `components/ChatArea.jsx` - Messages, typing, reactions via Convex queries/mutations - `components/Sidebar.jsx` - Channel/category creation, key distribution, invites, drag-and-drop via @dnd-kit - `contexts/VoiceContext.jsx` - Voice state via Convex + LiveKit + idle detection via `usePlatform().idle` - `components/TitleBar.jsx` - Conditional: only renders if `platform.features.hasWindowControls` - `components/UpdateBanner.jsx` - Conditional: only renders if `platform.features.hasNativeUpdates` - `components/ScreenShareModal.jsx` - Uses `usePlatform().screenCapture` - `components/MessageItem.jsx` - Message rendering, link opening via `usePlatform().links` - `platform/PlatformProvider.jsx` - React context providing platform APIs via `usePlatform()` hook ## Platform Abstraction (usePlatform()) All platform-specific APIs are accessed via the `usePlatform()` hook: - `crypto` - generateKeys, randomBytes, sha256, signMessage, verifySignature, deriveAuthKeys, encryptData, decryptData, decryptBatch, verifyBatch, publicEncrypt, privateDecrypt - `session` - save, load, clear - `settings` - get, set - `idle` - getSystemIdleTime, onIdleStateChanged, removeIdleStateListener - `links` - openExternal, fetchMetadata - `screenCapture` - getScreenSources - `windowControls` - minimize, maximize, close (Electron only, null on web) - `updates` - checkUpdate (Electron only, null on web) - `features` - hasWindowControls, hasScreenCapture, hasNativeUpdates ## Important Patterns - Channel IDs use Convex `_id` (not `id`) - all references use `channel._id` - Auth: client hashes DAK -> HAK before sending, server does string comparison - First user bootstrap: createUserWithProfile creates Owner + @everyone roles - Vite configs use `envDir: '../../'` to pick up root `.env.local` - 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 `:root` variables (`--bg-primary: #313338`, `--bg-secondary: #2b2d31`, `--bg-tertiary: #1e1f22`) - Sidebar width is 312px (72px server strip + 240px channel panel) - Channels grouped by `categoryId` with collapsible headers and @dnd-kit drag-and-drop - Members list groups by hoisted roles (isHoist) then Online/Offline - Unread tracking: `channelReadState` table per user/channel. ChatArea shows red "NEW" divider, Sidebar shows white dot - Server name from `serverSettings` singleton, editable via Server Settings (requires `manage_channels`) - AFK voice channel: VoiceContext polls idle time, auto-moves idle users - Custom join sounds: stored as `joinSoundStorageId` on `userProfiles` - Server icon: `serverSettings` stores `iconStorageId`, resolved to `iconUrl` - `userPreferences.js` `setUserPref` takes optional `settings` param for disk persistence via platform ## Environment Variables In `.env.local` at project root: - `CONVEX_DEPLOYMENT` - Convex deployment URL (set by `npx convex dev`) - `VITE_CONVEX_URL` - Convex URL for frontend (set by `npx convex dev`) - `VITE_LIVEKIT_URL` - LiveKit server URL - `LIVEKIT_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 1. `npm install` (installs all workspaces) 2. `npx convex dev` (starts Convex backend, creates `.env.local`) 3. Electron: `npm run dev:electron` 4. Web: `npm run dev:web` 5. Android: `npm run build:web && cd apps/android && npx cap sync && npx cap open android`