1effb1c53f2f11463ae6ac79b212568dbb641abf
Two follow-on bugs from the initial S2S presence rollout:
(1) New friend stuck offline until they reload: presence_update fires only on
transitions, so a remote user already online when their stub is created
locally never receives a relay event seeding their actual status. The
stub defaulted to 'offline' at creation and stayed there until the next
transition. Fix: extend FederationRelayProfileSnapshot +
FederationUserLookupProfile with status. Sender-side buildProfileSnapshot,
getDmParticipants, and lookup endpoint responses populate it for native
users only (replicated stubs hold stale status owned elsewhere).
resolveOrCreateReplicatedUser uses hints.status to seed the new row's
status column. Threaded through every call site (DM participants, group
bootstrap, friend events, ownership transfer). Stub backfill worker also
heals existing rows whose status was stuck at 'offline' from creation.
(2) 'Online' text updates but green avatar dot stays grey on the same page:
spaceStore.updateMemberPresence patches members[] (which feeds space UIs)
but never patches userViews — the cache useCanonicalUserView reads from.
The Avatar in FriendItem reads canonical.status; the text reads
friend.status (socialStore). Two sources, one stale until full
user_updated arrives. Fix: updateMemberPresence now mirrors status into
matching userViews entries, so canonical-view consumers re-render with
fresh status the moment the WS event lands.
Backspace
Open-source, self-hosted Discord alternative built with TypeScript.
Features
- Real-time text messaging with WebSocket
- Servers, channels, and role-based permission management
- Voice and video chat via LiveKit
- Screen sharing with configurable quality (VP9)
- Direct messages (1-on-1 and group DMs up to 10)
- DM voice/video calls with ringing
- Friend system with requests
- File uploads and image sharing
- Markdown message formatting with syntax highlighting
- Message reactions, replies, and editing
- Typing indicators, presence status, and read states
- Invite system with shareable codes
- Instance-level admin panel (streaming limits)
- Desktop app (Electron)
- Mobile-responsive web UI
- Docker deployment
Tech Stack
| Layer | Technology |
|---|---|
| Backend | Fastify + TypeScript |
| Database | SQLite (better-sqlite3) + Drizzle ORM |
| Auth | JWT + bcrypt |
| Real-time | WebSocket (ws) |
| Frontend | React 18 + Tailwind CSS + Zustand |
| Voice/Video | LiveKit |
| Desktop | Electron |
| Build | Vite + pnpm workspaces |
Quick Start with Docker
# Clone the repository
git clone https://github.com/your-username/backspace.git
cd backspace
# Create environment file
cp .env.example .env
# Generate a JWT secret
echo "JWT_SECRET=$(openssl rand -hex 32)" >> .env
# Start Backspace
docker compose up -d
Open http://localhost:3000 in your browser. A default server "Backspace" is created automatically.
Default admin account: admin / admin123 (change this after first login).
Development Setup
Prerequisites
- Node.js 20+
- pnpm 8+
Install
pnpm install
Configure
cp .env.example .env
# Edit .env with your settings (generate a JWT_SECRET)
Run
# Start both server and web dev server
pnpm dev
# Or start individually
pnpm dev:server # API server on :3005
pnpm dev:web # Vite dev server on :5173
Build
pnpm build
This builds the shared types, server, and web frontend. The server serves the built frontend in production mode.
Project Structure
Backspace/
├── packages/
│ ├── shared/ # Shared TypeScript types & permissions
│ ├── server/ # Fastify API + WebSocket server
│ ├── web/ # React frontend (Vite + Tailwind)
│ └── desktop/ # Electron desktop app
├── data/ # SQLite DB + uploads (created at runtime)
├── Dockerfile
├── docker-compose.yml
└── .env.example
Voice & Video
Voice and video requires a LiveKit server. Set these in your .env:
LIVEKIT_URL=wss://your-livekit-server
LIVEKIT_API_KEY=your-api-key
LIVEKIT_API_SECRET=your-api-secret
Without LiveKit configured, text chat works fully but voice/video channels will not connect.
API
The server exposes a REST API and WebSocket endpoint:
- REST API:
http://localhost:3000/api/* - WebSocket:
ws://localhost:3000/ws - Health check:
GET /api/health
See CLAUDE.md for the full API reference.
Desktop App
The Electron desktop app wraps the web UI and adds system tray, notifications, and native window controls.
cd packages/desktop
pnpm build:ts # Compile TypeScript
pnpm dev # Run in development
pnpm build # Package for distribution
License
MIT
Languages
TypeScript
96.8%
HTML
1.3%
Shell
1.1%
JavaScript
0.4%
CSS
0.3%