Skip to content

feat(web): redesign Discord settings as dedicated /discord route#115

Merged
CoderCoco merged 7 commits into
mainfrom
claude/issue-62-ePUwx
May 4, 2026
Merged

feat(web): redesign Discord settings as dedicated /discord route#115
CoderCoco merged 7 commits into
mainfrom
claude/issue-62-ePUwx

Conversation

@CoderCoco
Copy link
Copy Markdown
Owner

Replace the four-tab DiscordPanel crammed into the dashboard with a
proper /discord route built on the shadcn/ui foundation. Adds first-run
setup wizard, snowflake validation on every ID input, show/hide eye
toggles for the bot token + public key (with a green check when already
set), chip-based bulk-paste editors for admin user/role IDs, and an
inline-editable per-game permissions table.

Closes #62

https://claude.ai/code/session_01AiQPoJT9p8MS5odgMc8frh

claude added 4 commits May 2, 2026 05:31
Replace the four-tab DiscordPanel crammed into the dashboard with a
proper /discord route built on the shadcn/ui foundation. Adds first-run
setup wizard, snowflake validation on every ID input, show/hide eye
toggles for the bot token + public key (with a green check when already
set), chip-based bulk-paste editors for admin user/role IDs, and an
inline-editable per-game permissions table.

Closes #62

https://claude.ai/code/session_01AiQPoJT9p8MS5odgMc8frh
Extends the e2e harness with Discord fixtures (FIRST_RUN_DISCORD_CONFIG,
CONFIGURED_DISCORD_CONFIG, snowflake samples) and stub routes for
/api/discord/{config,guilds,admins,permissions,...} so non-Discord specs
hitting /discord (e.g. sidebar nav) no longer trip the catch-all 404.

New discord.spec.ts covers the AC from #62: setup wizard visibility on
first run, the bot-token "set" indicator + show/hide eye toggle,
verification that the API never echoes the secrets back, snowflake
validation on guild add, the Guilds table render, the per-game
permissions table, and the not-deployed empty state.

https://claude.ai/code/session_01AiQPoJT9p8MS5odgMc8frh
…er-deploy into claude/issue-62-ePUwx

# Conflicts:
#	app/packages/web/e2e/fixtures/game-data.ts
#	app/packages/web/e2e/fixtures/index.ts
#	app/packages/web/src/pages/DashboardPage.tsx
Copilot AI review requested due to automatic review settings May 4, 2026 22:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the old dashboard-embedded Discord configuration panel with a dedicated /discord page in the web app, moving the existing Discord management flows onto the newer shadcn/ui-based route structure.

Changes:

  • Adds a full DiscordPage with tabs for credentials, guilds, admins, and per-game permissions, plus a first-run setup wizard.
  • Removes the legacy DiscordPanel from the dashboard and leaves the dashboard focused on game/log views.
  • Adds Playwright fixtures and E2E coverage for the new Discord route and its basic states.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
app/packages/web/src/pages/DiscordPage.tsx New dedicated Discord settings page and all section components.
app/packages/web/src/pages/DashboardPage.tsx Removes the embedded Discord panel from the dashboard.
app/packages/web/src/components/DiscordPanel.tsx Deletes the legacy dashboard Discord panel implementation.
app/packages/web/e2e/specs/discord.spec.ts Adds E2E tests for the new Discord route.
app/packages/web/e2e/fixtures/index.ts Extends shared Playwright stubs with Discord endpoints and /api/games.
app/packages/web/e2e/fixtures/game-data.ts Adds Discord-specific fixture data and valid snowflake constants.

<p className="text-xs text-[var(--color-muted-foreground)]">
Full walkthrough:{' '}
<a
href="/docs/setup"
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d238092. Switched to the published Docusaurus URL https://codercoco.github.io/game-server-deploy/setup and added target="_blank" since the docs site is a separate origin from the management app.


Generated by Claude Code

Comment on lines +485 to +487
function handleRegister(guildId: string) {
setRegistered((prev) => new Set(prev).add(guildId));
onRegister(guildId);
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d238092. handleRegister is now async, awaits onRegister, and only sets the registered flag inside a try block — failures leave the badge in the not-registered state. The Register all action serialises so partial failures stay visible per-guild.


Generated by Claude Code

Comment on lines +329 to +334
<Input
id="client-id"
value={clientId}
onChange={(e) => setClientId(e.target.value)}
placeholder="000000000000000000"
/>
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d238092. Save now blocks with an inline error + aria-invalid when Client ID is non-empty and not a snowflake. Empty is still allowed so token-only updates don't require re-typing the ID.


Generated by Claude Code

claude added 2 commits May 4, 2026 23:14
- Setup-wizard docs link now points to the published Docusaurus URL
  (https://codercoco.github.io/game-server-deploy/setup) instead of the
  in-app /docs/setup path that 404s.
- "registered" badge in the Guilds table now flips only after
  api.discordRegisterCommands() resolves successfully; failures leave
  the badge in the not-registered state. The bulk action serialises so
  partial failures stay visible.
- Credentials form validates the Application (Client) ID as a Discord
  snowflake before submit, with an inline error and aria-invalid on
  the input. Empty is still allowed so operators can save token-only
  updates without retyping the ID.

https://claude.ai/code/session_01AiQPoJT9p8MS5odgMc8frh
Pins the fix from d238092 — the "registered" badge must only flip
after the API call resolves successfully. Two new specs:

- success path: clicking Register on a guild row flips the badge from
  "not registered" to "registered".
- failure path: when the register-commands endpoint returns 500, the
  badge stays at "not registered" so operators see the actual outcome
  instead of an optimistic success flash.

https://claude.ai/code/session_01AiQPoJT9p8MS5odgMc8frh
Copilot AI review requested due to automatic review settings May 4, 2026 23:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment on lines +891 to +902
{games.map((game) => (
<PermissionRow
key={game}
game={game}
initial={
cfg.gamePermissions[game] ?? { userIds: [], roleIds: [], actions: [] }
}
busy={busy}
onSave={(perm) => onSave(game, perm)}
onDelete={() => onDelete(game)}
/>
))}
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d4d1b77. The row is now keyed by `${game}:${JSON.stringify(initial)}` so it remounts whenever the server-side entry changes — Clear (and Save) reinitialise the chips and checkboxes from the freshly-fetched cfg. Pinned by the new e2e spec should reset chips on a permission row after Clear.


Generated by Claude Code

Comment on lines +487 to +494
const allGuilds = useMemo(
() => [
...cfg.baseAllowedGuilds.map((g) => ({ id: g, locked: true })),
...cfg.allowedGuilds.map((g) => ({ id: g, locked: false })),
],
[cfg.allowedGuilds, cfg.baseAllowedGuilds],
);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d4d1b77. The merge now dedupes by guild ID (terraform entry wins, so the row is shown as locked), and handleAdd rejects a guild that's already in either list with That guild is already allowlisted. instead of POSTing a duplicate. Pinned by the new e2e spec should reject adding a guild that is already allowlisted.


Generated by Claude Code

…n Clear

Two more Copilot review comments on PR #115:

- The Guilds table merged baseAllowedGuilds + allowedGuilds without
  deduping, so a guild that appeared in both rendered twice with a
  colliding React key. handleAdd also let an operator add a guild that
  was already in either list. The merge now dedupes (terraform entry
  wins) and Add rejects an ID that is already allowlisted with an
  inline error.

- PermissionRow's local userIds/roleIds/actions state was keyed only
  by game name, so clicking Clear (or Save) refreshed cfg without
  resetting the row — the cleared chips and checkboxes lingered until
  reload. The row is now keyed by `${game}:${JSON.stringify(initial)}`
  so it remounts whenever the server-side entry changes.

Also adds two e2e specs to pin the fixes:

- discord settings › should reject adding a guild that is already
  allowlisted
- discord settings › should reset chips on a permission row after Clear

https://claude.ai/code/session_01AiQPoJT9p8MS5odgMc8frh
@CoderCoco
Copy link
Copy Markdown
Owner Author

@copilot please review

Copy link
Copy Markdown
Contributor

Copilot AI commented May 4, 2026

@copilot please review

All five issues from both review rounds are addressed and the branch is in good shape. Lint is clean and all 258 unit tests pass.

Round 1 fixes (d238092):

  • Docs URL → https://codercoco.github.io/game-server-deploy/setup with target="_blank" rel="noreferrer"
  • handleRegister is now async, sets the badge only inside the try block, so a failed registration stays "not registered"
  • Client ID field blocks save with an inline aria-invalid error when the value is non-empty and not a valid snowflake

Round 2 fixes (d4d1b77):

  • Guild list deduped by ID in a useMemo (terraform entry wins/locked); handleAdd rejects a guild already in either list with "That guild is already allowlisted."
  • PermissionRow keyed by `${game}:${JSON.stringify(initial)}` so it remounts after Clear/Save and local chip state reinitialises from the fresh server response

Both re-key and duplicate-guild fixes are pinned by dedicated E2E specs (should reset chips on a permission row after Clear, should reject adding a guild that is already allowlisted).

@CoderCoco CoderCoco merged commit b5ff4fe into main May 4, 2026
8 checks passed
@CoderCoco CoderCoco deleted the claude/issue-62-ePUwx branch May 4, 2026 23:58
CoderCoco pushed a commit that referenced this pull request May 5, 2026
Resolves App.tsx/DashboardPage/CostsPage/DiscordPage/LogsPage conflicts
between the polling indicator work (#65) and the page-redesign series
that landed on main (#77, #79, #115, #116, #117).

- App.tsx now wraps the redesigned ApiTokenModal (rendered inline as a
  Dialog overlay rather than swapping the tree) inside PollingProvider
  + GameStatusProvider so polling persists across the auth-token flow.
- Each redesigned page (Dashboard, Costs, Discord, Logs) now hosts a
  PollingIndicator next to its existing header chrome (search input,
  range selector, ServerlessBadge, LIVE badge).
- LogsPage.test.tsx mounts the page through a PollingProvider wrapper
  so the new indicator can read the registry under jsdom.
- polling.spec.ts updated to use main's page-object fixtures
  (DashboardPage / AppLayout) and the redesigned STOPPED status label.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(web): redesign Discord settings as dedicated /discord route with subsections

4 participants