Commit Graph
109 Commits
Author SHA1 Message Date
Jannis Braun 23249be956 feat: integrate hwOverdrive into screen share start/stop/detect
- buildScreenShareOptions reads hwOverdrive from voiceStore instead of config.codec; both VP9 and H.264 paths now always get a VP8 SIMULCAST backup
- startScreenShare activates SDP override before WebRTC negotiation and deactivates on failure
- stopScreenShare and handleScreenShareUnpublished both deactivate the override and reset hwOverdrive: false
- scheduleEncoderDetection checks encoderImplementation in WebRTC stats after 4s and shows a warning toast if OpenH264 (software) is detected
2026-03-24 03:49:57 +01:00
Jannis Braun 0b49300d86 feat: add hwOverdrive SDP profile override module 2026-03-24 03:39:43 +01:00
Jannis Braun 07ac9cf505 fix: pass screenShareEncoding to override LiveKit's 15fps default
LiveKit uses screenShareEncoding (not videoEncoding) for screen share
tracks. Without it, both primary and backup fall back to the default
ScreenSharePresets.h1080fps15 (15fps). Primary was masked by our
overdrive mechanism, but backup had no overdrive and was stuck at 15fps.
2026-03-24 01:21:16 +01:00
Jannis Braun e53d3bcb98 fix: switch backup codec to VP8@30fps, dim H.264 pill with warning
- Backup codec changed from H.264 (OpenH264, slow) to VP8 (libvpx,
  fast) — same encoder family as VP9, lower dual-encode overhead
- Backup capped at 30fps with proportional bitrate to keep CPU
  overhead low during SIMULCAST dual-encoding
- H.264 pill visually dimmed with amber warning when selected
2026-03-24 01:14:47 +01:00
Jannis Braun 894ce68194 feat: add manual codec selection (VP9/H.264) to stream settings
VP9 is default — better quality per bit with H.264 SIMULCAST backup
for Safari. H.264 option available for zero-CPU hardware encoding.
Codec choice is independent of gaming/text mode, which still controls
contentHint and degradationPreference.
2026-03-23 22:44:31 +01:00
Jannis Braun e7a18bd28c fix: screenshare codec fallback, native FPS, and connection info
- Gaming mode uses H.264 primary (hardware NVENC encoding, zero CPU
  impact on games). Text mode uses VP9 primary with H.264 backup
  and SIMULCAST policy for Safari compatibility.
- Fix native mode starting at 30fps by decoupling frameRate constraint
  from resolution constraint in both screenShare.ts overdrive and
  useLiveKit.ts updateActiveTracks.
- Filter paused backup codec tracks in Connection Info stats so dead
  0kbps entries don't show alongside active codec tracks.
2026-03-23 22:10:16 +01:00
Jannis Braun 241451ca18 feat: gesture-aware voice connection, remove AppLayout auto-connect
Replace the useEffect-based auto-connect pattern in AppLayout with
direct connect/disconnect calls from user gesture contexts. This is
required for iOS (AudioContext.resume + getUserMedia must happen in
a gesture handler) and aligns with tightening autoplay policies on
desktop browsers.

Architecture:
- voiceStore gains connectFn/disconnectFn refs, registered by AppLayout
  from the single useLiveKit() instance.
- All voice join paths (ChannelSidebar, MobileSpacesScreen, MainContent,
  voice_moved WS handler) pass connectFn to joinVoiceChannel().
- All disconnect paths (VoiceControls, voiceActions, MobileVoiceFullScreen,
  MobileVoiceMiniBar, dm_call_ended/rejected WS handlers, ready handler)
  call disconnectFn() directly.
- dm_call_accepted WS handler calls connectFn() to initiate the DM call
  LiveKit connection.
- The 55-line auto-connect useEffect and lastAttemptedRef are removed.
2026-03-23 21:06:19 +01:00
Jannis Braun b3d4747126 chore: remove unused ConnectedInstance type import from federationOps 2026-03-23 00:57:36 +01:00
Jannis Braun 95cb9d4df2 feat: add background retry scheduler for federation password sync
Replace the give-up-after-3-retries path with a persistent background
scheduler: 10 retries every 30s (5 min), then 12 retries every 5 min
(1 hr). Active timers are keyed by origin so rapid password changes
cancel and replace the previous loop. All timers are cleared on logout
via clearPasswordSyncTimers() called from instanceStore.reset().
2026-03-23 00:52:40 +01:00
Jannis Braun 11081dbf5a refactor(voice): extract action handlers into voiceActions.ts, remove hard-coded M/D shortcuts
Move mute/deafen/camera/screen-share/disconnect logic out of VoiceControlBar into a shared voiceActions.ts utility so the same handlers can be called from both UI buttons and the upcoming keybind dispatcher.
2026-03-22 20:27:10 +01:00
Jannis Braun c0618a0b75 refactor: convert screenShare.ts to kbps with override-first lookup 2026-03-22 00:47:08 +01:00
Jannis Braun cb34bfc8ea feat: expand bitrate matrix, add native mode with pixel-proportional fallback 2026-03-21 23:26:54 +01:00
Jannis Braun cbef0fe0f8 fix: resolve federated avatar double-path URL that broke cross-instance profile pictures
profileSync stored avatar/banner paths with /api/uploads/ prefix on remote
instances, causing resolveAssetUrl to produce double-path URLs like
https://remote/api/uploads//api/uploads/file.jpg that 404'd. Store bare
filenames instead, strip prefix defensively in resolveAssetUrl and server-side
for existing data self-healing.
2026-03-17 00:36:45 +01:00
Jannis Braun 46f55643ae fix: eliminate screen share audio feedback loop + upgrade Electron 33→40
Screen sharing with audio captured the app's own voice playback, causing
users to hear themselves echoed back. Fixed via two layers:

- Add restrictOwnAudio constraint (Chrome 141+/Chromium 144) to exclude
  the app's own audio from system audio capture
- Add shareAudio toggle so users can disable system audio entirely
- Remove outdated macOS audio block (now supported via ScreenCaptureKit)
- Upgrade Electron 33→40 (Chromium 130→144) so restrictOwnAudio works
  natively in the desktop app
- Add NSAudioCaptureUsageDescription for macOS 14.2+ audio capture
- Add GTK 3 fallback for Linux GNOME compatibility (Electron 36+)
2026-03-16 18:01:40 +01:00
Jannis Braun 95ec3ff952 feat: Electron screen share picker, instance selector, and system audio loopback
- Custom screen share picker for Electron (ScreenSharePicker.tsx) with
  Aether Drift design: glass-bubble footer, adaptive grid, pill tabs,
  border-based selection (avoids overflow clipping), hover brightness
- Instance URL picker so Electron connects to any Backspace server
- System audio loopback on Windows/Linux via desktopCapturer callback
- macOS: video-only callback (OS blocks system audio capture)
- IPC bridge for screen source enumeration and selection
- Purge stale service worker caches on Electron launch
2026-03-16 03:50:07 +01:00
Jannis Braun 7c544c1ff4 feat: security hardening, DB indexes, token revocation, and input validation
- SSRF protection: DNS resolution + private IP blocking on metadata fetcher
- Upload security: CSP/X-Frame-Options headers, SVG forced download, nosniff
- Auth hardening: JWT secret min length, password min 8 chars, token revocation via password_changed_at
- Attachment ownership verification before linking to messages
- Message length limit (4000 chars) enforced on client and server
- Asset URL validation on avatar/banner updates
- Federation instance validation (domain regex, origin scheme, length limits)
- DB indexes on all FK columns for query performance
- Migrations: nullable moderator columns, dm_messages reply_to FK constraint
- File cleanup on avatar/banner replacement and space deletion
- Fastify trustProxy, AbortController on fetches, typing map size cap
2026-03-15 00:06:15 +01:00
Jannis Braun 3e97c2b0f5 feat: image optimization — client-side resize + server-side thumbnails
Avatars/banners now resize to max 512px/1920px and convert to WebP before
upload (zero server cost). Chat image uploads generate an 800px-wide WebP
thumbnail via Sharp; the feed shows the thumbnail, click opens the full-res
original. Adds lazy loading to avatars. Federation-compatible: remote
instances without this feature fall back gracefully.
2026-03-13 16:44:14 +01:00
Jannis Braun 8af155d08f fix: federation DM identity resolution — cross-instance isSelf() failure
Add a cross-instance self-ID registry to identity.ts so isSelf() can
recognize the current user's Snowflake IDs from all connected instances.
Previously, federated DMs showed the user themselves as the other party
because remote-instance IDs didn't match the home user ID.

- Register user IDs from every WS ready event (home + remote)
- Clear the registry on session reset (login/logout/register/delete)
- Fix isSelf() username comparison to parse both sides as federated
- Replace naive ID check in MessageList WelcomeHeader with isSelf()
2026-03-12 18:53:38 +01:00
Jannis Braun 83699d7e91 feat: LWW timestamps for bidirectional federation profile & layout sync
Profile and space layout changes on remote instances were being
overwritten by stale data on reconnect. Adds Last-Writer-Wins
timestamps so the client-relay mesh rejects stale writes:

- profile_updated_at column on users table with migration + backfill
- Server LWW guards on PATCH /users/@me and PUT /space-layout
- Bidirectional profileSync: pulls newer remote profiles to home
- LWW layout sync replaces home-authoritative _layoutFromTrueHome flag
- Layout pushes to ALL connected instances in parallel
2026-03-12 18:28:37 +01:00
Jannis Braun fc72e424d6 refactor: rename "Server Mute/Deafen" to "Space Mute/Deafen" across entire stack
Aligns voice moderation terminology with Backspace's "Spaces" branding.
Renames WS protocol strings, backend handlers, frontend store/hooks/utils,
user-facing labels, and documentation — 15 files, zero functional changes.
2026-03-12 00:12:33 +01:00
Jannis Braun 7572f165de feat: space avatar color with color picker UI
Add avatarColor field to spaces, matching the user avatar color system.
Spaces get a random color on creation and owners can change it in space
settings. The color controls the fallback gradient when no icon is uploaded,
replacing the old deterministic hash-based gradient. Includes full
federation support, explore page, mutual spaces, and color picker in both
create and settings modals.
2026-03-11 18:34:33 +01:00
Jannis Braun 8c8767ba2c feat: account deletion, username reuse, and real-time username availability
- Add account deletion with tombstone (isDeleted flag), password/username
  confirmation, owned-space guard, and full cleanup transaction
- Free deleted usernames by renaming to !deleted:<id> so they can be reused
- Add migration to retroactively free usernames from already-tombstoned users
- Add GET /api/auth/check-username endpoint with rate limiting for real-time
  availability checking during registration
- Add debounced username availability indicator on registration Step 1
- Add DeleteAccountModal with federation-aware remote account cleanup
- Add federation ops utility for remote instance management
- Update sanitizeUser to anonymize deleted user profiles
- Add instance store improvements and connected instances modal updates
2026-03-11 16:29:25 +01:00
Jannis Braun c8e2945c07 feat: two-step registration with avatar/color picker, fix focus ring clipping
Refactor RegisterPage into a two-step flow: credentials first, then
personalization (display name, avatar upload, avatar color). Replace the
dual-panel sliding layout with conditional rendering and CSS keyframe
animations to eliminate overflow-hidden clipping of focus rings.

Supporting changes:
- Server accepts avatarColor on registration
- Auth store resets all user-scoped stores on login/register/logout
- Voice store gains resetSession() for full session cleanup
- Sync presence status to federated instances
- Propagate presence_update to socialStore regardless of origin
2026-03-11 03:43:20 +01:00
Jannis Braun a5fcb78434 feat: sync user profile to remote instances on federation connect and profile update 2026-03-11 01:33:18 +01:00
Jannis Braun 5953a8985e fix: eliminate banner color bleed by baking alpha into gradient colors
Replace element-level opacity on banner fallback divs with rgba-based
gradient color stops via new mutedGradient() utility. This prevents
compositing layer artifacts that caused visible color seams at the
banner boundary in AccountPanel, UserProfilePopout, and UserProfileModal.
2026-03-10 22:03:51 +01:00
Jannis Braun 40e8293695 fix: banner/avatar overlap in settings preview and refine profile color system
Move bg-surface-channel from info section to card wrapper in AccountPanel
so the avatar's negative margin overlaps into the banner correctly. Replace
accent color presets with 7×3 banner color palette, remove accent color
tinting from display names in popout/modal, and refactor gradient entries
to expose from/to hex stops.
2026-03-10 21:40:57 +01:00
Jannis Braun d3f58f3787 refactor: derive accent color presets from avatar gradient palette
Replace the hardcoded 16-color ACCENT_PRESETS array in AccountPanel with
a shared export from gradients.ts that extracts both hex stops from each
avatar gradient, producing 14 harmonized colors in a 7×2 grid.
2026-03-10 20:32:16 +01:00
Jannis Braun d2697d87fa feat: user-choosable avatar colors with settings picker
Add avatarColor as a stored, user-selectable field (mint, sky, lavender,
coral, rose, teal, amber). Randomly assigned on registration, changeable
in profile settings. Existing users keep hash-based fallback until they
choose a color. Includes DB migration, API validation, gradient map,
live preview in settings, and banner fallback integration.
2026-03-10 20:26:28 +01:00
Jannis Braun d7449bdf42 fix: load social data on WS ready and use federation-safe canonical matching for friend button state
Friends/requests were only loaded when FriendsPage or ActivityPanel rendered,
so profile modals in space views always showed "Add Friend" even for existing
friends. Now loadFriends/loadRequests fire on every WS ready event.

Also adds canonicalUserMatch() with cascading ID/homeUserId/username+instance
fallback, replacing fragile homeUserId-only matching in getFriendshipStatus.
2026-03-10 19:56:13 +01:00
Jannis Braun 274f5a710e fix: use space gradients for mutual space avatars and add federation indicators in profile modal
Space avatars in the mutual spaces tab now use getSpaceGradient() instead of
a flat grey background, matching the sidebar appearance. Mutual friends and
spaces from remote instances show a globe icon with the instance hostname.
Also wires up federated mutuals loading, correct API client routing for
remote user profiles, and the new mutuals utility.
2026-03-10 19:19:05 +01:00
Jannis Braun 003eff2268 feat: user profile customization with banner, accent color, bio, and full profile modal
Add banner image, accent color, and bio fields to user profiles with
full-stack support: schema migration, API validation (hex color format,
190-char bio limit), sanitizeUser propagation, and new GET /users/:id/mutuals
endpoint. Rewrite AccountPanel with live preview card, avatar/banner upload
via ImageCropModal, 16-preset accent color picker, and bio editor. Enhance
UserProfilePopout with banner display, accent-colored names, bio rendering,
and mutual counts. Add new UserProfileModal with About/Mutual Friends/Mutual
Spaces tabs and friend action buttons.
2026-03-10 17:08:29 +01:00
Jannis Braun 907f8285cd fix: decouple voice intent from server enforcement to prevent involuntary unmute
When a moderator lifted a server mute/deafen, the client was involuntarily
turning on the user's microphone because isMuted/isDeafened conflated user
intent with server enforcement. Now intent (isMuted/isDeafened) is never
mutated by server events. Effective state (intent || serverEnforcement) is
computed at broadcast and hardware time via centralized helpers.
2026-03-09 22:25:56 +01:00
Jannis Braun 22e7616c70 feat: explore page space banners, icon-derived gradients, and space descriptions
- Redesign explore cards with banner images, overlapping icons, and frosted fade
- Extract dominant colors from space icons for dynamic banner gradients
- Add space description/banner fields to schema with migration
- Move origin label from banner overlay to content metadata row
- Support space descriptions in settings overview panel
2026-03-09 13:49:50 +01:00
Jannis Braun b598236f82 feat: add image crop modal to space icon picker
Wire react-easy-crop into CreateSpace so users can crop and zoom before
uploading a space icon. Adds reusable ImageCropModal component and
canvas crop utility for future use in space settings and avatar editing.
2026-03-08 22:08:58 +01:00
Jannis Braun fc06e25731 refactor: rename Server → Space across entire codebase
Renames all domain terminology from "Server" to "Space" throughout the
application — database schema, API routes, shared types, stores, components,
and UI strings. Files renamed: ServerSidebar → SpaceSidebar, CreateServer →
CreateSpace, JoinServer → JoinSpace, ServerSettings → SpaceSettings,
serverStore → spaceStore, routes/servers → routes/spaces.
2026-03-08 20:08:24 +01:00
Jannis Braun d18d9c51f7 fix: resolve duplicate voice user in federated channel sidebar
Use origin-aware user ID resolution for optimistic voice add/remove
instead of always using the home instance ID. Adds getMyUserIdForOrigin
resolver (same pattern as getApiForOrigin) so joinVoiceChannel and
leaveVoice use the correct federated user ID. Also fixes WS reconnect
voice re-registration, invite URL for remote servers, and chatStore
early-load guard for unmapped channels.
2026-03-07 14:33:37 +01:00
Jannis Braun d0b0441f81 fix: revert echo gate, restore Chrome AEC during screen share
The echo gate architecture bypassed the serialized mic management chain
and introduced race conditions. Chrome's AEC handles echo properly for
both headphone and speaker users without custom intervention.
2026-03-05 00:09:03 +01:00
Jannis Braun dd02c1ed8d fix: prevent ghost users in voice sidebar after deploy
Send voice_join to WS server only after LiveKit successfully connects,
not immediately on joinVoiceChannel() or WS reconnect. Background tabs
can reconnect WebSocket but not WebRTC, causing phantom voice users.
2026-03-04 22:11:50 +01:00
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