Changelog
Curated highlights of what’s shipped recently. The full history lives on GitHub.
August 2026 32 updates
-
Move the YouTube source onto the snapshot that fixes the player-api 400
Intros stopped playing, with the stream dying at NonMusicClient.loadTrackInfoFromInnertube on a 400 from the player API — for a video YouTube had described happily a moment earlier.
#766 -
Test the parts of purge, the now-playing embed and the player nothing reached
Coverage in this area pointed at three places where the tests stopped short of the code that does the work. 34 tests, no production changes.
#765 -
Test the seams around the now-playing message, and stop a dropped intro leaking
Follow-up to #763. The message-lifecycle fixes there were covered a piece at a time — the scheduler on its own, NowPlayingManager on its own — which left the seams between them untested, and the seams are where the bug was.
#764 -
Stop a failed intro leaving its now-playing message behind
An intro that dies now says so, and saying so is what turned up three separate ways its now-playing message outlives it. All three end the same way: an embed sitting in the channel with a frozen progress bar and nothing left that can remove it.
#763 -
Cover the audio bridge, the clip marker and the untested failure paths
Measured with Kover rather than guessed at. The intro and audio classes had 142 uncovered lines; filtering to what *never executed at all* in any test — rather than merely partly-covered branches — turned up several things that run on every single play.
#762 -
Pay for intros that played, retry ones that died, stop leaking tracks
> Stacked on #760. Until that merges this shows both commits; the one to review here is fff85bf.
#761 -
Stop intros failing silently, and say why when they do
An audit of every path from voice join to audio, against the goal: *no silent failing, and people should know why an intro didn't play*.
#759 -
Notice when a stream dies, and stop calling a load a play
Diagnosed from the production log at 13:55 today. An intro loaded, started, was written to the database as played, and then died half a second later:
#757 -
Make the changelog page readable: clean summaries, group by month
The changelog page had grown to 86 entries — 80 of them stamped "May 2026" — rendered as one flat list with the same date repeated on every row.
#756 -
Add a Purge from here right-click entry, in two halves
/purge count: asks moderators for the wrong unit. Nobody knows the mess was twenty-three messages long; they know *which* message it started at.
#755 -
Drive /magic in a real browser, and run it in CI
The most JS-heavy page in the app had no browser-level test — the Playwright suite was one responsive spec over six fixtures, and /magic wasn't one of them. Everything covering it was unit-level; nothing exercised the page actually working.
#754 -
Add Tip this and Profile right-click entries, cross-linked with intros
Two more Apps entries, and the buttons that join them to what was already there. Slots stand at 2/5 message and 2/5 user.
#753 -
Pace and cache the bot's Scryfall calls; bound the share table
The two items from the last review round.
#752 -
Split magic.js into three layers
2,790 lines in one IIFE. The layering was already latent in it, so this makes it physical:
#751 -
Make the Scryfall calls testable, and fix what that turned up
I went looking for what else was worth improving in the Magic area and ran a coverage pass to find where the tests were thin.
#750 -
Hand a drafter their seat, and say where a pack list came from
Two follow-ups to the shared-deal work in #748.
#749 -
Fix the pack-list round trip, pace Scryfall, and share a deal by link
Follow-ups around the pack tooling from #747, working outwards from the file format.
#748 -
Load a downloaded pack list back in to see the same packs
The Generate tab could write a deal out as a .txt, but there was no way back in.
#747 -
Show an active source outage in the intro report
The follow-on flagged in #741. /introstats is the one place an admin looks when several intros stop playing — and during a rate limit or IP block that's every intro at once, which is exactly when the report was least trustworthy.
#746 -
Play the intro you picked, not one of theirs at random
The View intros menu lists a member's intros by name, then offered a single ▶ Play theirs button which went through the ordinary rotation.
#743 -
Fix the filter factory throwing on every track it was handed
This is a live playback outage and it is mine. Found in a production log:
#742 -
Stop a blocked source being reported as everyone's broken link
When the host refuses us — rate limit, IP block, the "confirm you're not a bot" wall — every load fails at once.
#741 -
Fade intros, capture what's playing, and share intros (slash + right-click)
Four things, all in the same corner of the intro flow.
#740 -
Tidy up the intro save path: pending-intro cache, API timeouts, honest failure message
The three things from the last review, one commit each.
#739 -
Fix intros overwriting each other, and add a clip form to the context menu
Both reported problems, plus the feature that closes the second one.
#738 -
Fix the stuck-intro resume leak, and close four unauthenticated reads
Two defects, one commit each. Both are silent — nothing surfaces either to a user or an admin.
#737 -
Intro health, guild controls, loudness matching and play stats
Four intro improvements, one commit each. The first is a live defect; the rest are features that were missing rather than broken.
#736 -
Make the intro nudge a link instead of a conversation
The "you don't have an intro yet" DM asked the user to reply with a URL or an attachment, armed a five-minute waiter to catch the reply, and parsed whatever came back as free text. It was the last part of the intro system still doing that, and the weakest:
#735 -
Stop a new intro overwriting an existing one after a delete
#734 -
Fix command registration failing entirely on Discord error 50240
No commands have been registering at boot. From production:
#733 -
Add a per-intro on/off switch and a "Set as my intro" message action
Two ways of managing intros that the command surface couldn't express. Follows on from #731.
#732 -
Bring the Discord intro commands up to parity with the web dashboard
The intro page on the web can rename, re-volume, clip, reorder and undo-delete an intro. Discord could set one and change its volume — via a flow that asked you to type a bare number into the channel and raced a 10-second EventWaiter against you.
#731
July 2026 1 update
-
Add low-engagement lottery levers: rollover pot, streaks, scaled winner slots, configurable match odds
#728
June 2026 121 updates
-
Fix intro volume slider triggering row reorder
#727 -
Stop double now-playing render and clean up player when channel empties
Two follow-up quality fixes to the now-playing message lifecycle (builds on #725).
#726 -
Fix now-playing message recreated instead of edited between tracks
#725 -
Fix price-alert receipt mislabelling every trade as TOBY
#724 -
Generalize wallet leaderboard to show holdings across all coins
#723 -
Fix horizontal overflow on moderation settings/admin pages on mobile
#722 -
Fix price-watch remove button swallowing clicks on its SVG icon
The bug The price-watch Remove button renders an inline X icon. The delegated click handler guarded with:
#721 -
Make the site footer stick to the bottom on short pages
Why The footer looked "off" on the new coin portfolio page: it floated in the middle of the screen with empty space beneath it. Root cause isn't portfolio-specific — base.css set body { min-height: 100vh } but nothing pinned the footer to the bottom.
#720 -
Add TISM coin, a user portfolio page, and an all-coin wealth leaderboard
Follow-ups to the multi-coin market (PR #718, merged).
#719 -
Add volatile multi-coin markets to the fake economy
#718 -
Open card details in a dismissible overlay
#717 -
Activity: back-links return to the games selector, not the cross-guild pickers
#715 -
Refresh profile Level and Economy cards on streak claim
#716 -
Polish the unified card search on mobile
Three mobile refinements to the unified card search/viewer.
#714 -
Extend the never-drop-silently posting contract to the non-router posters
#713 -
Magic card tab: collapse into one unified search
#712 -
Activity landing: surface the guild's full games list
#709 -
Fix level-up announcement regression: restore pre-#492 "always attempt the post" behaviour + thread origins
#710 -
Hint at Scryfall's exact-name operator in the Advanced search placeholder
Follow-up to #706. The Advanced field passes its text verbatim to Scryfall, so !"exact name" is the easy way to do a single-card search from the same three grid-search fields — but the operator is non-obvious. Surface it in the placeholder:
#708 -
Card search: fix anonymous access + autosearch + click-through to details
Makes the web Magic card search fully work for anonymous users, plus two Scryfall-style UX upgrades. (The autosearch + click-through commits from #707 were folded onto this branch.)
#706 -
Multi-card Scryfall search for the Magic card tools (Discord + web)
#705 -
Fix broken OAuth login: run ForwardedHeaderFilter before the security chain
#704 -
Activity follow-up: poker tables + PvP challenge pings in the voice channel
#703 -
Activity follow-up: PvP arena (duel, RPS, tic-tac-toe, connect 4)
#702 -
Activity follow-up: game-picker landing with multiplayer blackjack
#701 -
Fix activity "disallowed web page" kill: mount casino in a nested iframe (Activities must be SPAs)
#700 -
Keep the Discord Activity inside the proxy sandbox (fix "disallowed web page" kill)
#699 -
Fix Discord Activity launch: iframe embedding, root-path launches, entry point command survival
#698 -
Run the casino as a Discord Activity (Embedded App SDK)
#697 -
Security audit pass 2: actuator lockdown, supply-chain pin, non-root container
Second security-audit pass over the codebase, following up #695.
#696 -
Security audit: fix open redirect and harden web-layer input handling
A security audit of the codebase (web layer, Discord bot, database, config).
#695 -
Promote solo blackjack tables to multiplayer when a second player joins
Removes the up-front "solo vs multi" decision in web blackjack.
#694 -
Expose multiplayer blackjack and ante limits in the web UI
Two related discoverability fixes for blackjack on the web surface. 1.
#693 -
Settle multi blackjack hands per seat vs the dealer, not from a peer pool
The multi-table resolver pooled every losing seat's stake, then split the remainder among winners after a flat rake — so two players who both beat the dealer would just trade refunds with no winnings, and a single winner's payout scaled with how many others…
#692 -
Fix crowded music dashboard by switching to 2-column layout
The music player dashboard was packing four cards into a 3-column grid that broke down badly at ~980px viewports — the Search input truncated to "URL or sear", playlist names collapsed to "Pl ", and the Save-queue/+New buttons stacked on top of each other.
#691 -
Wire Scryfall autocomplete on every /magic card-input field
The cube-list textarea was the only field with Scryfall autocomplete.
#690 -
Fix cube pack tiles stretching on desktop until whole row collapses
Each generated cube pack is a element inside a CSS grid (.cube-packs). The grid relied on the default align-items: stretch, so every tile in a row was sized to the tallest open sibling.
#689 -
Test coverage: add the two missing command-handler tests (/daily, /casinoholdem)
Last of the four follow-up directions — test coverage.
#688 -
Web: add member search to the leaderboard standings
Third of the four follow-up directions — web dashboard wins.
#687 -
Fix web /meme 403s by using Reddit OAuth like the bot (#403)
#686 -
Reliability: retry transient music loads, harden intro media download
Second of the four follow-up directions — reliability hardening, scoped to playback & media (the riskiest external-fetch surfaces). Two isolated commits.
#685 -
Fix the nowPlaying stale-message bug (#85) and the broken /meme Reddit auth (#107)
Fixes two long-standing maintainer-filed bugs. Two isolated commits.
#684 -
Player-side onboarding: first-game achievement, empty-state recovery, invite loop
Follow-up to #680/#681/#682. Those rewarded the owner at install; this turns attention to the players the owner is trying to keep, and closes the last onboarding dead-ends. Three isolated commits (disjoint files).
#683 -
Add /install summary + a View setup control-panel button
Follow-up to #680/#681. Gives server owners a way to audit their current setup and see what's still worth turning on — without re-running the wizard.
#682 -
Make the post-install launcher playable, persistent, and linked to the web app
Follow-up to #680. That PR turned the post-install "done" message into a one-click launcher (claim daily + help). This extends that launcher into a genuine, durable control panel. Three isolated commits:
#681 -
Make toby-bot's value land in the first 30 seconds for new installers
Closes the gap between *"setup done"* and *"value felt"* in onboarding. The install wizard already tells new owners to go type /blackjack; these four changes make the bot demonstrate its value instead, in the first few seconds after install.
#680 -
Build and edit playlists in the web UI without playing
Lets you build and edit playlists entirely from the web UI without playing anything. Before this, the only way to fill a playlist was to snapshot the live queue — so curating meant playing the tracks first. This decouples curation from playback end-to-end.
#679 -
Reuse shared primitives on the music player (toasts, modal, member-cell)
Three consistency refactors so the music player leans on site-wide primitives instead of bespoke / native equivalents. (Independent of #677 — touches different file sections, so the two merge cleanly in either order.)
#678 -
Fix music player horizontal overflow on mobile
#677 -
Polish music player to match other web surfaces
#676 -
Onboarding: fix the new-user first-play dead-end + richer /help
Follow-up to #674. That pass made onboarding *tell* users to "try /blackjack solo right now" — this pass makes sure that actually works, and makes /help genuinely useful.
#675 -
Improve first-time onboarding: in-Discord /help overview, value-forward copy, re-welcome on re-invite
#674 -
Surface "members reached" as live social proof on the homepage
#673 -
Unify Scryfall card parsing across the bot and web
#672 -
Second DRY/SOLID pass over the Magic features
#671 -
Refactor Magic features for DRY/SOLID and add missing test coverage
#670 -
Polish the web MTG combo lookup (chips, labels, numbered cards)
#669 -
Fix shared /magic?q= links landing on the hidden Card lookup tab
#668 -
Polish the navbar default-server (star) chip
#667 -
Share + mobile-polish the two legacy guild-picker pages
#666 -
Polish /admin/installs for mobile (card-per-row layout + reveal/count-up)
#665 -
Add health, liveness, feature-adoption and audience insights to /admin/installs
#664 -
Add install metrics, churn tracking and UI polish to /admin/installs
#663 -
Add operator-only /admin/installs page showing who installed the bot
#662 -
Reprioritise the Magic toolkit page as a multi-tool hub (not cube-first)
#661 -
Prefix the Magic commands with "mtg" so they're obviously MTG in Discord
#660 -
Fix stale /cube command mentions + add a single source of truth for command names
#659 -
Fix /magic 500 (Thymeleaf escaping) + render-test guard + rename cube.* → magic.*
Production hotfix + cleanup. /magic was returning 500 Internal Server Error.
#658 -
Reframe the Magic tools as a "Magic toolkit" at /magic (+ SEO)
The Magic web surface was a single tabbed page at /cube titled "Cube workshop" — a good hub pattern, but the title mislabelled the card/legality/reference/price-watch tabs as cube-only, and the /cube slug carries no "magic"/"mtg" keyword.
#657 -
Split the /cube super-command into focused MTG commands
/cube had grown to 13 subcommands spanning cube-building, card lookups, reference, and price watches. This breaks it into five themed top-level commands, so each is small and discoverable:
#656 -
Add card price watch alerts (Discord + web)
#655 -
Surface the card-centric MTG tools in the navbar The card tools (lookup, deck legality, sets & keywords) are generically useful, not cube-specific, but were only reachable by wandering into the cube-builder page. Add them to the global Magic dropdown as hash deep-links (already wired on the cube page), so they're discoverable from any page, ahead of the cube-builder entries. A navbar test pins the links. Stacked on the reference branch (links to its new #reference tab); retarget to main as the chain merges. https://claude.ai/code/session_01BB5Q4S4kpD1YgfqmNKhQFs
Surface the card-centric MTG tools in the navbar The card tools (lookup, deck legality, sets & keywords) are generically useful, not cube-specific, but were only reachable by wandering into the cube-builder page.
#654 -
Add set lookup and a keyword glossary quick-reference (Discord + web) Beginner-friendly quick reference that broadens appeal beyond cube builders. - common: MtgSet value type; MtgGlossary — a curated, built-in map of evergreen (+ popular) keywords to reminder text, with exact/prefix lookup. - Discord: /cube set <code> (Scryfall /sets/:code → setEmbed) and /cube rule <keyword> (glossary → ruleEmbed, no network). - Web: new Reference tab with set + keyword forms, backed by GET /cube/api/set and /cube/api/rule (anonymous). - Tests across common, web service/controller, jest and the Discord embeds/fetcher/command; stylelint + kover gates green. Stacked on the combo-finder branch; retarget to main after that merges. https://claude.ai/code/session_01BB5Q4S4kpD1YgfqmNKhQFs
Add set lookup and a keyword glossary quick-reference (Discord + web) Beginner-friendly quick reference that broadens appeal beyond cube builders.
#653 -
Add a combo finder via Commander Spellbook (Discord + web)
Look up the combos a Magic card is part of — the pieces it needs and the payoff — from the Commander Spellbook database.
#652 -
Add a deck legality checker (Discord + web)
First of the four features picked from the "what's next" list. Paste a decklist (or check a saved cube) against a format and find out whether it's legal — every banned, not-in-format, and restricted card is flagged.
#651 -
Surface cube value on generate, per-card prices in pack files, and value extremes
Three follow-ups that round out the cube-value tooling — the gaps noted after the prices/currency work landed.
#650 -
Group the Magic settings into their own admin section
The two Magic config keys were buried in the generic "Messages & leaderboards" section of the moderation settings page, mixed in with delete-delay and leaderboard config. This gives Magic its own home.
#649 -
Add card rulings lookup to the MTG tools (Discord + web)
Rounds out the "card bot" feature set: look up a Magic card's official rulings (the rules team's clarifications for tricky interactions) on both Discord and the web — the last of the three hot features picked earlier (prices ✅, legality ✅, rulings — this PR).
#648 -
Add card prices, format legality and cube value to the MTG tools
Surfaces Scryfall market prices (USD / EUR / MTGO tix) and current format legality across every Magic card surface, plus a cube-wide total value — three of the most-requested "card bot" features, which broadens install appeal for trading/deckbuilding…
#647 -
Richer, configurable card lookups: oracle text, DFC backs, opt-out toggle
#646 -
Add inline [[card]] Magic lookups in chat
#645 -
Harden the cube feature after an audit pass
#644 -
Feature the MTG cube workshop on the homepage
#643 -
Add a web single-card lookup (Card lookup tab)
#642 -
Add a Compare tab: diff two card lists on the web
#641 -
Add /cube card <name> single-card lookup
#640 -
Add "open a sample pack" to the web cube preview
#639 -
Add colour-pair (guild) & pip-weighted colour balance to the cube report
#638 -
Add copy / download of the cube list on the web preview
#637 -
Add the cube report to the web /cube preview
#636 -
Add the cube report to the Discord /cube preview embed
#635 -
Add shared cube-analytics core (curve, types, rarity, duplicates)
#634 -
Make cube packs & preview groups collapsible for easier navigation
#633 -
Cube UX: hide the source on the As-fan tab; clearer saved-cube picker
Usability pass on the /cube workshop page
#632 -
Switch cube tab on hash change so navbar deep-links work in-page
#631 -
Richer cube preview cards: mana cost, DFC backs, copy counts
#630 -
Skip deck-export section headers when parsing a cube list
#629 -
Move /meme Reddit fetching onto ktor + coroutines
#628 -
Move cube Scryfall fetching onto ktor + coroutines
#627 -
Add /cube saved subcommand to list saved cubes in Discord
#626 -
Web cube UX: shareable query links, live card count, loading spinner
First of two UX-polish PRs (the other adds /cube saved in Discord). Closes three of the usability gaps from the review.
#625 -
Bound oversized cube inputs (Scryfall lookups + stored text)
A second audit pass — three unbounded-input issues that could exhaust resources or get us rate-limited.
#624 -
Surface dropped cards: Discord "couldn't find" + web pool-cap note
The remaining transparency items from the audit ("do all").
#623 -
Guard pack generation against an Int-overflow OOM (web DoS)
Another from the deeper audit — a more serious one: an OOM / DoS in pack generation reachable from the web.
#622 -
Classify modal land-back cards by their front face (as-fan fix)
From the "any other latent bugs?" audit — a real as-fan miscount.
#621 -
Resolve full double-faced names by requesting Scryfall by front face
Fixes the screenshot bug: a cube whose list contains the full double-faced names (Archangel Avacyn // Avacyn, the Purifier, Fire // Ice, Huntmaster of the Fells // Ravager of the Fells, ) reported all 9 as "Couldn't find".
#620 -
Resolve double-faced cards by either face in pasted cube lists
Fixes double-faced / transform cards not resolving from a pasted list when you write just one face.
#619 -
Add shareable cube links (/cube/c/<token>)
Second of the two follow-ups (the first, #617, adds card image links to the Discord output). Per the choice of "server-stored short link".
#618 -
Show card image links in the /cube Discord pack list
First of two follow-ups (the other, server-stored share links, is coming next). Per the choice of "image links in the list", the /cube Discord output now surfaces each card's image.
#617 -
Autocomplete saved cubes (Discord + web) and card names (web)
Adds autocomplete to the cube tooling — saved cubes (Discord + web) and card names (web).
#616 -
Draft your saved cubes from the /cube Discord command
Follow-up to #614 (merged) — lets the Discord /cube command draft a cube you saved on the website.
#615 -
Draft from your own card list, saved to your account
Two related additions to the /cube web tool.
#614 -
Show card images and thumbnails on the cube web page
Follow-up to #612 (merged). The /cube web page now shows card images instead of plain names.
#613 -
Add MTG cube tooling: as-fan calculator and randomised pack generator
#612 -
Raise coverage to 85% line and pin a regression floor
#611 -
Monthly board posts a frozen midnight-boundary snapshot of last month
#610 -
Per-guild configurable UTC hour for daily cron jobs
#609 -
Extend the daily streak: PNG card badge, reset countdown, milestones
#608 -
Surface richer daily-streak data on the profile card
#607 -
Redesign the daily streak card on the profile page
#606 -
Monthly board posts last-month snapshot, not current standings
#605
May 2026 153 updates
-
Include bonus tickets in the lottery embed held-ticket total
#604 -
Surface lottery bonus tickets and milestone jackpot draws in embed buy
#603 -
Resolve winner names, drop ghost thinking, share resolution overlay
PvP polish across Discord and the web, addressing four reported issues: TicTacToe / Connect 4 / RPS win embeds tagged the winner as a raw mention when the Discord client couldn't resolve the user.
#601 -
Update D&D category blurb to remove tabletop-sessions copy
The D&D category blurb on the commands wiki page still read "Roll dice, look up spells, and run tabletop sessions." but the tabletop-session features (/campaign, /initiative, /character, etc.) were removed when DnD Beyond shipped their official Discord bot.
#600 -
Follow-up coverage for voice, modals, web utilities, and DTOs
Picks up the four P1 follow-up areas from PR #597's analysis. One source bug surfaced and is documented but not fixed in this PR — flagged below. All new test files only; no production source changes.
#598 -
/buy committed then threw UnknownFormatConversionException
After #596 landed, /tobycoin buy started showing the "Something went wrong running that command" message — but the buy transaction was still landing in the DB (coins added, credit deducted, price moved).
#599 -
Cover JDA dispatcher listeners and default managers
Test coverage analysis + top-priority gap fix
#597 -
Stop commands from getting stuck thinking without acknowledging
Tobybot occasionally showed the "Bot is thinking..." spinner forever (or "interaction failed") after a slash command or button click — visually indistinguishable from the bot being offline.
#596 -
Polyhedral dice shapes + quick-pick grid on /utils
Two related upgrades to the /utils dice card, shipped together since they share makeDie / rollAndRender: 1.
#595 -
Lift remaining engine dirs into casino/ and pvp/ parents
Completes the common-module consolidation started in #592.
#594 -
Polish /8ball /random /roll /meme to mirror the web utility page
Mirrors the /utils web polish (PR #588) into the four Discord slash commands that back it.
#593 -
Regroup events + lift casino engines from economy/
Follow-up to the packaging series (#586/#587/#589/#590/#591) extending the same theme to the common module, which the original plan deferred.
#592 -
Regroup 43 entity DTOs into themed sub-packages
Deferred follow-up to the packaging series (#586/#587/#589/#590). The flat database/dto/ directory held 43 JPA entity DTOs mixed together — opening it showed an alphabetised list of every entity type in the system.
#591 -
Regroup 19 button files into themed sub-packages
Eleventh wave — and PR 4 of 4 in the packaging-only re-organisation series (continuing from #586, #587, #589). With this PR the packaging plan is complete.
#590 -
Regroup 34 command/embeds files into per-game subdirs
Tenth wave of the refactor pass — and PR 3 of 4 in the packaging-only re-organisation series (continuing from #586, #587).
#589 -
Polish /utils page with animated 8-ball, dice, and reel
The /utils page used to be plain forms with a printing the answer in monospace text. Every utility now mirrors the thing it does: Magic 8-ball — a real ball that shakes for ~700 ms, then fades the answer into the triangular window.
#588 -
Regroup 77 files into themed sub-packages
Ninth wave of the refactor pass — and PR 2 of 4 in the packaging-only re-organisation series (continuing from #586).
#587 -
Regroup 88 files into themed sub-packages
Eighth wave of the refactor pass — and the first of four planned packaging-only PRs to improve navigation ease.
#586 -
Move PvpResolutionOutcome translation factories beside the DTO
Seventh wave of the refactor pass (continuing from #579, #580, #581, #582, #583, #584).
#585 -
Extract shared button-handler helpers (decline + access checks)
Sixth wave of the test-coverage + DRY/SOLID refactor pass (continuing from #579, #580, #581, #582, #583). Symmetric follow-up to #583 (which extracted the slash-command challenge ceremony).
#584 -
Lift shared challenge ceremony out of the three slash commands
Fifth wave of the test-coverage + DRY/SOLID refactor pass (continuing from #579, #580, #581, #582).
#583 -
Extract shared PvpButtonIdCodec for the three PvP embeds
Fourth wave of the test-coverage + DRY/SOLID refactor pass (continuing from #579, #580, #581).
#582 -
Unify PvpController RPS endpoints onto the board helpers
Third wave of the test-coverage + DRY/SOLID refactor pass (continuing from #579 and #580).
#581 -
Extract PvpSessionRegistry base + split ModerationController
Second wave of the test-coverage + DRY/SOLID refactor pass (continuing from #579).
#580 -
Refactor+test+build: BoundedSessionCache, Kover wiring, activity-tracking coverage
First wave of the test-coverage + DRY/SOLID refactor pass scoped out in the planning session.
#579 -
Split Casino + PvP, deep-link PvP games, promote Commands
PvP was nested under "Economy" in the navbar — bad fit, PvP has no market mechanics. The home page's numbers strip already treats Casino + PvP as peer categories ("15 Casino games" / "4 PvP games"); the navbar now mirrors that.
#578 -
Bound suspicion-tracker map + cache 12-month rollup query
Follow-up to the audit after #574 landed — that PR trimmed ProfileCardRenderer, which is a rarely-exercised path. The real memory pressure comes from surfaces the bot exercises all day.
#577 -
TicTacToe + Connect 4 frontends + home counter bump
Stacked on top of #573 (TTT + C4 backends) — this branch includes both PRs' commits. When #573 merges, GitHub will auto-strip those commits from this diff.
#576 -
Cache profile avatars + drop alpha from card buffer
Container-memory audit of the ~30 PRs since #536 (perf(memory): trim Hikari pool + gate non-core Lavaplayer sources). Heap budget is tight: -Xmx224m -XX:MaxDirectMemorySize=48m -XX:+UseSerialGC.
#574 -
TicTacToe + Connect 4 web backends
Mirrors PR #568's RPS backend shape for the two turn-based PvP games (TicTacToe + Connect 4). Both tabs still show "coming soon" on the page — frontend (HTML / JS / CSS) lands in the follow-up alongside the home counter bump.
#573 -
Group forms into Member / Channel card grids
Phase 4 of the moderation dashboard refresh — Actions tab. The audit of the eight tabs that didn't get a deeper pass in earlier phases turned up just one outlier worth touching: actions.html.
#572 -
Side-rail nav, stakes table, bulk apply
Phase 3 of the moderation dashboard refresh — the Settings tab. Replaces the ~986-line wall of stacked accordions with a navigable layout, a real stake-limits table, and a bulk "set every game at once" widget that lets admins update many keys in one shot.
#571 -
RPS frontend — SSE-driven panel with challenge, accept, pick
Activates the RPS tab on /pvp/{guildId} end-to-end. The backend that merged in #568 is now reachable from the browser — challenge, accept/decline, pick, forfeit all work without leaving the page.
#570 -
Unified dashboard header + .mod-page-intro shell
Phase 2 of the moderation dashboard refresh — unifies the shared chrome across all 10 tabs so each page reads as part of one designed dashboard instead of 10 stitched-together tabs.
#569 -
RPS web backend — endpoints, SSE fan-out, view DTOs
First slice of PR 1b. Wires the backend half of Rock-Paper-Scissors web play onto the existing /pvp surface; the page still shows the "coming soon" RPS panel until the next slice adds JS/HTML/CSS.
#568 -
Redesign Activity tab + pill-style sub-nav
The Moderation → Activity tab was previously two bare SVG polylines with a one-line "total · peak" caption — accurate but flat. This PR turns it into a small dashboard and gives the shared moderation sub-nav a chrome refresh that lifts every tab.
#567 -
Migrate /duel web surface to /pvp with 4-tab unified page
First slice of the /pvp web surface plan.
#566 -
Drive category visibility from section.hidden, not a hardcoded allowlist
Fixes the empty Games filter on /commands/wiki (regression from #563): the previous CSS enumerated every valid data-active-cat slug, the new games slug wasn't on the list, so clicking the chip hit the :not(...) fallback and hid every section.
#565 -
Unbreak channel picker after reinvite — keep configured channels visible + request Administrator on invite
Two related changes from the same user report: lottery announce channel "defaulted back to general" after a TobyBot uninstall/reinstall, and the saved channel was missing from the picker so it couldn't be re-selected.
#564 -
GameCommand interface + surface PvP in home counter
Introduces a GameCommand marker interface (sibling to EconomyCommand) so the type system can finally tell games apart from real economy commands.
#563 -
Left-align mobile XP + client-side sort the Members tab
Two follow-ups on the Members tab from screenshot review: 1. Mobile XP card — left-align under Credits PR #560 added the XP cell as a third row spanning all three grid columns, centred.
#562 -
Broken activity page — pre-compute chart view in controller
The Activity sub-page at /moderation/{guildId}/activity (added in PR #558) is rendering the bot's generic "Something went wrong / Error 200 — OK" error block mid-page and cutting off the second chart.
#561 -
Label XP on mobile leaderboard cards
PR #558 added an XP column to the Members standings table but missed updating the mobile card view (≤ 600px).
#560 -
Champions tab + XP ranking + Activity charts
Three additive gaps in the existing leaderboard + moderation surface, all leveraging data that's already in the database. 1. Champions tab on /leaderboards Fourth tab that ranks users by total PvP wins across /duel, /rps, /tictactoe, /connect4.
#558 -
/connect4 + extract TurnBasedBoardWager/SessionRegistry bases
Adds /connect4 @opponent [stake: ] — head-to-head 7×6 Connect 4. Third and final of the planned PvP mini-games trio (after /rps #553 and /tictactoe #556). Initiator plays 🔴 (drops first); opponent plays 🟡.
#557 -
/tictactoe slash command with optional social-credit wager
Adds /tictactoe @opponent [stake: ] — head-to-head 3×3 Tic-Tac-Toe between two users. Second of the three planned PvP mini-games (after /rps in #553; Connect 4 is the next follow-up). The initiator plays ❌ and moves first; the opponent plays ⭕.
#556 -
Move pure game engines from database/ to common/
The casino / mini-game engines and their supporting value types (cards, hands, tables) are pure logic — no Spring, JPA, or JDA dependencies — so they belong in the common module alongside the other shared types rather than in database where they were…
#555 -
Back PendingDuelRegistry + RecentDuelResolutions with Caffeine
Brings the two duel-area in-memory registries onto the same belt-and-suspenders memory protection that RpsSessionRegistry got in #553's review pass.
#554 -
/rps slash command with optional social-credit wager (first of 3 mini-games)
First of the planned mini PvP games — Rock-Paper-Scissors as a 2-player challenge with hidden picks.
#553 -
Break Spring cycle by removing JDA from ProfileCardAggregator
Hotfix — production bot crash-looping on main.
#551 -
Show role names in autorole errors, unbreak mobile forms
Reporter (mobile) tried to bind an auto-role on Welcome → Auto-assigned roles on join and saw the rejection toast as a raw role mention ( sits above TobyBot's role ), plus the Message template input above it was clipped to ~200px alongside its Save button so…
#549 -
Shared searchable channel picker across moderation pages
Every channel dropdown in the moderation UI now uses a searchable typeahead combobox that mirrors the existing userPicker UX (filter-as-you-type, keyboard nav, accessible combobox roles).
#547 -
Per-guild welcome/goodbye announcements + auto-role-on-join
Adds the table-stakes "this bot greets new members" feature that big servers expect from a main bot. Three independently-toggleable surfaces, configurable from both Discord (/welcome slash command) and the web dashboard (/moderation/{guild}/welcome tab).
#546 -
Keep commands toolbar below the nav drawer stacking context
When the mobile nav drawer is open on /commands/wiki, the sticky .commands-toolbar (search input + category chips) visibly renders on top of the drawer instead of being covered by it.
#545 -
Channel picker modals — min_values=1, plus Activity announce channels
Two changes in one PR — hotfix + small follow-up. 1. Hotfix: min_values >= 1 on every picker modal Every wizard channel-picker modal (Quick channels, Jackpot, Lottery) failed to open with "Interaction failed".
#544 -
Quick channels picker for jackpot + lottery sections
Follow-up to the revert PR. The General section already has a "Quick channels" entry (Move + Leaderboard channel pickers via InstallQuickChannelsModal).
#543 -
Revert mixed-modal channel pickers — Discord rejects them
Hotfix: PR #541 broke the setconfig modals for General, Jackpot, and Lottery pools — all show client-side "Interaction failed" on open. Discord's modal API rejects modals that mix TextInput and EntitySelectMenu components.
#542 -
Channel pickers replace typed-ID boxes
Replaces the typed-channel-ID TextInput boxes in /setconfig with Discord's native EntitySelectMenu channel picker — owners click their channel from a list, no IDs to copy.
#541 -
Un-overlap the commands search icon and placeholder
The search SVG on /commands/wiki was overlapping the first character of the "Search commands " placeholder text (visible in user screenshot of the input).
#540 -
Commands wiki was truncating mid-render — host loop on th:block
/commands/wiki was shipping a truncated 200 OK to production — the response cut off mid-stream at (14,233 bytes, no , no , no script tag). Browsers drew the static fallback and the empty-state 🔍 sat visible mid-page because commands.js never loaded.
#538 -
Unstick custom-setup section pick (deferEdit + bot-webhook)
Fixes the install wizard hanging after the first section pick in Custom setup. The embed swapped to the section-detail header but the components stayed as the disabled previous view (section menu + Optional features/Finish row) — owner stuck.
#539 -
Redesign the commands wiki as a searchable catalog
/commands/wiki used to render every slash command as raw bordered tables with enum-style type badges (STRING, INTEGER, BOOLEAN ) and nested bullet lists — readable as developer docs but a poor first impression for the two homepage CTAs that point at it.
#537 -
Trim Hikari pool + gate non-core Lavaplayer sources
Memory dashboard shows the 512 MB dyno sitting pinned at the cap with 102% peaks.
#536 -
ConfigService.upsertAll — collapse multi-row config writes
Adds ConfigService.upsertAll(guildId, rows): List so multi-row config writes land in one transactional commit instead of N sequential commits. Migrates every looping / multi-row caller in discord-bot.
#535 -
In-Discord install wizard (Express + Custom paths)
Server owners now get a welcome message + /install slash command that opens a streamlined setup wizard with two paths: Express setup — one click; writes INSTALL_MODE=express + INSTALLED_AT and shows a done embed. Defaults handle everything else.
#534 -
Split head fragments — stops the double in-page toast
Closes the double-toast loop.
#533 -
Surface engagement events as in-page toasts via per-user SSE
New per-user SSE endpoint GET /api/notifications/stream opens an authenticated stream that fans out four engagement events as in-page toasts: achievement unlocks, level-ups, tips received, and lottery draws (your ticket).
#531 -
Polish leaderboard LVL + title pill formatting
The Prestige cell on the leaderboard had three issues visible in the attached screenshot: 1. No gap between pills. The LVL 7 pill and 💎 Diamond Hands pill rendered as adjacent inline-block siblings with no spacing, so they touched each other. 2.
#530 -
Restore MemberCachePolicy.ALL — offline users broke leaderboards and auth
The narrowed MemberCachePolicy introduced in #525 (online != OFFLINE, voice, owner) silently breaks any feature that walks the member list for offline users: Leaderboards render "Unknown" for every offline player (as @user reported with screenshot).
#529 -
Bump metaspace cap to stop startup OOM crash
Production crashed with: 2026-05-20 08:58:42 [main] INFO o.s.o.jpa.LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit 'default' Terminating due to java.lang.OutOfMemoryError: Metaspace The OOM fired ~500ms after…
#527 -
Tighten JVM/JDA caps and evict per-guild caches on leave
Dyno memory profile was sitting at ~441 MB avg / 557 MB peak against the 512 MB quota — R14/R15s on most peaks. Two-pronged fix: Cap JVM + native footprint so the process can't grow past the dyno quota.
#525 -
Include site footer on every page
Adds the site footer fragment to every page template that was missing it: game tables (blackjack, roulette, slots, plinko, ), guild pickers, dashboards, settings, moderation surfaces, public utilities (utils, dndLookup), the error page, and the…
#526 -
Load footer styles on every page, not just home
The site footer renders flush-left and unstyled on every public page except / — visible on /commands, /commands/wiki, /changelog, /login, /terms, /privacy.
#523 -
Wire 13 casino games to their reserved achievement events
Follow-up to #520. That PR reserved 13 hidden roadmap stubs in AchievementCatalog for casino games that didn't yet publish domain events. This PR wires them up so every stub is hidden = false and PENDING_HIDDEN_CODES is empty.
#522 -
Public profiles via leaderboard click-through + level pill alongside title
Turns leaderboard rows into links to a read-only public profile so members can compare their level, streak, and achievements with anyone else in the guild — and surfaces each member's level next to their title across the leaderboard for at-a-glance prestige.
#521 -
Expand catalog with new tiers, counters, consolation, and roadmap stubs
Expands the achievements catalogue with 19 new visible specs + 13 hidden roadmap stubs, riding entirely on event listeners that already exist in AchievementEventHandler. No new domain events.
#520 -
Surface achievements and notifications on the homepage
Add two feature-cards to the existing Engagement section of home.html: Achievements (links to /profile/guilds) and Notifications (links to /preferences/notifications).
#519 -
Admin tabs no longer paint over the navbar / drawer / dropdowns
The previous attempts (#509 layout, #510 raising the drawer to z-index: 1000) didn't fix admin tab overlap because the cause wasn't z-index — it was selector scope. nav { position: relative; z-index: 10 } in nav.css matched every on the page, including in…
#516 -
Web buy/sell watches on the /economy market page
Surfaces the Discord /pricealert feature (#512) on the web dashboard.
#514 -
/pricealert auto-trade triggers with PRICE_ALERT receipt DM
Wires up the PRICE_ALERT notification kind, which previously had infrastructure (channel kind, router DM/PUSH surfaces) but no trigger source. Adds /pricealert add|list|remove.
#512 -
/jackpotadmin lottery_refresh_embed — manual embed rebuild trigger
#511 -
Nav drawer stacking, additive embed rebuild, top holders include bonus tickets
Three bugs reported after #509
#510 -
Tab-nav layout + silent web bonus award + broken bulk-buy hint
Three bugs reported after PR #508 merged
#509 -
Surface incentives on the player page + polish moderation pages
#508 -
Drop broken icon asset from sw.js so macOS Firefox renders
macOS Firefox test push from /api/push/test showed the full happy path server-side: WebPushAdapter: delivering to 2 endpoint(s) for 313049624636162059. WebPushAdapter: HTTP 201 from .../wpush/v2/gAAAAABqDCEy ; marking used.
#507 -
Reward buying more tickets via three configurable incentives
Some users had figured out that buying a single TICKET_WEIGHTED lottery ticket was enough to satisfy LOTTERY_DAILY_MIN_BUYERS and be entered — making one-ticket entries the optimal play.
#506 -
SOLID/DRY sweep across common, core-api, database, web
Top-6 focused SOLID/DRY refactor + test-gap sweep spanning all four priority modules. Each refactor lands with the tests it enables; behaviour is preserved (no public API breaks). Result: 19 modified / 10 new files. Net −221 LOC across modified files.
#505 -
Register BouncyCastle eagerly + add push-path observability
After PR #503, browser push still wasn't reaching users.
#504 -
Wire WebPushAdapter via @Autowired constructor
Setting TOBY_VAPID_PUBLIC_KEY + TOBY_VAPID_PRIVATE_KEY on Heroku crashed the dyno (Process exited with status 1, then H10 App crashed on every request). Root cause: WebPushAdapter has two constructors and neither was @Autowired.
#503 -
Align admin DM/channel settings with notification prefs
Three seams between the older admin pages (per-feature channel pickers, "DM-only" toggles) and the per-surface notification-preferences system that landed in V37. Achievement shoutout label was misleading.
#502 -
Auto-route /preferences + /preferences/notifications past the picker when anchored
Follow-up to #496. The Settings and Notifications navbar entries from that PR landed users on the /preferences hub or the notifications guild-picker even when they had already anchored a default guild via DefaultGuildCookie.
#499 -
Ratchet level/streak progress + regroup /achievements display by category
Two complaints addressed: 1. Progress on level_* (and streak_*) achievements is wrong.
#498 -
Surface settings + notification preferences in navbar
The notification-preferences feature shipped end-to-end in #491/#493 (backend service, REST endpoints in EngagementApiController, MVC controller, templates, JS), but the only navbar path to /preferences was the small default-server star chip in the…
#496 -
Wire Web Push adapter for Surface.PUSH
Replaces the no-op sendPush stub from #493 with a real Web Push (RFC 8030 / 8291 / 8292) delivery path. End-to-end: schema → DTO/persistence/service → adapter → router wiring → REST endpoints → service worker → toggle UI on /preferences/notifications.
#494 -
Per-surface notification preferences + push stub + blackjack_natural hookup
Today every user_notification_pref row is keyed by (discord_id, guild_id, channel_kind) — one flag per (user, kind).
#493 -
Centralise channel routing through NotificationRouter
Follow-up to #491. Pulls channel resolution + permission checks out of each notifier and into a single NotificationRouter.sendChannel(...) method, gated by a per-route ChannelRouteKey enum.
#492 -
Daily streak, achievements, notification preferences
Adds a cohesive engagement loop on top of the existing per-guild XP/credit infrastructure: Daily streak (/daily): claim once per UTC day to grow a per-(user, guild) streak.
#491 -
Trim low-value misc commands
You flagged /hellothere, /ch, and /brother as low-value. This PR removes the first two outright and reworks /brother into something a non-Toby guild would actually use.
#490 -
Restore desktop navbar identity/auth + dedupe drawer close ✕
Three follow-ups to the navbar redesign in #488 (now on main): 1. Desktop regression — Login + default-guild pill were invisible.
#489 -
Redesign mobile navbar as a slide-in drawer
The mobile navbar was broken (screenshot below): the hamburger ended up *below* the open menu because .nav-toggle sat after .nav-links in the DOM with flex-wrap: wrap.
#488 -
Add pick=true to remaining "All servers" back-links
Adds ?pick=true to the "← All servers" back-link on 11 per-guild pages (poker-lobby, blackjack-lobby/solo, lottery, profile, economy, titles, tip, duel, music-player, moderation).
#487 -
Auto-route through guild pickers when possible
Skips the /{thing}/guilds picker when the user only shares one server with the bot, or has anchored a default server.
#486 -
Reorganize moderation settings UI into Server/Economy/Casino pillars
A re-arranging pass on /moderation/{guildId}/settings to fix two awkwardnesses without rewriting the page: Blackjack and Poker each had settings split across two homes.
#485 -
Render full progress bar on level-up embed
The Progress field on the level-up embed was computed via LevelCurve.progress(totalXp), which returns xpIntoLevel = 0 when the user lands exactly on the level threshold.
#484 -
Gate purchases by level, add level-ladder titles, extend tiers
Gate purchasable titles by level. TitleDto.required_level (added in V34) is now enforced on the buy paths — Discord /title buy, web /titles/{guildId}/{titleId}/buy, and the one-click "Buy with TOBY" flow.
#483 -
Award XP for web casino/poker/duel/economy actions
Web gameplay surfaces (casino, blackjack, poker, duels, lottery, the economy market, tips) previously bypassed the XP system entirely — playing slots in Discord awarded 5 XP per slash invocation, but the same action through the browser awarded zero.
#481 -
Render Discord level-up as a tier-colored embed
Discord level-up announcement is now a tier-colored embed that mirrors the web profile card (templates/profile.html + static/css/profile.css): tier color (Bronze/Silver/Gold/Diamond), level badge in the title, an XP progress bar, and total lifetime XP.
#480 -
Dedicated tab + role-reward CRUD + title gating
Closes the moderation-UI gaps from #477.
#479 -
Collapse navbar dropdown sections on mobile
On a phone-width viewport (≤600px), the navbar hamburger opened a menu where all four dropdown sections (Play, Economy, Social, Tools) were already fully expanded — every link visible, scroll forever, and the section toggle buttons did nothing.
#478 -
Add per-guild XP, levels, role rewards and title gates
Adds a leveling / engagement system on top of the existing social-credit infrastructure. XP is tracked separately from social credit so spending or losing credit at the casino doesn't drag a user's level down — the long-term progression track is durable.
#477 -
Resume preempted audio after intro plays
Today, when a member joins voice while the bot is already playing audio, their intro is enqueued behind the currently-playing track and only plays after that track finishes — defeating the point of an intro.
#476 -
In-browser preview of search results and queue items
The /music-player dashboard's search panel lists up to 10 results per query, but the only way to find out which "Linkin Park — Numb" is the right one is to queue it and let the bot play.
#475 -
Expand Lavaplayer sources + polish embed + web dashboard
Three connected pieces shipped together: A — Expanded audio sources Registers SoundCloud, Bandcamp, Vimeo, Niconico (built-in Lavaplayer) and Spotify, Apple Music, Deezer, Yandex Music (via the LavaSrc 4.8.2 plugin) alongside the existing…
#472 -
Derive homepage game counts from a single GameCatalog
The homepage was rendering three different game counts because the stats strip, hero paragraph, and casino feature card each held their own hardcoded literal: | Location | Before | |---|---| | Stats strip (home.html line 39, homeStats.gameCount) | 12 (from…
#474 -
Add Ko-fi link across web, README and Discord bot
Surfaces a single Ko-fi link (https://ko-fi.com/fratlayton) in one spot per user-facing channel so supporters can find it without making the bot feel like a paywall: Web footer (fragments/footer.html) — appears on every marketing page (home, commands, terms…
#473 -
Redesign section tabs as segmented cards with counts
The Members / TobyCoin / Games tab strip shipped in #470 cloned the .lb-sort pill aesthetic (designed for a secondary inline switcher) and read as small + left-aligned + visually lost between the prominent podium above and the prominent tables below.
#471 -
Tabbed sections, month nav, hover contributors, deltas, sparklines
The leaderboard page used to stack three full sections vertically (credit standings, TobyCoin wallets, Top Games) and the Top Games list collapsed each game to a single number. This PR reshapes the page into three tabs and gives the Games panel real depth.
#470 -
Modal-driven /team split with preview-confirm flow + web presets
Rebuilds /team around a Discord modal, a preview-then-confirm flow, and a /teams/{guildId} web UI for saved rosters. Replaces the old one-shot slash command that immediately created voice channels with no chance to review.
#469 -
Add ban/timeout/purge/lock/slowmode tools + Actions UI tab
Replaces /shh and /talk (and their voice-tab Shh/Talk buttons) with a proper moderation toolkit. Every new action is reachable both as a Discord slash command and from a new Actions tab on the moderation dashboard.
#468 -
Mobile-friendly pass, breakpoint refactor, responsive tests
Full mobile-friendliness sweep across the dashboard.
#466 -
Align admin poll page with /poll modal (4 options, 1 min)
/poll was reworked into a Discord modal (PollModal.kt) that — bound by Discord's 5-component modal cap — accepts a paragraph question (1–500 chars) plus up to 4 single-line options, with only option 1 required.
#467 -
Revert YouTube proxy from LavaPlayer + Ktor paths
LavaPlayer was 407-ing every CONNECT through the YouTube proxy across all configured clients, surfacing in production as: dev.lavalink.youtube.AllClientsFailedException: (yts.version: 1.18.0) All clients failed to load the item.
#465 -
Authenticate HTTPS CONNECT to YouTube proxy
Intro preview was logging YouTube Data API preview returned HTTP 407 on every URL pre-flight when the YouTube proxy was configured with auth.
#464 -
Swap per-horse odds label when Win/Place/Show changes
The horse cards always rendered the WIN multiplier regardless of which bet type was selected, so the favourite looked like "3.1× win" even when Place was active (where it actually pays 1.7×).
#463 -
Add Horse Racing minigame (Win/Place/Show on 6-horse field)
New casino minigame: /horse-racing slash command + /casino/{guildId}/horse-racing web page with an animated six-lane track. Six horses, named with distinct win odds (favourite H1 to longshot H6); three bet types — Win (1st only), Place (top 2), Show (top 3).
#462 -
Add Plinko + Wheel of Fortune (low-RTP jackpot feeders)
Two new jackpot-eligible casino games that sit in the sub-92% RTP band (where only Slots, Scratch, and Dice currently live).
#461 -
Honor [hidden] on .btn-primary and .btn-secondary
Follow-up to #458. The user noticed the previous fix didn't actually hide the "Buy (sell N TOBY)" button on scratch even when their balance comfortably covered the stake (screenshot: balance ~100k, stake 100, button still visible reading "sell 0 TOBY").
#460 -
Load casino-win-settle.js on the bespoke web pages
User report: "no coins visible" on /blackjack/{guild}/solo after a winning hand — the chip-stack flourish that other casino pages drop on the felt never appeared.
#459 -
Split into subcommand-per-category modals
Splits the monolithic /setconfig slash command into 12 subcommands, each opening a category-scoped modal pre-populated with the guild's current values.
#457 -
Lock buy buttons through reveal + refresh TOBY button on balance changes
Two scratch-card UX gaps that diverged from how the other casino games (slots, dice, coinflip, keno, etc.) behave: Buy buttons re-enabled mid-reveal.
#458 -
SOLID/DRY pass + expand modal usage
Three workstreams in one PR: A. Discord-bot SOLID/DRY Loggable mixin in core-api/core/log — Command, Button, Menu, Modal each used to declare an identical val logger: DiscordLogger get() = DiscordLogger.createLogger(this::class.java). Now one interface.
#456 -
Tighter walk-and-shoot choreography + mobile-friendly overlay
Two follow-up tweaks to the duel resolution animation that merged in #453. Choreography. The figures now begin overlapping at the arena centre and walk apart back-to-back.
#454 -
Play a resolution animation when an offer is accepted on the web
Follow-on to #452 (which enriched the recipient + per-offer expiry on the web inbox).
#453 -
Show opponent name + avatar and per-offer expiry on web inbox
The web /duel/{guildId} page previously showed each pending offer's recipient as a raw 18-digit Discord ID (e.g. 123456789012345678) and gave no per-row expiry indication — only a static "offers expire after 3m" footer.
#452 -
Centralised searchable user picker for every user dropdown
Every form that lets you pick a member — tip recipient, duel opponent, excuse attribution, intro management, voice move targets, casino jackpot refund — used a plain populated with ${members}. On servers with lots of members those dropdowns are a long scroll.
#450 -
Resolve author to current Discord name at display time
Follow-up to #443. The previous PR stored author_discord_id at submission but kept rendering the snapshot author string everywhere, so a user changing their server nickname left stale names attached to old excuses.
#447 -
Render monster and equipment images in lookups
The dnd5eapi exposes an image field on monsters (e.g. /api/images/monsters/goblin.png) and equipment (e.g. /api/images/equipment/longsword.png). Other endpoints don't.
#446 -
Expand lookup to all 18 SRD categories with smart autocomplete
Discord /dnd and the /dnd web page now cover all 18 dnd5eapi.co 5e SRD categories (was 4) — adds monsters, classes, races, equipment, traits, ability scores, skills, and the rest.
#444 -
Premium spoke wheel with mobile sizing + click-to-spin
Three problems with the jackpot payout wheel (introduced in #442): Mobile sizing. width: 320px; max-width: 90vw with no responsive rules made the wheel dominate phone screens and visually crash into the stake input above and the banner description below.
#445 -
Subcommand rewrite + per-guild web UI
Polishes the excuse service end-to-end: /excuse rewrite to real Discord subcommands: random, submit, list, search, approve, delete. Replaces the previous free-form action option that silently fell through to "create" when no known action was matched.
#443 -
Tiered payout wheel replaces fixed-pct payout
A "compromise" jackpot mechanic — high win chance but small slices most of the time, rare jackpots for the big hit. Backend: JackpotHelper.rollOnWin returns a JackpotRoll(amount, tierIndex, tierPayoutPct).
#442 -
Replace campaign features with lookup page
DnD Beyond now ships an official Discord bot that does campaign management, initiative tracking, encounter building and character-sheet integration better than TobyBot can.
#441 -
Structurally exclude HighLow wins from rolling
The per-guild JACKPOT_RTP_MAX_PCT gate uses RTP as a proxy for "deserves a jackpot sweetener".
#440 -
Preserve action-row buttons on refresh for weighted mode
Companion fix to #438. After that PR the mode label on refreshed weighted-draw embeds was right, but the action-row buttons still disappeared.
#439 -
Use runtime mode constant when refreshing announcement embed
The lottery refresh job has been overwriting weighted-mode announcement embeds with the wrong "Today's draw" mode label (Pick 5 of 49) while the footer + yesterday's recap correctly say it's a weighted draw.
#438 -
Make testcontainers work on Windows with Docker Engine 29
Upgrade testcontainers to 1.21.1 and set docker-java API version to 1.44 for Docker Engine 29 compatibility Override DOCKER_HOST on Windows to use Docker Desktop's named pipe (npipe:////./pipe/dockerDesktopLinuxEngine) Share a single PostgreSQL container…
#437 -
Interactive buy button on announcement embed + modal infrastructure
Buy Tickets button on weighted lottery announcement embeds — opens a modal for ticket count, processes the purchase via buyTickets service, replies ephemeral Pick Numbers link button on number match lottery embeds — opens the web lottery page…
#436 -
Live-refresh announcement embed + thorough Command-helper rollout
Two independent changes, one per commit. 1.
#434 -
Wide-ping + clickable buy CTA on daily announcement, rich top-holders on web
Two improvements to the daily lottery so non-winners actually notice it and can act on it.
#433 -
Batch changelog entries into one PR (main is protected)
The changelog automation added in #426 has been silently failing on every merged PR since it landed — #427, #428, #429 all triggered it, and every run failed in ~5 seconds at the final git push origin main step. No chore(changelog) commit has ever landed.
#431 -
Moderation page split + searchable settings
The single moderation page became six focused sub-pages (Users, Settings, Voice, Poll, Casino, Lottery). The Settings page got a sticky search box and a dedicated Jackpot section exposing five eligibility/payout configs that were previously backend-only.
#424 -
Jackpot RTP eligibility gate
High-RTP games (blackjack, coinflip, baccarat, roulette) can now be excluded from jackpot rolls via a configurable RTP ceiling, so they don't double-dip on a sweetener they don't need.
#422 -
Anti-autoclicker channel logs
Suspected-bot session embeds now post to a configurable Discord channel and update in place as forced-loss substitutions accumulate over the session.
#420
April 2026 3 updates
-
Daily lottery (Pick 5 of 49 + weighted mode)
Auto-runs at 00:00 UTC: closes the prior draw, pays tier-based prizes, opens a fresh one seeded from the jackpot pool. NUMBER_MATCH for high-engagement servers, WEIGHTED for low-traffic ones; configurable per guild from the moderation Lottery sub-page.
#412 -
Casino refresh: Roulette + Casino Hold'em
Two new minigames join the quick-play roster, both wired into the per-guild jackpot pool with the same loss-tribute and stake-anchor rules as the rest.
#404 -
Multiplayer Blackjack tables
Up to 7 seats per multi-table, configurable shot clock, S17/H17 dealer rule, and 3:2 / 6:5 payout toggle. Solo blackjack shares the same stake bounds.
March 2026 2 updates
-
Poker tables (fixed-limit Hold'em)
2-9 seats, configurable blinds and bet structure, per-actor shot clock with auto-fold, and rake routed to the jackpot pool.
-
TOBY coin live market
Per-server live market with a 5-minute price tick. Earn social credit by being active, then trade it for TOBY coin.
February 2026 1 update
-
Web moderation admin
Browser-based moderation: toggle user permissions, adjust social credit, mute or move voice channels, post polls — no slash commands needed.