The <label for='avatar-file-input'> elements created a native label-input
association. In many browsers, e.preventDefault() on label click does NOT
reliably prevent the native for-attribute behavior, causing both the native
activation AND the JS fileInput.click() to fire simultaneously which
opens the file dialog twice and immediately cancels it.
Fix: Changed <label> to <button type='button'> and removed the for
attribute entirely. The JS click handler is now the sole mechanism for
opening the file dialog, eliminating the conflict.
- Reorganize header: search/pin/gear moved left next to channel name,
voice+leave always furthest right, flex spacer pushes them apart
- Delete channel moved into gear dropdown with TWO confirm dialogs
- Fix all slider tracks (slider-sm, ns-slider, volume-slider,
stream-vol-slider): add explicit ::-webkit-slider-runnable-track
and ::-moz-range-track with background + margin-top on thumbs
- Fix Dark Souls flame effect: CSS used 'dark-souls' but theme
button uses 'darksouls' (no hyphen) effects never activated
- Center trophy emoji in Shippy leaderboard circle button (flexbox)
- Replace window.open music popout with in-app floating PiP overlay
(same iframe = no playback position reset, draggable, themed)
- Fix mobile sidebar: add !important to position:fixed in 768px media
query (theme CSS was overriding with position:relative)
- Shippy Container: change 'flap' to 'jump' in instructions
- Shippy Container: leaderboard button now trophy circle at bottom-right
- Stream pop-out: collapse tile to 28px instead of dimming video, frees up space
- Favicon: hollow dark-purple hexagon with thick border (was filled light-purple)
- Server: add request-voice-users and voice-rejoin handlers for state sync
- Server: multi-tab safe disconnect (check socketId before removing users)
- Client: emit voice-rejoin on socket reconnect if in voice
- Client: request voice users on channel enter and every 30s refresh
Effects fix:
- Dramatically boosted opacity/alpha on ALL theme effects (were 0.04-0.06, now 0.15-0.35)
- Added position: relative to .main for each theme that needs it
- Ice icicles: replaced text-based with SVG mask icicle shapes
- Nord snowfall: doubled particle count (8->14), larger dots, full opacity
- Dark Souls: 3x brighter ember glow, added ambient firelight overlay
- FFX: 4x stronger water gradient, thicker wave bar with blur
- Bloodborne: doubled vignette intensity
- Matrix: 4x more visible rain lines
- LOTR: 3x brighter candlelight with flickering rhythm
- Abyss: deeper vignette
Windows 95 (replaces XP):
- Flat #c0c0c0 gray everywhere, navy #000080 accents
- All border-radius forced to 0 (global * rule)
- Classic outset/inset borders on all buttons, inputs, panels
- Blue gradient title bars (sidebar-header, modal h3)
- Highlight selection: navy bg + white text
- Dithered scrollbar track pattern
- Win95 checkerboard aesthetic throughout
- Pop-out: blank original iframe src when opening pop-out window, restore on close
- Sliders: remove entire _initSliderFills() system (MutationObserver, prototype patch,
injected style element, all _sliderFillUpdate refs). Vanilla sliders with themed
track bg and accent thumb only. Remove all ::-moz-range-progress fill rules and
theme-specific --track-fill overrides
- Status indicators: colorblind-accessible shapes - circle (online), half-dome (away),
square (DND), hollow circle (invisible/offline). Applied to member list dots,
status picker dots, and user bar dot
- Fix: PFP upload button - wrapped _initSliderFills Object.defineProperty in try-catch
so it can't crash the HavenApp constructor and prevent _setupAvatarUpload from running
- Add: Hide/show individual stream tiles - eye button on each tile, hidden-streams bar
with restore buttons, stream count label shows hidden count
- Add: Pop-out music player - opens YouTube/Spotify/SoundCloud embed in separate window,
minimizes in-app panel, shows indicator when pop-out window is active
- Fix: YouTube 'Video unavailable' for clients - added origin parameter to embed URL,
added ws: to CSP connectSrc (required for HTTP-mode Socket.IO connections)
- Add: Status indicator dots overlaid on user avatars (green=online, yellow=away, red=dnd, grey=offline/invisible) with dark border cutout, responsive sizing for mobile
- Fix: .env.example still referenced Tenor API updated to GIPHY with correct URL
- Fix: CSS comment 'Tenor key' 'GIPHY key'
- Sync: website/index.html replaced with canonical docs/index.html
- Add: Help & Support footer link to docs/index.html
- Add: Deploy checklist note in CHANGELOG.md
- Fix: Slider fill tracks now use injected <style> rules targeting ::-webkit-slider-runnable-track per-slider, bypassing Chrome shadow DOM limitation
- Fix: YouTube embeds use youtube.com/embed/ (not youtube-nocookie.com) to resolve 'Video unavailable' for other users
- Fix: Removed duplicate screen share tile creation from shareScreen() in voice.js
- Fix: PiP leavepictureinpicture event now on video element (not pipWindow)
- Fix: Transparent overlay on music iframes to force synced controls
- Fix: Music panel X minimizes with reopenable indicator in channel header
- Fix: Server tracks active music per voice room and sends to late joiners
- Replace dead mobile screenshot URLs with permanent ones from issue #4
- Add fill gradient to voice volume sliders (.volume-slider)
- Update dynamic voice sliders to track --fill CSS variable
- Make .channel-section flex:1 with overflow so sidebar scrolls properly
(fixes theme bar pushed off screen on mobile)
- Convert DM section to collapsible sub-menu (Discord-style toggle)
- Click header to expand/collapse, state persists in localStorage
- Shows total unread badge when collapsed
- Auto-expands when a new DM opens
- New docs/support.html: troubleshooting, FAQ, bug report instructions, contact info
- Added Help link to docs/index.html nav and footer
- Download buttons now point to v1.4.5 tagged release
- Version history updated with v1.4.5 as latest
- Fixed %ERRORLEVEL% bug in Start Haven.bat (parse-time expansion inside if block)
- Bat file now detects HTTP vs HTTPS and opens correct URL in browser
- Added SSL/HTTPS troubleshooting to README and GUIDE
- Bumped version to 1.4.5
- Rewrote 8-clause Release of Liability into 12-section Terms of Service,
EULA & Release of Liability (age restriction, indemnification, dispute
resolution, class action waiver, limitation period, IP rights, severability,
survival of terms, etc.)
- Added mandatory 18+ age verification checkbox (separate from EULA acceptance)
- Server enforces ageVerified on both /api/auth/login and /api/auth/register
- Database: age_verified column added to eula_acceptances table
- LICENSE: added Age Restriction (Section 4) and Indemnification (Section 5)
- EULA version bumped to 2.0 all existing users must re-accept on next login
- CSS: h4/ul styling for EULA content, widened modal to 700px, checkbox spacing
- Socket auth middleware now refreshes is_admin + display_name from DB
on every connect, not just from possibly-stale JWT payload
- Admin status synced from .env ADMIN_USERNAME on every socket connect
(not only login), so .env changes take effect without re-login
- New session-info event pushes authoritative user data to client on
every connect/reconnect, overwriting stale localStorage
- Client handles session-info to update display name, admin UI, and
localStorage in real time
- Decouple voice from text channels (Discord-style): dedicated voice:<code>
socket.io rooms, server-side leave on switch, broadcastVoiceUsers emits to
both voice and text rooms, lightweight voice-count-update to all clients
- Sidebar voice indicators: badge with user count per channel
- Mobile input: shorter placeholder, 34px buttons, tighter padding, 90px
auto-resize cap
- Mobile header: remove flex-wrap, hide 'Voice' label text at 768px,
allow voice-controls to shrink
- Roadmap added to README (webhooks, permissions, threads, files, E2EE)
- Version bump 1.4.0 1.4.1
- Display name now separate from login username (stored in display_name column)
- Login name set at registration, immutable, shown as @username subtitle
- Display name freely changeable, allows spaces, non-unique
- All messages/voice/leaderboards show display name via COALESCE
- JWT and auth responses include displayName field
- Mobile viewport fix: 100vh -> 100dvh (dynamic viewport height)
- Message input no longer hidden behind phone URL bar
- Mobile header decluttered: hide search/pin/delete/copy icons on <= 768px
- Mobile voice join button added to right-sidebar users panel
- Status bar hidden on mobile to reclaim vertical space
- Message toolbar tap-to-reveal at 768px breakpoint (was only 480px)
- GIF picker: corrected Tenor branding to GIPHY to match actual API
Fixed: static file caching changed from 1h maxAge to always-revalidate with ETags, preventing stale JS after server restarts
Improved: mobile message actions (react/reply/pin/edit/delete) hidden by default, revealed with a tap on the message, cleaned up phone UI significantly
- Play voice_leave tone when local user leaves voice chat
- Show 'Left voice chat' info toast on disconnect
- Destroy all screen-share tiles on voice leave (fixes ghost tiles)
- Escape key now closes all modal overlays (settings/bans/leaderboard/add-server)
- Replace MIT with MIT-NC: prohibit selling/monetizing without permission
- Add attribution link to original author (ancsemi)
- Add full Tenor API setup instructions to README
- Shorten Shippy Container game description
Features:
- Direct messages: private 1-on-1 via DM button in member list
- User status: online/away/dnd/invisible with custom text + auto-away
- Channel topics: admin-editable topic bar below channel header
- File sharing: upload PDFs, docs, audio, video, archives (25 MB limit)
- Persistent read state: server-tracked unread counts survive refresh
Database:
- New read_positions table for unread tracking
- New columns: users.status, users.status_text
- New columns: channels.topic, channels.is_dm
- New column: messages.original_name
- All migrations are safe for existing DBs
UI:
- Status picker dropdown in sidebar
- DM section in channel list
- File attachment cards with type icons
- Inline audio/video players in chat
- Status dots colored by user status
- Topic bar with click-to-edit for admins
Server:
- /api/upload-file endpoint with expanded MIME whitelist
- set-status, set-channel-topic, start-dm, mark-read socket events
- get-channels returns topic, is_dm, unread counts
- emitOnlineUsers includes status data
- Idle detection with 5-min auto-away
Voice audio (Bug 2):
- Guard _playAudio against duplicate Web Audio graph creation
- Prevents multiple GainNodes competing for the same stream
Talking indicator (Bug 3):
- Already fixed: 300ms hysteresis hold timer in both local and remote
talk detection (prevents flicker from micro-pauses between syllables)
Screen share (Bug 4):
- Add SDP rollback in voice-offer handler for renegotiation glare
- Guard voice-answer handler against stale answers after rollback
- Use event.streams[0] in ontrack for proper stream association
- Add track.onunmute to re-trigger screen display when data arrives
- Mute all screen share video tiles (prevents browser autoplay block)
- Explicit vid.play() call as autoplay backup
GIF search (Bug 1):
- Tenor API key can now be set from the admin GIF picker UI
- Stored in server_settings DB table (no .env editing needed)
- Server checks DB first, falls back to TENOR_API_KEY env var
- Shows step-by-step setup guide when key is missing
- Non-admins see a clear 'ask your admin' message