Commit Graph
723 Commits
Author SHA1 Message Date
Jannis Braun 4807bfeeb1 feat: read/write maxUploadSizeMb in instance settings API 2026-03-23 02:22:22 +01:00
Jannis Braun 5be2e73c8a fix: use per-request upload limit from DB and fix silent truncation
Reads max_upload_size_bytes from instance_settings per-request and
passes it to request.file() so Fastify kills the stream at the
admin's configured limit. Checks file.truncated to properly reject
files that exceed the limit instead of saving corrupted data.
2026-03-23 02:22:15 +01:00
Jannis Braun ee11408060 feat: add age-based media cleanup endpoint
Adds cleanupOldMedia() to the storage janitor and a new endpoint
POST /api/admin/storage/cleanup-media { maxAgeDays, dryRun }.
Deletes chat attachments older than the specified threshold while
preserving profile images.
2026-03-23 02:20:28 +01:00
Jannis Braun eb743ca2ca feat: add max_upload_size_bytes column and shared type 2026-03-23 02:19:42 +01:00
Jannis Braun 3c2c333a56 feat: show dangling attachment count in Storage panel
Adds a 'Dangling Records' card to the storage overview grid and
includes dangling records in the cleanup eligibility check.
2026-03-23 01:56:51 +01:00
Jannis Braun f634f35450 fix: add FK constraint to attachments.dm_message_id via table rebuild
Rebuilds the attachments table with ON DELETE CASCADE on dm_message_id.
Dangling records are excluded during copy — their files become standard
disk orphans detectable by the storage janitor. Also updates the raw SQL
in index.ts to include width/height/duration columns for fresh installs.
2026-03-23 01:55:26 +01:00
Jannis Braun 8bcb5cc977 fix: wrap DM message deletion in transaction for atomicity
Matches the space message delete handler pattern. Prevents inconsistent
state if the server crashes between individual delete operations.
2026-03-23 01:54:42 +01:00
Jannis Braun d687988ba8 fix: use explicit counter for danglingFilesOnDisk in referencedFiles calc
Matches the orphanedFiles counting pattern and avoids a subtle
discrepancy if two dangling records shared the same filename.
2026-03-23 01:53:25 +01:00
Jannis Braun b40999995b feat: detect and clean up dangling attachment records in storage janitor
Adds getDanglingAttachments() to find attachment records whose
messageId/dmMessageId points to a deleted message. Updates stats
to show dangling count and cleanup to delete them.
2026-03-23 01:49:41 +01:00
Jannis Braun 4494209779 feat: add danglingAttachments/danglingSize to StorageStats type 2026-03-23 01:47:42 +01:00
Jannis Braun b3d4747126 chore: remove unused ConnectedInstance type import from federationOps 2026-03-23 00:57:36 +01:00
Jannis Braun aa2c6f7aa1 feat: add 'Sync now' button for connected instances with pending password sync 2026-03-23 00:53:48 +01:00
Jannis Braun 95cb9d4df2 feat: add background retry scheduler for federation password sync
Replace the give-up-after-3-retries path with a persistent background
scheduler: 10 retries every 30s (5 min), then 12 retries every 5 min
(1 hr). Active timers are keyed by origin so rapid password changes
cancel and replace the previous loop. All timers are cleared on logout
via clearPasswordSyncTimers() called from instanceStore.reset().
2026-03-23 00:52:40 +01:00
Jannis Braun 678e1bf358 refactor: make pendingSyncOrigins reactive in Zustand store
Add `pendingSyncOrigins: string[]` to InstanceState so components re-render
when pending password-sync flags change. Hydrate from localStorage on
autoConnectAll, clear on reset, and remove the localStorage-only read path
from `hasPendingPasswordSync`.
2026-03-23 00:50:58 +01:00
Jannis Braun 1bd684a6b0 feat: self-healing login for federated users with stale password hashes
When a federated user's local password hash is stale (e.g. they changed
their password on the home instance and sync failed), the login handler
now falls back to verifying credentials against the home instance. If the
home instance accepts the password, the local hash is silently updated
without touching passwordChangedAt, so existing valid JWTs remain valid.
2026-03-23 00:48:32 +01:00
Jannis Braun 41caf5f90c fix: skip currentPassword check for federated users on change-password 2026-03-23 00:48:02 +01:00
Jannis Braun a888828941 chore: gitignore installers and include pending web changes
Add installers/ to .gitignore and remove from tracking — binaries
should not be in git.

Also includes:
- Re-push Electron activities after WebSocket reconnect (sleep/wake)
- Merge activity map instead of replacing to preserve local state
- Nori mascot design plan
2026-03-23 00:25:31 +01:00
Jannis Braun 100ac15939 fix: deduplicate keybind actions when native hook and web fallback both fire
When the app is focused and the native uiohook hook is active, both the
IPC bridge and the web fallback capture the same keypress, dispatching
the toggle action twice (~0-20ms apart). This double-toggles mute/deafen
back to the original state.

Add a 100ms dedup window in dispatchKeybindAction to suppress the second
dispatch. Safe for human input (key repeat starts at ~500ms).
2026-03-22 23:55:59 +01:00
Jannis Braun e4f6f9a4c4 fix: translate uiohook keycodes to match web UI keybind hashes
The web UI records keybinds as djb2 hashes of DOM event.code strings
(e.g. "KeyM" → 2090563626), but uiohook fires native scan codes
(e.g. M → 50). These never matched, so global keybinds only worked
in-focus via the web fallback, never out-of-focus via the native hook.

Add a uiohook-to-DOM-code mapping table and pre-compute the djb2
hashes so pressedKeys uses the same numbering as the keybind store.
2026-03-22 23:50:02 +01:00
Jannis Braun 313ff4ef87 feat: add Nori mascot to mobile empty states 2026-03-22 23:39:16 +01:00
Jannis Braun b3051c25cd feat: add lonely Nori mascot to empty Explore page 2026-03-22 23:39:15 +01:00
Jannis Braun 4e23cb5bf9 feat: add sleeping Nori mascot to empty DM sidebar 2026-03-22 23:39:13 +01:00
Jannis Braun a4ebd52900 feat: replace Wumpus empty states with Nori mascot on Friends page 2026-03-22 23:37:15 +01:00
Jannis Braun 43eaceadc0 fix: improve animation hook robustness
- 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
2026-03-22 23:31:50 +01:00
Jannis Braun 977983eed1 feat: add useMascotAnimation hook with WAAPI state machine 2026-03-22 23:26:53 +01:00
Jannis Braun e5495ac0d8 fix: resolve TS2322 ref type error and Tailwind class conflict in Mascot
- Change SvgProps.svgRef to React.Ref<SVGSVGElement> for React 18 compat
- className now replaces default w-32/h-32 instead of appending (avoids
  Tailwind specificity conflicts)
- Replace inline style={{ position: 'relative' }} with Tailwind 'relative'
2026-03-22 23:22:06 +01:00
Jannis Braun 386bd85c8d fix: attach svgRef to SVG elements in Mascot sub-components
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.
2026-03-22 23:18:37 +01:00
Jannis Braun 7d5b9c1eee feat: add Mascot component with state-driven SVG rendering
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).
2026-03-22 23:15:46 +01:00
Jannis Braun 623928812d fix: always enable web fallback keybind listeners alongside Electron IPC
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.
2026-03-22 22:31:59 +01:00
Jannis Braun e5bf5f71ab fix: unpack native .node binaries from asar for uiohook-napi
Removed blanket !node_modules exclusion and added asarUnpack for .node
files so uiohook-napi's native binary can be loaded at runtime.
2026-03-22 22:27:10 +01:00
Jannis Braun 39ee894d77 fix: fall back to web keybind listeners when Electron lacks keybind APIs
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.
2026-03-22 21:36:05 +01:00
Jannis Braun 5554b1abc7 fix: replace broken 2-char hash with djb2 for keybind code mapping
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.
2026-03-22 21:01:02 +01:00
Jannis Braun 3ce9ee0db0 feat(desktop): add KeybindManager with uiohook-napi for global shortcuts
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.
2026-03-22 20:47:41 +01:00
Jannis Braun 11d802f86c feat(keybinds): add KeybindsPanel settings UI with recorder and conflict detection 2026-03-22 20:42:48 +01:00
Jannis Braun 895f665da9 feat(keybinds): wire useKeybinds hook into AppLayout 2026-03-22 20:37:45 +01:00
Jannis Braun c6d345048a feat(keybinds): add useKeybinds hook with Electron IPC bridge and web fallback 2026-03-22 20:35:31 +01:00
Jannis Braun fb964cc01a feat(keybinds): add keybindStore with persist, conflict detection, and tests 2026-03-22 20:32:22 +01:00
Jannis Braun 11081dbf5a refactor(voice): extract action handlers into voiceActions.ts, remove hard-coded M/D shortcuts
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.
2026-03-22 20:27:10 +01:00
Jannis Braun 063d73cff1 feat(voice): add setMuted() idempotent setter and pttActive state for PTT keybinds 2026-03-22 20:23:23 +01:00
Jannis Braun 7694f732c9 fix: replace AccountPanel inline success banners with toasts
Missed in previous pass: "Profile updated!" and "Password changed!"
banners now use addToast instead of inline layout-shifting divs.
2026-03-22 04:57:55 +01:00
Jannis Braun dd33a62b22 fix: replace inline save banners with toast notifications
Removes layout-shifting "Settings saved" / "Role saved" inline
banners from 5 settings panels. Uses existing addToast from
uiStore instead. Error banners kept inline.
2026-03-22 04:49:10 +01:00
Jannis Braun ca5cf61e53 fix: replace inline save banners with toast notifications 2026-03-22 04:48:32 +01:00
Jannis Braun 7ff8614127 refactor: extract SettingsTabBar as reusable infrastructure component
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.
2026-03-22 04:42:46 +01:00
Jannis Braun 2d7395ba64 feat: add top tab bar to Instance settings panel
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.
2026-03-22 04:34:02 +01:00
Jannis Braun 9317ccb3a0 feat: add filter row with instance, role, date, and sort to UsersPanel 2026-03-22 04:26:00 +01:00
Jannis Braun 6615b0b700 feat: add filter params and listInstances to admin API client 2026-03-22 04:23:33 +01:00
Jannis Braun fd4c73c3cb feat: add filter and sort params to admin users endpoint
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.
2026-03-22 04:22:22 +01:00
Jannis Braun 4f143bb289 fix: add missing Instance sub-panel titles and overhaul reset password UX
- 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
2026-03-22 04:04:46 +01:00
Jannis Braun 437873c823 refactor: Instance settings back to separate pages with sidebar sub-links
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.
2026-03-22 03:52:26 +01:00
Jannis Braun ca9b971c29 fix: improve Instance settings section spacing and save UX
- 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)
2026-03-22 03:43:23 +01:00