Jannis Braun 8ba644fa44 fix(message-list): pagination flag and scroll restore leak across channel switches
Two bugs in handleScroll's loadMoreMessages flow surfaced after the
smooth-scroll race fix.

(1) isLoadingMore stuck across channels. setIsLoadingMore(true) → await
loadMoreMessages → setIsLoadingMore(false) was unguarded. If the user
switched channels during the await, the new channel inherited the flag (same
component instance, same useState slot) and rendered the pagination skeleton
even with no load in flight. Cleared only when the original await resolved
or the component remounted (e.g., navigating to Friends and back).

(2) Wrong-channel scroll restore. The post-await rAF set
container.scrollTop = container.scrollHeight - prevScrollHeight against the
new channel's container with the old channel's prevScrollHeight, yanking
the new channel to a wrong position.

Fix:
- try/finally around the await so setIsLoadingMore(false) always runs.
- currentChannelIdRef tracks the live channelId; capture requestChannelId at
  load start and compare both before scheduling the rAF and inside the rAF
  callback (the 16ms frame gap is enough for a switch).
- Belt-and-suspenders: setIsLoadingMore(false) in the channel-switch effect
  covers the case where the await never resolves (network hang). Without it,
  a stuck await would leave the new channel inheriting the flag indefinitely.

No request cancellation — out of scope; AbortController plumbing through
chatStore is a bigger refactor and the channelId guard already silently
drops stale results.

Spec updated. Smooth-scroll fix from the previous commit untouched.
2026-04-27 18:11:31 +02:00

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

S
Description
Fork proprio do Backspace — instancia chat.resenha.website
Readme AGPL-3.0
22 MiB
Languages
TypeScript 96.8%
HTML 1.3%
Shell 1.1%
JavaScript 0.4%
CSS 0.3%