Modern chat without mandatory cloud infrastructure.
Built for local-first communication on your LAN.
Peer-to-peer messaging, files, and images — no central server required.
Current version: v0.8.7-beta
| Channel | Version | Status |
|---|---|---|
| Latest | v0.8.7-beta | The "+" finally has one door instead of five, Orbit asks for the permissions it needs once — and only for the ones that are missing, no longer opening the camera on every launch to provoke the dialog — and four bugs are gone: a dismissed link preview that stayed dismissed, a contact menu that opened empty, a contacts grid wider than the screen, and a staged attachment that vanished on the desktop |
| v0.8.5-beta | The phone catches up with the desktop — Orbit is in the Android share sheet, a notification can be answered without opening the app, and there is a PIN lock, plus link previews, a real image viewer and a network map that shows faces instead of letters | |
| v0.8.4-beta | A group of images of different sizes no longer goes ragged — the attachment grid now follows Discord’s layouts. One tap on Send was also starting the mic — Android’s synthetic mouse events land on whatever is under your finger by then, which was the mic, so it recorded and sent a voice note on repeat | |
| Stable | v0.8.0-beta | Your theme and custom colours follow you between your own devices, message search runs on SQLite FTS5, and the update progress bar actually moves |
| Legacy Stable | v0.1.1-beta | Legacy stable release |
See CHANGELOG.md for detailed release notes.
![]()
Android app — conversations, a chat, the gallery, settings
Orbit exists for people who want real-time communication without handing their conversations to a cloud vendor.
Whether you are sharing files at home, coordinating in a small office, or experimenting with local-first software as a developer, Orbit keeps traffic on your network — peer-to-peer, discoverable, and under your control.
| Principle | What it means |
|---|---|
| Local-first | Messages and media stay on devices you own, not a remote account you rent. |
| Peer-to-peer | Clients talk directly over LAN sockets — no mandatory relay or signup server. |
| LAN-first | Auto-discovery finds nearby Orbit clients on the same network. |
| No cloud lock-in | No required SaaS backend, no vendor account, no subscription gate. |
| Open & approachable | MIT-licensed, readable stack (Electron + SQLite), built for transparency. |
Orbit is a beta-stage app for desktop and Android aimed at trusted private networks — not a replacement for hardened internet-scale messengers yet, but a serious step toward practical local messaging.
- The composer's "+" is one door now. It used to drop a five-item list whose first two entries were "Upload Images" and "Upload Videos" — one intent split by file type, decided before you had seen anything. It opens a sheet now, with a Photos · Poll · Commands · Files pill row along the bottom, and the picker decides between an image and a video. Camera goes straight to the camera; Gallery takes both.
- Orbit asks for permissions once, at startup. ⚠ They were requested from three unrelated places and none could read the current state, so a permission you had already granted was asked for again on every launch — and one of them opened the camera on every single start, purely to provoke the OS dialog. It reads what is granted first now and asks only for what is missing, once per version. If everything is already granted it does nothing at all — no sheet, no dialog, no camera.
- A dismissed link preview stayed dismissed all session. Reported as "pasted links aren't recognised, and links ending in
/aren't recognised" — neither was happening. The dismissal was keyed by URL for the whole session, so once the bar for a link had been dismissed, putting that same link back in the composer showed nothing. The one moment a preview is genuinely wanted. - The contacts grid was wider than the screen. ⚠ Reported as "it feels kinda too spaced out".
1fr 1frisminmax(auto, 1fr), and theautominimum is the content's min-content width — so one long unbreakable name pushed its column wider than its share. On a 390px phone the columns measured 188px + 229px = 426px and the right-hand cards were cut off. A layout complaint is a measurement, not an opinion. - The three-dots menu on a contact opened an empty sheet. It drew the sheet and nothing but the Cancel pill. A contact's actions are user actions, and there is already a sheet for exactly that — so a direct message opens it now: View Profile, Message, Mute, Close DM, Block.
- A staged attachment vanished on the desktop whenever the chat re-rendered — and it was found by a test that had been written off as flaky. The strip was rebuilt empty and hidden and nothing put the files back: they were still staged and would still be sent, but you could no longer see them or take them out.
v0.0.1-beta
- P2P messaging — Direct socket-based chat on your local network
- File & image sharing — Send attachments peer-to-peer (configurable limit, default 500 MB)
- Auto-discovery — Find other Orbit clients on the LAN without manual IP entry
- Profiles & themes — Custom display name, avatar, light/dark/system UI
- Gallery — Browse shared images with WebP thumbnails for fast scrolling
- System tray — Minimize to tray instead of quitting
v0.0.2-beta
- Persistent storage — Messages and media archived in SQLite (
better-sqlite3) - Privacy mode — Optional session-only attachment storage
- Integrity checks — SHA-256 validation on file transfers
v0.0.3-beta
- Group chat — Multi-peer group messaging with member management, roles (Owner/Admin/Member), avatars, and invite codes
- Message reactions — Emoji reactions on messages
- Markdown formatting — Rich message formatting with headings, lists, code blocks, and more
- Drag-and-drop uploads — Drop files and images directly into the chat panel
- Notification sounds — Web Audio notification chime with per-user mute settings
- Profile sidebar — Click any avatar to view a detailed profile panel
v0.0.4-beta
- See CHANGELOG.md, section 4.
v0.0.5-beta (Stable)
- End-to-end encryption — ECDH key exchange + AES-256-GCM message encryption for DMs. Toggle in Settings → Data Manager.
- Backup & Restore — Export/import full database as .orzip or .zip archives
- Unread Badges & Read Receipts — Per-chat unread counts, @mention badges, and read indicators
- Activity Center — Unified view of recent messages across all chats
- Customizable Sidebar — Show/hide Activity Center, Gallery, and Storage buttons in the left sidebar
v0.0.6-beta
- Custom Themes — True Dark, Dark Purple, Midnight, Sunset, Nord, Seasonal auto-rotating themes
- Custom Colors — Live preview color editor for all UI categories
- Profile Frames (experimental) — 12 decorative frame overlays on avatars
- Animated Avatars (experimental) — Subtle pulse animation on avatars
- Message Translate (experimental) — Translate messages via MyMemory API
- Compact Spacing (experimental) — Tighter message layout option
- App Zoom — Zoom slider with preview and restart notification
- Chat Settings — Enter to Send, Show Avatars, Image Preview toggles
v0.0.7-beta
- Project restructured — Desktop and mobile code separated into
desktop/andmobile/ - Cross-platform abstraction —
shared/modules for database, network, protocol, crypto - Privacy mode fixed — Thumbnails now generated, gallery sidebar works, backup/restore preserves temp files
- Android build pipeline — GitHub Actions builds
.apkvia Capacitor alongside desktop builds - Mobile UI shell — Touch-friendly layout with bottom navigation bar
v0.0.8-beta
- Rich Link Previews — Open Graph metadata (title, description, image) fetched via Electron IPC; styled cards with left accent bar, hover/active link colors
- Message Link Styling — URLs in chat text now clickable with hover (dark blue) and active (green) states on both desktop and mobile
- Cross-platform P2P — Desktop ↔ Android LAN discovery and messaging via TCP/UDP
- QR Code Fixes — Both desktop and mobile QR generation fixed; mobile QR moved to Add Friend modal
- Mobile Settings Wired — All toggles now have real behavior (time format, avatars, images, notifications, debug tools, file size, auto-delete, experimental features)
- Mobile Toast Overhaul — Type-based accent bar, icons, slide-in animation, progress bar
- Mobile Notification Sound — Web Audio beep on incoming messages
- Desktop Settings Tabs — Notifications (volume/sound/test), Network (collapsibles), About (version info)
- Chat Background Patterns — Diagonal Stripes, Crosshatch, Circles
- Desktop Bug Fixes — MIME mapping, cache headers, media retry, attachment URLs
v0.0.9-beta
- Android P2P Stability — 8 Java plugin fixes (multicast lock, beacon gating, TCP buffer, connection tracking) + 4 JS bridge fixes for reliable Android discovery and messaging
- Desktop P2P Stability — 9 fixes including per-connection write queue, oversized frame guard, socket error handlers, self-beacon IP filter, transfer backpressure, and clean restart support
- Mobile Group Info Panel — Full panel: edit group name/description, change avatar, invite code with Copy/Share, pin/mute toggles, member list with roles (Owner/Admin) + join dates, promote/demote/remove members, leave/delete group
- Cross-Platform Group Sync — Group creation (GROUP_CREATE) and leave (GROUP_LEAVE) broadcast compatible between mobile and desktop
- Pinned Messages — Pin/unpin in message action bar; pinned messages section in group info; cross-platform sync via PIN_MESSAGE/UNPIN_MESSAGE protocol
- Message Search — Search bar filters messages in real-time on mobile chat header
- Enhanced Message FX — Particle confetti system on sent messages (both platforms); safe CSS for Android WebView compatibility
- Mobile Settings Added — Font Size (Small/Medium/Large), Message Animation (Slide/Fade), Auto-Reconnect toggle, Connection Timeout (5/10/30/60s)
- Mobile DB Fix — Critical fix: migration now runs after user data loads to prevent identity corruption on restart
- Add Friend on Android — No more "P2P Preview" gating;
android:usesCleartextTrafficflag; plugin retry mechanism for reliable friend addition
v0.0.9.2-beta
- Mobile initP2P Logging — Detailed debug logs throughout P2P initialization and lifecycle
- Dev Mode DevTools — Toggling Developer Mode loads eruda on-device inspector panel
- Debug Log Buffer — Scrollable log overlay when dev mode is active
v0.0.9.3-beta
- Group Info Panel Overhaul — Redesigned with Add Member (friend picker), Leave Group, Transfer Ownership, member search bar, created date, online/total count
- GROUP_MEMBER_ADDED / GROUP_OWNER_TRANSFER — New protocol types with cross-platform handlers
- DM Context Menus — Desktop right-click and mobile long-press: Pin/Unpin, Mute, View Profile, Copy ID, Close DM
- Pinned DMs — Pinned state sorted first in sidebar with pin icon
- Close DM Removes Friend — Full cleanup from DB; persists closedDMs; auto-reopens on new message
- P2P Diagnostics Panel — Modal with P2P status, discovery, connected peers, log buffer
- Global Gallery Type Filters — All/Images/Files toggle; non-image files render with Lucide icons
- Gallery Sidebar Files Tab Fix — Format.bytes→fileSize; download button replaces window.open
- Create Group Modal Avatars — Friend list shows actual avatars with profile frames
- Context Menu & P2P Fixes — data-action rewrite, protocol type audit, TCP merge IP strip
v0.1.0-beta
- Performance: Up to 5× Faster Startup & Rendering — Selective store subscriptions, setStateBatch microtask coalescing, insertAdjacentHTML, event delegation for all message actions
- Startup: ~40% Faster (5s → 3s) — Deferred init phases (setTimeout(0) + requestIdleCallback), batched store IPC (7+ calls → 1), lazy message loading (last 50 per chat, load on demand)
- freezeGifImages — Canvas cache via _frozenCache Map; expanded selectors; global call on Reduce Motion toggle
- Data Manager "Load All Stored Data" — Double-confirmation button loads all messages from DB into memory on demand
- Bug Fixes — orbit-db://attachment/ 404, selective subscriber undefined changedState, message avatar click re-attached, loadFullChatMessages dropping existing messages
v0.1.1-beta (Stable)
- Voice & Video Calls (P2P WebRTC) — Full call system with incoming notification, mute/speaker controls, timer, ICE candidate exchange over P2P network layer
- Group Calls (Mesh) — Each participant gets their own RTCPeerConnection; video grid or avatar circles for audio-only; start/join/leave group calls
- Camera Toggle — On/off during calls with deterministic HSL avatar placeholder when camera is off
- Message Forwarding — Forward messages with attachments to any chat via chat picker modal (desktop + mobile)
- Block User — Block/unblock from context menu and profile sidebar; P2P filter drops blocked packets
- Search Within a Chat — Scoped search with chatId filter, sender filter, date inputs, context-aware placeholders
- Export Chat History — JSON or TXT export with timestamped downloads via Data Manager
- Save/Load Themes — Export current theme as JSON; import via file picker in Appearance tab
- Message Translate Unlocked — Always-on translate button (no experimental gate), default enabled in Appearance tab
- Mobile Reply fromName Fix — fromName now set on ALL outgoing MESSAGE packets for cross-platform reply consistency
- Lucide Icon Null Fix — All querySelector('i') changed to querySelector('svg') after createIcons replaces i tags with svg
- Online Status Improvements — lastSeen on BEACON; 30s interval checks for stale connections (120s timeout)
- Inline Code Blocks — CSS styling for code and pre elements (both platforms)
- Call Modal UI Polish — Proper centering, audio wave bars, hover button effects, local video as full grid tile in groups
v0.1.2-beta
- Manual Connect Bug Fixed — Manual "Add a Friend" IP connect now correctly remaps the TCP socket to the peer's real userId; messages reuse the existing connection instead of creating a new one
- Protocol Type Unification — All 47 protocol types unified between
shared/anddesktop/protocol.js; cross-platform call, file transfer, and group compatibility guaranteed - Build Pipeline Overhaul — Android
assembleReleasereplacesassembleDebug;SHA256SUMS.txtper platform; artifact verification fails on missing builds; build metadata (version, commit, date) and asset size table auto-injected into release notes
v0.1.3-beta
- Performance Mode — New Experimental toggle with two-step confirmation; kills all animations/transitions, freezes GIFs, skips link preview OG fetch, slows offline check to 60s, stops connection stats and dev overlay polling
- Image Viewer Fix — Clicking image thumbnails now reliably opens viewer after restart; hit-test fallback for DOM recreated mid-click; removed
'friends'from store subscription to stop re-renders on friend status beacons - Forced Reflow Cascade Eliminated — ResizeObserver disconnected before
innerHTMLinrenderChat, reconnected after all DOM changes; throttled to 1s to prevent cascade from async image loads - Native Android Notifications — Messages show as real system notifications when app is backgrounded via
@capacitor/local-notifications - Desktop Notification Avatars — Sender/group avatar shown as notification icon (previously static Orbit icon)
- Connection Stats Panel — Live P2P status overlay with peer count, uptime, sent/received counters
- Video File Support — Upload, render, and view video files in chat with play overlay and full-screen preview modal
- Video Compression — Large videos (>5MB) auto-compressed to 720p/500kbps before sending
- P2P Discovery Optimized — Beacon interval 5s→10s, stale threshold 120s→180s, exponential chunk retry backoff
- image-viewer.js Null-Safety —
openFromMessagechecks null store/messages with fallback;close()andopenVideo()wrapped in try/catch;init()usesreadyStateguard - Compact Spacing & Swipe-to-Reply — Moved from Experimental to general chat settings
v0.1.4-beta
- P2P Auto-Connection Stabilization — PING/PONG keep-alive heartbeat, 8s connection timeout, exponential backoff reconnect (max 5 attempts), stale peer pruning (180s), network IP change detection, auto-connect duplicate protection
- Desktop P2P Bugfix Audit (17 fixes) — Socket 8s timeout disabled after connect, write-queue key collision fixed, reconnect .catch() + counter reset, GROUP_CREATE publicKey enrichment, GROUP_JOIN_REQUEST fields, PIN/UNPIN/SYSTEM routing
- Translation Engine Rewrite — In-memory cache, request dedup, AbortController, inline retry link
- Image Viewer Overhaul — Quick-save button (File System Access API), keyboard navigation, swipe, download fix for custom protocol URLs, loading placeholder CSS
- Voice Messages Stabilization — Content-Type fix, onerror auto-retry, chunked transfer detection with MIME mapping
- Performance Mode — Two-step confirmation, CSS class on
<html>, runtime guards in chat-panel and app.js - Mobile Protocol.js Synced — 15+ missing types added (46 total, matching desktop)
- Mobile Settings Parity — 11 desktop defaults ported; logLevel filters debugLog; tcpPort/udpPort in beacon/P2P; netReconnectInterval in reconnect; netKeepAlive in heartbeat; netBandwidthLimit throttles FILE_CHUNK
- Mobile DB Migration Fixed — Runs before MStore.load(); visible console output; reload safety net
- Mobile profileFrame Clean-Up — Helper function defends all 7 render locations; TCP beacon stores 0 correctly
- Mobile Changelog — What's New modal in About tab (v0.0.2 through v0.1.4)
- Desktop group sync fixes — GROUP_OWNER_TRANSFER, GROUP_LEAVE cleanup, GROUP_INVITE init
- Desktop reconnect settings bridge — preload forwards reconnectEnabled/reconnectIntervalMs to main process
v0.1.5-beta
- Account Switcher (Experimental): Right-click avatar → panel to add/switch/logout accounts. Logout quits app (accounts persist in DB). Last active user auto-loaded on launch. Gated behind Experimental Features toggle in Advanced settings.
- PIN Lock Screen (2FA Experimental): 4-8 digit numeric PIN with SHA-256 hashing (Node
cryptoon main process). Numpad UI, 5-attempt cooldown (30s), "Forgot PIN" reset (data intact). Only prompted on app launch (never on resume/sleep). Gated behind Experimental Features. - Settings Security Tab: PIN setup, change, disable flows. Full modal re-render when Experimental Features toggled to show/hide tab.
- Orbit Echo Welcome Sequence: 4 welcome messages with 5-8s typing indicator delays. Fires only when echo chat is empty. Echo excepted from all offline checks.
- Group Avatar Backfill: Async fetch of
orbit-avatar://for groups withavatarPathbut noavatarDataUrlon store init + group info open. Non-blocking, errors silently caught. - SidebarLeft.renderAvatar crash before init: Added
if (!this.container) return;guard to prevent TypeError when called beforeSidebarLeft.init(). - Store.js syntax error: Removed trailing commas between class methods that caused "Unexpected token ','".
window.ChatPanel.showChat is not a function: Removed prematureChatPanel.showChat()call fromreloadDataForCurrentUser().require('crypto')in renderer: Changed towindow.cryptofor invite code generation.- Migration v11 robustness: Column-existence checks before
ALTER TABLE. Guard inmigrations.run()handles non-transactionaluser_version— if version ≥ 11 but columns missing, resets to 10 and re-runs. - Identity System:
Identity.init()loads from multi-user DB.getAll()returns all saved users.switchTo(userId)swaps identity and updates last active tracking.saveUserstoresprofileFramewith!= nullguard. - Database Schema: v10:
ALTER TABLE users ADD COLUMN profileFrame INTEGER DEFAULT 0. v11:accountOwnerId TEXTon friends, groups, group_members tables. - Network Restart: Account switch calls
networkStop(nullifies all instances) →Identity.switchTo()→networkStart(re-creates with new identity). All TCP connections drop and re-establish. - Migration Rollback Guard:
db.pragma('user_version')is non-transactional. Added column-existence check at start ofmigrations.run()to detect partial migration state and recover.
v0.1.6-beta
- Android Foreground Service (Background Execution): P2P networking extracted into persistent Foreground Service with notification, WakeLock, START_STICKY. BootReceiver restarts on device boot. Service survives Activity/WebView destruction.
- P2P Connectivity Fixes: Desktop auto-connect port fixed (was hardcoded 46000); reconnect now uses per-peer stored TCP port; mobile disconnect handler fixed (connectionId→friend lookup); mobile beacon handlers store tcpPort/connectionId/ip; mobile auto-reconnect uses peer's port.
- Message Editing & Reactions: Desktop edit broadcast loop eliminated; mobile edits broadcast over P2P to DMs and groups; mobile reaction UI (6 emojis, toggle, P2P broadcast).
- OrbitForegroundService: Full P2P engine (TCP server, UDP multicast, connection map, thread pool) as Android Service. Plugin proxies via Binder. Event queue drained every 100ms.
- Silent Bug Fixes: 7 fixes from service audit — sendFailed/connectFailed events, serverSocket volatile, PeerConnection map leak, executor shutdown, eventQueue clear, SO_REUSEADDR, Android 10+ joinGroup fix.
v0.1.7-beta
- fMP4 Video Playback Fixed: PIPELINE_ERROR_DECODE root cause fixed — Content-Type serving in main.js now correctly serves video files. Videos play continuously.
- Re-render Guard: Message re-renders blocked during video playback (except chat switches). Prevents player destruction from innerHTML re-renders.
- Decode Error Retry: On audio packet decode failure, source reloads and skips forward +2s (up to 3 attempts).
- Larger Media Players: Video 720×600, audio waveform 200px — rendered outside image grid as standalone blocks at full width.
- Fullscreen Theme Blend: Letterbox uses
var(--bg-surface)— matches active UI theme.
v0.1.8-beta
- Store Class Ported to Mobile: Inline MStore (~350 lines) extracted into dedicated
store.js(760 lines) — full Store class with backward-compatible property-based access (532+ references) plus desktop-style getState()/setState()/subscribe(). - Desktop Parity Features: subscribe/notify, blockUser/unblockUser, pinMessage/unpinMessage, markAsRead, toggleMute, group management (addGroup/removeGroup/addMemberToGroup), DM management (closeDM/togglePinDM/reopenDM), E2EE key storage, transfer tracking, addOrUpdatePeer.
- Bug Fixes: addMessage() unread tracking fixed (counted every message as unread — removed); mutedChats aliased to settings.mutedChats for mobile read path; setState() now handles currentUser key.
- CSP & Prism.js Fixes: Desktop CSP updated for Prism.js (cdnjs.cloudflare.com); Prism loaded before app.js on both platforms; language-* class on pre elements for immediate syntax highlighting.
v0.1.8.1-beta
- Media Persistence Fixed: Received files (images, audio, video) no longer lost after app restart. _dataUrl stored alongside blob URL for recovery; incoming MESSAGE attachments preserve _dataUrl.
- renderMessages No Longer Corrupts Store: data:→blob URL conversion no longer mutates MStore.messages in-place — eliminates silent data loss from subsequent saves (reactions, edits, deletes).
- .webm Misclassification Fixed: Removed .webm from audioMatch regex — .webm videos no longer misclassified as audio. Video checked before audio in type detection.
- Desktop isVideo Added: Desktop file-received handler now classifies incoming videos as type 'video' with correct MIME instead of 'file' / application/octet-stream.
- Mobile Video Aspect Ratio Fixed: .ovp-video now has object-fit: contain, max-height: 50vh (was 300px), and #000 letterbox background — videos scale correctly to any aspect ratio.
v0.1.9-beta
- CRITICAL: Mobile base64 Decode Corruption Fixed — atob() on Android WebView corrupts bytes >127 — all 7 binary decode sites replaced with safe manual decoder.
- CRITICAL: Desktop→Mobile Chunk Joining Fixed — desktop btoa()'s each 64KB chunk independently; mobile chunks.join('') produced invalid base64. Per-chunk independent decode + ArrayBuffer concat.
- CRITICAL: WriteStream Race Fixed — mobile→desktop truncated files due to async stream.end(). stream.on('finish') wraps completion.
- CRITICAL: P2P cleanup() Race Fixed — Android stopService() async race skipped service restart on re-init. Java cleanup is now a no-op.
- Mobile Video Type Override Fixed — FILE_TRANSFER_END no longer overwrites video/audio type with extension regex.
- Mobile Video Compression Dropped Audio Fixed — disabled lossy compression; raw video with audio sent.
- Group Chat File Routing Fixed — chatId added to FILE_TRANSFER_START/END packets.
- Mobile Metadata Preload — muted=true + preload=metadata forces immediate duration display.
- Unstable AV Transfer Warning Modal — alert-triangle modal with "Don't show again" checkbox.
- Auto-Discovery Diagnostic Logging — [AutoConnect] logs for firewall debugging.
v0.2.0-beta
- CRITICAL: Mobile Background Notifications Fixed —
document.hiddenunreliable in Capacitor WebView. Fixed: JS tracks background viaappStateChange; Java plugin creates notifications directly viaNotificationManager. - CRITICAL: Large File Persistence on Mobile — Files >10MB in IndexedDB lost blob: URLs on restart. Added
BlobStoreDB+_restoreAllBlobAttachments(). - Mobile base64 Streaming Optimizations — All 7 binary decode sites rewritten as single-pass streaming decoders (no intermediate strings).
- Desktop AV Type Honor Fix — Desktop honors sender's type classification; .webm audio no longer misrouted to video player.
muted=trueGated to Mobile Only — Desktop players no longer start muted.- Blob MP4 Duration Parsing — fetch(blob:) + Uint8Array byte access for duration detection on large blob URLs.
v0.2.1-beta
- CRITICAL: WriteStream race fixed (mobile→desktop truncated files)
- CRITICAL: Duplicate messages for large files fixed
- File transfer stability improvements
v0.2.2-beta
- Mobile metadata preload (muted=true forces immediate duration)
- Blob MP4 duration parsing via fetch(blob:)
- Mobile changelog modal
v0.2.3-beta
- Video player complete overhaul (MP4 duration parsing, multi-layered fallback)
- Audio player matching architecture
v0.2.4-beta
- Activity Center overhaul with Lucide icons, badges, duration/size
- Transfer UX progress display
v0.2.5-beta
- 4-layer fullscreen bg fix, manual fullscreen only
- Light mode flashbang prevention, theme transitions + easter egg, midnight sleep reminder
- Full undo/redo system (100-action stack, Ctrl+Z/Y, privacy mode)
- Avatar frame per-account fix, Konami code→dev mode
v0.2.6-beta
- File persistence investigation: root cause found (P2P chats invisible to _restoreAllBlobAttachments at startup)
- Player file identity enforced (byte-identical across platforms)
v0.2.7-beta (Stable)
- Video duration: 4-layer fix (backward moov scan, max return priority, durationchange→knownDuration, dur() max safety net)
- Mobile A/V persistence: all-chat pre-loading, exponential backoff retry, local send path cleanup
- Status circle: PONG/MESSAGE/onPeerFound all update lastSeen properly
- Emoji reactions: packet.from routing fix, data-msg-id on reactions-row
- Overlay controls: touchstart handler, _touchTap guard
- Code blocks: redesigned with Copy button + language badge
- Chat list: markdown-stripped previews
v0.2.8-beta
- Settings Redesign — Card-based items with Lucide icons, gradient containers, and search filtering across all 7 sections
- Message Re-Animation Fix — Granular
data-msg-animattribute only on new messages (cross-platform) - Auto-Scroll Fix — Removed
scroll-behavior: smoothfrom feed, deferred re-scroll for lazy-loaded media - Own Message Bubble Unified —
var(--bg-surface)with right-alignment instead of blue accent - Message Text Color Fixed —
var(--text-primary)on own messages instead of hardcoded#fff - Settings Search Bar — Type to filter settings cards by keyword with empty state
- Version Auto-Detection —
version.jsgenerated frompackage.json, About section reads live value - Theme CSS Cache-Busting —
?v=20260721on all theme<link>tags (both platforms)
v0.2.9-beta
- Mobile Settings Additions — App Zoom slider (50–200%) in Appearance, Disable Light Mode Flashbang toggle, flashbang warning dialog on Light theme selection, GitHub Repository and Report an Issue links in About.
- Desktop Global Gallery Layout Fix — Scroll wrapper separated from inner layout container for grid/compact/masonry/list display modes — no more layout breaks with >10–20 items.
- Experimental Card Icon Fixed —
flaskrenamed toflask-conical(Lucide v1.17.0 rename) so the icon renders correctly on mobile. - Version bumped to v0.2.9-beta across all manifests. Android web assets synced via
npx cap sync android.
v0.3.0-beta
- Image Cropper (Desktop + Mobile) —
shared/ui/image-cropper.jswith drag, zoom, rotate, mirror, reset. Avatar circular crop guide with three-level SVG mask. Integrated into desktop settings and mobile profile sheet. - Mobile Gallery A/V Playback Fixed —
data:→blob:URL conversion for native Android WebView playback. - Real-Time Profile Card Updates — Beacon handlers on both platforms refresh profile overlays/cards instantly on friend changes.
- Android Immersive Mode — System bars auto-hide on full-screen content (Android 7+).
- Desktop "Receiving Audio/Video..." — Transfer progress now shows file-type-specific labels and Lucide icons.
- Input Box Draft Saving (Both Platforms) — Chat drafts persisted in
localStorage, restored on chat switch, cleared on send. - Release Signing Infrastructure — GitHub Actions release workflow, GPG-signed SHA256SUMS.txt,
SECURITY.md, and release verification documentation.
v0.3.1-beta
- Android Immersive Mode Fixed — Taskbar now hides correctly across all Android versions. Capacitor SystemBars conflict resolved, lifecycle handlers added.
- Real-Time Profile Card Updates Fixed — Avatar/banner/bio/frame changes now propagate instantly to peers. BEACON broadcast added to both desktop and mobile save paths.
- Image Cropper Redesigned — Premium UI with Lucide icons, checkerboard preview, corner handles, rule-of-thirds grid. Smoother zoom with sub-pixel positioning and finer steps.
- Mobile Performance — Faster startup (lazy message loading, early-exit blob check, deferred cropper load).
- DM Avatar Render Fix — No more missing avatars during rapid chat switching.
- Bottom Sheet Cleanup — Removed safe-area padding (no longer needed with immersive mode).
v0.3.2-beta
- Android Crash-on-Launch Fixed — SplashScreen theme attributes, plugin lifecycle order fix, missing colors.xml created.
- Profile Card Real-Time Updates (4 bugs fixed) — Duplicate DOM IDs, cropper preview sync, avatar/banner misassignment, profile pill update wiring.
- Frame Picker Redesigned (Mobile) — Compact fixed-position bottom sheet with slideUp animation replaces full-screen backdrop overlay.
- Frame Preview Fixed — No longer clipped by circular avatar border. Flexbox-centered inside the 40×40 button.
- Status Selection (Mobile) — New dropdown with Online/Away/DND/Offline. Saves and broadcasts BEACON.
- "Busy" Status Removed — Cleaned up from both platforms.
- Desktop Status Icons — Changed to filled colored circles.
- Status Text Color Fix — Profile pill no longer gray on startup. Local
statusColorsmap in home-screen.js. - Mobile Search Enhanced — Categorized results (Chats/Friends/Messages), text highlighting, recent searches (max 5), empty-state prompt on focus.
- Search CSS — New
.search-results-*and.recent-search-*styles in mobile.css. - Mobile Gallery Bug Fixes (7 bugs) — Null guards on gallery button/close bindings; null checks in show/hide gallery; blob URL memory leak fixed (video/audio now revoked properly); filter index mismatch fixed; date-group visual order mismatch fixed (clicking media now opens correct item regardless of date sorting); missing scaleIn animation defined; lightbox close button respects safe-area-top.
v0.4.0-beta (Stable)
- Message Long-Press Menu Fixed — Press-and-hold on message bubbles opens the reactions + actions sheet again (wired into the live chat render path).
- Message Effects Graduated — Moved from Experimental to Chat settings with one-time migration.
- Profile Frames Graduated — Moved from Experimental to stable settings (Appearance on mobile, Account on desktop); on by default.
- Profile Frame Leak Fixed — 4 renderers gated so frames never appear when the setting is off.
- Folders Gated Behind Experimental — New
experimentalFolderstoggle (off by default); folder tab icons removed for clean text-only tabs. - Experimental Toggle Audit — Value-based selectors fix Avatars/Frames/Perf Mode off-states; Compact Spacing attribute selector restored; FPS Monitor & Dev Overlay resume on reload.
- Profile Frame Icon Removed (Desktop) — Account settings header shows title only.
v0.4.1-beta
- Friend / Peer Avatars Not Loading Fixed — UDP beacon now sends a 128×128 JPEG thumbnail and the Android receive buffer grew to 64KB, so discovery no longer truncates avatars.
- Other-User Avatar in DM Tab Fixed — DM records seed
avataron creation and sync it from the friend record when beacons arrive. - Orbit Echo Bot Avatar Restored — Avatar sanitizer now allows relative app-asset paths while still blocking scheme-based vectors.
- Image Compression Transparency + Hang Fixed — JPEG compression fills a white background (no more black PNG avatars) and falls back gracefully on encode failure.
- Stored XSS Paths Escaped — All peer-controlled values rendered into HTML (avatars, previews, members, mentions, toasts) are escaped.
v0.4.2-beta
- Chat Folders (Desktop, Experimental) — New Folders rail in the sidebar: create/rename/delete folders, add/remove chats via context menu, dedicated folder view with an "Add Chats" picker. Per-device persistence (no sync yet).
- Folder Tabs Polish (Mobile) — Uniform tab width, centered Friends/Groups/folder trio with one folder, scrollable sub-rail with several; active-tab underline centered.
- Delete Folder Crash Fixed (Desktop) — Notification isolation, confirm-dialog close hardening, and safe reply-quote sender resolution eliminate the crash.
- Sidebar Rail Buttons Fixed (Desktop) — Folders/DMs/Settings/Profile buttons re-attach after settings re-renders.
- Folder Picker Fixed (Desktop) — Outside-click dismisses; interior clicks no longer navigate into the folder.
v0.5.0-beta (Stable)
- Resumable File Transfers (Desktop) —
FILE_TRANSFER_RESUMEprotocol resumes partial chunked transfers from the last stored chunk (migration v13, partial-hash verification, auto-resume on reconnect, 24h stale-partial sweep). - Network Topology Visualizer (Desktop + Mobile) — Live canvas map in Settings → Network: self node centered, peers orbiting by hash angle, RTT color-coded edges (green <150ms / yellow / red), transfer pulse badges, and activity flashes.
- Message Threading (Desktop + Mobile) — Replies persist across restart (migration v14
messages.replyTo); threaded chains render indented with connector lines, "N replies" chips on parents, and a View thread panel with click/tap-to-jump. - Local Vault (Mobile) — Settings → Connection → Local Vault exports all
orbit_*localStorage keys + IndexedDB blobs/partials toOrbitVault-*.json, with optional PBKDF2 + AES-GCM encryption, restore picker with decrypt prompt, and auto-backup on background. - Group Slash Commands (19, Group-Only) —
/help,/poll(+ builder),/me,/shrug,/tableflip,/unflip,/lenny,/roll,/flip,/spoiler,/clear,/invite,/members,/topic,/leave,/shout,/countdown,/nick,/kick— DMs show a "group chats only" toast. - Voice-Note Type Stamps —
type/mimeTypestamped inFILE_TRANSFER_START; receivers honor explicit stamps so voice clips (*.webm) stay audio. - Voice Recorder Level Meter (Desktop) — Live 32-bar AnalyserNode level meter (0–6kHz) in a floating bar above the chat input.
- Android Mic Permissions —
RECORD_AUDIO+MODIFY_AUDIO_SETTINGSfor WebViewgetUserMedia. - Playwright E2E Suite + CI —
desktop/tests/e2e/(echo chat, folders, theme, settings, persistence, navigation) run on pull requests. - Mobile /help Fixed — Hardened
window.OrbitSheetreference + cache-bust restored slash help in mobile groups. - Desktop Slash Commands Added — Full parity implementation matching the mobile command set.
- Poll Builder & Sheets Centering Fixed (Mobile) — Poll/invite/members sheets centered; duplicate Cancel pill removed.
- Invite Share Button Fixed (Both Platforms) — Uses the system share sheet with clipboard fallback instead of posting the invite into whatever chat was open.
- Music Player Seek Bar Harmonized (Desktop) — Audio player duration line now matches video player styling.
- Group-Create Friend Picker Fixed (Desktop) — Capped height with internal scroll; footer pinned to the bottom.
- Group E2EE Design Doc — Shared-group-key scheme documented for group end-to-end encryption.
v0.5.1-beta
- Stalled Mobile Transfers No Longer Lose Their Progress — The 120s reaper deleted the persisted partial of an interrupted receive, leaving the sender streaming into a void (and a restarted sender unrecoverable). The checkpoint now survives reaps: late chunks/END lazily restore it and the transfer continues; restored partials also get a fresh grace window after app start.
- Desktop Receive Robustness (Transfers) — Receive-side write streams handle disk errors gracefully instead of risking the main process, and reconnect-resume verifies/truncates crash-lagged partials correctly so valid progress resumes instead of forcing a full re-send.
- /help Fixed on Mobile Soft Keyboards — IME guard (isComposing/keyCode 229), keyCode 13 fallback, and an insertLineBreak safety net so keyboard-Enter triggers slash commands just like the send button.
- /h Shortcut — Short alias for /help on both platforms.
v0.5.2-beta
- Cross-Platform E2EE Unified — Desktop adopts mobile's encryption scheme end to end: SPKI key encoding, HKDF-SHA256 derivation (16-byte zero salt,
orbit-e2ee-v1info) and the two-field envelope{v:2, ciphertext, nonce}. No handshake is needed — the peer's advertised key format is the capability signal, so SPKI peers use the unified path and legacy raw-hex peers keep the old SHA-256 + packed envelope. Existing desktop keypairs migrate in place with the private key preserved. Encrypted desktop↔Android DMs now work in both directions. - Silent E2EE Downgrade Eliminated (4 sites) — A missing peer key or a failed encryption could previously send a message as plaintext while the UI still showed E2EE as on; one mobile path added the message locally before encryption resolved, so a failure left a message on screen that was never sent. Desktop now blocks the send and names the peer; mobile 1:1 has no plaintext path left; mobile groups skip keyless members instead of downgrading for them, and report who was skipped.
- QR Pairing v2 —
shared/network/qr-pairing.jsbuilds a{v:2, id, n, t, ips[], port, pk, pkf}payload carrying every LAN address (a locally-generated QR cannot use the UDP source-address trick discovery relies on) plus the peer's public key for TOFU pinning. The code is treated as untrusted input: size, id charset, IPv4 range (RFC1918 + link-local only — anything wider is a LAN SSRF primitive), port range and key format are validated, and self-connect is rejected. v1 codes still parse; v3+ reports "needs a newer version"; every failure now shows a toast instead of failing silently. - QR Scanning on Both Platforms — Desktop gains an image scanner (paste with Ctrl+V, drag-drop, or file picker) since a desktop has no camera. Android enumerates its own LAN addresses so mobile QRs carry real addresses. Fixed a bug where mobile's own QR encoded a bare user id instead of the payload — mobile→mobile scanning could never have worked, and an empty
catchhid it. - In-App Update Notifications — New
shared/network/update-check.jslists GitHub Releases and picks the newest by semver (GitHub's/releases/latestexcludes prereleases, and the wholev0.5.xline is one). Highlights come from the release body, falling back to theCHANGELOG.mdsection at that tag — which is what actually works, since the CI release body carries only download/install boilerplate. Desktop shows a heads-up card plus a What's New dialog with a one-click download of the right installer; Android shows the same dialog with the APK. Throttled to 6 hours, skippable per version, and switchable off in Settings → About. - Tabbed Add-a-Friend Modal (Desktop) — Switch between entering an IP address and pairing by QR code, with the image scanner mounted inline.
shared/Was Never Packaged Into Desktop Builds — electron-builder's app directory isdesktop/, andfiles: "**/*"cannot reach outside it, so every../../shared/*reference resolved to a non-existent path in shipped installers. The audio player, video player and image cropper had been 404-ing in every release, and the main process could not have required the shared crypto spec. Fixed withextraResources.- Local Vault OOM Crash on Android — The export assembled the whole blob store in memory with about four copies live at peak, and the store is unbounded while
maxFileSizedefaults to 500 MB. An Android WebView OOM is not catchable — it kills the renderer. Now a 32 MB export budget stops encoding once exceeded, the vault file records what was excluded and why, and auto-backup (which fires on app-background, exactly when Android is most likely to reclaim the process) attaches a real.catch()instead of a synchronous try/catch around an async call. - Mobile Bottom Sheets Opened Behind the Soft Keyboard —
.bottom-sheet-overlayisposition: fixed; top: 0; bottom: 0, which resolves against the layout viewport — and Android does not shrink that when the keyboard opens. Every sheet-opening command (/help, the/pollbuilder, folder rename) was invisible on device while working fine in a desktop browser. Sheets now size tovisualViewport.heightand dismiss the keyboard. - Chat Header Went Stale on Presence and Frame Updates — The header re-rendered only for a fixed set of state keys and
friendswas not among them, yet peer presence and profile frames both arrive as friends-only changes. Fixed with a header-only repaint, because a full re-render ends by force-scrolling the message feed and would yank the reader to the bottom on every status blip. - Chat Header Was Missing Its Profile Frame — The one avatar surface in the app not applying the frame overlay.
- Group Avatar Rendered the Literal Word "undefined" —
varhoisting meant the initial was read before assignment. The four places rendering group avatars had drifted into four different behaviours and are now one shared implementation (group-avatar.js) with a member-avatar grid. - Image Cropper Exported a Different Zoom Than It Previewed — The preview sized with
Math.min(contain) while the export composed withMath.max(cover), so a non-square photo in a square crop exported about a third more zoomed than the user had lined up. Both paths now useMath.min. - Group Image Upload — The create-group modal can now set a group image (URL or upload, square crop, live preview).
- Desktop Reported the Wrong App Version —
preload.jsreadprocess.env.npm_package_version, which is only set when Electron is started through an npm script; in a packaged build it fell back to a hardcoded0.1.2-beta, which Settings → About displayed. - Dead Code Removed — 877 lines across 8 files (
fix-*.js,trace.js,debug.js, two.baks) thatindex.htmlnever loaded. - Test Suites — Unit assertions grew 158 → 267 across four suites; the desktop Playwright E2E suite grew 9 → 34 specs. The interop suite exercises the real
desktop/e2ee.jsagainst a WebCrypto peer mirroring the mobile implementation, proving both directions, unicode round-trips, legacy-peer compatibility and keypair migration — and it caught three bugs before they shipped.
v0.5.3-beta
- Voice & Video Calling on Android — New
mobile/src/js/components/call-manager.jsimplements the whole stack the protocol already had room for: outgoing calls, incoming ring with Accept/Decline, mute/speaker/camera toggles, a duration timer, picture-in-picture self-view, and teardown that stops every track so the microphone indicator does not stay lit. Signalling rides the existing P2P transport, so it is encrypted in transit and needed no protocol change. Verified with two renderers running a realRTCPeerConnectionagainst each other: voice and video connect both ways, and decline, busy and hang-up all tear down cleanly. - Real Ringtone on Both Platforms —
shared/sounds/Call-ring-1.mp3loops for 1m30 while a call rings, on both platforms. Desktop had no ringtone at all before this. The ring window doubles as the no-answer timeout, and a callee whose ring expires is dismissed as a missed call rather than left on a stuck ring screen. - Call Log Entries in the Chat — A finished call leaves a message showing the kind of call, how long it lasted, and a Call again button that redials the same kind. Stored as an ordinary message with a
callobject instead of text, so it inherits ordering, timestamps, unread counts and deletion with no new packet type — the same shape polls already use. Coversended(with duration),missed,declined,no-answer,busyandfailed. - The Encrypted Vault Export Never Worked —
_vaultCrypto()returnscrypto.subtle, andgetRandomValueswas then called on it, but that method lives oncrypto. Every encrypted export threw before encrypting anything; thecatchtoasted and returnednull, so it read as flaky rather than broken. Fixed and verified end to end — the exported file is genuinely encrypted (the plaintext marker is absent) and restores both localStorage data and IndexedDB blobs. - Vault OOM, Properly This Time — The v0.5.2 budget covered only the
blobsstore; thepartialsstore (base64 chunks of in-progress transfers, up to 500 MB) was unbudgeted. Measured on a realistic account: 101 MB payload, ~290 MB peak, dominated by 55.9 MB of partials. One budget now spans both, measured by serialised size (base64 is 4 chars per 3 bytes, so the old raw-byte check let 1.33x more through), walked attachments-first for determinism. The budget is also derived from the WebView's own heap ceiling and halved for the encrypted path, which holds four copies of the payload at once. Verified by simulating a 192 MB heap: the budget dropped to 15.4 MB on its own and the export still succeeded. - Vault Restore No Longer Holds Every Attachment at Once — Restore built one
Promise.allover every blob write, keeping each decoded ArrayBuffer alive until all of them finished. Now batched three at a time, with the raw file string released before the object graph is walked, and the recordedattachmentsExcludedmetadata surfaced in the restore toast. - Mobile Bottom Sheets Could Not Be Closed — The Cancel pill was appended to the sheet's own scroll container, so on a long sheet it sat ~1400px below the fold (measured at y=2153 in a 754px viewport). Separately,
hide()only toggles a class while the drag handlers write an inlinetransformon everytouchmove, and neither drag block handledtouchcancel— which Android fires routinely. The leftover inline transform then outranked both the open and closed CSS rules, sohide()visibly did nothing, permanently. The sheet is now a flex column whose content scrolls, Cancel is a pinned footer, transforms are reset on open and close, andtouchcancelis handled. - Some Bottom Sheets Would Not Scroll —
max-heightused70vh, which resolves against the layout viewport Android does not shrink for the keyboard, while the overlay is sized fromvisualViewport.height. Now both track--sheet-vh, so the sheet can never be taller than the space it is anchored in. - Group Avatars Match Desktop on Mobile — Mobile still drew a single initial in five places where desktop rendered a member-avatar grid. One shared implementation now (
shared/ui/group-avatar.js), with asetResolver()hook so mobile can enrich members from the friend record — needed because a peer-synced group can carry members as bare userId strings. - Desktop Calls Had No No-Answer Timeout — An unanswered call left the caller on a dead call screen indefinitely. Now the same 1m30 window as the ringtone.
- Ringtone Kept Ringing After the Callee Answered — Caught by testing the transitions rather than just the happy path. Desktop's ring decision cannot key off
isIncomingalone, because that flag isfalsefor both "outgoing" and "accepted an incoming call". - Test Suites — Unit assertions remain 267/267; the desktop Playwright E2E suite remains 34/34 (run sharded). This release additionally verified mobile voice and video calls end to end, decline and busy handling, the call log across all eight outcome variants on both platforms, and a full encrypted vault round trip.
v0.6.0-beta (Stable)
- Android Notifications — Six channels (messages, mentions, calls, updates, background, service) and nine icons generated from the Lucide set the app already vendors. Messages group by chat so a burst collapses into one entry.
- Background Notifications Had Never Worked — Three independent causes:
orbit_messageswas posted to but never created (Android silently drops a notification on an unknown channel); the JS path referencedic_stat_icon, a drawable that has never existed; and the foreground listener came from@capacitor/app, which is not installed, so the native path's!_isForegroundgate never opened. The plugin now tracks the activity lifecycle itself. - The Icon Was Always the Generic "i" — Every notification used
android.R.drawable.ic_dialog_info. - Offline Send Queue — Messages to an unreachable peer are held in localStorage and delivered on reconnect, with a pending marker on the bubble. Typing, presence, reactions and call signalling are deliberately excluded — a late typing indicator is worse than none.
- Local Vault v2 — A manifest plus one file per 8 MB chunk instead of one in-memory JSON, with per-chunk AES-GCM so the export can stream. The manifest is written last, so an interrupted export can never look complete. v1 backups still restore.
- Backup Save & Share — Zipped natively, then saved to Downloads via MediaStore (or the share sheet), because app-private storage is not somewhere a backup can be retrieved from.
- In-App Android Updates — Streaming download with progress, installer hand-off, and a prompt to close Orbit so the update applies. If "install unknown apps" is not allowed, Orbit opens the right settings screen and says so.
- Delete a Stored Backup — With a confirmation naming the file. No automatic pruning, deliberately.
- Avatar Frames Unified — One geometry (125%, centred) replaces six inconsistent sets across ~15 surfaces.
- Vault Chunk Filename Collision Fixed — Names came from the sanitised key, so
a@banda#bcollided and could restore each other's data. Now keyed on entry and chunk indices. - Auto-Backup No Longer Writes Plaintext — With encryption enabled and no passphrase available it cancels instead of downgrading.
- The Vault Reported 0.0 MB — The summary counted only the blob store; messages and inline images live in localStorage.
- Restore Batching — Attachments restore in small batches rather than all at once.
- Test Suites — Unit assertions remain 267/267; the desktop Playwright E2E suite remains 34/34.
v0.6.1-beta
- Notifications Never Appeared —
POST_NOTIFICATIONSwas never declared in the manifest, so on Android 13+ the permission could not be granted, theSecurityExceptionwas swallowed with a comment asserting the user had declined, and the system discarded every notification before it reached the status bar. Declared, and requested at runtime on API 33+ fromMainActivity. - Large Transfers Exhausted Memory — Chunks arrived as base64 strings (~356 MB of heap for a 133 MB file), were decoded into a second full copy at the end, then merged into a third. Peak ~620 MB, which OOMs a WebView. Chunks are now decoded on arrival and held as bytes; the periodic checkpoint became a reference copy; the decode-and-merge step is gone. Peak ~266 MB.
- The Chunk Shape Change Touched Every Consumer —
_chunkToBytes()accepts anArrayBufferor a legacy base64 string, because the resume path, the merge loop and a size readout all assumed strings and would have broken. Older partials still hold strings, so both shapes must work mid-upgrade. - Receive Cap Corrected — 5000 chunks (320 MB) to 2400 (~150 MB), named and documented as tunable, with a user-facing message when a transfer is refused.
- Text and Files Split Into Separate Messages — The large-file marker was removed from the text message on all four send paths (mobile group, mobile DM encrypted, mobile DM plain, desktop). Both receivers already did find-or-create, so no receive-side change was needed. Inline attachments are unaffected.
- Gallery Sidebar Fix — The nav buttons were built from
activeView === 'folders'alone, so DMs lit up everywhere else including the gallery, and the gallery button had no active logic of its own. Any re-render stole the gallery's highlight. Both now derive from state. - Test Suites — Unit assertions remain 267/267.
v0.6.2-beta
- Notification Icons Fixed — paint attributes sat on a VectorDrawable
<group>, which only takes transforms, so the drawable was invalid and failed to inflate. Moved onto the<path>. All nine re-rendered and checked at 24px and 72px. - In-App Update Check Fixed —
CapacitorHttpwas neither installed nor enabled, so the check fell through towindow.fetchand hit WebView CORS. - Update Download Fixed — enabling CapacitorHttp buffers response bodies, so the download's stream reader was unavailable. Streams when possible, buffers when not.
content-lengthIs Optional — a missing header could fail the whole download; it only drives the progress readout.- Gradle 9 —
proguard-android.txtremoved; switched toproguard-android-optimize.txt.
v0.6.3-beta
- Desktop: Every File Send Threw —
CHUNK_SIZEwas a localvarinsidesendMessage()but used in_sendFileChunks(), a separate method. Out of scope, so the first chunk threw and nothing ever appeared. Hoisted to module scope. - Desktop: Attaching a File Produced No Message —
File.pathwas removed in Electron 32, sofile.path || file.namefell through to a bare filename the transfer could not open. Now useswebUtils.getPathForFilethrough the preload, at both the file-input and drag-and-drop sites. - Mobile: Chunks Could Overtake the Transfer Start —
FILE_TRANSFER_STARTwent through the outbox, which flushes asynchronously, while the chunk driver runs synchronously — so chunks could arrive before the START that explains them. Reverted to a direct synchronous send. - Desktop: Reading Attachment Bytes Was Blocked by the CSP —
connect-srclisted noblob:,orbit-db:ororbit-file:, so the renderer refused to read its own attachments and album art silently never appeared.orbit-db:/orbit-file:also cover attachments restored after a restart. - Playing Media Stopped on Re-render — Both shared players looked the feed up as
#chat-message-feed; mobile's is#message-feed. The lookup returnednull, so a detached player was never re-attached and the rebuild produced a fresh paused one — your audio stopped when a new message arrived. - Text and File Arranged Differently Per Platform — Desktop composed attachment-then-text, mobile text-then-attachment. Desktop now matches mobile.
- Test Suites — Unit assertions now 289 across 6 suites, including 13 new cases for the desktop chunk/ACK/RESUME state machine.
v0.6.4-beta
- Real waveform — Visualiser → Off draws the track's actual decoded shape; oversized files fall back to the flat row; cached per URL so re-renders never re-decode
- File name above the timer — both renderers now pass the attachment name; long names ellipsise so they never shove the volume button off the row
- ⋮ menu expanded — Visualiser (Bars / Wave / Off), Time (Elapsed / Remaining), Copy file name, on top of the existing Loop and Playback Speed
- First-run feature slides — 7 slides in the chat panel describing Orbit, shared by desktop and mobile; position remembered across re-renders; "Done" collapses to the plain empty state
- Idle audio bubbles — unplayed bubbles show the track's waveform instead of a void; the draw loop stops on pause and restarts on play
v0.6.5-beta
- In-app update download (desktop) — the notice fetches the installer into
Downloads/Orbit Updateswith progress and a cancel, verifies it against the release'sSHA256SUMS.txt, and offers Open installer / Show in folder; a mismatch is deleted - Right installer per OS and CPU — assets come back alphabetically, so an Intel Mac used to be offered the arm64
.dmgand Linux was never offered the AppImage; architecture and format preference are now explicit - Windows checksum verification fixed — the manifest keeps CI's spaced filename while GitHub rewrites the asset to dots, so the lookup never matched
- Playback no longer escapes its bubble — a player left mid-playback stayed in the wrong chat forever; it now keeps playing, stays hidden, and re-attaches to its own message
/helpsheet affordances — always-visible close button, draggable body, and a scroll fade so a 19-command list does not look like it simply ends- Emoji picker actually themed — it had been rendering the library's defaults (stray outline included) because the CSS used variable names the library does not read; skin tone is remembered now
v0.7.0-beta
- "Close DM" keeps the conversation — it deleted the messages and pins out of state (and state is persisted), so closing a DM destroyed its history
- Orbit Echo cannot be closed — it is not a peer you can re-add, so closing its DM removed it for good; the menu entry is now a locked item that explains why
- The friends directory — an "All friends" button in the Friends header opens a searchable, filterable list of everyone, closed DMs included, in a list or a grid
- The
/helpsheet — it was capped to the keyboard-up viewport measurement taken when it opened, and never re-measured; it now opens at 92% with a scroll thumb, and rows no longer repeat the command name - Notifications — one Orbit icon for every kind, and the sender's avatar as the large icon (the background path had none at all)
- Orbit opens on the feature tour instead of the last conversation, and the first-run slides are desktop-only again
- The people icons use their rounded variants —
user,users,user-plus,user-xand the Experimental flask
v0.7.1-beta
/helpsizing — it was sized from the keyboard-shrunken viewport, which on Android can stay wrong indefinitely; sheets with no text field now use the layout viewport, and a late keyboard dismissal grows the sheet back- The emoji picker's
"undefined"— a tone event with no detail had persisted the literal string, which the picker then painted across its search row; validated on both sides and cleared on read - A drag could take over a scroll — the body-drag was decided at touchstart, before the direction was known
v0.7.2-beta
/helpopens over the message input — sized from the layout viewport with a screen-height sanity check, so it is no longer a short strip; the list scrolls and Cancel is on screen- The in-app update verifies its download — it checks the size against the release before handing the APK to Android, and refuses a partial file or a release page
- Every empty state uses one treatment, on both platforms — a glass icon tile with a bloom, a title and a hint, in compact and muted variants
- The desktop welcome slides have atmosphere — an aurora behind a glass card, with a staggered entrance per slide
- The light-mode "Next" button is readable on hover — it was white on pale grey
- The tour starts at the beginning on every launch, and "Done" stays done
- A false security claim was removed from the tour — it promised a key-change warning that is not implemented
v0.7.3-beta
- Custom Colors covers all 17 colour tokens — real colour pickers, browser-validated values, a way back to your saved colours, and Android support where there was none
- The emoji picker is themed — the app's tokens instead of the library defaults, a remembered skin tone, and no more clipped last column behind the Windows scrollbar
- Drafts end when you send — an unsent message stays in its chat; sending no longer lets the draft come back
- The empty-chat placeholder is centred in the conversation, on both platforms
- Group info — how many members are online, when the group was created, and a Danger Zone for the owner
- Groundwork for settings sync — the packet type and the rules, with unit tests; nothing sends or receives yet
v0.7.4-beta
- The in-app Android update works — the APK was one byte too long, so Android refused it every time
- Update progress with the time remaining, on both platforms
- Transfer progress on mobile, matching the desktop
/helpoffered in DMs, and it reports a failure instead of failing silently- Pinned messages — a card in the conversation, a list with reordering, 3 in a DM and 5 in a group, and a note in the chat when the order changes
v0.7.5-beta
-
/helpopens the command list again on mobile — the tap that opened it also closed it -
Jump to latest, with a word when you are reading old messages
-
Commands in the
+menu, and copyable user IDs on both platforms -
Swipe the pinned bar between pins
-
Members folded into the
⋮menu, and a search ✕ that clears before it closes -
No more "Created Unknown", and a setting for the group created date
-
A sheet that opens off-screen says so, instead of eating what you typed
-
55 profile frames, up from 43, with a picker that lists what exists
v0.7.6-beta
- Shared Media rebuilt — search, filters by sender, kind and age, a grid/list toggle, jump-to-message on every item, and multi-select with download-all
- It renders 60 items at a time, with a "Showing 60 of 412" count, instead of building the whole list on every keystroke
- Storage stats — how much your media takes and which chats it came from, biggest first
- Select and delete media, with a confirm that says the message goes for both people
- A month scrubber down the side of the gallery
- The composer says what it can do — a plus that rotates into an ×, a send button that lights up and fills, and a random icon if you hover a disabled one
- The slides have some pop — the carousel and tutorial animate their parts in sequence
- Fixed — the Images tab showing video and audio, the display toggle refreshing the whole window, two galleries at once, no way out of select mode,
NaNday groups, and long filenames overflowing their cards
v0.8.0-beta (Stable)
- Your theme follows you between your own devices — including a custom theme's colours
- Message search runs on SQLite's FTS5 — boolean queries, prefix matching, relevance ranking, per-chat scoping
- The update progress bar moves — it had no denominator to divide by
- The Images tab is images — video and audio went to Files, where their icons work
- Changing the display no longer refreshes the whole window
- Fixed — two galleries at once, no way out of select mode, and file cards spilling over their neighbours
v0.8.1-beta
- Your account travels from the desktop to your phone — encrypted, over your own network, and imported as a second account rather than overwriting the one already there. A link token authorises it: 128 random bits, shown only as a code on the desktop's screen, burned the moment a transfer starts.
- The mobile UI redesign is finished — filled tab icons, day separators and rebalanced bubbles in the conversation, a three-control composer, a gallery that names its conversation, a profile card that opens from the header avatar, and full-bleed settings sections.
- Search actually searches — message search used to find nothing outside the open chat; it now spans everything, with filters on both the global and in-chat surfaces.
- A test suite for the mobile — 66 checks in
mobile/tests/,npm run test:mobile - Fixed — the settings search that never hid anything, the composer's mic/send swap, the Activity title 20px off centre, the navbar moving on tab change, the clipped last row of every settings section, the off-centre toggle knob, and an account switch that could take the wrong data with it
v0.8.2-beta
- A maintenance release — the mobile restyle's own regressions, closed out
- Diagnostics, rebuilt — a verdict with a pass count, the checks in one grouped card, and the report shown rather than only described
- Fixed — the contacts list flipping to a flat list on almost any update, the Friends/Groups tabs hidden unless you already had a folder, and custom profile banners that had stopped rendering
- Mobile test suite — 66 → 160 checks
v0.8.3-beta
- The conversation screen, reworked — text keeps its bubble, images lose theirs and keep their real proportions, files get a surface with a type-correct icon, video and audio float on a shadow
- Your own messages match everyone else’s — same surface, with a Classic Bubbles toggle for the old accent
- The desktop got the same treatment, so both platforms lay a conversation out the same way
- Reactions straddle the bubble’s edge, and do the same against a photo when there is no bubble
- Fixed — hardcoded bubble colours that made message text vanish in a light theme, a duplicate × on the attachment pills and in every sheet, an unparseable backup time that rendered the words Invalid Date, and desktop grid images that rendered at half height
- Mobile test suite — 160 checks
v0.8.4-beta
- One tap on Send no longer fires the mic — Android’s synthetic mouse events land on whatever is under the finger by then, which after the swap was the mic
- Both controls were double-firing on a real tap — the mic started on touchstart and on the synthetic mousedown, send on touchend and on mouseup
- Mobile test suite — 183 checks
v0.8.5-beta
- Orbit is in the Android share sheet — share a photo, video, PDF or link from any app, then pick a chat
- Reply from the notification — direct messages get a Reply box; replies typed with Orbit closed are queued and sent when it next opens, and the notification stays until then
- App Lock — a six-digit PIN, PBKDF2-hashed, five wrong tries and a 30-second cooldown that survives a restart
- Link previews — the page’s own title and image for the most recent link in the chat
- Image viewer — previous/next, zoom by button or pinch, and download
- Undo send — a real retraction that tells the other side too; off by default
- Fixed: the What’s New button did nothing, and the viewer’s arrows were never styled at all
- Mobile test suite — 196 checks
v0.8.6-beta
- Restore from a backup file — the Local Vault finds
.orzip/.zipbackups in the app’s own folder and invault/, the files that survive deleting the app, and adds the account alongside what is on the phone rather than over it - An emoji picked from the picker could not be sent — not the empty-check, which was fine; the picker set the input’s value directly and assigning
.valuefires noinputevent, so the send button never appeared and the composer kept showing the mic - Link previews while you type — the bar used to appear only after the message had gone, and the card inside a sent message printed the domain twice
- Contacts — filter chips with live counts, a list/grid display style, and actions on every row; groups had none at all before
- The attachment preview, rebuilt — a spinner while an image decodes, an × on each to take it out of the send, and tapping one looks at it full screen instead of sending
- Only the active tab keeps its label
- Fixed: tapping “Pair a desktop” re-opened the profile card on top of the camera, and the restore button had no corner radius at all — it was written against a token this app does not define
- Mobile test suite — 196 checks
v0.8.7-beta
- The composer's "+" is one door now — a media panel with a Photos · Poll · Commands · Files pill row, instead of a five-item list whose first two entries were "Upload Images" and "Upload Videos"; the picker decides between an image and a video, so that split is gone
- Permissions are asked for once, at startup — only for the ones that are actually missing, once per version. If everything is already granted it does nothing at all, and it no longer opens the camera on every launch just to provoke the dialog
- The vault's "Restore From a Backup File" row moved above the Export and Restore buttons — it is not an action on this vault, it is the recovery path for one that is already gone
- Fixed: a dismissed link preview stayed dismissed for the whole session, so re-pasting that link showed nothing; the ⋮ on a contact opened a sheet containing nothing but Cancel; the contacts grid was 426px wide on a 390px screen, so the right-hand cards were cut off; and on the desktop a re-render made staged attachments vanish while they were still staged and would still be sent
- Mobile test suite — 299 checks
See CHANGELOG.md for the full version history.
Pre-built Windows installers are published on GitHub Releases.
| Release | Platform | Notes |
|---|---|---|
| All releases | Win / Mac / Linux / Android | Most recent build first |
| v0.0.2-beta | Windows | SQLite storage, privacy mode, large file transfers |
| v0.0.1-beta | Windows | Original release |
The Releases page is the source of truth — every current release is a prerelease (
-beta), and GitHub'sreleases/latestshortcut deliberately skips prereleases, so it will not resolve to an Orbit build.
Windows may show SmartScreen for unsigned builds. Choose More info → Run anyway if you trust the source. macOS users may need to right-click → Open on first launch for unsigned apps.
Requirements: Node.js 18+ (LTS recommended), npm.
git clone https://github.com/D4niel-dev/Orbit-beta.git
cd Orbit-beta-main
cd desktop
npm install
npm startPeers on the same LAN are discovered automatically. Open Orbit on another machine to start chatting.
# From repo root
cd mobile
npm install
npm run shared:sync # copy cross-platform shared modules
npx cap sync android # sync Capacitor Android project
npx cap open android # open in Android Studio for buildingOr let GitHub Actions build it automatically — push a v* tag or trigger the workflow manually.
| Layer | Technology |
|---|---|
| Desktop shell | Electron 32 |
| Mobile shell | Capacitor 8 (Android) |
| Runtime | Node.js (desktop), WebView (mobile) |
| UI | HTML, CSS, JavaScript |
| Storage | better-sqlite3 (desktop) · IndexedDB + localStorage (mobile) |
| Networking | Raw TCP P2P sockets, LAN multicast discovery |
| Calling | WebRTC (peer-to-peer audio/video, STUN for address discovery); signalling rides the existing P2P transport |
| Media | sharp (WebP thumbnails) |
| QR codes | qrcode-generator (encode) + jsQR (decode) — vendored on mobile, bundled on desktop so pairing works with no internet |
| Testing | Playwright E2E (desktop, drives the app's own Electron binary) + plain Node unit suites |
| Desktop packaging | electron-builder (Windows NSIS, macOS DMG, Linux AppImage/deb) |
| Mobile packaging | Gradle (Android APK/AAB) |
Orbit is peer-to-peer: clients connect directly over your local network with no central server.
┌──────────────────────────┐ ┌──────────────────────────┐
│ Desktop Orbit │ │ Android Orbit │
│ (Electron) │ │ (Capacitor) │
└────────────┬─────────────┘ └────────────┬─────────────┘
│ │
│ TCP P2P · E2EE │
├───────────────────────────────────┤
│ │
│ UDP Discovery · multicast │
│ │
└────────────────┬──────────────────┘
│
┌───────┴───────┐
│ Local Network │
└───────────────┘
Orbit runs on two platforms with a shared cross-platform core:
| Layer | Desktop | Mobile |
|---|---|---|
| Main process | Electron (main.js) — owns networking, DB, E2EE, IPC |
Native Java plugin (OrbitP2PPlugin) — networking & beacon, bridged via Capacitor |
| Preload | Context-bridge (preload.js) — typed IPC surface |
N/A (uses Web APIs instead) |
| Renderer | desktop/src/ — HTML/CSS/JS chat UI via <script> tags |
mobile/src/ — same UI adapted for touch |
| Shared core | shared/ — env detection, database factory, protocol, crypto |
Same shared/ modules, mobile backends |
Mobile keeps a working copy of the shared core at mobile/src/shared/, refreshed automatically by npm run shared:sync before every cap sync/build — so shared/ is always the single source of truth.
Security defaults (desktop): nodeIntegration: false, contextIsolation: true.
Orbit-beta/
├── package.json # Workspace root — desktop/mobile build scripts
├── CHANGELOG.md # Release notes
├── README.md # This file
├── SECURITY.md # Security policy
│
├── desktop/ # Electron desktop app (Windows / macOS / Linux)
│ ├── main.js # Main process — networking, DB, IPC, window/tray
│ ├── e2ee.js # E2EE (SPKI keys, HKDF-SHA256, v1+v2 envelopes)
│ ├── preload.js # Context-isolated IPC bridge (orbitAPI)
│ ├── electron-builder.yml # Packaging (NSIS / DMG / AppImage+deb)
│ ├── package.json
│ ├── tests/e2e/ # Playwright E2E specs + shared launch helpers
│ └── src/
│ ├── index.html # App shell
│ ├── js/
│ │ ├── app.js # Renderer entry — UI wiring, message rendering
│ │ ├── store.js # Renderer state store
│ │ ├── identity.js # Local identity & account switching
│ │ ├── components/ # Toasts, modals, profile-card, emoji-picker,
│ │ │ # webrtc-call, image-viewer, context-menu,
│ │ │ # qr-scanner, update-notice, ...
│ │ ├── views/ # Chat panel, settings modal, sidebars, gallery
│ │ ├── network/ # socket.js, discovery.js, protocol.js, transfer.js
│ │ ├── database/ # SQLite wrapper + schema migrations
│ │ └── utils/ # format, sanitize, profanity, storage
│ ├── styles/ # base, layout, components, animations, themes/
│ └── icons/ # App icons & screenshots
│
├── mobile/ # Capacitor Android app
│ ├── capacitor.config.json
│ ├── build-android.ps1 # APK build script
│ ├── icons/ # PWA-style icons (48–512px)
│ ├── android/ # Native Android project (Gradle)
│ ├── package.json
│ └── src/ # Mobile web UI (runs in WebView)
│ ├── index.html
│ ├── js/
│ │ ├── app.js # Entry — UI wiring, message rendering
│ │ ├── store.js # State store + localStorage persistence
│ │ ├── version.js # Auto-generated APP_VERSION
│ │ ├── components/ # navigation, home-screen, chat-screen,
│ │ │ # bottom-sheet, update-notice, group-avatar,
│ │ │ # call-manager (voice/video calling)
│ │ └── debug.js # Dev-mode helpers
│ ├── styles/ # base, layout, mobile, components, animations, themes/
│ ├── lib/ # Vendored: lucide, qrcode, jsqr, emoji-picker-element
│ ├── shared/ # Auto-synced copy of ../shared (shared:sync)
│ └── icons/ # In-app icons (frames, status dots, ...)
│
├── shared/ # Cross-platform modules (desktop + mobile)
│ ├── core/env.js # Runtime detection
│ ├── database/ # index.js factory + sqlite-desktop / sqlite-mobile
│ ├── network/ # protocol.js packet defs, qr-pairing.js,
│ │ # update-check.js, p2p-mobile.js
│ ├── crypto/ # e2ee-key.js (shared key/envelope spec),
│ │ # e2ee-desktop / e2ee-mobile
│ ├── ui/ # audio-player, video-player, image-cropper,
│ │ # group-avatar (shared member-avatar grid)
│ ├── sounds/ # Call-ring-*.mp3 ringtones (both platforms)
│ └── utils/ # format.js, sanitize.js
│
├── tests/unit/ # Node unit tests (npm run test:unit)
├── docs/ # Landing / documentation page
├── security/ # Release signing keys & docs
├── plans/ # Planning notes
└── .github/ # CI/CD workflows
- Discovery — Clients announce themselves over UDP multicast on the LAN (
discovery.js/OrbitP2PPluginbeacon) and answer peer pings. - Connect — Two peers open a direct TCP socket and exchange identity + E2EE public keys.
- Send — The UI calls
store→ the platform backend encrypts (AES-256-GCM) and frames the packet (protocol.js) → writes it to the socket. - Receive — The socket handler validates the frame, decrypts, and dispatches the event to the store → the renderer updates the chat in place.
Orbit serves local resources through privileged custom schemes instead of exposing raw filesystem paths to the renderer:
| Protocol | Purpose |
|---|---|
orbit-db:// |
Serves attachment BLOBs and thumbnails from SQLite through the main process — stable URLs that survive app restarts. |
orbit-file:// |
Serves ephemeral files (e.g. privacy-mode temp storage or in-flight transfers) without granting the UI direct disk access. |
This keeps the renderer sandboxed while still allowing rich media in chat and the gallery sidebar.
Every Orbit release since v0.3.0-beta includes cryptographic signatures so you can verify the authenticity and integrity of your download.
- Provenance — confirms the release was created by the official Orbit repository, not an impostor
- Integrity — guarantees the file hasn't been tampered with in transit
- Trust — builds confidence in the software supply chain
# 1. Import the Orbit Release public key
curl -O https://raw.githubusercontent.com/D4niel-dev/Orbit-beta/main/security/public-key.asc
gpg --import public-key.asc
# 2. Download your release assets (Orbit-v0.4.0-setup.exe, SHA256SUMS.txt, SHA256SUMS.txt.sig)
# 3. Verify the signature on the checksums file
gpg --verify SHA256SUMS.txt.sig SHA256SUMS.txt
# 4. Verify your downloaded file matches the published checksum
sha256sum --check SHA256SUMS.txt --ignore-missingThe --ignore-missing flag skips checks for files you didn't download, so you can verify a single artifact without downloading everything.
Desktop shortcut: since v0.6.5-beta the desktop app does step 4 for you. When it downloads an update it checks the file against the release's published
SHA256SUMS.txtand reports Checksum verified before offering Open installer — and deletes the file if it does not match. It does not check the GPG signature, so steps 1–3 above are still worth doing if you want to confirm provenance rather than just integrity.
The public key is also available in the security/ directory of this repository.
Orbit stores settings and the database under your OS user data directory (Electron userData). There is no .env required for normal use.
Notable settings (in-app Settings):
| Setting | Description |
|---|---|
| Attachment storage | Persistent (default) or privacy mode (temp files cleared on exit) |
| Clear saved attachments | Remove attachment BLOBs from the database |
| Theme / profile | Display name, avatar, light or dark theme |
| Profile frame | Decorative frame overlaid on your avatar, shown to peers |
| Automatic update checks | Whether the app asks GitHub for a newer version (Settings → About; the manual "Check for updates" button always works) |
| Sidebar buttons | Choose which buttons appear in the left sidebar (Appearance → Text & Layout) |
Transparency matters in beta. Current constraints include:
| Limitation | Details |
|---|---|
| LAN-focused | Peers must be reachable on the local network. NAT traversal is not implemented. |
| E2EE needs both peers on v0.5.2-beta or newer | The unified scheme shipped in v0.5.2-beta. A peer running an older build still advertises the legacy key format, so encrypted DMs fall back to the legacy path — desktop↔Android encryption only works once both sides are updated. |
| Unsigned builds | Installers are not code-signed; Windows SmartScreen warnings are expected. |
| Update checks contact GitHub | At most once every 6 hours the app asks api.github.com for the newest release and, if the release body has no notes, reads CHANGELOG.md from raw.githubusercontent.com. Downloading an update (desktop: the installer; Android: the APK) additionally fetches that file and SHA256SUMS.txt from github.com. Nothing about you, your identity or your chats is sent, and the check can be turned off in Settings → About. |
| Third-party data egress | Message Translate sends the message text to MyMemory (api.mymemory.translated.net); GIF search queries Giphy. Both are user-initiated and optional, but neither is covered by Orbit's E2EE — that content leaves your device in plaintext. |
| Calls are LAN-only | Like messaging, a call needs both peers on the same network. Media is peer-to-peer with STUN for address discovery — there is no relay and no NAT traversal, so calling across the internet is not supported. |
| Group calls are desktop-only | Desktop has mesh group calls; mobile handles 1:1 calls, and a group call offer is declined as busy rather than half-joined. |
| Queued messages do not survive a restart | A send that fails is queued and re-sent when the peer reconnects, but that queue lives in memory — killing the app loses anything still waiting. File bytes are held in memory too, so an interrupted transfer needs the app to stay open. |
| No key-change warning | Peer keys are pinned on first use (QR pairing), but Orbit does not yet warn if a peer’s key later changes, and there is no fingerprint to compare out of band. For an app built on E2EE that is a real gap. |
| No iOS support | Android is the only mobile platform — iOS/iPadOS is not planned. |
- Backups are not pruned automatically. Each export leaves a full copy in app storage, and old ones are deleted by hand from the restore list. On a large account that adds up quickly.
- A large backup is awkward to share. Saving to Downloads is fine, but the share sheet will refuse a 500 MB zip — email and most chat apps have their own limits.
- Calling has not been tested on physical hardware. Voice and video calls were verified against a two-peer WebRTC harness (real
RTCPeerConnection, synthetic camera and microphone) in a desktop environment. Real device hardware, Android audio routing (earpiece vs speaker) and behaviour when the app is backgrounded mid-call still need a phone. - The speaker button mutes remote audio rather than switching Android's audio route — that needs a native
AudioManagercall. - Large file transfers between Desktop and Android are under active development
- An account transfer is all-or-nothing, and needs both devices on the same network — it rides the LAN like everything else
- Discovery reliability depends on local network configuration
- Beta stability: occasional UI quirks and forced reflow warnings in DevTools
- Account transfer over the internet — It works across your LAN today; NAT traversal is the same gap that keeps messaging and calling on the local network
- Partial account transfer — Bringing across one conversation rather than the whole account
- Group calls on mobile — Desktop has mesh group calls; Android currently handles 1:1 only
- Search inside attachments — Search covers message text and chat names; the contents of a file or the text in an image are not indexed
- Native audio routing during calls — Switching between earpiece and loudspeaker needs a native
AudioManagerbridge; today the speaker button mutes the remote audio instead - Group E2EE — Extend end-to-end encryption to group chats (currently DM-only); membership changes will need re-keying
- Signed builds — Code signing for Windows and macOS, which is also the prerequisite for true silent auto-update
- Large file transfer stability — Cross-platform transfer hardening
- Custom notification sounds — Per-chat and per-contact sound profiles
- WebRTC fallback — Partial connectivity path for difficult network conditions
- WebRTC-based NAT traversal — Connect across subnets
- Plugin system — Community extensions API
Roadmap items are intentions, not commitments. See GitHub Issues for tracking and discussion.
# In the app folder
cd desktop
npm install # Install requirements
npm start # Launch Electron
npm run build:win # Build Windows installer
npm run build:mac # Build macOS .dmg (macOS host required)
npm run build:linux # Build Linux .AppImage + .deb
npm test # Playwright E2E suite (drives the app's own Electron binary)
npm run test:sharded # Same suite split in two — use this if a single run stalls# In the app folder
cd mobile
npm install # Install requirements
npm run shared:sync # Copy shared modules into mobile/src/
npx cap sync android # Sync Capacitor Android project
npx cap open android # Open in Android StudioOr use the pre-made build script:
# In the app folder
cd mobile
powershell -File build-android.ps1 # Full build → APK| Command | Description |
|---|---|
npm start |
Launch desktop Electron app |
npm run test:unit |
Run the Node unit suites for the shared modules (no browser needed) |
npm run desktop:build:win |
Build Windows installer |
npm run desktop:build:mac |
Build macOS .dmg |
npm run desktop:build:linux |
Build Linux packages |
npm run mobile:sync |
Sync Android project |
npm run mobile:build |
Build Android APK |
Desktop build configuration lives in desktop/electron-builder.yml.
Mobile build configuration lives in mobile/android/ (Gradle).
Built artifacts (desktop/dist/, mobile/android/app/build/) are gitignored — attach them to GitHub Releases instead of committing binaries.
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-change) - Commit your changes and open a pull request!
Bug reports and feature ideas are welcome via GitHub Issues.
MIT — Copyright (c) 2026 D4niel-dev & Orbit Team. See LICENSE for the full text.
Orbit Team · Lead developer D4niel-dev
Local-first communication for private networks