Commit Graph
71 Commits
Author SHA1 Message Date
Jannis Braun a5073f89fd fix: optimistic voice state so user appears in sidebar immediately
Previously, the voice channel sidebar only showed users after a server
round-trip (voice_state_update broadcast). After a deploy/reconnect,
this left the user invisible in the sidebar despite being connected.
Now joinVoiceChannel, leaveVoice, and the WS ready handler all
optimistically update voiceUsers for the local user immediately.
2026-03-04 21:48:39 +01:00
Jannis Braun 5e4a5c2ee9 fix: clean up cross-instance voice state when switching federated servers
Send explicit voice_leave to the old instance when joining voice on a
different origin, preventing stale voice state from showing the user in
two channels. Also make WS reconnect voice re-registration origin-aware
so remote reconnects properly restore voice state.
2026-03-04 20:57:15 +01:00
Jannis Braun c65a7387fa fix: remove spurious reconnect sound on deploy/laptop wake, fix federated typing/asset display 2026-03-04 20:40:05 +01:00
Jannis Braun 6e44a4ef2f fix: DM avatar color and reactions in federation + explore/server discovery
- Fix DM welcome header avatar using home identity for correct gradient color
- Register DM channel IDs in channelOriginMap so federated DM operations
  (reactions, messages, typing) route to the correct instance
- Pass origin when creating DM channels from friends list and WS events
- Add server discovery/explore page with public server listings
- Add server visibility and description fields
2026-03-04 18:02:43 +01:00
Jannis Braun dca9c4dc83 feat: stateless federated identity resolver + federation UX improvements
Add identity.ts with isSelf() and resolveDisplayIdentity() — pure
stateless functions that detect replicated-self using the immutable
(username, homeInstance) composite key. No store lookups, no data
mutation. Fixes wrong avatar gradient and missing edit/delete on
own messages in remote channels.

Also includes: optimistic message dedup fix for cross-instance
messages (content-only matching), federation toast notifications,
Username component with @domain display, invite parser, and
deploy script simplification.
2026-03-04 03:37:49 +01:00
Jannis Braun 64fd8edfc9 feat: federation batch 1+2 — bug fixes and password enforcement
Batch 1 — Bug fixes:
- Fix stale voice state on remote reconnect (clearVoiceUsersForOrigin)
- Fix logout not cleaning remote servers from store
- Fix reply-to asset normalization for remote messages
- Fix HTTPS hardcoded in autoConnectAll (store full origin, legacy fallback)

Batch 2 — Password enforcement + replication flow:
- Add connectToRemote() with home password verification before remote auth
- Auto-cascade: verify home password → register on remote → login fallback
- Replace Register/Login tabs with single password field in ConnectedInstances
- Add DifferentPasswordError for typed fallback-login UI transition
- Fallback login form shown only when remote has different password
2026-03-04 01:51:38 +01:00
Jannis Braun 758c6a7b5b feat: normalize remote asset URLs and route UI actions by instance origin
Phase 5 of multi-instance federation. Adds a two-layer fix:

Layer 1 — Data ingestion normalization: Remote instance user avatars,
server icons, and attachment filenames are rewritten to absolute URLs
when entering the app (via WebSocket events or API responses), so all
downstream components render them correctly without changes.

Layer 2 — Outbound action routing: wsSend calls (voice join/leave/status,
typing) and file uploads in UI components now route through the correct
instance based on the active channel's origin.
2026-03-03 00:28:24 +01:00
Jannis Braun 7f7656ed24 feat: Phase 5+6 Aether Drift — legacy token removal, colorful avatars & Backspace branding
Phase 5: Remove all 54 legacy Discord token definitions from tailwind.config.js,
migrate body classes in index.html to Aether Drift tokens, update ARCHITECTURE_AUDIT.md.

Phase 6: Replace flat purple fallback avatars with deterministic colorful gradients
(mint, sky, lavender, coral, rose, teal, amber) based on user/server ID hash.
Replace Discord logo SVG with Backspace "B" in server strip. Add per-server gradient
icons with hover glow. Update profile popout banner and incoming call modal to match.
2026-03-02 00:56:34 +01:00
Jannis Braun e219229b63 feat: add Backspace design prototype and rebrand from Opencord
- Add Backspace-design-prototype.html: finalized "Aether Drift" design
  language with warm matte surfaces and subtle frosted glass accents
- Update CLAUDE.md with DESIGN SYSTEM section and remove Discord clone references
- Rename all Opencord references to Backspace across the full codebase
- Archive outdated design experiments and Discord-specific assets
- Add science-backed accessibility fallback (prefers-reduced-transparency)
2026-03-01 21:26:57 +01:00
Jannis Braun 773a03b1aa feat: instance-level streaming limits with admin settings panel
Add a server-side instance_settings table (single-row, CHECK(id=1))
that stores admin-configurable streaming bounds: bitrate min/max/step,
allowed resolutions, and allowed framerates.

Backend:
- New instance_settings schema + migrations (is_admin on users, default
  settings row, first-registered-user promoted to admin)
- GET/PATCH /api/settings/streaming endpoints with admin-only writes
  and full input validation including cross-field checks

Frontend:
- settingsStore fetches limits on WebSocket ready, exposes isAdmin flag
- ScreenShareSettingsPopover reads bounds from store instead of
  hardcoded constants, auto-clamps stale localStorage values
- buildScreenShareOptions() clamps bitrate to server limits at build
  time as enforcement backstop
- ServerSettings modal gains a "Streaming" tab (admin-only) with
  bitrate range sliders, resolution/framerate toggles, and save/reset
2026-02-26 03:33:29 +01:00
Jannis Braun 61e0bf6fe8 fix: lower screen share default bitrates for VP9 codec efficiency 2026-02-26 02:43:17 +01:00
Jannis Braun 4bb69851d3 fix: use LiveKit track.attach() for adaptive stream and switch screen share to VP9 single-layer
Camera was stuck at 180p because our custom <video> rendering bypassed
LiveKit's adaptive stream observer. Replaced manual srcObject binding
with track.attach()/detach() in VoiceUser, StreamTile, and PictureInPicture
so the SFU receives viewport dimensions and forwards the correct
H.264 simulcast layer.

Screen share VP9 SVC with L3T3 spatial layers failed because hardware
VP9 encoders (NVENC, QSV, VCE) don't support spatial scalability —
Chrome silently degrades to L1T1. Reverted to VP9 single-layer
(simulcast: false, no scalabilityMode). Also targets encodings[length-1]
in applyOverdrive() for correct simulcast layer targeting.
2026-02-26 02:33:18 +01:00
Jannis Braun 2186235d65 feat: manual bitrate override slider for screen share settings
- Add customBitrateKbps to ScreenShareConfig (null = auto matrix lookup)
- Slider in Stream Settings popover: 500 kbps–20 Mbps, step 500 kbps
- "Reset to Auto" clears override back to preset-derived bitrate
- Live updates via existing applyOverdrive() pipeline on active streams
- Persist version 5 → 6 with migration
2026-02-26 00:28:20 +01:00
Jannis Braun 8030c89c6c feat: bitwise RBAC engine with channel-level permission overrides
Replace string-based role checks (role === 'admin') with a bitwise BigInt
permission system. Adds computePermissions() resolution engine following
Discord's model: @everyone base → role union → admin shortcut → channel
overrides (role deny/allow → member deny/allow). Ready payload now filters
channels by VIEW_CHANNEL and attaches per-user myPermissions to each
server and channel. Includes channel_overrides table, @everyone role
auto-creation, migration for existing servers, and override CRUD API.
2026-02-24 05:08:59 +01:00
Jannis Braun 36e27121da fix: harden data integrity, connection stability, and memory management
Wrap all multi-write DB operations in atomic transactions (server/channel
creation, message+attachment linking, DM creation, friend acceptance,
cascading deletes) to prevent partial-write corruption.

Batch N+1 queries in WS ready payload into O(1) bulk fetches with
chunked inArray() to respect SQLite's variable limit.

Fix chat history regression where background WS messages bypassed
channel load by switching the guard from messages.has() to hasMore.has().

Add LRU channel eviction (20 cached, evict to 15) and per-channel
message cap (200) to bound client memory growth.

Shorten WS heartbeat from 30s to 15s for aggressive proxy/NAT
environments. Clear all user-scoped stores on logout to prevent
cross-session data leaks.

Extract LiveKit internal accessors into shared livekitInternals utility.
2026-02-24 03:52:22 +01:00
Jannis Braun 2342396fce fix: disable browser DSP on screen share audio and harden ICE stats resolution
Screen share audio was muffled/gated because getDisplayMedia used plain
`audio: true`, letting the browser apply voice-optimized DSP (NS, AEC, AGC)
to desktop audio. Now passes explicit constraints disabling all processing
and requesting stereo capture.

Also improves WebRTC stats: three-tier ICE candidate-pair discovery
(transport → active-bytes heuristic → legacy fallback), height-based
simulcast layer inference, and documents mDNS obfuscation limitation.
2026-02-24 02:31:24 +01:00
Jannis Braun f808a204e7 refactor: dynamic screen share engine with independent resolution/fps/mode axes
Replace rigid SCREEN_QUALITY_MAP (6 hardcoded VideoPreset strings) with a
builder function that computes bitrate, degradation preference, and content
hint from three independent axes (height, fps, content mode). Camera is
decoupled onto a fixed 720p30 preset so screen share changes no longer
affect camera quality. New ScreenShareSettingsPopover replaces the old
VideoQualityPopover with pill-style selectors. Store migrated to v5 with
backwards-compatible migration from videoQuality string.
2026-02-24 02:16:15 +01:00
Jannis Braun 5e34b39b78 fix: broadcast camera & screen share status via WebSocket for sidebar visibility
Camera and LIVE badges in the channel sidebar were only visible to users
who had joined the same LiveKit room. Widen the voice_status WS event
from {isMuted, isDeafened} to {isMuted, isDeafened, isCameraOn, isScreenSharing}
so all server members see camera/screenshare indicators without joining voice.
2026-02-23 20:07:42 +01:00
Jannis Braun edcdf8b207 fix: defer AEC toggle until after screen picker resolves to prevent mic dropout
AudioManager.setScreenShareActive(true) was firing before the browser's screen
picker, killing the mic stream. The picker suspends getUserMedia while its secure
overlay is open, trapping the mic in a dead state for 5-30s. Now the AEC rebuild
fires after the track is acquired — mic stays alive during the picker.
2026-02-23 19:27:49 +01:00
Jannis Braun fe97212c64 fix: screen share gaming stutter by switching to maintain-framerate + contentHint motion
Root cause: degradationPreference 'maintain-resolution' forced FPS drops under CPU
pressure during fast-motion gaming. Changed to 'maintain-framerate' so the encoder
drops resolution temporarily instead of stuttering. Added contentHint='motion' to
optimize for temporal prediction (more P-frames, fewer I-frames).
2026-02-23 03:46:06 +01:00
Jannis Braun 7e7de41718 fix: centralize screen share pipeline to fix 270p resolution ramp-up failure
Screen sharing was publishing at h360 (640x360) and never ramping to target
resolution due to stale closure in setTimeout, wrong initial quality anchor,
and 5 competing code paths with inconsistent bitrates.

- Create utils/screenShare.ts as single source of truth for all screen share ops
- Publish at target resolution from the start (not h360 → ramp)
- Read store at call time in timers (eliminates stale closure bug)
- Use maintain-resolution for screen content, maintain-framerate for camera
- Fix OS-level "Stop sharing" not resetting store or restoring AEC
- Enable dynacast for SFU quality signaling
- Reconcile QUALITY_MAP to canonical bitrates across all 7 files
2026-02-23 03:25:52 +01:00