Skip to content

0.16.0

Choose a tag to compare

@Dim145 Dim145 released this 12 May 01:25
· 280 commits to main since this release

[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 /notifications page 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 redesigned ReportModal is an "incident slip"
    with a chip-based reason picker, character counter, anonymity reassurance,
    and a serrated bottom edge.
  • Moderator triage at /mod/reports rebuilt 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 into rejected via the shared transitionStatus() helper,
    sends upload_rejected to 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/dashboard call.
  • /mod/pending defaults 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 NuxtLoadingIndicator so 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-form Other reason 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 renders v0.16.0 (read from pkg.version) instead of a stale
    vdev.
  • /api/admin/reports returns a target object (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-history ships a typed torrentCount field per row.

Fixed

  • A11y critical: --fg-faint contrast 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.vue and ReportModal.vue now
    auto-focus their panel on open, ship aria-modal="true" + tabindex,
    and bind Esc on window so 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-visible gold-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 :title for 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 $fetch in onMounted for useFetch; 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.link so 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/categories modal 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-+vdev fallback in the navbar subtitle eliminated.
  • Duplicate-import warning in the API build (Nitro's auto-import
    scanner found NotificationPayload in both channels/index.ts and
    channels/types.ts) silenced by dropping the re-export.

Performance

  • notify.ts fans the user-channel + admin-channel + locale lookups
    out via Promise.all; the previous code serialised them (3 round-trips
    per recipient in notifyMany loops).
  • /api/admin/stats caches the Redis SCAN + HGETALL roll-up in Redis
    for 30 s and pipelines the HGETALLs per scan batch.
  • useBranding composable centralises /api/branding behind
    useState; the layout, the homepage hero, and the auth pages used to
    fetch the payload independently 4× per SSR request.
  • useNotifications switches from module-level let-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
    Reports cap at Math.min(i, 8) so a 30-item list doesn't produce a
    1.5 s cascade.

Removed

  • /admin/reports and /admin/hnr duplicate routes. The shared
    components are now reached exclusively through /mod/reports and
    /mod/hnr.
  • The "Copy hash" button on the torrent detail page (the report button
    takes its slot).