Releases: PlexiOSS/Omniplex
Release list
v0.2.0
Added
- Admin Search (
/admin/search) now covers every entity type bots,
servers, packs, teams, and users instead of just bots and servers,
each with its own Actions menu (backed by Popplio's newly-extended
SearchEntitys). - A
/staffknowledge base section (redirects to a newstaffKB
category) covering the real staff hierarchy, the full permissions
reference, how reports/tickets/applications actually reach staff, and
staff conduct/transparency all sourced directly from the live
staff_positionstable and Popplio's permission catalogue, not
guessed. Linked from the footer. - Public user profiles (
/user/[id]) now show a user's servers and every
public link they've added, not just Bots and a hardcoded website/GitHub
pair. New sharedTeamCardcomponent (also now used on the dashboard's
Teams tab, replacing a near-identical inline copy) so Teams can be
shown too. The "hasn't listed anything" empty state now checks bots,
servers, packs, and teams together instead of just bots. - Report detail (
/admin/reports) now shows the actual bot/server/pack/team
the report is about — icon, name, and a link straight to its listing —
instead of just the raw target type/id. An "Actions" button next to it
loads Arcadia's staff RPC methods filtered to whatever that target type
actually supports (e.g. force-removal for bots) via the new, reusable
GenericRpcModal, so resolving a report and actually acting on the
reported content no longer requires leaving the modal. - "Platform safety" stats (banned users, vote-banned bots) on the
Moderation Transparency page (/about/moderation), backed by Popplio's
extendedGET /list/stats. Styled as its own card-grid section,
matching the existing "Bot review pipeline" section the reports table
itself is unchanged. - A "Support Tickets" check on the Status page (
/about/status), backed
by the publicGET /tickets/topicsendpoint.
Fixed
- Team/server/bot avatar and banner updates weren't reflecting instantly
after upload — Next's image optimizer was caching the old asset
in front of already-correct origin cache headers.Avatar/Bannernow
render withunoptimized, since every source is already our own CDN
proxy or Discord's hash-versioned CDN. - The dashboard's profile editor (
EditProfileTab) had its own,
slightly-different links list (different remove icon, different "Add
link" button style) instead of the sharedLinksEditorcomponent every
other edit form already uses — now reuses it.
Changed
- Admin panel nav decluttered: Queue, Applications, Reports, and Tickets
are now grouped under a single "Moderation" dropdown instead of four
separate top-level links. - Dashboard bot/server cards moved the "Upgrade" and "Shop" actions into
the existing "..." menu, keeping only View/Edit inline, to cut down on
how crowded each card had gotten. - API Tokens tab (
TokenManager) now puts the "Create Token" button and
count at the top, with the create form appearing directly below it
instead of at the bottom of the list; button variant matched to the
secondaryconvention used by Teams/Packs/Applications. - Security tab cards given filled backgrounds and the same heading
weight/size as the rest of the dashboard, and laid out side-by-side on
wide screens instead of stacked in a narrow column. - Consistency pass across the admin panel: list spacing, card padding, and
hover treatment brought in line across Applications, Queue, and Tickets;
removed the deprecated, permanently-zero "Changelogs" stat from the
admin Overview page; Dashboard Overview's stat tiles restyled to match
the admin panel's icon+label convention.
v0.1.4
Fixed
- The Exit Panel button was desktop-only (
hidden md:block) and, on
mobile, only reachable by opening the hamburger menu — not a persistent,
obvious way back to the main site from anywhere in the admin panel.
Replaced with an always-visible icon button in the header's icon row
(same treatment as Customize/notifications/theme), on both desktop and
mobile.
Added
/admin/tickets— a staff ticket queue, filterable by open/closed. The
API and even a working staff-capable thread view already existed
(/tickets/{id}already grants any staff member withview_tickets
view/reply access, andmanage_ticketsfor reopening — confirmed by
readingget_ticket's existing owner-or-staff check), so this just adds
the missing "find a ticket to act on" step: list view backed by
Popplio's newGET /staff/tickets, each row linking straight into the
existing/tickets/{id}page rather than a new detail view.
v0.1.3
Added
-
A "Security" tab on the dashboard (
src/app/dashboard/SecurityTab.tsx)
exposing Popplio's data-export/account-deletion pipeline
(POST /users/{id}/data), which existed server-side with zero frontend
consumer until now — closes the loop on the Privacy Policy's "Your
rights" section, which previously only pointed at a support ticket for
something that can be self-service. Download builds a per-table
row-count summary plus a client-side JSON download from the completed
task's output; deletion requires typing your exact username in a modal
before it's enabled, since it's a real, irreversible
DELETE FROM users. Both pollGET /users/{id}/tasks/{tid}every 2s
(task id/key persisted tolocalStorageso a page refresh mid-poll
resumes instead of losing the reference), capping at 5 minutes before
telling the user to check back later rather than erroring. -
Vanity URL self-management:
BotEditModal/ServerEditModalgained a
"Vanity URL" field wired toPATCH /{target_type}/{target_id}/vanity
(vanityResource.update), previously a read-only value in the UI
despite the endpoint existing. Server-side validation errors (taken,
blacklisted, contains@) are surfaced verbatim rather than
reimplemented client-side. -
A "Recent voters" section (
src/components/votes/VoterList.tsx) on bot
and server pages, backed by the public
GET /{target_type}/{target_id}/votes/user-listendpoint (bare Discord
snowflakes, no auth needed) which had no frontend consumer before this.
Resolves the first 12 voters to a username/avatar viausers.getUser
(Promise.allSettled, falls back to the raw ID if resolution fails for
any one voter) with a "+N more" tail count from a real paginated total. -
A staff "Applications" review page (
/admin/applications) for the 7
positions registered in Popplio's/appssystem (certification,
partnership, server certification, staff, ban appeals, etc.) — the
PATCH /staff/apps/{id}approve/deny endpoint had no staff-facing UI
anywhere before this, not even in Arcadia as far as this workspace can
tell. Required adding a new Arcadia RPC wrapper,
arcadia.popplioStaff()(src/lib/arcadia/client.ts), mirroring
Popplio's ownpopplioStaffproxy action
(popplio/arcadia/panel/ops_proxy.go) that relays a request into
Popplio's legacy-header-gated/staff/*API and returns its status/body
verbatim — this wasn't hand-ported into Omniplex's Arcadia client until
now, so nothing could reach/staff/apps*at all despite the backend
bridge already existing. Approve/deny is otherwise fully
server-side-driven (grants/unbans happen inside Popplio's own handler);
the page only submits{approved, reason}and refetches. -
A self-hosted Legal hub at
/legal, replacing the Footer's links out to
nodebyte.co.uk/legal/*. Four documents written fresh for what Omniplex
actually does rather than adapted from the parent brand's general-purpose
pages: Terms of Service, Privacy Policy, Acceptable Use (the single
authoritative version of conduct rules that were previously scattered
across a few KB articles), and a Service Agreement covering premium/shop
purchases and refunds. Same markdown+frontmatter pattern as the
Knowledge Base (gray-matter, rendered through the existingMarkdown
component), but flat (src/content/legal/*.md→/legal/[slug]) since
there's no category nesting to model. Two KB articles
(bots/rules.md,servers/listing-rules.md) already linked to
/legal/termsas if it existed; that link now resolves instead of
404ing. Added to the sitemap alongside everything else. -
The Friday-Sunday double-vote weekend bonus is now actually visible:
VoteButton/ServerVoteButtonshow a "Double-vote weekend" banner
above the vote buttons when it's live and the entity isn't premium
(premium already gets a flat shorter cooldown instead). The bonus check
now reads UTC (getUTCDay()) rather than the viewer's local day, to
match Popplio's now-explicit UTC pinning (see Popplio's changelog) —
previously the local-time check could disagree with the server near a
day boundary depending on the viewer's timezone. Voting Rules also now
states the boundary is UTC, not just "Friday through Sunday". -
Premium and Shop now work for servers, matching bots (backend change,
see Popplio's changelog for the full breakdown — servers previously had
apremiumfield and a "Certified" badge with no way to actually earn
either):/premiumand/shopboth gained a Bot/Server toggle; picking Server
lists servers from your teams instead of your own bots, and every
checkout/purchase call now sends the target type through.ServerCard, the server detail page, and the dashboard's server
cards all get the same Supporter badge / Vote Blitz banner / Upgrade- Shop buttons bots already had.
/appsgained a "Server Certification" position alongside the
existing bot one.
-
Certification requirements got more lenient and multi-metric instead of
a strict two-thresholds-at-once bar (see Popplio's changelog) —
Certification and Partner Requirements in the Knowledge Base rewritten
to match, and to note certification now covers servers too. -
Knowledge Base coverage for everything shipped this cycle that had none:
three new categories — Account (Alerts & Push Notifications, Vote
Reminders), Premium & Shop (Premium Plans & Checkout, Vote Credits &
the Shop), Support (Support Tickets) — plus a new How to Apply
article in Programs covering the/appsmechanics that Certification
and Partnership used to each describe informally. Also fixed three
articles that had gone stale from earlier work this cycle:
Partnership's "How to apply" still pointed at Discord instead of the
in-app form, Voting Rules' "Vote credits" section predated the Shop and
called credits opt-in (they're not), and Getting Started's pack step
only described bot packs, not the server/emoji pack types packs have
supported for a while. -
Home page rebalanced toward servers, which previously only got one
section (Top Servers) against four bot-only ones:- The hero headline now rotates through "bots" / "servers" / "packs"
(RotatingWord, crossfades in place with no layout shift, extra
words are a one-line addition later) instead of hardcoding "bots". HomeTabsgained a Bots/Servers toggle alongside its existing
Top Voted/New/Most Viewed tabs, using server index data
(servers.getIndex()) that was already being fetched but mostly
unused.- Certified and Premium sections (previously bot-only, with servers'
certified/premium data fetched but never shown) are now a single
"Spotlight" block using the same toggle, replacing three separate
sections with one. - The standalone "Top Servers" section was removed since the Bots/
Servers toggle on the main tabs now covers the same ground. - The main tabs gained a "Random" tab backed by Popplio's
/bots/@random
and/servers/@random(both existed already;bots.getRandom()was
even already written but never called anywhere, and there was no
servers.getRandom()at all until now). Re-rolls client-side on every
visit to the tab, with a manual shuffle button to draw again. - The home page's "Packs" section (renamed from "Bot Packs", since packs
have supported bot/server/emoji for a while) and the Featured Bots
section were showing only 6 cards despite Popplio returning up to
9/12 — now show 9, matching the Top Voted/Certified/Premium tabs,
which were already at 9 on both ends.
- The hero headline now rotates through "bots" / "servers" / "packs"
-
Moderation Transparency page now shows a bot review pipeline (Approved /
Certified / Awaiting Review / Denied) alongside the existing report
counts. Popplio's/list/statsgainedtotal_pending_botsand
total_denied_botsto back it — everything else on the page was already
public data, this just closes the last gap. -
The "Bots" stat on the home page and the "Listed Bots" / "Total
Submitted" stats on the About page now consistently readtotal_bots—
every bot ever submitted, not just the approved ones — so the same
number shows everywhere instead of quietly differing by page. -
Applications — staff, dev team, partnerships, and certification have had
a full submit-and-review pipeline in Popplio that nothing in Omniplex
ever surfaced. New frontend-only surface:/appslists open positions pulled live from Popplio (tags, a short
teaser, closed/open state)./apps/[id]renders each position's full description and a form built
dynamically from its question set (short answers vs. long-form,
matching the backend's own length rules so validation errors are rare
by the time it hits the server).- A new "Applications" tab on the dashboard lists everything a user has
submitted, with state (Pending/Approved/Denied) and any staff feedback
once reviewed.
-
Premium Popplio's Stripe/PayPal checkout and booster-offer redemption
have been fully wired backend-side with nothing in Omniplex to start a
purchase. New:/premiumlists the Bronze/Silver/Gold plans, lets you pick one of
your own approved/certified, not-yet-premium bots, and pay with card
(Stripe Checkout) or PayPal — both just redirect to the provider's
hosted checkout, no card data ever touches Omniplex. Server boosters
get an extra "redeem free" option on the Bronze plan./payments/successand/payments/cancelled— Popplio's Stripe and
PayPal flows redirect back to these by hardcoded URL, so they had to
exist for checkout to complete at all.- An "Upgrade" button on eligible bots in the dashboard's Bots tab links
straight into/premiumwith the bot preselected.
-
Shop — vote...
v0.1.2
Added
- Packs now come in three flavors — Bot, Server, and Emoji — instead of
bots-only./packs/addgains a pack-type selector as its first step;
bot/server packs reuse the existing search-and-pick flow restricted to
one type at a time, while emoji packs get a new upload sub-flow
(EmojiPackBuilder.tsx, up to 50 emojis, 256KB each, static or animated
GIF) built on the existing/api/uploadsgateway (newpack-emojikind,
gated by a newedit_packspermission check against Popplio). Emoji
images live at a deterministic CDN path (packEmojiUrl(), same
convention asbannerUrl()) rather than a database-stored URL. Pack
cards, the pack detail page, and the/packslisting (now filterable by
?pack_type=) all show aPackTypeBadgeand type-appropriate preview. - A "Report this pack" flow (
components/reports/ReportModal.tsx, built
generic over target type so bots/servers can reuse it later) for
flagging license/ToS violations, spam, or anything else — motivated
specifically by user-created emoji packs raising real rights questions.
Reports are reviewed by staff only, at a new/admin/reportspage
(status-filterable queue + detail modal with Resolve/Dismiss and an
optional note) gated on a newreview_reportspermission — reporter
identity is visible there and nowhere else; a pack owner never learns
who reported them, only that a report exists and its reason category. ReportModalrolled out to bot and server detail pages, not just packs —
it was already built generic over target type for exactly this.- An RSS feed for
/changelog(/changelog/rss.xml), hand-built (no
existing precedent in this repo for a manual XML route — sitemap/robots
use Next's typedMetadataRouteconvention, which has no RSS
equivalent) from the samegetChangelogEntries()the page itself uses,
same 15-minute cache window. Linked from the changelog page header and
its metadata (<link rel="alternate" type="application/rss+xml">). - A "Trending" sort on
/botsand/servers, alongside the existing
newest-first default — ranks by net votes in the last 7 days (via
Popplio's new?sort=trendingparam) instead of raw vote count, so a
bot/server picking up votes right now surfaces even if its all-time
total is small. /about/moderation: a public, anonymized breakdown of content reports
by reason and status (counts only — no report/target/reporter
identity), backed by Popplio's newGET /reports/stats. Linked from
/aboutand the footer, next to the existing status page link./emojis: a browse page aggregating custom emojis/stickers across every
server that's opted in to showing them (show_emojis), grouped by
source server and reusing the existingEmojiStickerGallerycard
treatment. Backed by Popplio's newGET /servers/@emojis, since the
regular paginated server listing excludes emoji/sticker data entirely.
Fixed
- Uploaded images (banners, avatars, pack emojis — anything served through
/cdn/[...path]) could stay visibly stale for up to a day after a
re-upload: the fixed per-entity URL never changes, but the response was
cachedmax-age=3600, stale-while-revalidate=86400, so a browser (or
anything in front of it) could keep serving the old bytes long after a
new upload landed. The route now setsmax-age=0, must-revalidateand
compares the request'sIf-None-Matchagainst RustFS's own ETag (now
captured ingetObject) — an unchanged image gets a bodyless 304 (cheap,
effectively instant), a changed one gets the new bytes on the very next
request instead of waiting out the old cache window.
Security
/cdn/avatar-mirror/[targetType]/[id]'s?src=param was passed
straight tofetch()with no validation (CodeQLjs/request-forgery)
— since this route is public and unauthenticated, anyone could point
?src=at an arbitrary internal URL and make the server fetch it, and
the response would then getputObject'd into the shared bucket at a
predictable path, serving whatever the attacker's URL returned to every
future visitor of that avatar.?src=is now checked against an
allow-list of exactlycdn.discordapp.com(the only host dovewing ever
actually resolves an avatar to) before the fetch happens at all, not
after.
v0.1.1
Added
-
New public
/changelogpage, replacing the old database-backed changelog
system (popplio'schangelogstable,arcadia/panel/ops_content.go's
updateChangelogRPC, and theChangelogActionDTOs) — that system was
already fully dead: the RPC unconditionally returned 403 "not
implemented", the panel's "Changelogs" stat was hardcoded to 0, and there
was no route or admin UI on either side./changeloginstead pulls
releases directly from GitHub (lib/github/releases.ts) for a
configurable list of repos (lib/github/config.ts— currently Popplio
and Omniplex itself), merges them into one reverse-chronological
timeline with a per-repo filter, and renders each release's body through
the existing sanitizedMarkdowncomponent. No new database or admin
surface — GitHub Releases is the source of truth now, cutting a release
there is the entire authoring flow. Cached for 15 minutes
(next: { revalidate: 900 }) to stay off GitHub's unauthenticated rate
limit without needing a redeploy to pick up a new release; set
GITHUB_TOKEN(server-only) to raise that limit if it's ever hit. -
Real image hosting, replacing the informal legacy-CDN-path guessing used
everywhere so far (bannerUrl/partnerAvatarUrl/teamAvatarUrl
previously just hoped a file existed at a fixed path, with no way to add
new ones). The old on-diskcdn.omniplex.ggstatic files were migrated
into a private RustFS (S3-compatible) bucket; since it's private, none of
it is reachable directly, so everything now goes through two new
same-origin proxy routes that hold the only S3 credentials (server-side
only, never shipped to the client):/cdn/[...path]— serves uploaded assets (partner logos, team
avatar/banner, bot/server banner) straight from the bucket./cdn/avatars/{bots,servers}/[id]— bot/server avatars are Discord's
own, synced live via dovewing/Infernoplex, not an upload; this mirrors
a copy into the bucket on first request and re-serves it for 24h
before quietly re-mirroring, cutting down on repeated live hits to
Discord's CDN across every card/avatar in the app.- A new
/api/uploadsroute backs real upload UI in the admin partner
editor, team settings (avatar + banner), and the bot/server edit
modals (banner). Every upload re-verifies identity and the specific
permission needed server-side before writing a single byte — a staff
loginTokenchecked viaarcadia.hellofor partner logos, or a user
session token checked viaPOST /auth/testplus Popplio's entity-perms
lookup for everything else — since the client-sidehasPermString
checks that gate the upload buttons were only ever a UI hint, not a
security boundary.
-
The Customize panel is now tabbed (Colors / Fonts / Layout / Content)
instead of one long stacked list, and grew three new levers:- Colors — 5 more accent options (cyan, teal, lime, red, pink), 12 total.
- Fonts — added Inter and Space Grotesk alongside the existing four.
- Layout — Compact/Comfortable/Wide page width, applied through a
--container-maxCSS var so it works on server-rendered pages too
(Containerreads the var instead of a fixedmax-w-7xl). - Content — a "Hide NSFW content" toggle that removes
nsfw-flagged
bots/servers from every browse/search/profile listing site-wide, via a
data-nsfwattribute onBotCard/ServerCard/PackCardand a plain
CSS rule ([data-hide-nsfw="true"] [data-nsfw="true"] { display: none })
— no per-page filtering logic needed. Detail pages are unaffected by
design (this hides cards from browsing, not a page someone linked
directly). A "disable cookies" option was requested alongside it but
skipped: Omniplex doesn't set any analytics/tracking cookies to begin
with, only the session cookie sign-in itself requires, so there'd be
nothing for the toggle to actually do. Alongside it, a "Blur NSFW
thumbnails" toggle (on by default) blurs just the avatar/banner images
on nsfw-flagged cards via[data-blur-nsfw="true"] [data-nsfw="true"] img { filter: blur(...) }, leaving the title/description legible. It's
disabled in the UI (and its applied state forced off) whenever "Hide
NSFW content" is on, since there's nothing left to blur once nsfw cards
never render at all.
-
Banner images for bots, servers, and teams (
BotCard/ServerCard, plus
the bot/server/team detail pages). Popplio's live API has nobanner
field at all any more — Popplio's own conformance notes confirm the whole
CDN-upload pipeline it depended on was removed — but a historical
one-time migration left every existing banner sitting at a fixed CDN path
keyed by the entity's own ID (banners/{bots,servers,teams}/{id}.webp),
discovered from that migration script since Popplio's API no longer
advertises it anywhere. NewbannerUrl()builds that URL; newBanner
component renders it and falls back to a themed gradient (using the
viewer's own accent color, so it tracks Customize) for anything that 404s
or never had one uploaded — same idea as the existing partner-avatar CDN
fallback. -
Webhook management for bots and servers, from a new "Webhooks" dashboard
dropdown item on each listing: create/edit/delete webhooks (HMAC, simple
secret, or legacy auth), pick an event whitelist, send test deliveries
with dynamically-rendered variable inputs per event type, and browse
paginated delivery logs. Built entirely on existing Popplio webhook
routes that had no Omniplex UI before now. -
A "Change Team" dashboard action for bots, using Popplio's
PATCH /users/{uid}/bots/{bid}/teams, letting an owner move a bot to any
other team they have "Add Bots" on. Servers have no equivalent transfer
endpoint in Popplio, so this is bots-only for now. -
A public
/partnerspage — theGET /list/partnersclient and types
already existed (used on the homepage) but nothing ever linked to a
dedicated page. Groups partners by partner type, shows their links and
(if set) a direct link to their bot listing. -
The main header nav gets the same dropdown-grouping treatment as the admin
one: Bots/Servers/Packs collapse into "Browse", and Blog/Partners/
Documentation/About — previously footer-only — collapse into "Community",
reachable from anywhere now instead of just the footer. -
Public and admin Search both pre-fill with real content on load instead of
a blank page: admin search runs an empty query (which matches everything)
on mount and on target-type switch; public search browses the full
bot/server listing (real backend pagination via/bots|servers/@all)
until a query or tag is submitted, at which point it switches to paginated
search results (client-side, since/list/searchhas no server-side
pagination). -
Three new staff admin sections, backed by Arcadia panel RPC methods that
already existed in Popplio but had no Omniplex UI at all:- Blog (
/admin/blog) — full list/create/edit/delete for posts on the
public/blogsection, using theUpdateBlogRPC (manage_blog).
New posts publish immediately; existing ones can be toggled to draft. - Partners (
/admin/partners) — full CRUD for featured partners
(UpdatePartners,manage_partners), including link validation
(must behttps://). Partner types aren't manageable from here yet —
there's no RPC for creating one, only for assigning an existing type
to a partner. - Disciplinary Types (
/admin/staff/disciplinary-types) — full CRUD
for staff warning/suspension templates (UpdateStaffDisciplinaryType,
manage_disciplinaries): self-assignable, additory, needs-approval,
max expiry, and a permission-limit picker reusingArcadiaPermSelector.
Two other backend-ready gaps were identified but deferred (larger scope):
staff application review (GET/PATCH /staff/apps*) and the shop/economy
admin surface (vote-credit tiers, shop items, item benefits, coupons,
bot whitelist — five separate CRUD areas underops_shop.go). - Blog (
-
A "Test" button on a freshly created token, using Popplio's existing
POST /auth/testto confirm it actually authorizes before dismissing it —
disabled for now pending a Popplio deploy (see Popplio's changelog for the
bug that endpoint needed fixed first). -
API token management, backed by Popplio's existing (previously unexposed
on the frontend) generic/{target_type}/{target_id}/sessionsendpoints:
a new "API Tokens" dashboard tab for a user's own personal tokens, and a
"Tokens" button on bot/server cards (visible to team members with
view_sessions/manage_sessions) for tokens scoped to that bot or
server. Supports creating a token with a name, expiry, and an optional
restricted permission set (via the samePermSelectorused for team
member permissions), and revoking existing ones. A newly created token's
raw value is shown exactly once, since Popplio never stores or re-serves
it after creation. -
Votes now go through Popplio's new self-hosted proof-of-work captcha
(see Popplio's changelog) automatically, when the bot/server hasn't opted
out viacaptcha_opt_out.useVotefetches a challenge, solves it
client-side withcrypto.subtle(lib/captcha/pow.ts), and submits the
solution alongside the vote; both vote buttons show "Verifying…" while
that's in progress. No user-facing setup — it's invisible on a successful
vote and only costs a brief moment of CPU work.
Changed
- Homepage, bot/server index/listing/detail pages, and blog list/post pages
now fetch withcache: "no-store"instead of Next's ISR (revalidate)
— blog posts in particular had crept to a 1-hour window, meaning an edit
or correction could take up...
v0.1.0
Added
- Downvotes: bot and server vote buttons now both show an upvote and a downvote control (bots previously only supported upvoting, both on the API and in the UI — see the matching Popplio change).
- Statistics: bot and server owners/team members can now view a "Stats" panel from the dashboard, showing votes, page views, invite clicks and (for bots) uptime check results — data the API already returned but the frontend never surfaced.
- A "Create" menu in the header (Add a Bot/Server/Pack, Create a Team), so those entry points are reachable from anywhere on the site, not just the footer and dashboard.
- Team management: Add Bot and Add Server now let you choose which of your existing teams (that you have the relevant
add_bots/add_serverspermission on) should own the new listing, instead of always creating a brand-new team. A new/teams/addpage lets you create a team directly, linked from the dashboard's Teams tab and the footer. Since team names aren't unique, the picker shows each team's avatar and member/bot/server counts to tell same-named teams apart, and gains a search box once you have more than a handful of eligible teams. - Knowledge base (
/kb) section, with guide articles ported from the legacy
site: Getting Started, Server Listing Rules, Pack Rules, Voting Rules & FAQ,
and Partner Requirements. - Full staff admin panel (
/admin), backed by the Arcadia panel API:- Discord OAuth2 login with mandatory TOTP MFA.
- Bot Queue with quick actions (Claim/Unclaim/Approve/Deny), a generic
dynamic-form modal covering every other RPC action, submission-order
position numbers, and safe (no-permissions) vs. submitted invite links
shown side by side. - Global entity Search (bots/servers) with the same action modal.
- Staff Positions and Staff Members management, with hierarchy-aware
edit/delete locking. - A live + hand-maintained permission catalog and selector for granting
staff permissions. - Pagination on the queue, search results, staff members list, and RPC
audit log. - Overview page (
/admin) summarizing bot/server/ticket counts, total
users, and changelog count via Arcadia'sBaseAnalytics. The Bot Queue
moved to/admin/queue. - Bot Queue now resolves
claimed_byDiscord IDs to usernames via
Arcadia'sGetUser, instead of showing the raw ID.
- Reviews: star ratings and threaded replies on bots and servers, full
create/edit/delete for the review's author, and a read-only view for staff
in the admin panel. - Packs can now include servers alongside bots, and any existing bot or
server can be added to a pack regardless of who owns it — packs are
curated lists, not ownership-restricted. - Bot presence indicator (online/idle/dnd/offline) on bot cards and the bot
detail page, sourced from a bot's self-reported status. /about/statusnow also monitors Packs, Blog, Search, and the Staff Panel
API, on top of the existing checks.- A reusable multi-link (
extra_links) editor, wired into the bot add/edit
forms and the new server add/edit forms — previously only a single
synthetic invite link could ever be sent. - A working "Add a Server" flow: paste a Discord invite link and the server
is resolved and listed automatically. The previous version of this page
called a backend endpoint that didn't exist. - A dashboard "Servers" tab and
ServerEditModalfor managing servers owned
by a team you belong to. - Widget embed system (Markdown/HTML/URL snippets, customizable stats and
colors) for bots, servers, and packs. - The bot and server add forms now link to the relevant Knowledge Base rules
(Bot Rules, Page Rules, Server Listing Rules) before submission. - The staff Member edit modal now shows a member's positions and their full
resolved permission set (positions + overrides combined), not just the
override layer being edited. - Add Bot and Add Server are now step-based flows, ported from the legacy
site's pattern and extended with a confirmation step: enter an ID/invite
and look it up, and a preview card shows the bot's real Discord
name/avatar/server count/flags (or the server's name/icon/member counts)
with a "Looks right — continue" / "Change" choice before the rest of the
form unlocks — instead of blindly trusting whatever the submitter typed.
Add Bot also carries over the legacy site's already-listed/not-public
checks and thefallback_bot_idrecovery path for when the anti-abuse
provider is down. - Add Bot and Add Server drafts are now persisted to
localStorage, scoped
to the signed-in user (usePersistedFormDraft), so an in-progress
submission survives not just a refresh but closing the browser entirely.
Unlike the legacy site's single global draft key, drafts are namespaced
per user so they can't leak between accounts on a shared browser, and
they're cleared automatically on a successful submit. - Server owners can opt in ("Show this server's emojis & stickers") to
displaying their server's custom emojis and stickers on its listing page,
via a new toggle inServerEditModal. The gallery only renders when the
owner has opted in and there's actually synced data to show — nothing
appears while the tracking bot isn't a member of the server. - Add Server now warns (without blocking submission) when the tracking bot
isn't currently a member of the server being added, with a direct invite
link — several features (emoji/sticker sync, real invite generation, live
member counts) silently never work otherwise, and previously nothing told
the owner why.
Changed
TagPickernow always shows a listing's actual tags as removable chips,
even when they fall outside the curated suggestion list, and supports
adding free-form custom tags — tags were never restricted to an enum on
the backend, so the old fixed-list picker could silently make existing
tags disappear from the editor.PermSelectorandArcadiaPermSelectornow surface any granted
permission that isn't in their known catalog instead of hiding it
entirely, so a permission the UI doesn't recognize is still visible and
removable.ArcadiaPermSelectorcheckboxes now reflect a member's fully resolved
permissions (position grants combined with overrides), not just the raw
override array — a permission granted through a staff position now shows
as checked (labeled "via position"), and unchecking it correctly emits an
explicit per-member revoke (~permission) instead of doing nothing.- Admin panel navigation is now part of the site's main header instead of
running as a separate nav bar — one drawer-style menu across the whole
site, admin section included, with an automatic "Staff panel" shortcut
for staff and an "Exit staff panel" action while inside it. - Homepage and About page bot totals now include certified bots, not just
approved ones, so the two pages no longer disagree with each other.
Fixed
- The entire frontend permission system was still built for Popplio's old
namespace.actionmodel (dotted strings,*wildcards,~negators,
global.*for "grants everything") after the backend moved to flat,
self-describing permission names (edit_servers,manage_webhooks,
owner) with no namespaces, wildcards or negation. Every permission
check across the team and staff/admin panel UI was comparing against
names that no longer exist, so team owners (and staff) with correct
permissions server-side could still be denied client-side, or see broken
panels:lib/permissions.tsrewritten for flat exact-match, withowner/
administratoras the two domain "super" permissions that imply
everything (was reimplementing full namespace/wildcard/negator parsing).- Every literal permission string across
dashboard,teams/[id],
teams/[id]/settings, and the/adminstaff pages (positions, queue,
members) updated to its flat equivalent (e.g.team_member.add→
add_team_members,rpc.Claim→review_bots,global.*→owner). PermissionData(the shape ofGET /teams/meta/permissionsand
GET /staff/meta/permissions) still had the old kittycat fields
(supported_entities,data_override); the real response is
{id, name, desc, category, dangerous}. This was the direct cause of
TypeError: s.supported_entities is not iterablecrashing the team
settings page — something iterated a field that no longer exists.PermSelectorandArcadiaPermSelectorrewritten to group by the new
categoryfield instead of a namespace derived from
supported_entities, and to toggle flat permission ids directly
instead of buildingnamespace.permkeys.ArcadiaPermSelectoradditionally dropped negation support
(~permission) entirely — the new model's overrides are purely
additive (nothing subtracts from the union), so revoking a
position-granted permission from a specific member is no longer
possible from this editor; it now shows those as read-only ("via
position") instead of offering a toggle that would silently do nothing.- Removed
lib/arcadia/permissionCatalog.ts, a hand-maintained list of
every permission string that existed because standalone Arcadia had no
live catalog endpoint. Now that Arcadia is merged into Popplio, both
GET /teams/meta/permissionsandGET /staff/meta/permissionsare
real live endpoints (newstaffAPI resource added), so the staff
panel's permission pickers can't drift out of sync with the server the
way the static list could.
- Server avatars showed the initials fallback everywhere (server cards,
detail pages, widgets, OG images, pack pickers) —IndexServer.avatarwas
still typed and handled asAssetMetadata(the old CDN-asset shape from
before that sy...