Commit Graph
180 Commits
Author SHA1 Message Date
Jannis Braun 0aba5ef8c1 fix: retain textarea focus on send and auto-scroll on content expansion
Remove disabled={isUploading} from the textarea so focus() isn't silently
ignored on a disabled element. Fix ResizeObserver effect deps so the
observer is actually created after the loading spinner transitions to
message content.
2026-03-08 21:01:27 +01:00
Jannis Braun edcd2ff728 fix: restore textarea focus after sending a message 2026-03-08 20:36:15 +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 a9a68187b0 fix: add activity panel toggle button to Explore page header 2026-03-05 02:05:02 +01:00
Jannis Braun aef15a7cf7 fix: guard auto-channel redirect against stale cross-server channels
The redirect effect used the shared `channels` array which could contain
stale data from a previously viewed server during async loadServerDetail.
Added channelToServerMap ownership check to ensure channels belong to
the target server before redirecting. Also persists last-visited channel
per server in localStorage for better return navigation.
2026-03-05 01:57:49 +01:00
Jannis Braun b642989e6e fix: give Explore its own /explore route so PiP navigates back to voice correctly
The Explore page shared /channels/@me with Friends, differentiated by a
showExplore UI flag. When PiP navigated to a voice channel, the stale
flag caused MainContent to render text chat instead of the voice grid.
Replace the flag with a dedicated /explore route so the URL is the
single source of truth.
2026-03-05 01:29:16 +01:00
Jannis Braun b840d2f82b feat: show joined servers in explore page instead of empty state
Joined discoverable servers now appear in a collapsible "Joined" section
at the bottom of the explore page, so the page never feels dead when all
servers have been joined. A mint success banner replaces the old empty
state text.
2026-03-05 01:11:23 +01:00
Jannis Braun bd7f65d33c fix: use getRawDb() instead of drizzle $client for explore tab
$client doesn't exist in drizzle-orm 0.33, causing a TypeError and
500 on every /api/servers/explore request. Export the raw better-sqlite3
instance directly from db/index.ts instead.
2026-03-05 00:51:55 +01:00
Jannis Braun 39372980f7 fix: re-send voice_join on WS reconnect to restore sidebar after server restart 2026-03-05 00:35:26 +01:00
Jannis Braun 09b9d1d55a feat: enable auto gain control by default for new users 2026-03-05 00:19:39 +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 a5ea7633fc fix: explore tab first-click navigation and misleading empty state
Clear stale currentChannelId when clicking Explore/DMs in ServerSidebar,
and guard AppLayout route effect from clobbering showExplore. Move member
exclusion into SQL for correct pagination/totals, surface allSettled errors,
and show context-aware empty state messages.
2026-03-04 21:24:43 +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 65e9ee5203 fix: prevent duplicate DMs at creation time, clean up corrupted read states
Replace the unreliable client-side DM dedup loop in populateFromReady with
a creation-time guard (findExistingDmForUser) that checks all instances
before opening a new DM. Guards added to FriendsPage, NewDmModal, and
UserProfilePopout.

Also fixes: corrupted read_states from temp_ optimistic message IDs (server
migration + client-side validation), federation-aware closeDm/addDmMember
API routing, isSelf-based DM member filtering in sidebar/header, and WS
event error isolation.
2026-03-04 20:04:15 +01:00
Jannis Braun 33ae79bae9 fix: federation avatar colors, cross-instance DMs, and instance persistence
- Fix avatar gradient colors in 7 UI sites by using homeUserId fallback
- Merge remote DM channels in populateFromReady (no longer home-only)
- Remove isHome guards from 8 DM WS event handlers for cross-instance DMs
- Route DM typing events to correct instance origin
- Fix autoConnectAll deleting disconnected instances from localStorage
- Distinguish network errors from auth errors in instance reconnection
- Start WebSocket retry for network-unreachable instances (auto-recovery)
- Add reconnectInstance method and Reconnect button in UI
2026-03-04 18:46:16 +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 72d1fab930 feat: federate friends/social system across connected instances
Rewrites socialStore to aggregate friends and requests from all connected
instances using Promise.allSettled. Parses user@domain in friend requests
to route to the correct instance. Removes !isHome guards on social WS
events so remote friend requests arrive in real-time. Shows "via hostname"
labels on remote friends/requests in the UI.
2026-03-04 17:28:28 +01:00
Jannis Braun c7c3331dbf fix: persist homeUserId for federated users to ensure consistent avatar colors
Store the original home snowflake ID (homeUserId) during federation replication
so that avatar gradient colors resolve identically across instances. Previously,
replicated users got new snowflake IDs on each instance, causing different
gradient colors. Now Avatar, UserProfilePopout, VoiceUser, StreamTile, and
VoiceChannel all resolve through homeUserId when available. Includes backfill
logic for existing federated users missing the field.
2026-03-04 13:45:25 +01:00
Jannis Braun 9432f81ba3 fix: resolve federated avatar color to home instance identity
Federated users get a different snowflake ID on remote instances,
causing avatar gradients to mismatch their home identity. Apply
resolveDisplayIdentity/isSelf resolution in VoiceUser, StreamTile,
VoiceChannel sidebar, and MemberSidebar so the current user's avatar
color is consistent across all views.
2026-03-04 04:27:35 +01:00
Jannis Braun 2d6cdd0a3c fix: relocate typing indicator into the input bubble
Move TypingIndicator from document flow (between MessageList and
MessageInput) into MessageInput's outer wrapper using absolute
bottom-full positioning. Fixes indicator being hidden behind the
floating glass input bubble on desktop.
2026-03-04 04:27:26 +01:00
Jannis Braun f7809cf457 fix: federation reaction identity + remote server reload race condition
- Include user object in reaction_added WS broadcasts for isSelf() resolution
- Use isSelf() instead of userId comparison for reaction ownership checks
- Load remote server detail after ready event to prevent empty channel list
2026-03-04 03:53:07 +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 3f15a8e282 docs: add federation roadmap with batched execution plan
Comprehensive audit of the federation implementation identified:
- Solid data plumbing (multi-WS, origin routing, asset normalization)
- Missing on-ramps (can't join remote servers, no discovery, no UI indicators)
- 4 bugs (stale voice state, logout cleanup, reply-to normalization, HTTPS assumption)

Roadmap defines 6 batches for incremental execution:
- Batch 1: Bug fixes
- Batch 2: Password enforcement (same password across all instances)
- Batch 3: Cross-instance server joining (invite URL parsing, remote join routing)
- Batch 4: Phase 5 UI (instance grouping, badges, username collision display)
- Batch 5-6: Deferred (explore system, cross-instance friends)
2026-03-04 01:27:21 +01:00
Jannis Braun b44c2a7651 feat: multi-target deploy script supporting Pi and Beta VM 2026-03-04 00:36:45 +01:00
Jannis Braun 9ae9a0f3dc fix: prevent install.sh crash when .env exists without expected keys
The env_val() function uses grep which returns exit code 1 when no
match is found. Under set -euo pipefail, this kills the script.
Adding || true prevents this on upgrade installs with older .env files.
2026-03-03 23:32:28 +01:00
Jannis Braun fd175023c1 fix: use correct working directory for pnpm module resolution in install.sh
The node -e command to set instance name needs to run from
/app/packages/server where pnpm's symlinked node_modules resolve
better-sqlite3, not from /app root.
2026-03-03 19:57:32 +01:00
Jannis Braun 617999500b feat: all-in-one production deployment with install script
Redesign deployment as a single docker-compose with Backspace, Caddy
(auto-HTTPS), and LiveKit (voice/video) using hybrid networking:
Backspace+Caddy on isolated bridge, LiveKit on host mode for WebRTC.

- Add install.sh: interactive installer that handles Docker setup,
  domain/DNS verification, secret generation, LiveKit config, and
  deployment with health-check wait
- Add Caddyfile: static reverse proxy config using Caddy env vars,
  routes /livekit/* to host-mode LiveKit via host.docker.internal
- Rewrite docker-compose.yml: all-in-one with profiles (voice),
  no external volumes/networks, bind mount ./data for visibility
- Fix livekit.ts: use LIVEKIT_URL env var directly instead of
  Host-header derivation that made the env var dead code
- Fix Dockerfile: health check reads $PORT dynamically
- Update .env.example: add DOMAIN, COMPOSE_PROFILES documentation
- Update .gitignore: add livekit.yaml (contains secrets)
2026-03-03 19:51:51 +01:00
Jannis Braun e37f9ea24c feat: route LiveKit token requests to correct instance for cross-instance voice 2026-03-03 00:37:20 +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 72e07c1cc1 fix: persistent random Snowflake worker ID + clean reaction API
Two fixes addressing architectural review feedback:

1. Snowflake ID collisions: Replace process.pid-based worker ID with a
   cryptographically random value (0-1023) generated once at first boot
   and persisted to instance_settings.worker_id. Eliminates deterministic
   ID collisions between Docker instances that all run as PID 1.

2. Reaction API leak: Revert addReaction/removeReaction signatures to
   (messageId, emoji) — the store now resolves the channel internally by
   scanning its message cache, keeping routing logic out of the UI layer.
2026-03-03 00:03:29 +01:00
Jannis Braun 5194dbef25 feat: multi-WebSocket connection manager for instance federation
Refactor the WebSocket layer from a singleton connection to a connection
map supporting N concurrent instances. Each connection has its own
heartbeat worker, reconnect state, and token. Stores are now
instance-aware: serverStore merges servers by origin, chatStore routes
API calls through the correct client, and instanceStore triggers WS
connect/disconnect on add/remove. DM, social, and voice events remain
home-only.
2026-03-02 23:53:30 +01:00
Jannis Braun ef7aa3ff5a feat: add instance store and Connected Instances settings UI
Introduce instanceStore (Zustand) with full federation lifecycle:
probe remote instances, register with username collision fallback,
login to existing accounts, sync instance list across all connected
instances, auto-reconnect from cached tokens on login/page load, and
cleanup on logout. Add ConnectedInstances component to User Settings
with home instance card, remote instance management, and inline
add-instance flow (URL probe → register/login → connected).
2026-03-02 23:31:28 +01:00
Jannis Braun c72fcdda70 refactor: extract API client into parameterized BackspaceApiClient class
Refactor singleton api object into a class with constructor-scoped
closures parameterized by baseUrl and getToken. Export backward-
compatible api singleton, createApiClient factory for remote instances,
and new instance.info() and users.verifyPassword() methods for
federation support.
2026-03-02 23:21:32 +01:00
Jannis Braun 22440b5a24 feat: implement Phase 1 server-side federation groundwork
Add multi-instance support foundation: shared federation types
(ReplicatedInstance, InstanceInfoResponse, VerifyPasswordRequest),
database schema changes (home_instance, replicated_instances on users,
instance_name on settings), public instance info endpoint, auth
registration with homeInstance and username@domain collision fallback,
password verification endpoint, and replicatedInstances sync on user
profile. Extract duplicated sanitizeUser into shared utility across
8 server files.
2026-03-02 23:15:43 +01:00
Jannis Braun 37dc73231d refactor: convert color system to RGB channels for native Tailwind opacity
Root cause fix for all broken CSS variable opacity modifiers. Converted
every hex CSS variable to space-separated RGB channels (e.g. #fda4af →
253 164 175) and updated Tailwind config to use rgb(var(...) / <alpha-value>).
This makes bg-accent-rose/10, border-border-hard/50, etc. work natively
everywhere — no more currentColor fallback. Reverted all previous explicit
rgba() workarounds back to clean Tailwind syntax. Also fixed the voice
connection wifi icon SVG (was bottom-left aligned, now centered).
2026-03-02 17:46:21 +01:00
Jannis Braun 0e235fdf1c fix: DM reactions + eradicate broken CSS variable opacity modifiers
Server-side: handleReactionAdd/Remove now check dm_messages table when
message isn't found in server messages, enabling DM emoji reactions
via the same unified client event path.

Client-side: replaced all remaining broken opacity modifiers on CSS
variables (border-border-soft/50, bg-accent-rose/10, bg-surface-*/N,
etc.) with explicit rgba values. Tailwind can't decompose var() for
opacity, causing fallback to currentColor. Fixes 16 files across
error boxes, table borders, surface tints, and input separators.
2026-03-02 17:36:33 +01:00
Jannis Braun 2c98704198 fix: chat scroll-to-bottom on channel open and bottom spacing
Consolidated two competing scroll effects into one to fix an effect
ordering bug where prevMessagesLength was updated before the initial
scroll check could read it. Channel switch now resets tracking state
so the snap-to-bottom fires reliably via requestAnimationFrame.
Increased message list bottom padding to clear the glass input bubble.
2026-03-02 17:17:30 +01:00
Jannis Braun 907728ab24 fix: replace broken border-hard/50 opacity with subtle white borders
Tailwind can't decompose CSS variables for opacity modifiers, so
border-border-hard/50 fell back to white currentColor. Replaced with
border-white/[0.06] on image attachments, code blocks, and action
toolbar. Also applied frosted glass material to message action toolbar.
2026-03-02 17:07:33 +01:00
Jannis Braun 7608afccc4 fix: panel separator borders and reaction spacing to match prototype
Replace @apply border-none with @apply border-0 on global * selector.
border-none sets border-style: none, which silently kills all Tailwind
border utilities (they only set width/color, relying on preflight's
border-style: solid). border-0 sets border-width: 0 instead, preserving
the solid style so border-r/border-l/border-b utilities render correctly.

- Add border-r border-border-hard to ChannelSidebar (both DM and server views)
- Add border-l border-border-hard to MemberSidebar and ActivityPanel
- Replace shadow-header with border-b border-border-hard on sidebar headers
- Remove duplicate mt-1 on reaction container (parent gap-1 already provides 4px)
2026-03-02 16:51:17 +01:00
Jannis Braun b8ce05ad6c fix: precise chat layout alignment to match Aether Drift prototype
Message rows: 20px horizontal padding, 3px vertical, 56px avatar column,
subtle 2.5% hover. Channel headers: border instead of shadow, text # hash,
10px gap, 6px button radius. Date dividers: 8px margin, 11px text, border-hard
lines. Input bubble: 34px buttons with 18px SVGs, 10px textarea padding.
Inline code: lavender on #111115 with border-soft border.
2026-03-02 16:17:42 +01:00
Jannis Braun 9491edb3c9 fix: reaction pill frosted glass + compact sizing to match prototype
Replace inline JS hover handlers with .glass-pill CSS classes for proper
Aether Drift frosted glass depth (blur, inset highlight, box-shadow).
Constrain emoji size and use exact prototype padding (2px 8px) for compact pills.
2026-03-02 15:55:42 +01:00
Jannis Braun cdee450cf5 fix: channel sidebar and reaction bubbles to match Aether Drift prototype
Channel sidebar: subtle bg-surface-elevated highlight with rounded-[6px],
active channel left pill, rose unread dot on right, smaller #/voice icons
(18px), 10px horizontal padding, 24px voice user avatars at 36px indent.

Reaction bubbles: inline styles for background/border colors to bypass
Tailwind opacity modifier failures on hex CSS variables. Both mine (mint
tint) and non-mine states now render visible pill containers reliably.
2026-03-02 14:45:47 +01:00
Jannis Braun 31562c2b29 fix: avatar and server icon font size/weight to match prototype
Avatar: font-semibold (600) → font-bold (700), replaced Tailwind text
classes with exact pixel sizes matching prototype (24px→10px, 32px→12px,
40px→15px, 56px+→18px). Server sidebar: font-medium → font-bold.
2026-03-02 14:08:34 +01:00
Jannis Braun 88b29200d5 fix: avatar status dot size to match prototype cutout dimensions
Reduced visible dot from 12px solid to 6px with 3px mask cutout gap,
matching the prototype's 12px border-box dot with 3px border exactly.
2026-03-02 03:35:57 +01:00
Jannis Braun dcd4ef0011 feat: Aether Drift UI polish — popout redesign, sidebar cleanup, status dot fix
- Redesign UserProfilePopout with glass material, viewport clamping, flow-based
  avatar layout, and subtle send-message button
- Server sidebar: downsize icons to 40px (matching prototype), remove nonfunctional
  Explore button, add separator before action buttons, replace Tooltip with native
  title, remove hover boxShadow artifacts
- Avatar status dot: fixed 12px size with semi-transparent border for universal
  cutout effect matching the design prototype
- MemberListToggleButton: distinct three-person icon (was duplicate of Add Friends)
- FriendsPage: fix Add Friend button contrast (dark text on mint background)
- ChannelSidebar: rename Nitro/Shop to Coming Soon placeholders
2026-03-02 03:21:12 +01:00