Commit 47b342e
feat: replace server-data slash commands with #info embeds (#59)
* docs: add #info server-data embeds design spec
Replaces the nine server-data slash commands (/pop, /time, /wipe,
/online, /offline, /team, /alive, /small, /large) with three
auto-refreshing embeds in the per-server #info channel: Server,
Events and Team, below the existing map image embed.
The in-game !command surface is untouched. The "Switch active player"
select moves off the embed into a new /server player command.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: add #info server-data embeds implementation plan
Ten TDD tasks: move DurationFormat/Daylight into Abstractions, open the
renderer contracts, build the three embeds in the projects that own their
data, declare the specs, add a render-gated refresh path plus its hosted
service, delete the nine slash commands, and add /server player.
Also corrects the spec: the Team renderer lands in Features.Players (not
Connections, which cannot see GridReference), ServerResolver moves rather
than staying put, and offline members carry no fabricated duration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: move DurationFormat to Abstractions and extract Daylight helper
Both are needed by the #info renderers, which sit below Features.Commands
in the project graph. Behaviour is unchanged; TimeCommandHandler now shares
its day/night math with the forthcoming server embed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: make the workspace message-renderer contracts public
Lets Features.Events and Features.Players contribute renderers for the new
#info embeds without moving the state interfaces into Abstractions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add players, time and wipe to the #info server embed
Renders the MaxPlayers/QueuedPlayers/WipeTimeUtc already carried by
ServerInfoSnapshot but never shown, replacing /pop, /time and /wipe.
Drops the swap select ahead of /server player replacing it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add the #info events embed
Cargo, patrol heli, chinook and both oil rigs in one embed, replacing
/small and /large. Reads in-process marker and rig state only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: correct build/test gates in the info-embeds plan
Pre-flight against the previous feature's ledger surfaced four gate
violations in the plan as written:
- 28 invocations used `dtk build X`; dtk wraps dotnet, so the form is
`dtk dotnet build X`, and `-maxcpucount:1` is mandatory (parallel
MSBuild races ConfigureGitHooks on .git/config, silently dropping a
whole test assembly that then reports 0 tests and looks green).
- The jb cleanupcode invocation lacked --no-build --verbosity=ERROR.
- Global Constraints omitted -warnaserror, the CA1305/1307/1310 rules,
the no-FluentAssertions rule, the global `using Xunit`, the commit
trailer, and the 18-assembly baseline of 1028+1.
- Two steps told the implementer to create a test project and add an
InternalsVisibleTo grant that both already exist.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add the #info team embed
One line per member with presence glyph, grid reference and state age,
replacing /team, /online, /offline and /alive. Sorted online-alive, AFK,
dead, offline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Pin the online-alive survival-time tiebreak in ServerTeamMessageRendererTests
Online_alive_sort_before_afk_dead_and_offline puts every member in a distinct
RankOf bucket, so the renderer's ThenByDescending(SurvivedFor) tiebreak was
never exercised. Add a dedicated test with two online-alive, non-AFK members
at different survival times to pin the longest-surviving-first ordering.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: declare the events and team message specs in #info
Declaration order fixes the on-screen order: map, status, events, team.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add a render-gated in-place refresh for #info
Skips the provisioning lock and channel walk that ReconcileServerAsync
performs, and suppresses PATCHes for unchanged renders. Any edit failure
invalidates the gate and escalates to a full reconcile.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: propagate cancellation and strengthen gate-invalidation test in ServerInfoRefresher
Finding 1: EditMessageAsync's broad catch swallowed OperationCanceledException
on shutdown, invalidating the gate and reconciling with an already-cancelled
token instead of letting cancellation propagate. Finding 2: the failed-edit
test's ShouldSend probe used a dummy literal that passed regardless of
whether the gate was correctly invalidated or wrongly committed; it now
probes with the exact attempted canonical, verified by mutation to fail
under a commit-before-edit bug and pass under the correct Invalidate call.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: refresh #info on a configurable interval
Tracks connected servers off the connection status event and re-renders
their #info embeds every Workspace:InfoRefreshInterval (default 1m),
floored at one second so a misconfiguration cannot spin the loop.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: delete the nine server-data slash commands
Their data now lives in the #info embeds. The in-game !pop/!time/!wipe/
!online/!offline/!team/!alive/!small/!large handlers are untouched.
ServerResolver moves to Features.Connections, which /server player needs
because Features.Commands cannot see WorkspaceComponentIds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add /server player to replace the #info swap select
Replies ephemerally with the same select and custom id the embed used to
carry, so ConnectionComponentModule handles it unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix EditMessageAsync silently no-op'ing on a missing Discord channel
DiscordWorkspaceGateway.EditMessageAsync returned silently when the channel
was absent from the socket cache, while PostMessageAsync threw for the
identical condition. ServerInfoRefresher commits a render to the RenderGate
right after EditMessageAsync returns, so the silent no-op let the gate
record a render that never landed, leaving the rarely-changing #info
Events/Team embeds stale until content next changed. Throwing symmetrically
lets the refresher's existing catch -> invalidate -> reconcile self-heal
path handle it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: update README for #info embeds and /server player
The nine server-data slash commands are gone; their data now renders in
the auto-refreshing #info dashboard (Server / Events / Team embeds) and
stays available in-game via !commands. Active-player hot-swap moved from
the #info select menu to the new /server player command.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address Copilot review on #info refresh (PR #59)
- ServerInfoRefreshHostedService: enumerate connectable servers from the
store each tick instead of tracking them from ConnectionStatusChangedEvent.
The connection can publish "connected" before this service subscribes on
startup, so an event-only set missed it and that server's #info embeds
never refreshed. Mirrors InfoMapHostedService's source-of-truth-each-tick
pattern; removes ConnectedServerSet and the event loop. A disconnected
server renders cheaply (live queries return null with no round-trip), and
a transient store-enumeration failure no longer kills the tick loop.
- WorkspaceOptions doc: the steady-state cost is four Rust+ calls per tick
(server info, time, team, map dimensions), not three.
- help.slash.server.player: add the terminal period, en + fr, to match the
sibling help entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent db2591d commit 47b342e
61 files changed
Lines changed: 4951 additions & 560 deletions
File tree
- docs/superpowers
- plans
- specs
- src
- RustPlusBot.Abstractions
- Connections
- Formatting
- RustPlusBot.Features.Commands
- Formatting
- Handlers
- Help
- Modules
- Servers
- RustPlusBot.Features.Connections
- Modules
- Servers
- RustPlusBot.Features.Events
- Messages
- RustPlusBot.Features.Players
- Messages
- RustPlusBot.Features.Workspace
- Gateway
- Hosting
- Messages
- Reconciler
- Registry
- Specs
- RustPlusBot.Host
- RustPlusBot.Localization
- tests
- RustPlusBot.Abstractions.Tests/Connections
- RustPlusBot.Features.Commands.Tests
- Formatting
- Servers
- RustPlusBot.Features.Connections.Tests
- Servers
- RustPlusBot.Features.Events.Tests/Messages
- RustPlusBot.Features.Players.Tests/Messages
- RustPlusBot.Features.Workspace.Tests
- Fakes
- Hosting
- Messages
- Reconciler
- Specs
- RustPlusBot.Localization.Tests
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
34 | | - | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
35 | 38 | | |
36 | 39 | | |
37 | 40 | | |
| |||
51 | 54 | | |
52 | 55 | | |
53 | 56 | | |
54 | | - | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
55 | 60 | | |
56 | | - | |
57 | | - | |
58 | 61 | | |
59 | 62 | | |
| 63 | + | |
60 | 64 | | |
61 | 65 | | |
62 | 66 | | |
| |||
Lines changed: 2772 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 374 additions & 0 deletions
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
Lines changed: 3 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | | - | |
6 | | - | |
| 5 | + | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
Lines changed: 0 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
9 | 8 | | |
10 | 9 | | |
11 | 10 | | |
| |||
62 | 61 | | |
63 | 62 | | |
64 | 63 | | |
65 | | - | |
66 | | - | |
67 | 64 | | |
68 | 65 | | |
69 | 66 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
0 commit comments