- usePermissions ORs current user roles + @everyone only (not all server roles)
- cache myRolesByServer; load on active server; refresh after self role edit
- gate/notify @everyone and @channel; plain @username push; special mention UI
- refresh FEATURE_PARITY (DMs exist; drop stale critical gaps)
- README production deploy notes dumpster.service
- unit tests for permission bits and broadcast mention tokens
Members API appends server bots (is_bot). Sidebar groups ONLINE / OFFLINE / BOTS.
Bots get green BOT badge, no kick menu or profile. Mentions and DMs skip bots.
- Read states store: smarter hasUnread compares against latest message
- ConversationList: orange dot + bold name for unread DMs
- DMChat: auto-mark-read when viewing conversation
- WS handler: mark DM read on new message while active+focused
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.
- 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.
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.
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.
gif.url is the giphy.com page link, gif.images.fixed_height.url is
the actual media CDN URL. react-markdown was rendering a dead link
instead of an embedded image.
Phase 7.1 — Per-channel notification settings
- New notification_settings table (user_id, channel_id, level)
- GET/PUT/DELETE handlers under /channels/{channelID}/notifications
- Push dispatch and @mention loops filtered by notification level
- Frontend: bell icon per channel cycling all/mentions/none
Phase 7.4 — Read receipts
- New read_states table (user_id, channel_id, last_read_message_id)
- PUT /channels/{channelID}/read and GET /users/me/read-states
- Auto-mark-read when messages load in ChatArea
- Unread dot for channels never opened
Also: favicon/icon refresh in index.html
Backend:
- Add GET /api/v1/servers/{serverID}/members endpoint
- Wire MemberHandler routes in main.go
Frontend:
- Create member store (fetchMembers by server ID)
- MemberList now fetches members from API instead of empty props
- ChatArea: show channel name instead of ID, add error display,
add flex-1 + min-w-0 to input for proper sizing, error handling
on sendMessage