A new Auto-Mod setting, "New accounts must wait before posting (minutes)",
sits next to the new-account link rule. 0 (the default) turns it off; the
maximum is 10080 minutes (one week).
While an account is younger than that, the server refuses channel messages,
forum topics, attachments, thread replies, polls, scheduled messages and DMs
to other people. A DM with yourself is allowed. Admins and members at or above
the Auto-Mod exempt role level are not held back. If the check itself fails,
the post is refused rather than let through.
The refusal says how long is left, and the app puts the message (or the
forum post being written) back so nothing is lost.
A self-destructing message the server refused came back (if at all) with
the timer off, so a resend went out permanent. Too long, it now returns
with the timer on; refused for permission, the text returns so it can be
sent normally by choice. Cancelling the dialog puts focus back in the
message box, and the unit buttons say which is chosen. The cache busters
for every file the feature touched are bumped, since a stale module would
have thrown on channel switch.
The native GStreamer screen share path is gone: in a peer-to-peer mesh every
hardware and OS combination behaved differently, and keeping them in step cost
more than it saved. Screen sharing uses Chromium's own capture everywhere.
- Screen share bitrate is a setting: a stepper from 300 to 10,000 Kbps or
unlimited, with press-and-hold repeat and typed entry. Changes apply live,
including to a share going through the voice relay (debounced, so a hold
becomes one update). The default stays 1080p at 30 fps with an 8,000 Kbps cap.
- Black screen share tiles on GPU encoders: the sharer asks for a fresh
keyframe after negotiation, and the watchdog only re-adopts tracks a tile is
not already showing.
- Relay screen publishing is serialized and tied to the share it was started
for, so a stop, a new share or a relay restart can never publish a stale
producer, and a failed first publish retries on its own.
_setupSocketListeners was one 2,602-line function. Its statements are now six
_listen methods called in the original order, so every listener is registered
in the same order as before; no local variable crosses from one part to
another. The parts live in app-socket.js (session, connect and wake),
app-socket-channels.js (channels, messages, members, voice rosters) and
app-socket-events.js (everything else the server pushes).
Checked: all 1,124 other methods have identical source, the six parts rebuild
the old function exactly, and in a running app every one of the 108 events
the old function listened for still has its listener. Messages, edits,
deletes, reactions, pins, settings and reconnecting were exercised live.
All 437 empty catch blocks in public/js were reviewed. Failures worth knowing
about (desktop bridge calls, native screen share cleanup, saved output device
routing, DM and thread render steps, resync steps, server list sync, settings
and library loads, offer rollbacks) now print a tagged console warning.
Expected failures (blocked storage in private mode, autoplay and fullscreen
refusals, connections that are already closed, malformed saved values) carry a
one-line reason. A few try blocks around code that cannot throw were removed.
Behaviour is otherwise unchanged.
* Optional tags on uploads, plus tag: search and clickable tags
Attach an optional tag to any image or file before sending it, from a new bar under the composer previews. Up to three tags per attachment. Clicking a queued attachment switches the bar to that attachment's tags. Applying an existing tag is open to anyone who can upload; creating a new one needs the new Manage Tags permission. Tags are one global vocabulary, kept separate from the forum topic tags.
Messages that carry tagged attachments show a Tags footer listing every tag, and each chip is clickable to search for that tag.
The search bar gains a tag: token with a dropdown picker of existing tags. The match is a loose prefix, so tag:do finds dog, dogs and doghouse. When what you type matches no current tag, a Search removed tags button runs the search anyway, so a tag that gets removed later still finds its old attachments.
New tables upload_tags and attachment_tags store the vocabulary and the links.
* Retroactive tag editing, gallery tag chips, filter, bulk manage
Phase 3a: edit tags on already-sent attachments from the message context menu, manage_tags-gated for others' messages, with live footer updates.
Phase 3b: show tags in the Files & Media gallery, filter by tag (AND, exact match), and bulk append or replace tags across selected attachments from select mode. Replacing with no tags clears them behind a second confirmation.
Photo and video tiles render their tag chips in a bottom overlay so the tile's aspect-ratio and overflow:hidden no longer clip them, and the toolbar buttons are sized to match the Sort and Size selects.
* Admin-configurable tag limits and a Tags management panel
Adds the max tags per attachment and max tag length as server settings,
surfaced in the Limits admin section and clamped to the same ceilings on
both sides. The tag apply, edit and bulk paths read the live limits.
Adds a Tags admin section (manage_tags) to add, rename and delete
vocabulary entries. Rename and delete are hard and propagate to every
attachment through the association's tag_id, so both go behind a
destructive confirm. A rename that collides with an existing tag merges
onto it. A shared helper collects the affected messages and rebroadcasts
their footers so open views update live.
Also groups the media gallery toolbar so the Tags filter button sits with
Sort instead of floating in the middle of the row.
* Move Manage tags beside the Tags filter and scope gallery Select all to the tag filter
Relocate the gallery's Manage tags control out of the select-mode action
group (where it sat directly beside Delete selected) to next to the Tags
filter button, so a tag action and the destructive delete are no longer
adjacent. Visibility is still driven by the same _refreshMediaGalleryToolbar
logic, so it only appears in select mode for users with manage_tags.
Fix Select all so it only selects the items visible under the active tag
filter, reusing _filterMediaItemsByTags, instead of every item in the tab.
Previously it selected attachments hidden by the filter, which made a
follow-up bulk action apply far more widely than the visible selection.
---------
Co-authored-by: bo0sted <mc526+github@proton.me>
On a server where most channels have screen share and music turned off, the two crossed-out icons on every channel were clutter that also truncated names. A server-wide setting under the admin options hides them for everyone; the slow mode and cleanup badges stay.
Five items from Dispencer2's wishlist, on top of the bug PRs.
- Role Management: New Role opens a template picker (Moderator, Helper,
Trusted member, Event host, Media poster, Group, Blank), each with a
level, colour and permission set ready to edit.
- Channel Functions: Required roles row with an Any / All switch. The gate
sits on top of membership: getEnrichedChannels, get-messages and
send-message all check it, losing a role leaves the room live, admins are
exempt, and delete-role strips the role from every gate.
- roles.max_upload_mb: a role can raise the upload cap for its holders. The
upload routes, the disk guard margin and the composer's pre-check all read
the same effective cap; get-server-settings reports it per user as
max_upload_mb_effective and a role change pushes the new value.
- Create Channel: template picker (built-ins plus templates saved from any
channel through Channel Functions). create-channel accepts topic, readOnly,
announcement, slowMode, mediaEnabled and voiceEnabled so a template lands
in one trip.
- Role menus: create-role-menu posts a message with one emoji per role and
seeds the reaction chips; toggle-self-role and the add/remove-reaction
handlers grant or drop the role, and every reader gets their own held list.
test/rolesWishlist.test.js drives all of it against a real server.
Co-authored-by: ancsemi <ancsemi@gmail.com>
The header read presence from the online list of whichever channel was on screen, so a partner outside that channel showed as away with an initial until the DM was opened full screen. The DM target now carries avatar fields, the client keeps every channel's online list and reads the DM's own, asks for it when the PiP opens, and the server refreshes DM rooms whenever a member connects, disconnects for good, or changes status.
A protected topic showed no shield on its card and, with no archived flag on the element, the context menu offered Protect again instead of Unprotect. The card now sets the flag and shows the shield, the protect and unprotect events update the card live, and the cached topic keeps the state across re-renders.
Closing the top-bar banner, or ticking Don't show this again on the Android promo, now keeps it gone on that account across reloads and devices. The permanent flag it checked was never written anywhere, so it returned on every page load.
A forum channel now opens as a list of topic cards, newest activity on top, each with its title, tags, author, reply count and first image, and a Gallery view of square tiles. The toolbar sorts by Recently active or Date posted, filters by the channel's tags (match some or all), and a New Post button opens a composer with title, body and tags. Admins keep the tag list in Channel Functions, and the author or a moderator can retitle or retag a topic from its card. Also in: an NSFW switch per channel with a Hide NSFW channels setting, a search box inside Settings (Ctrl+F), Create Temp Channel opening the normal create form with Temporary ticked, and & and + allowed in channel names. Requested by test2 and Dispencer2.
The preferences handler restored the effects pick and relied on the theme pass to apply it. With no personal theme and no server default theme there is no theme pass, so the pick sat in storage until the next reload.
The effects pick lived only in localStorage, so a desktop launch that landed on a different storage origin came back with the theme's default effects while the theme itself survived through the server. Every picker write now mirrors to user_preferences like the theme does, and the preferences handler restores it before the theme is applied. Reported by Dispencer2.
The desktop and mobile promo popups and the recovery-codes notice remember Don't show again on the account instead of in localStorage, so hardened browsers that wipe storage stop re-showing them. The 18+ and Terms checkboxes now appear only when creating an account; login carries a note that signing in accepts the current Terms. Thanks to @Bo0sted.
Server-side signaling and codec negotiation for a native Desktop screen share path. Off unless the user opts in from Settings, and the toggle only appears inside Haven Desktop builds that carry the native helper. Browser and per-app audio capture are unchanged. Thanks to @bernardokcosta.
A forum is a regular channel where every top-level message is a topic.
Replies go in the message's thread, the topic shows its reply count, and a
reply bumps the topic to the newest end of the channel, so old topics
resurface instead of sinking. Toggle it in Channel Functions or tick Forum
when creating a channel.
The history query orders topics by their latest activity, with cursors
resolved to activity stamps so paging keeps working, and topics never
compact into each other so a bump can move the node in place. Covered by an
integration test that boots a scratch server.
PR #5554 folded the moderation actions into the right-click menu, which stays.
This restores the hover preview card behind a new Settings > Chat toggle (on by
default) and brings back the gear buttons on member rows and the profile card.
Both gears now open that same unified menu anchored to the button, so touch and
keyboard users keep a visible way into the moderation actions.
* Remove hover profile card, copy its styling to the click card
* Anchor the profile card to the right of the clicked name or icon
* Toggle the profile card when the same user is clicked
* Unify the user menus into one right-click context menu
Fold the mod-only gear menu into the member-list context menu so
right-clicking a user (member list or chat author) opens one menu with
everything: profile actions plus role/channel/moderation, gated as
before. Remove all gear-icon entry points (profile card and sidebar),
the old gear menu, and the profile card's Set Nickname button, since the
context menu now covers them. Add a "Role Management" label across locales.
* Open the full-res avatar in the lightbox from the profile card
Clicking a real avatar (not the letter fallback) on a profile card opens
the image in the existing lightbox, for other users and yourself. The
card stays open behind it: the lightbox now sits above the card, and the
card's outside-click handler ignores clicks on the lightbox so dismissing
the image keeps the card open.
---------
Co-authored-by: bo0sted <mc526+github@proton.me>
* Add Navidrome rich presence integration
Shows what a user is playing in Navidrome on their Haven profile, through the
Havidrome companion plugin posting to a reserved per-user webhook.
Users generate a revocable webhook token in Settings > Activity > Navidrome.
The webhook takes strict multipart: title and duration are required, and cover
bytes are magic byte checked and held in Haven's memory. The Haven server hands
those bytes to viewers' browsers from its own /api/activity/navidrome-cover
path, so Navidrome is never contacted and its address never leaves the plugin.
Presence runs through the existing activity slot so it stays online gated,
clears on disconnect, and auto expires at track end. The profile card gains a
live progress bar; pause freezes it, stop clears it.
* Update the open profile card live from presence
The profile card was a snapshot, so a track pausing, resuming, changing or
ending did nothing until it was reopened. It now re-renders its activity from
the online-users broadcasts the client already receives, so the bar freezes on
pause, resumes on play, swaps on a new track and clears on stop while the card
stays open. It rides the existing presence stream, no new traffic.
* Make the whole profile card live and stop it clipping off-screen
The open profile card only re-rendered its music activity from presence;
status (online/away/dnd/offline) and custom status text stayed frozen at
whatever they were when it opened. It now refreshes all three from the
online-users broadcasts the client already receives, so a user going away,
setting a status or dropping offline updates the card in place while it
stays open. Shared _statusDotClass/_statusLabel/_profileStatusTextHtml
helpers keep the initial render and the live refresh in sync, and the
custom-status line gets a stable slot so it can appear and clear. Rides
the existing presence stream, no new traffic.
Positioning measured the card with a hardcoded 320x400 guess, but its
width is a rem value and its height varies with content, so once font-size
or zoom differed from 16px the real card was larger than the guess and the
viewport clamp let it spill off the right edge (desktop app and web). It
now measures the real offsetWidth/offsetHeight before clamping.
* Expire a paused Navidrome track after 5 minutes idle
A paused track cleared its play-to-end timer and set no replacement, so
the slot lingered indefinitely with no further posts. Since presence is
gated on being online, the stale track stayed hidden while offline but
reappeared on reconnect. Arm a 5-minute idle timeout while paused instead;
every update resets it, so an actively-paused client stays shown.
* Rate-limit the Navidrome cover route
The cover-serving GET had no limiter while the webhook it pairs with did.
It's viewer-facing (every open profile card fetches it), so add a separate,
looser cap (120/min vs the webhook's 60/min) rather than reusing the
webhook limiter, keeping the two routes consistent without throttling
legitimate viewing.
* Make the music presence webhook generic, not Navidrome-only
The rich presence webhook was tied to Navidrome and a single companion
plugin, so Haven had a settings page that only worked with one source and
would point at nothing if that plugin went away.
It is now a source-agnostic Listening webhook. Any music player that can
run a plugin or a script posts a title, artist and duration (plus optional
album, position, cover art and a source label) to a personal URL. The
contract is documented in listening_api.md.
- Route is /api/webhooks/listening/:token, cover is
/api/activity/listening-cover/:userId. Table navidrome_tokens is renamed
to listening_tokens with a migration that keeps existing tokens.
- A track is identified by a server-side hash of title, artist and
duration rather than a poster-supplied id, so nothing untrusted is used
as an identifier.
- Expiry is a capped lease instead of trusting the reported duration: a
playing track is held until it would end, up to 60 minutes, and long
media stays visible by sending a heartbeat. A fake duration can no
longer leave a status stuck.
Anyone still using the old Navidrome webhook needs to point their plugin
at the new /api/webhooks/listening URL.
* Scroll long track titles and artists instead of clipping them
A long title or artist was cut off with an ellipsis at whatever width
the viewer's profile card happened to be, so different people saw
different amounts of the same track. Past 30 characters the text now
scrolls in a seamless loop at a constant speed, gated on character
count rather than pixel width so it reads the same for everyone.
Shorter text is unchanged, and the scroll respects reduced-motion.
* Trim the redundant intro in the listening API doc
The invite_users permission unhid the Invite Code nav item, but the Admin tab
switch and the admin panel container each kept their own permission lists,
and neither included it. Holders saw an Admin tab that did nothing when
clicked, and the section stayed display:none behind it.
All three places now share one gate, _hasAnyAdminSettingsAccess, so a
permission cannot be added to one list and missed in the others. The rename
handler uses it too, which stops a moderator from losing the Admin tab after
changing their display name.
* Allow level 0 role levels to be created
Also change the text where appropriate to indicate level 0 roles can be created.
* Allow roles to also be edited to be level 0.
* let server allow role updates to level 0 be saved
* fix non-english locals litterally saying "self-serve" in english
* Enforce permission removal server-side on create-role and update-role
* Fix update-role audit log values
The values sent to the audit log were sending True for parameters that were supplied. It wasn't checking if something actually changed.
Fixed to properly set the values based on what changed.
Signed-off-by: birdcrazy <227081272+birdcrazy@users.noreply.github.com>
* Add spacing between audit-detail-pair
There was no space between values and the following key in the audit log
Signed-off-by: birdcrazy <227081272+birdcrazy@users.noreply.github.com>
* Fix lvl 0 assignment reporting the previous roles in audit log
Signed-off-by: birdcrazy <227081272+birdcrazy@users.noreply.github.com>
* Hide permissions list role editors when role lvl=0
* Change wording for level_0_role_note
* Role list refinement
Add level indicators to roles in the role list.
Add divider between level-0 roles and leveled roles.
Add "Groups" lable above level-0 roles.
* Change role sidebar level display to match the style of the channel role manager
* fix html class defined with a .
* Add "groups" divider to channel roles modal.
* fix translation pointing to wrong key
* Add groups to profile settings.
list user's current groups.
Group assignment still needs to be added.
* add missing translation key, change groups list style
* Hide Groups section if no lvl 0 roles are available
Add group-manager so user can select groups to join
as well as a save button and a socket to allow user to apply lvl 0 roles to themselves.
* bug fix: groups section doesn't display because roles arn't loaded.
* add style to groups manager, and fix bug where saving rename causes user's groups to disapear until hard refresh
* Add methods to allow user to see which channels each "Group" grants.
Using temporary tooltip for now.
* First attempt at a popup containing the channel grants
* channel access refinements
keep within window bounds.
only list sub-channels if the group grants the parent, or the user already has access to the parent.
* remove owner parent channel check as that is overcomplicating things for now.
* Add translations for group channel access popup
* bug fix: two groups both grant the same channel, user removes one, and adds the other at the same time, the channel would be removed.
* Also save groups on save-rename-btn click
---------
Signed-off-by: birdcrazy <227081272+birdcrazy@users.noreply.github.com>