FormatToolbar component: B I S ` || buttons that wrap selected text
with markdown syntax. Added to both channel and DM input areas.
Keyboard shortcuts: Ctrl+B bold, Ctrl+I italic already work via
browser defaults on the rendered markdown.
- Added GiphyPicker to DM input area (toggle with [GIF] button)
- Added EmojiPicker/kaomoji to DMs (toggle with [☺] or Ctrl+E)
- GiphyPicker shows above input when toggled
- Kaomoji appends to input text
- Channel store saves {serverId, channelId} to localStorage on select
- ServerBar restores last server+channel after fetching servers
- Fetches channels for the saved server, then sets the channel if valid
- Gracefully handles missing/deleted servers or channels
requireInteraction can suppress heads-up delivery on some Android
versions when screen is off. Removed it so the OS can auto-dismiss
after showing the notification.
- Remove Topic header (causes issues with Apple push service)
- Log push response status codes for debugging
- Log endpoint prefix on errors for identification
- Auto-cleanup 410 (gone) subscriptions on success path too
iOS requires user gesture for Notification.requestPermission().
- Added NotificationPrompt banner that shows on permission='default'
- autoSubscribePush only fires when already 'granted' (re-subscribe on restore)
- Banner uses tap handler for permission request (works on iOS)
- Gruvbox themed, dismissible, bottom-center toast
- DM handler now sends push notifications to other conversation members
- Cleared stale push subscriptions (old broken VAPID keys)
- Auto-subscribe will re-create them on next page load
- Auto-subscribe to push on login, register, and session restore
- Guard against nil db in SendChannelNotification
- Log push send count per channel message
- Bump SW cache to v5
Old implementation just copied random bytes as the public key (not real EC math).
New cmd/keygen calls the library's proper P-256 key generation.
Removed broken local GenerateVAPIDKeys from push/handlers.go.
viewport-fit=cover already set. Added env(safe-area-inset-*) padding
to outer container so content doesn't hide behind Dynamic Island,
notches, or the home indicator bar.
- Mobile (<md): single-panel view, sidebar/chat/members as overlays
- Hamburger button toggles sidebar overlay (ServerBar + ChannelList)
- Members button toggles MemberList overlay
- ← back button in ChatArea and DMChat headers returns to sidebar
- Auto-switch to chat view when channel or DM is selected
- Tap empty area to dismiss overlays
- Desktop layout unchanged (all panels side by side)
- Status/settings buttons hidden on small screens to save space
- [FEATURES] tab next to PINNED in channel header
- Create, vote/unvote, filter by status (open/planned/done/rejected)
- Sort by vote count (most voted first)
- Status selector for moderation
- DB: feature_requests + feature_request_votes tables
- Backend: full CRUD + vote endpoints under /servers/{id}/feature-requests
inputRef-based listener never fired (ref null at effect time or
element-level listeners don't catch events in this React setup).
Switched to window.addEventListener matching the pattern every other
keyboard handler in this codebase already uses.
Moved keydown handler from inline React prop to useEffect with
addEventListener on the input ref. Uses a ref to track latest
dropdown state, avoiding stale closure issues that prevented
arrow keys from working. Added stopPropagation on Enter to
prevent form submission race. Guard in handleSubmit to bail
when a dropdown is open.
Previously selecting a slash command with Enter just filled the
input, requiring a second Enter to send. Now Enter directly
executes the command transform (or opens /poll modal) and sends.
Arrow up/down cycles through items with highlight. Enter selects
the highlighted item. Escape dismisses the dropdown. Index resets
when the query changes.
Type / at start of message to see available commands.
Commands: /shrug, /tableflip, /unflip, /lenny, /bear,
/disapprove, /facepalm, /cry, /dance, /hug, /greet,
/me (action text), /spoiler (hidden text).
Autocomplete dropdown with tab-complete. Commands transform
input text client-side before sending. Unknown /commands
pass through as regular messages.
Moved EmojiPicker inside the form element so it anchors to the
input area (form is already position:relative). Picker now pops
up above the input instead of floating in the message scroll area.
Server interface had 'ownerId' (camelCase) but API returns
'owner_id' (snake_case). isOwner was always false, hiding
groups/roles/audit tabs for the server owner.
renderContent: mention span now includes trailing space when the
next text segment doesn't start with one (React collapses whitespace
between sibling inline elements).
handleMentionSelect: avoid double space when the text after cursor
already starts with a space.
The backend was broadcasting TYPING_START with only channel_id.
Frontend required user_id + username to show who is typing.
Added Username to Client struct, fetched on WS connect via JOIN.
Typing events now enriched with sender identity before broadcast.
Wired existing typing store (backend WS broadcast + frontend
_handleTypingEvent with 3s auto-expiry) into ChatArea and DMChat.
Typing events throttled to once per 3s. Shows 'X is typing...' or
'X and N others are typing...' below the messages, above the input.
Both ChatArea and DMChat now detect when you scroll near the top
and fetch the next 50 older messages. Scroll position is preserved
so you don't lose your place. Works for both server channels and
DM conversations.