Install Lunex using Winget - wingetCollections
Go back Packages Lunex Use this command to install Lunex:
winget install --id=Lunex.Lunex -e Copy WinGet command to clipboard Lunex is a secure, real-time desktop chat application designed to provide private communication with end-to-end encryption. Built using Tauri, React, and Convex, Lunex prioritizes privacy and speed while offering a seamless user experience.
Key Features:
End-to-End Encryption: Ensures that all messages, media, and reactions are encrypted before transmission, safeguarding your conversations from unauthorized access.
Real-Time Messaging: Delivers instantaneous communication through Convex-powered reactive queries, with features like typing indicators and read receipts for enhanced interaction.
Media Sharing: Supports encrypted sharing of images, videos, and files, with automatic deletion of media after six hours to maintain privacy.
Message Management: Allows editing, deleting (for self or all), replying, and bulk actions on messages, giving users full control over their conversations.
Privacy Controls: Offers granular settings for online status, typing indicators, read receipts, and notifications, ensuring users can tailor their visibility preferences.
Native Experience: Built with Tauri, Lunex provides a lightweight, fast, and low-memory desktop app experience without relying on browser-based technologies.
Audience & Benefit:
Ideal for individuals and teams seeking secure communication tools, Lunex empowers users to control their privacy while enjoying real-time chat features. Its emphasis on zero-knowledge data handling ensures that only you and your intended recipients can access your conversations, making it a reliable choice for private discussions.
Lunex can be installed via winget, offering a straightforward setup process for users looking to enhance their communication security.
README
Table of Contents
0.1.2
Copy WinGet command to clipboard
Overview Lunex is a native desktop chat application built with privacy and security as first principles. Every message, every media file, every emoji reaction is encrypted end-to-end using NaCl box cryptography before it ever touches the server. Convex powers the real-time backend β but Convex itself never sees plaintext. Your data belongs to you.
Lunex is built on Tauri v2 (Rust + WebView), meaning it ships as a lean native binary not an Electron app bundled with a browser. The result is a fast, low-memory, native-feeling chat application for Windows and Linux.
Installation
Windows Via winget (recommended):
winget install Lunex.Lunex
Via installer:
Download the latest .msi or .exe from the Releases page and run it.
Linux Download the package that matches your distribution from the Releases page :
Package Distro .AppImageUniversal β works on any Linux distro .debUbuntu, Debian, Linux Mint .rpmFedora, openSUSE, RHEL
chmod +x Lunex_*.AppImage
./Lunex_*.AppImage
Features
End-to-End Encryption Every piece of data that leaves your device is encrypted before transmission. Lunex uses NaCl box (TweetNaCl) β the same cryptographic primitive used by Signal and WhatsApp for all message encryption.
On signup, each user generates an asymmetric key pair (public key + private key) derived from a BIP-39 mnemonic phrase (12 words)
The private key (secretKey) never leaves your device and is never sent to Convex
Messages are encrypted using NaCl box with the sender's private key and the recipient's public key this is Diffie-Hellman key exchange baked in
Media files are encrypted with AES-GCM using a per-file random IV before upload
Emoji reactions are individually encrypted before being stored
Even the last message preview in the chat list is encrypted the server only ever stores ciphertext
src/crypto/encryption.ts β NaCl box encrypt/decrypt + AES-GCM symmetric encrypt/decrypt
src/crypto/keyDerivation.ts β base64 encode/decode for key material
src/crypto/mediaEncryption.ts β media file AES-GCM encryption/decryption
src/crypto/mnemonic.ts β BIP-39 mnemonic generation and parsing
src/crypto/pinEncryption.ts β PIN-based AES-GCM encryption for App Lock
src/crypto/dpEncryption.ts β display picture (profile photo) encryption
Real-time Messaging Powered by Convex reactive queries. When a message is sent, all participants see it instantly via WebSocket subscription no polling required.
Messages load with the latest 30 messages on chat open (paginated scroll to top loads older messages)
Real-time typing indicators β see when the other person is typing
Read receipts β Empty circle (sent), circle with one tick (delivered), filled circle with one tick (seen)
Delivery receipts β messages are marked delivered when the recipient's app receives them
Online/offline status is updated on app open, app close, system shutdown, and window hide
Media Sharing Send images, videos, and documents all encrypted before upload.
Files are encrypted client-side with a random AES-GCM IV before being sent to Convex storage
The encrypted blob and its IV are stored; only the recipient (who holds the correct private key) can decrypt
Media expiry β media files on Convex are automatically deleted after 6 hours via a cron job
Batch uploads β multiple files sent in one message are grouped by uploadBatchId and displayed as a media grid
Upload progress is tracked per-conversation in a pending uploads list shown above the input bar
Supported types: image, video, file
Media is decrypted in memory only when displayed never written to disk as plaintext
src/hooks/useMediaUpload.ts β file selection, encryption, upload, progress tracking
src/components/chat/media/PendingUploadsList.tsx β in-progress upload display
convex/media.ts β Convex storage URL generation
convex/cleanup.ts β media expiry deletion function
convex/crons.ts β scheduled cleanup every 6 hours
Message Management
Edit messages β edit your own sent messages; edited messages are marked with an "edited" label
Delete for me β remove a message from your view only
Delete for everyone β remove a message from both sides (hard delete on Convex)
Bulk delete β enter select mode via context menu, select multiple messages, delete all at once
Reply to messages β reply to any specific message; reply preview shows in the input bar
Message info β see exact sent, delivery and read timestamps per recipient
Context menu β right-click anywhere in the chat area for quick actions (select messages, close chat)
src/components/chat/area/ChatAreaDeleteDialog.tsx β delete confirmation dialog (for me / for everyone)
src/components/chat/area/ChatAreaContextMenu.tsx β right-click context menu
src/components/chat/misc/MessageInfoPanel.tsx β delivery/read info panel
src/hooks/useMessageSelection.ts β bulk selection state and delete handlers
convex/messages.ts β all message mutations (send, edit, delete, react, star, pin)
Privacy Controls Each user has granular privacy settings for four attributes, each independently configurable to:
Everyone β visible to all users
Nobody β hidden from all
Only these β allowlist of specific contacts
All except β blocklist of specific contacts
Online status β whether others see you as online or offline
Typing indicator β whether others see "typing..."
Read receipts β whether others see filled circle with one tick
Message notifications β whether you appear as a notification sender
Block any user β they can no longer send you friend requests or messages
Blocked users are listed in your profile panel and can be unblocked at any time
src/components/sidebar/settings/SettingsPrivacySection.tsx β privacy settings UI
src/components/sidebar/settings/PrivacySelectorModal.tsx β everyone/nobody/only-these/all-except picker
src/components/sidebar/settings/ContactPicker.tsx β contact picker for exception lists
convex/users.ts β privacy field reads with exception enforcement
convex/friends.ts β block/unblock, friend request mutations
Privacy by Default β Zero-Knowledge Session Model Lunex is designed so that no sensitive data ever touches persistent storage by default. The privacy model has two distinct tiers, and you choose which one fits your needs.
Tier 1 β Full Session Privacy (Default) This is the default behaviour when App Lock is disabled .
Every time you open Lunex and log in with your 12-word mnemonic phrase, your private key is derived and held exclusively in RAM for that session. The moment you close the app, every trace of your identity your private key, your decrypted messages, your session state is gone. Nothing is written to disk. Nothing persists.
App opens β mnemonic entered β secretKey derived in RAM β session active
App closes β RAM cleared β secretKey gone β no trace left on device
What this means in practice:
Every new session requires your 12-word phrase no shortcuts, no remembered state
A forensic examination of your device's storage after closing the app finds nothing belonging to Lunex
If someone steals your laptop while the app is closed, there is nothing to extract
The system tray toggle interacts with this model: if you enable system tray , closing the window keeps the app running in the background (RAM still holds your session, app stays usable). If you disable system tray , closing the window terminates the process and wipes RAM full privacy on every close
Tier 2 β Persistent Session with App Lock (Opt-in) If re-entering 12 words on every launch is inconvenient, you can opt into App Lock in Settings. This enables a 6-digit PIN that persists your session across app restarts without compromising your private key security.
Here is exactly what happens when you enable App Lock:
User enables App Lock β sets 6-digit PIN
ββ secretKey (from RAM) is encrypted with AES-GCM using PIN as key source
ββ encrypted key blob stored in Tauri plugin-store (OS-level secure storage)
ββ RAM cleared of raw secretKey
App restarts β PIN lock screen shown
ββ user enters 6-digit PIN
ββ AES-GCM decrypt β secretKey recovered β loaded into RAM
ββ session resumes β no mnemonic needed
What this means in practice:
Your raw private key is never stored in plaintext β only as an AES-GCM encrypted blob
The PIN itself is never stored anywhere β it is only used transiently as key material during decrypt
Without the correct PIN, the encrypted blob is cryptographically useless
App Lock also hides your profile picture and bio on the lock screen β the lock screen itself reveals nothing about whose app this is
Auto-lock timers (1 min Β· 5 min Β· 30 min Β· 1 hr) re-engage the PIN screen after inactivity
Upon logout, the userβs PIN is permanently cleared and the encrypted key material stored on the system is securely deleted.
Choosing between the two Tier 1 (Default) Tier 2 (App Lock) Login required every launch Yes β 12-word phrase No β 6-digit PIN Private key on disk Never AES-GCM encrypted only Data after app close Zero Encrypted key blob only Best for Maximum privacy Daily convenience
src/store/authStore.ts β secretKey lives here in RAM only (never persisted without App Lock)
src/crypto/pinEncryption.ts β AES-GCM encrypt/decrypt of secretKey with PIN
src/store/appLockStore.ts β App Lock enable state and auto-lock timer
src-tauri/src/lib.rs β system tray toggle that controls whether close = exit or close = minimize
App Lock Protect your Lunex session with a 6-digit PIN . When App Lock is enabled:
A PIN lock screen covers the entire app on startup and after the auto-lock timer fires
The mnemonic phrase is encrypted with AES-GCM using the PIN as the key source β the PIN itself is never stored anywhere
Auto-lock timers : 1 minute, 5 minutes, 30 minutes, or 1 hour of inactivity
Profile picture and bio are hidden on the lock screen (zero information leakage)
Incorrect PIN entries show a shake animation with attempt feedback
src/components/sidebar/settings/AppLockPanel.tsx β App Lock settings (enable/disable, change PIN, timer)
src/components/sidebar/settings/AppLockPinPad.tsx β 6-digit PIN pad component
src/components/sidebar/settings/AppLockTimerSection.tsx β auto-lock timer radio selector
src/components/auth/PinLockScreen.tsx β full-screen PIN entry overlay
src/store/appLockStore.ts β isAppLockEnabled, isLocked, autoLockTimer state
src/crypto/pinEncryption.ts β AES-GCM mnemonic encryption/decryption with PIN
Disappearing Messages Both participants in a conversation can enable disappearing messages.
Available timers: 1 hour Β· 6 hours Β· 12 hours Β· 1 day Β· 3 days Β· 7 days
New messages sent after enabling automatically have a disappearsAt timestamp
A Convex cron job runs periodically to hard-delete expired messages from the database
The chat header shows a timer indicator when disappearing mode is active
Either participant can change or disable the timer; changes are logged as a system message
Global default β users can set a default disappearing timer in Settings that applies to all new conversations automatically.
src/components/chat/misc/DisappearingPicker.tsx β timer picker panel
src/components/sidebar/settings/SettingsTimerSection.tsx β global default timer setting
convex/conversations.ts β setDisappearingMode mutation
convex/cleanup.ts β expired message deletion
convex/crons.ts β scheduled cleanup job
Chat Themes Per-conversation color customization. Each chat can have its own visual theme, independent of the global app theme.
My message bubble color
Other person's bubble color
My message text color
Other person's text color
Chat background color
Named preset themes
Themes are stored in Convex and sync across sessions automatically.
src/components/chat/misc/ChatThemeCustomizer.tsx β full theme editor (color pickers, preset grid)
src/hooks/useChatTheme.ts β applies per-chat theme CSS variables to the chat area
src/store/themeStore.ts β global app theme state (light/dark/system) + chat presets
convex/chatThemes.ts β getChatTheme query, setChatTheme mutation
Notifications Native desktop notifications via Tauri's notification plugin.
New message notifications fire when the app is in the background or when a different chat is open
Notification content respects privacy settings β if the sender has disabled notification privacy, no notification is shown for their messages
Notifications work on both Windows and Linux
Clicking a notification brings the app window to focus
src/hooks/useAppNotifications.ts β subscribes to incoming messages and fires native OS notifications
System Tray Lunex minimizes to the system tray instead of closing β your chats stay connected in the background.
Left-click the tray icon to show the window
Right-click for a context menu: Show/Hide Window and Exit
Exit fires a system-shutdown event that sets your status to offline before quitting cleanly
Tray icon toggle β Turn ON or OFF System tray
The toggle_tray Tauri command controls tray icon visibility from the frontend
src-tauri/src/lib.rs β tray setup, menu items, click handlers, close-to-tray window event
src/pages/ChatPage.tsx β listens for system-shutdown to set offline before quit
Auto Updater Lunex checks for updates automatically using Tauri's updater plugin.
All updates are cryptographically signed with a private key β only official builds can be installed
updater.json in the repo root describes the latest version, download URLs, and per-platform signatures
On startup, Lunex checks the updater endpoint; if a newer version is available, the user is prompted to install and restart
Three versions have been released and the update chain is live and tested
updater.json β update manifest (version, platforms, download URLs, signatures)
src-tauri/src/lib.rs β tauri_plugin_updater registration
src-tauri/Cargo.toml β tauri-plugin-updater = "2" dependency
In-Chat Search Search through messages within any open conversation directly from the chat header.
Click the Search icon in the chat header to open the search panel (slides in from the right)
Type any query β results filter in real-time from the currently loaded decrypted messages
Each result shows the sender name, timestamp, and the matched text highlighted in context
Click any result to jump to that message β it scrolls into view and briefly highlights
The search panel is a full sidebar panel, separate from the profile/info panel
> Note: Search currently covers messages loaded in the current session. Full-history search across all messages is planned for a future release.
src/components/chat/misc/ChatSearchPanel.tsx β search UI, real-time filtering, jump-to-message
src/components/chat/misc/ChatHeader.tsx β Search icon button that opens the panel
src/store/chatStore.ts β searchPanelOpen state, currentDecryptedMessages for search
Starred Messages Star any message to save it for later reference. All starred messages are accessible from the sidebar.
Use the message context menu to star or unstar a message
The Starred Messages panel (accessible from the 3 Dots Menu) shows all starred messages across all conversations, sorted by time
Each entry shows the conversation it came from, the sender, the timestamp, and the message content
Starring state is stored in Convex on the starredBy array of the message document
src/components/sidebar/StarredMessagesPanel.tsx β starred messages list with jump-to-chat
convex/messages.ts β starMessage / unstarMessage mutations
Friend System Lunex uses a friend-request model β you must be friends with someone before you can open a conversation.
Search users by username from the Requests Page Find tab
Send a friend request β the recipient sees it in their Requests Page Received tab
Accept or reject incoming requests
Once accepted, a conversation is automatically created and appears in both users' chat lists
Block any user from their profile panel or from the blocked list in Settings
Blocked users cannot send friend requests to you and cannot message you
src/components/friends/ β friend request UI cards
src/components/chat/list/ChatList.tsx β tabs: Chats / Requests / Search
convex/friends.ts β sendFriendRequest, acceptFriendRequest, rejectFriendRequest, blockUser, unblockUser
convex/conversations.ts β createConversation (called automatically on friend accept)
Pinned Messages Pin important messages in a conversation for quick reference.
Use the message context menu to pin or unpin a message
You can pin a maximum of 3 messages in one chat
Pinned messages appear in a pinned bar at the top of the chat area, below the header
If multiple messages are pinned, the bar cycles through them on each click with a counter indicator
Clicking the pinned bar jumps the scroll position to that message
Pinned message IDs are stored in the conversations.pinnedMessages array on Convex
src/components/chat/area/ChatAreaPinnedBar.tsx β pinned message bar with cycle navigation
convex/messages.ts β pinMessage / unpinMessage mutations
Message Reactions React to any message with any emoji.
Hover on a message to open the emoji reaction bar
Reactions are individually encrypted β each emoji is AES-GCM encrypted before being stored on Convex
Each message shows a reaction summary (emoji + count) below the bubble
Remove your own reaction by clicking it again
The last reaction in a conversation is stored on the conversation document for quick display in the chat list
src/components/chat/bubble/ β message bubble with reaction display and picker trigger
convex/messages.ts β addReaction / removeReaction mutations with encrypted emoji storage
Tech Stack
Self Hosting
Step 1 β Clone and install git clone https://github.com/miangee21/Lunex.git
cd Lunex
npm install
Step 2 β Set up Convex (development) In Terminal 1 , start the Convex dev server:
You will be prompted to log in to Convex (browser opens)
Select Create a new project , name it lunex or my-chat-app
Convex automatically creates .env.local in your project root with your dev deployment URL
Step 3 β Run the app in development In Terminal 2 , start the Tauri dev build:
The app window will open. Create accounts, add friends, and send messages β everything is fully functional in development mode.
Step 4 β Deploy Convex to production
Go to your Convex Dashboard
Open your project β click the Production tab
Under Settings , copy these three values:
Production Deploy Key β looks like prod:f...
Convex URL β looks like https://f***.convex.cloud
Convex Site URL β looks like https://f***.convex.site
Create .env.production in your project root:
VITE_CONVEX_URL="https://f*********************.convex.cloud"
VITE_CONVEX_SITE_URL="https://f*******************.convex.site"
Step 5 β Generate signing keys Tauri requires all distributed builds to be cryptographically signed. Generate your key pair:
npx tauri signer generate
You will be prompted to set a password β choose a strong one and save it somewhere safe. You will need it every time you produce a release build.
Tauri outputs a private key and a public key . Save both.
Create .env in your project root:
TAURI_SIGNING_PRIVATE_KEY="your_private_key_here"
TAURI_SIGNING_PRIVATE_KEY_PASSWORD="your_password_here"
> Important: Add .env to your .gitignore. Never commit your signing private key to version control.
Environment file summary After completing setup you will have exactly 3 env files in your project root:
File Purpose Created by .env.localDev Convex deployment URL Convex CLI (auto-generated in Step 2) .env.productionProduction Convex URLs You (Step 4) .envTauri signing keys You (Step 5)
Building from Source
Prerequisites
Node.js v20+
Rust stable toolchain β run rustup update stable
Linux only: install system libraries first:
# Ubuntu / Debian
sudo apt install libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
# Fedora
sudo dnf install webkit2gtk4.1-devel openssl-devel gtk3-devel libappindicator-gtk3-devel librsvg2-devel
Windows Build Open PowerShell in the project root. Run these commands in order :
# 1. Deploy Convex schema and functions to production
$env:CONVEX_DEPLOY_KEY="prod:f**********************************"
npx convex deploy
# 2. Set signing key environment variables
$env:TAURI_SIGNING_PRIVATE_KEY="your_private_key_here"
$env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD="your_password_here"
# 3. Build the app
npx tauri build
Output files (inside src-tauri/target/release/bundle/):
File Description msi/Lunex_x.x.x_x64_en-US.msiWindows Installer package nsis/Lunex_x.x.x_x64-setup.exeNSIS installer executable
Linux Build > The Convex production deployment only needs to be done once. If you already ran npx convex deploy on Windows, skip that step here.
Open a terminal in the project root:
# 1. Set signing key environment variables
export TAURI_SIGNING_PRIVATE_KEY="your_private_key_here"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="your_password_here"
# 2. Build the app
npx tauri build
If you encounter AppImage build errors, use this alternative command:
NO_STRIP=1 APPIMAGE_EXTRACT_AND_RUN=1 npm run tauri build
Output files (inside src-tauri/target/release/bundle/):
File Description appimage/lunex_x.x.x_amd64.AppImageUniversal Linux portable app deb/lunex_x.x.x_amd64.debDebian / Ubuntu package rpm/lunex-x.x.x-1.x86_64.rpmFedora / openSUSE / RHEL package
Project Structure Lunex/
βββ .github/
β βββ assets/
β βββ app.png # App screenshot for README
βββ convex/ # Convex backend (serverless functions + schema)
β βββ schema.ts # Database schema (all tables and indexes)
β βββ messages.ts # Message CRUD, reactions, starring, pinning
β βββ conversations.ts # Conversation creation, disappearing mode
β βββ users.ts # User queries, privacy, profile, online status
β βββ friends.ts # Friend requests, blocking
β βββ chatThemes.ts # Per-chat theme get/set
β βββ media.ts # Convex storage URL generation
β βββ typing.ts # Typing indicator set/clear
β βββ presence.ts # Online/offline presence tracking
β βββ cleanup.ts # Media expiry + disappearing message deletion
β βββ crons.ts # Scheduled jobs (cleanup every 6 hours)
βββ src/ # React frontend
β βββ main.tsx # App entry point (Convex provider setup)
β βββ App.tsx # Root component (renders AppRouter)
β βββ App.css # Global styles, custom scrollbar, theme tokens
β βββ crypto/ # All cryptographic operations
β β βββ encryption.ts # NaCl box + AES-GCM symmetric encrypt/decrypt
β β βββ keyDerivation.ts # Base64 encode/decode for key material
β β βββ mediaEncryption.ts # Media file AES-GCM encryption/decryption
β β βββ mnemonic.ts # BIP-39 mnemonic generation and parsing
β β βββ pinEncryption.ts # PIN-based AES-GCM for App Lock
β β βββ dpEncryption.ts # Display picture (profile photo) encryption
β β βββ index.ts # Re-exports
β βββ store/ # Zustand global state stores
β β βββ authStore.ts # userId, username, publicKey, secretKey
β β βββ chatStore.ts # activeChat, sidebar views, panel states
β β βββ appLockStore.ts # isAppLockEnabled, isLocked, autoLockTimer
β β βββ themeStore.ts # Global app theme + chat presets
β β βββ settingsStore.ts # Misc settings state
β βββ hooks/ # Custom React hooks
β β βββ useChatData.ts # Fetches raw messages, handles pagination
β β βββ useDecryptMessages.ts # Decrypts raw messages into DecryptedMessage[]
β β βββ useChatScroll.ts # Scroll container, scroll-to-bottom, load-on-top
β β βββ useChatTheme.ts # Applies per-chat theme CSS variables
β β βββ useMessageSelection.ts # Bulk selection state and delete handlers
β β βββ useMediaUpload.ts # File encrypt, upload, and progress tracking
β β βββ useAppNotifications.ts # Native desktop notifications on new messages
β β βββ useOnlineStatus.ts # User presence (online/offline)
β β βββ useProfilePicUrl.ts # Fetches and decrypts profile picture URL
β β βββ useSecureAvatar.ts # Secure avatar display with decryption
β βββ pages/ # Top-level page components
β β βββ ChatPage.tsx # Main app layout (SlimBar + sidebar + chat area)
β β βββ LoginPage.tsx # Login with username + mnemonic phrase
β β βββ SignupPage.tsx # New account creation (key pair + mnemonic)
β β βββ SplashPage.tsx # Initial loading splash screen
β βββ routes/
β β βββ AppRouter.tsx # Auth gate + App Lock PIN screen gate
β βββ components/
β β βββ auth/
β β β βββ PinLockScreen.tsx # Full-screen PIN entry when app is locked
β β βββ chat/
β β β βββ area/ # Main chat panel
β β β β βββ ChatArea.tsx # Chat area root component
β β β β βββ MessageList.tsx # Message list with media grid grouping
β β β β βββ ChatAreaPinnedBar.tsx # Pinned message bar with cycle navigation
β β β β βββ ChatAreaDeleteDialog.tsx # Bulk delete confirmation dialog
β β β β βββ ChatAreaContextMenu.tsx # Right-click context menu
β β β βββ bubble/ # Message bubble components
β β β βββ input/ # Chat input bar
β β β β βββ ChatInput.tsx # Input bar with send/emoji/attach/select mode
β β β βββ list/ # Chat list sidebar
β β β β βββ ChatList.tsx # Tabs: Chats / Requests / Search
β β β βββ media/ # Media components
β β β β βββ PendingUploadsList.tsx # In-progress uploads display
β β β βββ misc/ # Chat utility panels
β β β βββ ChatHeader.tsx # Chat header (avatar, name, online, actions)
β β β βββ ChatSearchPanel.tsx # In-chat message search panel
β β β βββ ChatThemeCustomizer.tsx # Per-chat color theme editor
β β β βββ DisappearingPicker.tsx # Disappearing message timer picker
β β β βββ MessageInfoPanel.tsx # Delivery/read timestamps panel
β β β βββ MessageStatusTick.tsx # Sent/delivered/read tick component
β β βββ friends/ # Friend request UI components
β β βββ profile/ # Profile panels
β β β βββ MyProfilePanel.tsx # Your own profile editor
β β β βββ OtherUserPanel.tsx # Other user's profile view
β β βββ sidebar/ # Sidebar components
β β β βββ SlimBar.tsx # Narrow icon bar (leftmost column)
β β β βββ AvatarMenu.tsx # Avatar click dropdown menu
β β β βββ DotsMenu.tsx # Three-dot dropdown menu
β β β βββ AboutPanel.tsx # About / version info panel
β β β βββ StarredMessagesPanel.tsx # All starred messages list
β β β βββ settings/ # Settings sub-panels
β β β βββ SettingsPanel.tsx # Main settings root
β β β βββ SettingsPrivacySection.tsx # Privacy toggles
β β β βββ SettingsTimerSection.tsx # Global disappearing timer
β β β βββ AppLockPanel.tsx # App Lock settings
β β β βββ AppLockPinPad.tsx # 6-digit PIN pad component
β β β βββ AppLockTimerSection.tsx # Auto-lock timer selector
β β β βββ PrivacySelectorModal.tsx # everyone/nobody/only-these picker
β β β βββ ContactPicker.tsx # Contact picker for exception lists
β β βββ shared/ # Shared utility components
β β β βββ LunexLogo.tsx # App logo component
β β βββ ui/ # shadcn/ui primitives
β βββ types/
β β βββ chat.ts # DecryptedMessage, ActiveChat, and core types
β βββ lib/
β βββ utils.ts # cn() utility (tailwind-merge + clsx)
βββ src-tauri/ # Tauri Rust backend
β βββ src/
β β βββ main.rs # Binary entry point
β β βββ lib.rs # App setup: tray, plugins, commands, window events
β βββ capabilities/ # Tauri permission definitions
β βββ icons/ # App icons (all sizes, all platforms)
β βββ Cargo.toml # Rust dependencies
β βββ tauri.conf.json # Tauri config (bundle ID, window, updater)
βββ public/ # Static assets
βββ updater.json # Auto-update manifest
βββ index.html # Vite HTML entry point
βββ vite.config.ts # Vite configuration
βββ tsconfig.json # TypeScript configuration
βββ package.json # npm dependencies and scripts
Convex Schema All sensitive fields (message content, IVs, reactions) are stored as ciphertext β Convex never receives or stores plaintext.
Table Purpose Key fields usersUser accounts and settings username, publicKey, isOnline, lastSeen, privacy settingsconversationsChat sessions between two users participantIds, disappearingMode, pinnedMessages, lastMessageAtmessagesAll messages encryptedContent, iv, type, sentAt, readBy, deliveredTo, reactions, starredByfriendRequestsPending/accepted/rejected requests fromUserId, toUserId, statusblockedUsersBlocked user pairs blockerId, blockedIdtypingIndicatorsReal-time typing state conversationId, userId, isTyping, updatedAtchatDeletions"Delete for me" history conversationId, userId, deletedAtchatThemesPer-chat color themes userId, otherUserId, bubble colors, text colors, preset name
messages.by_conversation on [conversationId, sentAt] β efficient paginated message loading
messages.by_expires on [mediaExpiresAt] β media cleanup cron target
messages.by_disappears on [disappearsAt] β disappearing message cleanup
users.by_username β username search
friendRequests.by_pair β duplicate request prevention
chatDeletions.by_user_conversation β fast "delete for me" filtering per chat
Cryptography Lunex's security model treats the Convex server as untrusted . Everything sensitive is encrypted before it leaves your device.
Key generation (signup) BIP-39 mnemonic (12 words)
ββ SHA-512 hash (via @noble/hashes)
ββ first 32 bytes β NaCl secretKey (private key)
ββ NaCl box.keyPair.fromSecretKey() β publicKey
The publicKey is uploaded to Convex. The secretKey is derived fresh from the mnemonic on each login and kept only in memory (authStore). The mnemonic is shown once at signup and never stored by the app unless App Lock is enabled β in which case it is AES-GCM encrypted with the PIN before storage.
Message encryption sender secretKey + recipient publicKey
ββ NaCl box (Curve25519 DH + XSalsa20-Poly1305)
ββ { encryptedContent (base64), iv (base64 nonce) }
ββ stored in Convex messages table
Media encryption file bytes
ββ crypto.getRandomValues(12 bytes) β IV
ββ AES-GCM encrypt (secretKey[:32] as key)
ββ encrypted blob β uploaded to Convex storage
ββ { mediaStorageId, mediaIv } β stored on message
App Lock (PIN) user PIN
ββ AES-GCM key derivation
ββ AES-GCM encrypt(mnemonic phrase)
ββ stored in Tauri plugin-store (encrypted at rest)
β on unlock: AES-GCM decrypt β mnemonic β re-derive secretKey
Contributing Contributions are welcome. To contribute:
Fork the repository
Create a feature branch: git checkout -b feat/my-feature
Make your changes
Test with the dev build: npx tauri dev
Commit with a descriptive message: git commit -m "feat: add my feature"
Push and open a Pull Request
TypeScript strict mode β avoid any
Components go in the appropriate subdirectory under src/components/
New Convex queries and mutations go in the relevant file in convex/
State goes in a Zustand store β no prop drilling for global state
All user data passed to Convex must be encrypted client-side first
Roadmap These features are actively planned and will be added in upcoming releases.
Feature Status β¬ Mobile App π Planned
Mobile App A native mobile version of Lunex for Android and iOS, built on the same Tauri + React codebase.
Full feature parity with the desktop app β end-to-end encryption, disappearing messages, media sharing, app lock, and all privacy controls
Same Convex backend β your account, contacts, and message history carry over seamlessly between desktop and mobile
Native push notifications
Biometric unlock (fingerprint / Face ID) as an alternative to PIN
License MIT Β© 2026 Muhammad Hassan