0.16.0
[0.16.0] - 2026-05-12
A wide-ranging release built around three themes: a complete notification
system (in-app + 10 external channels), an end-to-end reporting flow, and a
top-to-bottom redesign of every admin / mod surface with new accessibility
primitives wired in throughout.
Added
Notifications
- In-app notification system. Per-user bell with unread badge, SSE-backed
live feed, dedicated/notificationspage with filtering, and a profile
bonus tile. Backed by 35+ notification types covering moderation,
H&R, security, social, bonus, and admin actions. - 10 external channel adapters. SMTP, Telegram, Discord, Slack,
Mattermost, ntfy, Gotify, Pushover, generic Webhook, and Apprise. Each
adapter ships with an admin-side configuration form, a user-side opt-in
form, a per-channel rate budget, and a circuit breaker that disables
flaky channels until manually re-tested. Every channel uses native HTTP
(no Python / Apprise runtime required, except optionally for Apprise
itself). - Per-user routing. Members pick which channel each notification type
is delivered through (or none) from the redesigned/settings
notifications section — built around real brand logos.
Reporting
- Per-torrent "Report" button on every torrent detail page, gated to
authenticated users. The redesignedReportModalis an "incident slip"
with a chip-based reason picker, character counter, anonymity reassurance,
and a serrated bottom edge. - Moderator triage at
/mod/reportsrebuilt as numbered case files with
status rails, target enrichment (clickable torrent / user link), live
filter chips, and pagination. - Cascade on acceptance. Accepting a torrent report now auto-transitions
the torrent intorejectedvia the sharedtransitionStatus()helper,
sendsupload_rejectedto the uploader with the report reason as the
rejection note, and re-evaluates their auto-roles. The reporter is
notified for both accept and dismiss outcomes.
Admin / Mod surfaces (all rebuilt)
/admin— operations control room with live metrics tiles (torrents,
peers, seeders, leechers), protocol matrix, copy-to-clipboard endpoint
list, and a status badge that pulses red when the tracker is offline./admin/branding— identity atelier with a sticky 3-sample live
preview (header, browser tab, homepage hero) that mirrors the operator's
edits in real time./admin/categories— atlas / encyclopaedia layout. Newznab IDs sit in
call-number panels, sub-categories thread under their parent via a gold
rail, and a real-time search filter auto-expands matching parents./admin/tags— colour palette with full-width swatch chips and YIQ
contrast picking./admin/bonus-events— signal-tower schedule. Active events get a
pulsing ON-AIR panel with a live countdown and window-progress bar;
others are bucketed under Upcoming / Paused / Archive./admin/bonus-rules— tier-curve SVG visualisations of the seed-count
and age multipliers./admin/invites— registrar's ledger with snapshot strip and per-user
grant autocomplete./admin/upload-rules— gate-by-gate console with regex testing./admin/notifications— operator dispatch board for channel admin./shop— editorial "cabinet of curiosities" layout./mod— watchtower with time-aware greeting, live clock, four
stat tiles (pending / reports / H&R / personal 7-day actions), and a
two-column "freshest pending uploads + activity log" feed driven by a
single/api/mod/dashboardcall./mod/pendingdefaults to the "pending" filter so triage lands on the
actionable bucket out of the box.
User-facing
- Bonus-event modal (broadcast bulletin): pulsing ON-AIR header, live
countdown, paired multiplier meters with directional gauges, window
timeline with a moving marker, and a "what this means" explainer block. - Notification bell popover rebuilt as an inbox console with a
prominent "mark all read" button, a two-chip filter (unread / all)
with a pulsing blip for fresh items, per-row mark-read affordance, and
tone-coloured rails on the left edge. - Top-of-page
NuxtLoadingIndicatorso route navigations in the
static SPA build show progress instead of freezing on the previous page.
Changed
- Seeding bonus ledger. The seeding rule now rolls every torrent the
user was seeding into one ledger row per tick (e.g. "Cadence de seed
pour 17 torrents") instead of inserting one row per (user, torrent)
pair. Same total points; far less ledger noise for heavy seeders. - Reports POST. Self-reports (
targetType=user,targetId === user.id)
are now rejected at the route. Long-formOtherreason value lengthened
past the 10-char Zod floor. - Admin endpoints panel drops the redundant HTTP/UDP/WS badges next to
each copy button — the row label already carries the protocol. - Branding subtitle fallback. When empty, the navbar / footer subtitle
now rendersv0.16.0(read frompkg.version) instead of a stale
vdev. /api/admin/reportsreturns atargetobject (name + link) per
report so the moderation surface can deep-link directly to the
reported torrent or user without a second round-trip./api/me/bonus-historyships a typedtorrentCountfield per row.
Fixed
- A11y critical:
--fg-faintcontrast bumped from 1.84:1 to 5.2:1
so the dozen-ish mono micro-labels that read as text reach WCAG AA. - A11y: focus management.
Modal.vueandReportModal.vuenow
auto-focus their panel on open, shiparia-modal="true"+tabindex,
and bind Esc onwindowso a user who tabs out of the modal can still
dismiss it. - A11y: prefers-reduced-motion global guard so the pulsing dots /
ring expansions stop animating when the OS asks. - A11y:
:focus-visiblegold-outline default so keyboard users can
see where they are on dark surfaces. - A11y: aria-labels added to every icon-only button across
BonusEvents/Categories(kept the:titlefor the hover tooltip). - A11y: touch targets bumped from 26–32px to 36px on
.entry-act,
.entry-toggle,.act,.slip-close,.nbell-row-clear. - NotificationBell mark-read moved from
opacity:0(invisible-but-
focusable) to a permanently visible 0.45-opacity baseline so keyboard
users tabbing into the row aren't surprised. - BonusEvents.vue swaps
$fetchinonMountedforuseFetch; the
ticker is now driven by a watcher on[activeEvents, upcomingEvents],
fixing the case where a scheduled event flipping to active mid-session
left the countdown frozen until reload. - Branding.vue dirty tracking moves from a per-keystroke
JSON.stringify
pair to a deep watcher that just sets a boolean. Major perf win when
editing WYSIWYG fields. - Branding sanitizeHtml local regex (which let
<b onmouseover=alert(1)>slip through) replaced by the
DOMPurify-backed auto-imported helper. Effective scope was a self-XSS
on the operator's own preview; the public render path already used
DOMPurify. - Reports.vue NuxtLink guards on
target.linkso a race between
report creation and target deletion can't ship<NuxtLink to="">. - Categories.vue picks up a double-click guard on delete and a mobile
breakpoint that stacks the action cluster under the identity column at
≤ 640px. /admin/categoriesmodal call-number panels now fit 6-digit Newznab
ids (the Prowlarr/Jackett 100000–199999 range)./api/admin/reports/[id]cascade is wrapped so a partial
notification failure doesn't leave a half-applied transition.- Self-
+vdevfallback in the navbar subtitle eliminated. - Duplicate-import warning in the API build (Nitro's auto-import
scanner foundNotificationPayloadin bothchannels/index.tsand
channels/types.ts) silenced by dropping the re-export.
Performance
notify.tsfans the user-channel + admin-channel + locale lookups
out viaPromise.all; the previous code serialised them (3 round-trips
per recipient innotifyManyloops)./api/admin/statscaches the Redis SCAN + HGETALL roll-up in Redis
for 30 s and pipelines the HGETALLs per scan batch.useBrandingcomposable centralises/api/brandingbehind
useState; the layout, the homepage hero, and the auth pages used to
fetch the payload independently 4× per SSR request.useNotificationsswitches from module-levellet-singletons to
useState('notifications'), so two simultaneous SSR requests no longer
share the same store ref (a quiet cross-request leak in dev).- Stagger animations on
BonusEvents(upcoming / paused) and
Reportscap atMath.min(i, 8)so a 30-item list doesn't produce a
1.5 s cascade.
Removed
/admin/reportsand/admin/hnrduplicate routes. The shared
components are now reached exclusively through/mod/reportsand
/mod/hnr.- The "Copy hash" button on the torrent detail page (the report button
takes its slot).