feat: Add new emoji assets and an UpdateBanner component.
Some checks failed
Build and Release / build-and-release (push) Failing after 3m28s
Some checks failed
Build and Release / build-and-release (push) Failing after 3m28s
This commit is contained in:
117
CLAUDE.md
117
CLAUDE.md
@@ -4,14 +4,60 @@ See also: [CONVEX_RULES.md](./CONVEX_RULES.md) | [CONVEX_EXAMPLES.md](./CONVEX_E
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Monorepo**: npm workspaces (`packages/*`, `apps/*`)
|
||||
- **Backend**: Convex (reactive database + serverless functions)
|
||||
- **Frontend**: React + Vite (Electron app)
|
||||
- **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**: Client-side via Electron IPC (`window.cryptoAPI`)
|
||||
- **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)
|
||||
@@ -33,50 +79,53 @@ See also: [CONVEX_RULES.md](./CONVEX_RULES.md) | [CONVEX_EXAMPLES.md](./CONVEX_E
|
||||
- `gifs.ts` - search, categories (Node actions, Tenor API)
|
||||
- `readState.ts` - getReadState, markRead, getAllReadStates, getLatestMessageTimestamps (unread tracking)
|
||||
|
||||
## Frontend Structure (Frontend/Electron/src/)
|
||||
## Shared Frontend (packages/shared/src/)
|
||||
|
||||
- `main.jsx` - ConvexProvider + VoiceProvider + HashRouter
|
||||
- `pages/Login.jsx` - Convex auth (getSalt + verifyUser)
|
||||
- `pages/Register.jsx` - Convex auth (createUserWithProfile + invite flow)
|
||||
- `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 reordering via @dnd-kit
|
||||
- `contexts/VoiceContext.jsx` - Voice state via Convex + LiveKit room management + custom join sound playback
|
||||
- `components/ChannelSettingsModal.jsx` - Channel rename/delete via Convex mutations
|
||||
- `components/ServerSettingsModal.jsx` - Role management via Convex queries/mutations
|
||||
- `components/MessageItem.jsx` - Individual message rendering with unread divider support
|
||||
- `components/Avatar.jsx` - Reusable avatar component (image or colored-initial fallback)
|
||||
- `components/FriendsView.jsx` - User list via Convex query
|
||||
- `components/DMList.jsx` - DM user picker via Convex query
|
||||
- `components/GifPicker.jsx` - GIF search via Convex action
|
||||
- `components/VoiceRoom.jsx` - LiveKit token via Convex action
|
||||
- `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 config uses `envDir: '../../'` to pick up root `.env.local`
|
||||
- `socket.io-client` fully removed, all socket refs replaced with Convex
|
||||
- No Express backend needed - `Backend/` directory is legacy and can be deleted
|
||||
- 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 are grouped by `categoryId` (references `categories` table) 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
|
||||
- Channels grouped by `categoryId` with collapsible headers and @dnd-kit drag-and-drop
|
||||
- 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: `channelReadState` table stores last-read timestamp per user/channel. ChatArea shows red "NEW" divider, Sidebar shows white dot on unread channels
|
||||
- Server name: `serverSettings` singleton stores `serverName` (default "Secure Chat"), editable from Server Settings Overview tab (requires `manage_channels`). Sidebar header, tooltip, voice panel, Chat header, and welcome text all use the dynamic name.
|
||||
- AFK voice channel: `serverSettings` singleton table stores `afkChannelId` + `afkTimeout`. VoiceContext polls `idleAPI.getSystemIdleTime()` every 15s; auto-moves idle users to AFK channel via `voiceState.afkMove`. Users in AFK channel are force-muted and can't unmute. Sidebar shows "(AFK)" label. Server Settings Overview tab has AFK config UI.
|
||||
- Custom join sounds: Users can upload a custom audio file (max 10MB) via User Settings > My Account. Stored as `joinSoundStorageId` on `userProfiles`. When joining voice, `VoiceContext` plays the user's custom sound (or default `join_call.mp3`). Other users in the channel hear the joiner's custom sound via reactive `voiceStates` tracking (not LiveKit events) to avoid race conditions with URL availability.
|
||||
- Server icon: `serverSettings` stores `iconStorageId`. `get` query resolves it to `iconUrl`. Server Settings Overview tab has upload UI using `AvatarCropModal` with `cropShape="rect"` (square). Sidebar displays the icon image in the server strip (fallback to text initials). `AvatarCropModal` accepts a `cropShape` prop (`"round"` default for avatars, `"rect"` for server icon).
|
||||
- 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
|
||||
|
||||
@@ -90,6 +139,8 @@ In `.env.local` at project root:
|
||||
|
||||
## Running the App
|
||||
|
||||
1. `npm install && npm run install:frontend`
|
||||
1. `npm install` (installs all workspaces)
|
||||
2. `npx convex dev` (starts Convex backend, creates `.env.local`)
|
||||
3. In another terminal: `cd Frontend/Electron && npm run dev` (or `npm run electron:dev`)
|
||||
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`
|
||||
|
||||
Reference in New Issue
Block a user