Commit Graph
100 Commits
Author SHA1 Message Date
Jannis Braun 720a3c4553 feat: wire up enhanced message context menu with all actions 2026-03-20 22:19:12 +01:00
Jannis Braun 3aa7e93a4d feat: pass previousMessageId prop from MessageList to Message 2026-03-20 22:16:55 +01:00
Jannis Braun 8da412c0d4 feat: create message context menu builder function 2026-03-20 22:15:31 +01:00
Jannis Braun 896cb77b5c feat: add markUnread store action and WS handler 2026-03-20 22:13:47 +01:00
Jannis Braun 2e26f98246 feat: add mark_unread WebSocket event handler 2026-03-20 22:11:42 +01:00
Jannis Braun 3c23893549 feat: add mark_unread to WebSocket event types 2026-03-20 22:09:40 +01:00
Jannis Braun c90ba431e8 fix: resolve mentions in reply previews to display usernames
Reply preview was rendering raw <@userId> tokens as plain text instead
of resolving them to MentionBadge components with proper usernames.
2026-03-20 21:25:45 +01:00
Jannis Braun 9fe46ad19b fix: voice drop zone was swallowing channel reorder drops
handleVoiceDropZoneDrop called stopPropagation and clearState
unconditionally, preventing container's handleContainerDrop from
firing when dropping a channel onto a voice channel. Add early
return for non-voiceUser drags so channel/category drops bubble
through to the container handler correctly.
2026-03-20 21:15:13 +01:00
Jannis Braun 99e6f2f249 fix: normalize drop indicator to single line between channels
Port the SpaceSidebar normalization pattern: convert 'before B'
to 'after A' so only one drop indicator renders at any gap.
Fixes double-line visual glitch and unreliable drops when cursor
is between two channels. Also adjust indicator offsets from
top-0/bottom-0 to -top-[1px]/-bottom-[1px] to center in the gap.
2026-03-20 21:07:20 +01:00
Jannis Braun ebe34a710b refactor: update VoiceChannel to use useDragManager hook-provided handlers 2026-03-20 20:46:49 +01:00
Jannis Braun 439a71dc13 refactor: wire useDragManager handlers into ChannelSidebar JSX and ChannelItem 2026-03-20 20:45:30 +01:00
Jannis Braun 999c13cdab refactor: replace old drag state with useDragManager hook in ChannelSidebar 2026-03-20 20:43:41 +01:00
Jannis Braun eb6f4bf94e fix: stabilize useDragManager callback refs and containerHandlers memo
Remove activeDrag from drag-start dependency arrays to prevent
unnecessary re-renders during drag. Wrap containerHandlers in
useMemo for consistent reference stability.
2026-03-20 20:39:52 +01:00
Jannis Braun 2952799d00 feat: add auto-scroll during drag near sidebar edges 2026-03-20 20:35:47 +01:00
Jannis Braun a8befcc701 feat: add voice user and voice channel drop zone handler factories 2026-03-20 20:35:13 +01:00
Jannis Braun 23c102a3cb feat: add channel/category handler factories and container handlers 2026-03-20 20:34:37 +01:00
Jannis Braun 9670450eec feat: scaffold useDragManager hook with types and state 2026-03-20 20:34:05 +01:00
Jannis Braun eb835f7e22 docs: add system prompt and design system reference files 2026-03-20 19:06:10 +01:00
Jannis Braun 0dc3eb1884 fix: notify displaced tab when user joins voice from another session
When a user joins a voice channel while already connected from another
tab, the server now sends a 'displaced' reason on voice_disconnected.
The client tears down LiveKit and shows an informational toast.
2026-03-20 19:03:35 +01:00
Jannis Braun 68378c7d83 fix: deduplicate VolumeSliderItem and add missing stopPropagation
- Extract VolumeSliderItem to voiceMenuItems.tsx (was duplicated in
  VoiceUser.tsx and VoiceChannel.tsx)
- Add e.stopPropagation() to all imperative context menu handlers
  for consistency with useContextMenu hook behavior
2026-03-20 18:55:58 +01:00
Jannis Braun 443259a527 chore: delete old context menu components replaced by centralized system 2026-03-20 18:48:40 +01:00
Jannis Braun 8eb3fc84a5 refactor: migrate voice context menus to centralized store 2026-03-20 18:47:45 +01:00
Jannis Braun cba19e8b20 refactor: migrate SpaceSidebar context menus to centralized store 2026-03-20 18:41:17 +01:00
Jannis Braun 15af93b979 refactor: migrate ChannelSidebar context menus to centralized store 2026-03-20 18:35:55 +01:00
Jannis Braun 3ead68b3e7 refactor: migrate Message context menu to centralized store 2026-03-20 18:32:18 +01:00
Jannis Braun ba0243d88c feat: mount ContextMenuRenderer in AppLayout 2026-03-20 18:30:58 +01:00
Jannis Braun 9e93734719 feat: add centralized ContextMenuRenderer with submenu and mobile support 2026-03-20 18:28:10 +01:00
Jannis Braun 6fb8801960 feat: add useContextMenu consumer hook 2026-03-20 18:24:44 +01:00
Jannis Braun 9a1b266760 feat: add context menu Zustand store and types 2026-03-20 18:24:25 +01:00
Jannis Braun e3e3eb82ff fix: restore missing logo and fix mobile space icon URLs
Restore logo.png lost during fast-forward merge and replace broken
resolveAssetUrl pattern in MobileSpacesScreen with proven desktop logic.
2026-03-17 16:16:09 +01:00
Jannis Braun f505f6578b fix(mobile): add viewport-fit=cover for safe area insets on notched devices 2026-03-17 15:55:06 +01:00
Jannis Braun ed70099963 fix(mobile): resolve all TypeScript errors from integration
- Fix voiceStore method names (toggleMic, leaveVoice, setCurrentVoiceChannel)
- Fix MemberWithUser nested user property access
- Pass required channelId props to MessageList, TypingIndicator, MessageInput
- Fix ConfirmDialog props (isOpen, description, variant, onClose)
- Add mobileStyle prop to Modal component (fullscreen + sheet modes)
- Fix fromId/isAdmin camelCase property names
- Guard possibly undefined touch events and array accesses
- Fix notification channelId resolution
2026-03-17 15:54:39 +01:00
Jannis Braun 7619b091da feat(mobile): route user profile to pushed screen instead of popout 2026-03-17 15:49:36 +01:00
Jannis Braun 1e0e957824 feat(mobile): route notification taps through mobile screen stack 2026-03-17 15:48:23 +01:00
Jannis Braun 206a0491ea feat(mobile): add full-screen voice view with controls 2026-03-17 15:47:41 +01:00
Jannis Braun 913b4c7365 feat(mobile): add mobileStyle to all modals (sheet for forms, fullscreen for settings) 2026-03-17 15:46:28 +01:00
Jannis Braun a073613b43 feat(mobile): disable tooltips, render context menus as bottom sheets 2026-03-17 15:44:46 +01:00
Jannis Braun 183290ef07 fix(mobile): stacked Add Friend input, responsive tab bar, mobile header 2026-03-17 15:43:43 +01:00
Jannis Braun 2790e9e088 feat(mobile): add voice mini-bar with quick controls 2026-03-17 15:42:16 +01:00
Jannis Braun af84674963 feat(mobile): add MobileSettingsScreen with drill-down navigation 2026-03-17 15:41:19 +01:00
Jannis Braun 934a1b85a7 feat(mobile): add MobileYouScreen with profile card and action links 2026-03-17 15:40:23 +01:00
Jannis Braun 49e4402802 feat(mobile): add MobileDmsScreen with activity row and DM list 2026-03-17 15:39:19 +01:00
Jannis Braun 0222946f04 feat(mobile): add swipe-from-left-edge back gesture 2026-03-17 15:37:27 +01:00
Jannis Braun e45bf1927e feat(mobile): add MobileChatScreen with back nav and member list access 2026-03-17 15:36:50 +01:00
Jannis Braun eac4e5f5eb feat(mobile): add MobileSpacesScreen with space strip and channel list 2026-03-17 15:35:53 +01:00
Jannis Braun 8463ca9ba8 feat(mobile): add MobileShell and split AppLayout for mobile/desktop 2026-03-17 15:32:52 +01:00
Jannis Braun 2386f497aa feat(mobile): add 3-tab bottom nav with unread badges 2026-03-17 15:31:06 +01:00
Jannis Braun cbfff701ce feat(mobile): add MobileScreenStack transition manager 2026-03-17 15:30:10 +01:00
Jannis Braun b0647fccb6 feat(mobile): add slide-up-sheet animation and screen transition CSS 2026-03-17 15:29:41 +01:00
Jannis Braun 8bd74ec608 feat(mobile): add screen stack navigation state to uiStore 2026-03-17 15:28:36 +01:00
Jannis Braun 8dda8b0b83 chore: add .worktrees/ to .gitignore 2026-03-17 15:26:43 +01:00
Jannis Braun ac7d3cb71b feat: add start-at-boot setting and fix tray icon to use app logo
Add auto-launch settings (start at boot + start minimized) to the
Desktop section in Account settings, using Electron's built-in
setLoginItemSettings API across macOS, Windows, and Linux.

Replace the fallback colored-circle tray icon with proper B logo assets:
template images for macOS (adapts to light/dark menu bar) and colored
icons for Windows/Linux. Fix BGRA channel order bug in fallback generator
and move tray icons to resources/ so they're packaged into the app.
2026-03-17 02:28:38 +01:00
Jannis Braun e5f89d4c3f feat: persist scroll position per channel with Jump to Present button
- Save the top-visible message ID on channel leave, restore via
  scrollIntoView on return (immune to lazy-loaded image reflow)
- Add floating glass-bubble "Jump to Present" button when scrolled
  5000px+ from bottom
- Clear stale scroll anchors when user returns to bottom
- Evict scroll positions alongside channel cache eviction
2026-03-17 01:46:25 +01:00
Jannis Braun 42e765b877 fix: use capture-phase load listener to scroll chat after GIF/image load
ResizeObserver alone misses scroll adjustments when multiple images load
in quick succession due to browser layout-loop suppression. A capture-phase
load event listener on the content wrapper reliably catches all descendant
image loads and re-scrolls to bottom.
2026-03-17 00:53:41 +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 bc4dd81632 fix: exclude voice channels from unread computation to eliminate ghost notifications
Voice channels rendered with VoiceChannel component have no text reading/acking
UI, so messages in them created phantom unread indicators on the space sidebar
that users could never clear. Root cause was a message in the counter-strike
voice channel with no read state.

Three-layer fix:
- spaceStore: track voiceChannelIds set, exclude voice channels from
  channelLastMessageIds so setReadStates never marks them unread
- useWebSocket: skip markChannelUnread for voice channels on message_created,
  prune orphaned unreads on every ready event
- chatStore: validate preserved unreads against channelToSpaceMap to drop
  orphans that don't map to any known channel
2026-03-16 23:57:24 +01:00
Jannis Braun bf64c4678b fix: prevent logout 401 and login double-attempt caused by LWW layout push race
resetUserStores() was abusing populateFromReady('', [], [], []) to clear the
space store. Its LWW timestamp logic fired an async pushLayoutToOrigin when
_layoutUpdatedAt > 0, which read a null token from localStorage (already
removed on logout, not yet set on login). The 401 response triggered
handleUnauthorized(), deleting the freshly-stored login token and forcing a
full page reload — requiring users to log in twice.

Replace with a proper reset() method that synchronously sets all state to
initial values with no LWW comparison or API side effects.
2026-03-16 22:52:32 +01:00
Jannis Braun 1c806be94f fix: clear message cache on reconnect to prevent stale scroll position
After a server restart, navigating to a previously-visited channel showed
messages at a stale middle position instead of scrolling to the bottom.
The in-memory message cache survived the reconnect, so loadMessages()
bailed (cache hit) and the scroll-to-bottom logic never fired.

Now the ready handler clears the messages and hasMore maps for all
channels belonging to the reconnecting origin (including DMs for home).
The currently open channel is force-reloaded immediately; other channels
get fresh-fetched on next visit, triggering proper scroll-to-bottom.

Also fixes voice channel settings gear icon placement to match text
channels (flex-1 pushes icon to right edge).
2026-03-16 22:34:21 +01:00
Jannis Braun bfecb41c66 fix: derive voice sidebar from LiveKit participants to eliminate desync
The channel sidebar voice user list was maintained by a separate
voiceUsers Map (fed by WS events + fragile hydration code) that diverged
from reality after server restarts — users shown in wrong channels,
duplicated across channels. The VoiceGrid was always correct because it
reads LiveKit participants directly.

Now VoiceChannel.tsx derives its user list from LiveKit participants for
the connected channel (single source of truth) and only falls back to
server-provided voiceUsers for channels the user is not connected to.

Removed all hydration band-aids that tried to sync the two systems:
- useLiveKit ParticipantDisconnected → removeVoiceUser
- useLiveKit ConnectionStateChanged → addVoiceUser hydration loop
- useWebSocket ready handler → dynamic import LiveKit hydration

Also includes: voice channel settings gear icon on hover, persist
per-user volume/mute prefs across sessions, default screen share
audio off on Electron (no system audio capture support).
2026-03-16 22:10:53 +01:00
Jannis Braun e809adff3e fix: sync channel sidebar voice users with LiveKit participant disconnects
The sidebar used voiceStore.voiceUsers (WebSocket-driven, 5s delay) while
voice panels used LiveKit's real-time participants. Now ParticipantDisconnected
also removes the user from voiceUsers for immediate sidebar updates.
2026-03-16 20:41:36 +01:00
Jannis Braun e9c43f21f1 fix: eliminate phantom notifications across the entire read-state pipeline
Root cause: own messages echoed by the server marked channels unread when
the user had already navigated away. Seven related bugs compounded the
problem — stale read states, missing cleanup on space/DM removal, REST
broadcast ignoring VIEW_CHANNEL, and no validation on channel_ack writes.

Frontend:
- Skip markChannelUnread for the user's own messages (federation-aware)
- Walk backward past temp_ IDs in ackChannel instead of bailing
- Re-fire ack timer when temp message is replaced by server-confirmed ID
- Add removeChannelStates to clean up unread/read/message caches
- Clean up chatStore on removeSpace, removeDmChannel, removeInstanceSpaces

Server:
- Use sendToChannel instead of sendToSpace for REST message creation
- Clean up read_states on space deletion, member kick/leave, and ban
- Validate channel membership before accepting channel_ack writes
- Clean up read_states on DM leave and DM channel deletion
2026-03-16 20:20:25 +01:00
Jannis Braun 0815963e1b fix: update screen share audio warning to recommend Chrome browser 2026-03-16 19:27:12 +01:00
Jannis Braun 26f2e2a817 fix: remove experimental useSystemPicker causing double-picker and no audio on macOS
useSystemPicker triggers applyConstraints to re-open the system picker
(Electron #44684) and doesn't reliably pass audio capture. All platforms
now use the custom Aether Drift picker with explicit loopback control.
2026-03-16 19:13:14 +01:00
Jannis Braun bda7930e61 fix: restore setDisplayMediaRequestHandler with useSystemPicker
Electron requires setDisplayMediaRequestHandler for getDisplayMedia() to
work — removing it broke screen sharing entirely. Restored the handler
with useSystemPicker: true, which on macOS 15+ uses the native system
picker (honoring restrictOwnAudio), while Windows/Linux fall back to
the custom picker with the shareAudio toggle for echo control.
2026-03-16 18:34:50 +01:00
Jannis Braun 9ff3761640 fix: remove setDisplayMediaRequestHandler to let restrictOwnAudio work natively
The custom screen share picker intercepted getDisplayMedia() and created
a raw loopback stream, bypassing Chromium's constraint pipeline entirely.
restrictOwnAudio was silently discarded. Removing the handler lets
Chromium 144's native getDisplayMedia run end-to-end with restrictOwnAudio
applied, eliminating the audio feedback loop in the desktop app.
2026-03-16 18:18:42 +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 7a86cde67e feat: frameless title bar for Windows/Linux with native overlay controls
Use titleBarStyle: 'hidden' + titleBarOverlay on Win/Linux to remove the
ugly native title bar and menu bar while keeping OS-rendered min/max/close
buttons. Hidden Edit menu preserves keyboard shortcuts (Ctrl+C/V/X/Z/A).
Title bar drag region and separator line rendered via web frontend, with
colors matching the Aether Drift design system.

Also adds electron-builder metadata (description, author, homepage,
artifactName) and multi-size icons for cross-platform builds.
2026-03-16 06:16:43 +01:00
Jannis Braun cd2f1e52ef fix: proper macOS dock icon via .icns bundle replacement
Replace the Swift squircle hack with the correct Apple approach:
generate a proper .icns using sips + iconutil, then replace
electron.icns in the Electron bundle before launch. macOS applies
its native squircle mask + shadow from the .icns automatically.
2026-03-16 04:29:09 +01:00
Jannis Braun c176ea801a feat: squircle dock icon for macOS dev mode
macOS only applies the squircle mask to packaged .app bundles. For dev
mode, generate a pre-masked icon-dock.png via Swift/AppKit at launch
and set it with app.dock.setIcon(). Silently skipped on non-macOS.
2026-03-16 04:15:09 +01:00
Jannis Braun da05eb3262 fix: replace placeholder purple square icons with actual Backspace logo
Regenerate all web icons (favicon, PWA, apple-touch) from master 1024x1024
icon.png. Add BrowserWindow icon property for Windows/Linux taskbar icon
in Electron dev mode.
2026-03-16 04:01:52 +01:00
Jannis Braun 2e919445c1 feat: wire up master app icon for Electron builds
Point prebuild script at the 1024x1024 master icon.png at project root.
electron-builder auto-generates .icns (macOS), .ico (Windows), and
multi-size PNGs (Linux) from build/icon.png during packaging.
2026-03-16 03:57:30 +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 8ae3ffc912 feat: Electron desktop app — hardening, IPC bridge, and dev launch fixes
- Dev/prod URL auto-detection (Vite 5173 in dev, server 3000 in prod)
- Typed IPC bridge via preload (notifications, badge, window controls, updates, deep links)
- Native OS notifications via NotificationController with window focus suppression
- Auto-update via electron-updater with UpdateToast UI
- Deep linking (backspace:// protocol) for macOS and Windows/Linux
- Window state persistence (position, size, maximize across restarts)
- Tray icon with graceful fallback when icon asset missing
- Suppress PWA service worker polling/reloads inside Electron
- Platform detection layer (isElectron, getElectronAPI)
- Root workspace scripts (dev:desktop, build:desktop)
- Document BACKSPACE_URL and BACKSPACE_UPDATE_URL env vars
2026-03-16 00:10:47 +01:00
Jannis Braun 56811b9333 fix: avatar upload during registration completes before redirect
Registration avatar upload raced with AuthRedirect — setting the Zustand
token triggered navigation before the upload could finish. Now the token
is stored in localStorage (for API auth) but not in Zustand until the
avatar upload completes, so the page stays mounted throughout.

Extracts initSession() from login/register for reuse.
2026-03-15 19:39:56 +01:00
Jannis Braun 2e6fa3cdc6 feat: optimize profile image sizes, silent PWA updates, storage cleanup fixes
- Resize avatars/icons to 256px and banners to 1280px (client crop + server safety net)
- Add server-side resizeProfileImage() for federation/API uploads without crop modal
- Fix unconstrained crop on RegisterPage and CreateSpace (was missing maxOutputDimension)
- PWA: switch to autoUpdate with skipWaiting/clientsClaim for seamless deploys
- Storage janitor: exclude profile images from unlinked cleanup, delete stale thumbnails
- Add deleteAttachmentByFilename() to clean orphaned attachment records for profile images
- Migration: one-time cleanup of stale profile image attachment records
- GeneralPanel: wrap in <form> to prevent implicit submission
2026-03-15 19:16:48 +01:00
Jannis Braun 4d230711fc feat: launch readiness — PWA, API hardening, memory leak fixes, sticker removal
- Add PWA infrastructure: vite-plugin-pwa, manifest, service worker,
  SW update prompt component, placeholder icons, Apple meta tags
- Harden API client: 401 auto-logout, AbortController timeouts
  (30s standard, 120s uploads), onUnauthorized callback
- Fix memory leaks: clear voice user status on leave, clean up all
  Maps (channelToSpaceMap, permissions, etc.) on removeSpace
- Upgrade error boundary to Aether Drift design with Try Again button,
  collapsible stack trace, and componentDidCatch logging
- Configure desktop icon paths in electron-builder.yml
- Remove sticker feature (server routes, schema, types, UI components)
- Fix Docker build: use **/node_modules in .dockerignore to prevent
  COPY from clobbering pnpm-installed workspace dependencies
- Add vite-env.d.ts declarations for noise suppressor wasm imports
- Exclude test files from tsc build via tsconfig
2026-03-15 15:41:22 +01:00
Jannis Braun b08f40feb7 feat: inline delete confirmation on message hover bar
Click trash icon to arm (morphs to green checkmark), click again to
confirm. Auto-cancels after 3s or 2s after mouse leaves. Uses CSS
scale+opacity transitions for a smooth icon swap animation.
2026-03-15 02:48:07 +01:00
Jannis Braun 6701ccc9b4 fix: emoji picker uses full panel width with no dead space
Remove width: 100% !important override that fought emoji-mart's shadow
DOM grid. Container now uses w-fit to wrap content tightly. GIF picker
gets explicit w-[390px] to maintain its own width independently.
2026-03-15 02:39:52 +01:00
Jannis Braun 42266ff963 fix: match Klipy CDN domain for inline GIF rendering
The GIF URL regex matched media.klipy.com but Klipy serves from
static.klipy.com, causing GIFs to render as plain links instead
of inline images.
2026-03-15 02:09:01 +01:00
Jannis Braun 3de6e4a668 feat: GIF search (Klipy), stickers, emoji picker, and bug fixes
- Add GIF search powered by Klipy API with correct response mapping
  (file.sm/hd tiers, not flat files structure)
- Add sticker system: packs, upload with auto-downscale, send in messages
- Add tabbed InputPopover with emoji, GIF, and sticker pickers
- Fix GIF API key migration race condition (column-add loop vs rename)
- Fix masked API key corruption on settings save (server + client guards)
- Fix sticker packs 403 (reversed isMember parameter order)
- Fix emoji picker not filling popover width (perLine 8→9, CSS 100%)
- Add error logging for Klipy API failures
2026-03-15 02:04:37 +01:00
Jannis Braun 7113f47b17 fix: soften incoming call animations with liquid ripple and refraction effects
Replace hard-edged ring ripples with blurred radial gradient orbs, add
subtle glass refraction shimmer, and use gentler glow/breathing curves
for a calmer incoming call experience.
2026-03-15 00:33:05 +01:00
Jannis Braun 3a266e07ed fix: allow bare filenames in avatar/banner validation, fix password min length
isValidAssetUrl() was rejecting bare filenames (e.g. "1234567890.webp") which
is the established convention the frontend sends. Now accepts bare filenames
while still blocking path traversal and unsafe schemes.

Also updates client-side password validation to match server's 8-char minimum.
2026-03-15 00:11:10 +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 ed4dcdcf69 fix: thumbnail content-type, animated GIF preservation, and janitor cleanup
- Add extension-based mimetype fallback in uploads route so thumbnail
  files serve correct Content-Type (image/webp) instead of falling back
  to application/octet-stream when DB lookup misses
- Skip animated images (metadata.pages > 1) during thumbnail generation
  to preserve GIF/WebP animations instead of flattening to static frame
- Remove redundant explicit thumbnail deletion in storageJanitor since
  deleteUploadFile() already auto-deletes the thumbnail variant
2026-03-14 21:49:11 +01:00
Jannis Braun 1750f12c85 fix: input depth styling, missed fields, and header button order
- Add subtle border + inset shadow to input tiers for resting-state visibility
- Fix DmSearchBar and SearchPopover containers missing input depth treatment
- Fix focus ring clipping in settings panel scroll container
- Swap search and member list toggle button positions in channel/DM headers
2026-03-14 14:04:14 +01:00
Jannis Braun 836f0acef6 feat: standardize input styling with tier system, add depth and admin features
- Define 4 input tier CSS classes (input-standard, input-search, input-embedded, input-danger)
  in globals.css, migrating ~50 inputs across ~28 component files to use them
- Add subtle border and inset shadow to solid input tiers for resting-state visibility
- Fix focus ring clipping in settings panel scroll container
- Fix phantom Tailwind tokens (border-border-primary, placeholder-txt-muted)
- Add admin user management panel, storage management, and account deletion utilities
2026-03-14 13:49:32 +01:00
Jannis Braun afbc4b5e31 fix: use consistent picture frame icon for video quality button in VoiceControlBar 2026-03-13 23:07:14 +01:00
Jannis Braun b194cc1915 feat: discover people tab, privacy settings, and friend request button fix
- Add "Discover People" section to Add Friend tab with user cards, mutual counts, and inline actions
- Add discoverStore for fetching/searching discoverable users across local and federated instances
- Add PrivacyPanel to user settings with discoverability toggle
- Add is_discoverable column to users table with migration
- Fix "Send Friend Request" button vertical alignment using transform centering
2026-03-13 22:54:19 +01:00
Jannis Braun 9382477e33 fix: real-time friend request cancel/decline via WebSocket
Cancelled requests now disappear from receiver's UI instantly, and
declined requests revert the sender's discover card from "Request
Pending" to "Send Friend Request" — no page refresh needed.

Also includes the discover endpoint and sendFriendRequest return type
changes from the prior session.
2026-03-13 22:42:25 +01:00
Jannis Braun d3101c4ba4 feat: backfill thumbnails for existing image attachments on first startup
One-time async migration runs after server is listening — generates 800px
WebP thumbnails for all existing image attachments that lack one. Gated by
a persistent flag in instance_settings so it never re-runs.
2026-03-13 17:07:29 +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 12b450b7b9 feat: inline DM search bar — find or start conversations from sidebar header
Replace the static placeholder button with a fully functional search bar
that filters existing DM conversations instantly and searches for users
via the API with debounce. Supports keyboard navigation, federation-aware
DM creation, group DM display, and portal-based glass dropdown.
2026-03-13 04:47:18 +01:00
Jannis Braun 06aa3bb9aa fix: preserve original invite origin during federation double-redirect 2026-03-13 04:17:40 +01:00
Jannis Braun 759f4c11ab fix: JoinPage design polish — broken divider token, width/padding/label/hover consistency 2026-03-13 04:10:18 +01:00
Jannis Braun fed2a64d3f fix: JoinPage graceful already-member handling + "I use another instance" for auth'd users
- Add spaceId to InvitePreview so the client can navigate to the space
- Detect "already a member" error and show green success card with auto-redirect
- Add "I use another instance" link for authenticated users alongside "Not you? Log in"
2026-03-13 03:58:28 +01:00
Jannis Braun a3a045905e fix: "Not you? Log in" on JoinPage now clears session before navigating
The link was bouncing back to JoinPage because AuthRedirect on /login
saw the existing token and redirected right back. Now calls logout()
first so the login form actually renders.
2026-03-13 03:45:41 +01:00
Jannis Braun a4be895670 fix: JoinPage identity card + equal-weight other-instance option
Add avatar/name/username identity card in the federation connect phase
so users can confirm their account before entering their password.

Promote "I use another instance" from a hidden <details> collapsible to
an equal-weight button with an "or" divider, using a dedicated
'other-instance' phase with Back navigation.
2026-03-13 03:26:32 +01:00
Jannis Braun c3617de832 feat: public invite landing page with federation redirect
Make /join/:code a public route with a standalone JoinPage that shows a
space preview and handles authenticated, unauthenticated, and cross-instance
users. Adds GET /api/spaces/invite/:code/preview (no auth) endpoint,
?redirect= param support on login/register, and cleans up dead invite
handling from AppLayout and JoinSpace modal.
2026-03-13 03:04:52 +01:00
Jannis Braun 07ef49eac0 feat: channel permissions UI, DM Sans font, private channel filtering, migration fix
- Rewrite ChannelSettingsModal with full tri-state permission override UI
  for roles and members (allow/neutral/deny per permission bit)
- Switch font from Inter to self-hosted DM Sans (woff2 variable fonts)
- Add client-side VIEW_CHANNEL filtering in ChannelSidebar for private channels
- Broadcast isPrivate flag on channel override changes
- Fix voice permission bit migration: gate behind persistent flag to prevent
  repeated re-runs that stripped STREAM from @everyone roles
- Add speakingUserIds set to voice store for efficient user-level lookups
- Clear current channel view when a channel is deleted
- Move .glass-strip to @layer utilities for proper CSS specificity
- Simplify avatar initials font size to proportional formula
2026-03-13 02:47:32 +01:00
Jannis Braun 2b810055f7 fix: PiP selection logic — last speaker persistence + focused stream support
Track last speaker via ref so avatar fallback doesn't snap to array-order
first remote when everyone stops talking. Parse :stream suffix from
focusedParticipantId so clicking a stream tile in the grid pins that
screen share to PiP. Remember last focus via ref so PiP content persists
when grid focus is cleared.
2026-03-13 02:05:46 +01:00