Fixes system-wide freeze when Accessibility permissions are enabled.
Two changes to libuiohook/src/darwin/input_hook.c via pnpm patch:
- Switch kCGEventTapOptionDefault to kCGEventTapOptionListenOnly
- Strip mouse move/drag/scroll/left/right-click events from mask
Listen-only taps are passive observers that cannot freeze the system.
Keeps keyboard events and mouse buttons 3+ for PTT side buttons.
Windows and Linux native paths are completely untouched.
- Add .catch handler to scheduleNext to prevent unhandled promise
rejections from killing the scheduling loop
- Replace `as unknown as Animation` cast with Cancellable interface
for type-safe cleanup handles
The svgRef was created but never passed to the sub-component SVG
elements, meaning it would always be null when the animation hook
tries to use it. Now passed via SvgProps interface.
Implement the <Mascot> component that renders Nori in four emotional
states (idle, sleeping, excited, lonely) with distinct SVG geometry,
color palettes, and facial expressions. Includes useMascotAnimation
hook stub for Task 2 and full test coverage (15 tests).
When the Electron main process can't start uiohook (macOS Accessibility
denied, Linux missing input group), the IPC path silently produces no
events. Previously this left zero keybind listeners active. Now the web
capture-phase fallback is always set up as a safety net, ensuring
keybinds work in-app even when the OS-level hook fails.
When running in Electron but the desktop app hasn't been rebuilt with
keybind support (no syncKeybinds/onKeybindAction in preload), the hook
now falls through to the web capture-phase listener instead of silently
doing nothing. This ensures keybinds work immediately on the web-served
UI regardless of the desktop app version.
The previous hash (charCodeAt(0)*256 + charCodeAt(1)) only used the
first 2 characters of KeyboardEvent.code strings, causing all Key*
codes (KeyA, KeyB, ..., KeyZ) to collide to the same numeric value.
This made Ctrl+D and Ctrl+M appear as identical bindings (false
conflict) and broke keybind matching in the web fallback.
djb2 hashes the full string, producing unique values for all codes.
Adds keyboard/mouse shortcut support for mute, deafen, camera,
screenshare, push-to-talk, and disconnect. Desktop app uses
uiohook-napi for OS-level global shortcuts that work while minimized.
Web fallback uses capture-phase listeners. Settings UI with keybind
recorder, conflict detection, and platform-specific permission handling.
Installs uiohook-napi for OS-level non-consuming input hooks, creates
KeybindManager class that receives keybind configs from the renderer,
matches the OS-wide key/mouse event stream, and sends matched actions
back via IPC. Exposes syncKeybinds, onKeybindAction, onAccessibilityStatus,
onKeybindHookError, and checkAccessibility APIs through the preload bridge.
Move mute/deafen/camera/screen-share/disconnect logic out of VoiceControlBar into a shared voiceActions.ts utility so the same handlers can be called from both UI buttons and the upcoming keybind dispatcher.
Replace one-off inline tab bar in InstancePanel with a reusable
SettingsTabBar that reads from SettingsSectionsContext. Any panel
using tab mode can drop it in with zero configuration.
Polish: proper underline indicator, consistent gap spacing,
negative top margin to sit flush with content area padding.
Renders tab pills (General, Streaming, Storage, Users) at the
top of the content area, matching Discord's Family Center pattern.
Works alongside the sidebar sub-links — both navigate the same tabs.
Add homeInstance, role, joinedAfter, joinedBefore, and sort query
params to GET /api/admin/users. Add GET /api/admin/users/instances
endpoint returning distinct home instance domains.
- Add <h2> title headings to General, Streaming, Storage, Users panels
- Reset password now shows confirmation dialog before executing
- Temp password displays inline below the user row (not at top of list)
- Temp password has copy button and dismiss action
The continuous scroll was wrong for Instance — its sub-panels are
fundamentally different page types (user table, storage tool, bitrate
matrix, toggles). Revert to conditional rendering.
The hook now supports two modes:
- Scroll mode (default): smooth scroll + IntersectionObserver spy
- Tab mode (onNavigate callback): sidebar sub-links switch tabs
Instance uses tab mode. The scroll-spy infrastructure remains for
future panels that benefit from it (e.g. Account sections).
Also reverts GeneralPanel to sticky save bar since it's on its own
page again with no overlap risk.
- Section headings: text-lg with descriptions, dividers spaced my-10
- GeneralPanel: convert to auto-save (toggles save immediately,
text fields save on blur) — no more hidden save bar
- StreamingPanel: restore sticky save bar (complex multi-field form
needs explicit save, and it's the only sticky bar now)
- Memoize context value in SettingsSectionsProvider
- Use ctxRef pattern in hook to avoid depending on ctx object
in useLayoutEffect deps (ctx object changes on every render,
causing effect → setState → re-render → effect loop)
Remove `as const` + spread pattern that created a new array on
every render, triggering useLayoutEffect re-registration. Use
typed SettingsSection[] constant instead.
- Add role="status" and aria-label to all skeleton containers
- Refactor ChannelSidebar from conditional siblings to ternary
for consistent mutual exclusivity with MemberSidebar pattern
Content was left-aligned in the content pane, making it appear
off-center relative to the full modal. Add mx-auto to center
the max-w-[640px] wrapper within the content area, matching
Discord's centering behavior.
Replace the stale-flag-based voice re-registration in the WS ready
handler with a server-authoritative check. If the server's voiceStates
doesn't include us (server restarted, lost in-memory voiceRooms),
cleanly tear down the LiveKit session via leaveVoice() + disconnect().
If the server still knows about us (WS blip), do nothing.
- Import BITRATE_MATRIX_KBPS from shared constants
- Parse and return bitrateMatrixOverrides in rowToLimits
- Widen updateData type to accept null values
- Raise maxBitrateKbps and minBitrateKbps caps from 50 000 to 1 000 000 kbps
- Add full validation for bitrateMatrixOverrides (null to clear, key/value checks)
After creating or copying a role, the name input is now auto-focused
with text selected so the user immediately starts typing. A subtle
"Role created" banner appears until the user makes any change.
RoleEditView used useState(role.name) for draft state, but React
reuses the component instance when editingRoleId changes, ignoring
the new initial value. Adding key={editingRoleId} forces a remount,
re-initializing all draft state (name, color, permissions) correctly.
Replaces hardcoded 3-resolution/3-framerate arrays with ALL_RESOLUTIONS
and STANDARD_FRAMERATES from shared constants (540p–Native, 30–120 fps).
Adds amber warning banner when high-end options (≥1440p or ≥75 fps) are
enabled, and fixes toggleResolution to handle 'native' with correct sort.
Replace hardcoded VALID_RESOLUTIONS/VALID_FRAMERATES with shared STANDARD_RESOLUTIONS/STANDARD_FRAMERATES constants. Update rowToLimits to handle the 'native' resolution value in DB parsing, and update all PATCH validation blocks to accept the expanded resolution/framerate sets and the 'native' option.
Optimistic message used content: '' while the server normalized it to
null, causing the content-based dedup to fail and leaving both the
empty temp message and the real attachment message in the list.
- Add GET/PUT/DELETE /api/categories/:id/overrides endpoints with
privilege escalation guard matching existing channel override pattern
- Add isCategoryPrivate() and broadcastCategoryOverrideChange() helpers
that cascade visibility changes to all channels in a category
- Fix PATCH /api/channels/:id to recompute permissions via
broadcastOverrideChange when categoryId changes (different category
overrides may apply)
- Include isPrivate flag on categories in GET /api/spaces/:id payload
using batch-fetched @everyone overrides
- Include isPrivate on categories in broadcastChannelLayout and
PATCH /api/categories/:id broadcasts