Files
DiscordClone/CLAUDE.md
Bryan1029384756 fe869a3222
Some checks failed
Build and Release / build-and-release (push) Failing after 3m28s
feat: Add new emoji assets and an UpdateBanner component.
2026-02-13 12:20:40 -06:00

9.3 KiB

Update this file when making significant changes.

See also: CONVEX_RULES.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