Skip to content

chore: deferred tech debt and reviewer-flagged improvements (#144)#306

Merged
BillChirico merged 69 commits intomainfrom
chore/issue-144-tech-debt
Mar 19, 2026
Merged

chore: deferred tech debt and reviewer-flagged improvements (#144)#306
BillChirico merged 69 commits intomainfrom
chore/issue-144-tech-debt

Conversation

@BillChirico
Copy link
Copy Markdown
Collaborator

Addresses items from #144 — deferred tech debt collected from PR reviews, code audits, and session notes.

Workstreams

✅ Code Quality

  • Extracted events.js handlers — Pulled inline handlers (clientReady, commandInteraction) into separate modules under src/modules/events/. Main events file is now a clean dispatcher.
  • Browser logger shim — Upgraded web/src/lib/logger.ts with proper client-side logging (structured, level-aware)
  • console.error cleanup — Replaced raw console calls in dashboard components with logger

🔄 In Progress (will merge as subagents complete)

  • Dashboard UX — Loading skeletons, error boundaries, toast notifications, mobile responsiveness
  • State Management — Zustand stores for member + moderation pages
  • Infrastructure — XP proxy validation, config validation, rate limit sweep, migration gap, stats accuracy

Not Included

  • Review bot consolidation (deferred per Bill's request)

Tests

  • New tests for extracted event handlers (clientReady, commandInteraction)
  • All existing tests pass

Closes #144

Bill Chirico and others added 28 commits March 7, 2026 14:50
Replace raw loading text with animated Skeleton components on:
- Audit log, tickets, conversations, temp-roles pages (skeleton table rows)
- Role selector and channel selector popovers (skeleton list items)

Partially addresses #144
- Create ErrorBoundary class component in web/src/components/ui/
- Shows friendly error card with AlertTriangle icon and 'Try Again' button
- Supports custom fallback render prop for flexible error UIs
- Wrap AnalyticsDashboard (main dashboard page) and ModerationStats
- Dev mode surfaces the raw error message for easier debugging

Partially addresses #144
Use sonner toasts to surface success/error feedback for:
- XP adjustment (toast.success / toast.error alongside inline state)
- Member CSV export (toast.success on download, toast.error on failure)

Config editor already had comprehensive toasts. Partially addresses #144
Validate the incoming JSON body before forwarding to the bot API:
- Reject non-object payloads with 400
- Require 'amount' field as a finite number
- Reject non-string 'reason' values
- Strip unknown keys (only forward amount + reason)

Defense-in-depth: catches bad clients before they hit the backend.
Partially addresses #144
- Export startRateLimitCleanup() for testing and graceful re-init
- Add 'stale entry cleanup (interval sweep)' test that verifies the
  5-minute setInterval sweep actually removes entries whose last activity
  is older than their retention window (uses fake timers)

The cleanup itself was already implemented; this adds test coverage and
makes the function available for external callers.

Partially addresses #144
- Replace console.error in ErrorBoundary with project logger
- Use 'skeleton-${i}' key prefix instead of bare index (noArrayIndexKey)
- Organize imports in tickets and conversations pages
- Format pass (biome)
- Upgrade web/src/lib/logger.ts browser runtime from no-op to structured
  console wrapper with [VolvoxDash] prefix and level tags
- Browser logger suppresses debug/info in production, surfaces warn/error
- Replace console.error in ErrorBoundary with logger.error + sonner toast
- Replace console.warn in config-categories with logger.warn
- Remove all raw console.* calls from browser code
- Update src/logger.js TODO to reflect browser shim is implemented
- Extract slash command dispatch (interactionCreate) into
  src/modules/events/commandInteraction.js
- Extract ClientReady slash command registration into
  src/modules/events/clientReady.js
- Move shardDisconnect handler into events/errors.js alongside
  existing error handlers (was duplicated in index.js)
- Remove duplicate client.on('error') handler from index.js
  (already handled by registerErrorHandlers)
- Clean up unused imports (Events, getConfig, debug) from index.js
- Update events.js dispatcher with organized handler groups
- index.js reduced from 529 to 413 lines
…ions

- Move fetchMembers into members-store with abort signal support
- Move fetchStats, fetchCases, fetchUserHistory into moderation-store
- Delete use-moderation-cases, use-moderation-stats, use-user-history hooks
- Pages now use store actions directly instead of local useState/useEffect
- Add unified 'ok' | 'unauthorized' | 'error' return type for fetch actions
- Moderation store clearUserHistory now resets data/error state inline
- 15 tests for members-store: sync actions, fetchMembers success/error/401/abort
- 24 tests for moderation-store: sync actions, fetchStats/fetchCases/fetchUserHistory
- Covers URL construction, filter params, sort reversal, error extraction
…onsiveness to dashboard pages

- Wrap all dashboard pages with ErrorBoundary component
- Add reusable skeleton components (StatCardSkeleton, TableSkeleton, etc.)
- Replace alert()/confirm() with sonner toast + Dialog in temp-roles
- Add toast feedback to performance threshold saves
- Make filter bars stack vertically on mobile (flex-col → sm:flex-row)
- Add responsive column hiding to temp-roles table
- Improve search input width behavior at small breakpoints

Part of #144 (Dashboard UX)
- Reject amount=0 (meaningless operation)
- Enforce ±1,000,000 bounds on XP adjustments
- Cap reason length at 500 characters
- Add comprehensive test suite (15 tests)
- Fix @vitejs/plugin-react version (^6 needs vite 8, we have 7)
- Test all reusable skeleton components (StatCard, Table, Chart, etc.)
- Test ErrorBoundary rendering, custom fallback, and reset behavior
- 15 passing tests

Part of #144
- Add tests/modules/events/commandInteraction.test.js (10 tests)
- Add tests/modules/events/clientReady.test.js (3 tests)
- Remove moved handler tests from tests/index.test.js
- All 189 test files, 3869 tests passing
…aints

- Add min/max to numeric schema fields (historyLength, retentionDays, etc.)
- Add maxLength to string fields (systemPrompt capped at 4000 chars)
- Enforce enum values for ai.defaultChannelMode
- Validate min/max and maxLength in validateValue()
- Add 20 new tests covering range, length, enum, and openProperties
- Remove unused imports in test files
- Fix import ordering (biome auto-fix)
- Remove unused eslint-disable comment from logger.ts
- Fix ternary formatting in logger.ts
@BillChirico BillChirico added this to the v0.1.0 - "Big Boy MVP" milestone Mar 15, 2026
Copilot AI review requested due to automatic review settings March 15, 2026 17:54
Copilot AI review requested due to automatic review settings March 17, 2026 21:40
@BillChirico BillChirico force-pushed the chore/issue-144-tech-debt branch from 8b6a32e to ea60e6e Compare March 17, 2026 21:40
@railway-app railway-app Bot temporarily deployed to volvox-bot / volvox-bot-pr-306 March 17, 2026 21:40 Destroyed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/api/routes/community.test.js (1)

243-267: ⚠️ Potential issue | 🟡 Minor

Add a regression test for Number.MAX_SAFE_INTEGER clamping.

The updated route logic now clamps large totals, but this suite still only checks a normal value. Please add an overflow-path test so this behavior is locked.

🧪 Suggested test addition
 describe('GET /api/v1/community/:guildId/stats', () => {
   it('returns community stats', async () => {
@@
   });
+
+  it('clamps totalMessagesSent at Number.MAX_SAFE_INTEGER', async () => {
+    mockPool.query
+      .mockResolvedValueOnce({ rows: [{ count: 42 }] }) // memberCount
+      .mockResolvedValueOnce({ rows: [{ total: '9007199254740999' }] }) // bigint-like PG value
+      .mockResolvedValueOnce({ rows: [{ count: 15 }] }) // activeProjects
+      .mockResolvedValueOnce({ rows: [{ count: 88 }] }) // challengesCompleted
+      .mockResolvedValueOnce({ rows: [] }); // topContributors
+
+    const res = await request(app).get(`/api/v1/community/${GUILD_ID}/stats`).expect(200);
+    expect(res.body.totalMessagesSent).toBe(Number.MAX_SAFE_INTEGER);
+  });

As per coding guidelines, "Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/api/routes/community.test.js` around lines 243 - 267, Add a regression
test in the same describe block for GET /api/v1/community/:guildId/stats that
simulates an overflow by mocking the query sequence used in the route
(mockPool.query.mockResolvedValueOnce ...) but return a huge value for
totalMessagesSent (e.g., BigInt(Number.MAX_SAFE_INTEGER) + 1 or a numeric >
Number.MAX_SAFE_INTEGER) for the second mock call, then call
request(app).get(`/api/v1/community/${GUILD_ID}/stats`) and assert that
res.body.totalMessagesSent is clamped to Number.MAX_SAFE_INTEGER; keep the other
mocked responses (memberCount, activeProjects, challengesCompleted,
topContributors) similar to existing test so you only verify the
overflow/clamping behavior for the route handling logic.
♻️ Duplicate comments (2)
src/modules/events/commandInteraction.js (1)

101-102: ⚠️ Potential issue | 🟡 Minor

Inconsistent error handling for replyErr.

Lines 101 and 107 access .message directly on replyErr, but elsewhere in this file (lines 31, 83, 88) the pattern err instanceof Error ? err.message : String(err) is used. If a non-Error value is thrown, this logs undefined instead of the actual error content.

Proposed fix
       if (interaction.replied || interaction.deferred) {
         await safeFollowUp(interaction, errorMessage).catch((replyErr) => {
           debug('Failed to send error follow-up', {
-            error: replyErr.message,
+            error: replyErr instanceof Error ? replyErr.message : String(replyErr),
             command: commandName,
           });
         });
       } else {
         await safeReply(interaction, errorMessage).catch((replyErr) => {
-          debug('Failed to send error reply', { error: replyErr.message, command: commandName });
+          debug('Failed to send error reply', {
+            error: replyErr instanceof Error ? replyErr.message : String(replyErr),
+            command: commandName,
+          });
         });
       }

Also applies to: 107-107

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/events/commandInteraction.js` around lines 101 - 102, The code is
inconsistently accessing replyErr.message which yields undefined for non-Error
throws; update both places where replyErr is logged (the object with keys error:
replyErr.message, command: commandName and the other occurrence at the later
log) to use the existing pattern used elsewhere: capture the error text as
replyErr instanceof Error ? replyErr.message : String(replyErr) so non-Error
values get stringified; keep the same key names (error, command) and ensure both
occurrences use this normalized error extraction.
web/src/app/dashboard/moderation/page.tsx (1)

229-237: ⚠️ Potential issue | 🟡 Minor

Add an accessible name to the clear-history button.

This icon-only button relies on title, which is not a reliable accessible name. Add aria-label so screen-reader users can discover its purpose.

Suggested fix
                 <Button
                   type="button"
                   variant="ghost"
                   size="sm"
                   onClick={handleClearUserHistory}
                   title="Clear user history"
+                  aria-label="Clear user history"
                 >
                   <X className="h-4 w-4" />
                 </Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/app/dashboard/moderation/page.tsx` around lines 229 - 237, The
clear-history Button (the <Button ... onClick={handleClearUserHistory} ...>
containing the <X /> icon) lacks a reliable accessible name; add an aria-label
(for example aria-label="Clear user history") to the Button element alongside or
instead of the title attribute so screen readers can announce its purpose,
keeping the existing onClick handler (handleClearUserHistory) and icon intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/app/dashboard/moderation/page.tsx`:
- Around line 87-89: The call to fetchUserHistory violates the formatter because
its arguments must each be on their own line; update the invocation of
fetchUserHistory so guildId, lookupUserId, userHistoryPage, and the options
object ({ signal: controller.signal }) are each on separate lines (preserving
the trailing comma and indentation) to satisfy formatting rules and fix the
pipeline failure.
- Line 113: The dependency array on the useEffect hook is a single long line
exceeding 100 characters; split the array across multiple lines so each
dependency is on its own line (or grouped logically) to meet the line-length
guideline. Locate the useEffect dependency array containing guildId,
lookupUserId, userHistoryPage, fetchStats, fetchCases, fetchUserHistory, and
onUnauthorized and reformat it into a multi-line array (e.g. [
  guildId,
  lookupUserId,
  userHistoryPage,
  fetchStats,
  fetchCases,
  fetchUserHistory,
  onUnauthorized,
]) without changing the dependencies themselves.
- Line 207: The Search element's className string is over 100 chars; split it
into multiple smaller pieces to satisfy the line-length rule (e.g., replace the
long inline string on the <Search ... /> element with a multi-line expression
like className={['absolute left-2.5','top-1/2 h-4','w-4
-translate-y-1/2','text-muted-foreground'].join(' ')} or use your existing
cn/classNames helper) so the JSX line for the Search component is wrapped across
lines while preserving the same classes.

---

Outside diff comments:
In `@tests/api/routes/community.test.js`:
- Around line 243-267: Add a regression test in the same describe block for GET
/api/v1/community/:guildId/stats that simulates an overflow by mocking the query
sequence used in the route (mockPool.query.mockResolvedValueOnce ...) but return
a huge value for totalMessagesSent (e.g., BigInt(Number.MAX_SAFE_INTEGER) + 1 or
a numeric > Number.MAX_SAFE_INTEGER) for the second mock call, then call
request(app).get(`/api/v1/community/${GUILD_ID}/stats`) and assert that
res.body.totalMessagesSent is clamped to Number.MAX_SAFE_INTEGER; keep the other
mocked responses (memberCount, activeProjects, challengesCompleted,
topContributors) similar to existing test so you only verify the
overflow/clamping behavior for the route handling logic.

---

Duplicate comments:
In `@src/modules/events/commandInteraction.js`:
- Around line 101-102: The code is inconsistently accessing replyErr.message
which yields undefined for non-Error throws; update both places where replyErr
is logged (the object with keys error: replyErr.message, command: commandName
and the other occurrence at the later log) to use the existing pattern used
elsewhere: capture the error text as replyErr instanceof Error ?
replyErr.message : String(replyErr) so non-Error values get stringified; keep
the same key names (error, command) and ensure both occurrences use this
normalized error extraction.

In `@web/src/app/dashboard/moderation/page.tsx`:
- Around line 229-237: The clear-history Button (the <Button ...
onClick={handleClearUserHistory} ...> containing the <X /> icon) lacks a
reliable accessible name; add an aria-label (for example aria-label="Clear user
history") to the Button element alongside or instead of the title attribute so
screen readers can announce its purpose, keeping the existing onClick handler
(handleClearUserHistory) and icon intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 79d07e2b-680c-43ca-96ea-5200fc928a67

📥 Commits

Reviewing files that changed from the base of the PR and between 94a2d74 and 8b6a32e.

📒 Files selected for processing (5)
  • src/api/routes/community.js
  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
  • tests/api/routes/community.test.js
  • web/src/app/dashboard/moderation/page.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (11)
**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ESM-only syntax: import/export, never require()/module.exports

Files:

  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
  • tests/api/routes/community.test.js
  • src/api/routes/community.js
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes
Always include semicolons at the end of statements
Use 2-space indentation (spaces, not tabs)
Always include trailing commas in multi-line arrays, objects, and function parameters
Maintain a maximum line width of 100 characters

Files:

  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
  • tests/api/routes/community.test.js
  • web/src/app/dashboard/moderation/page.tsx
  • src/api/routes/community.js
src/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.js: Never use console.* methods; use the Winston logger instead via import logger from '../logger.js' (adjust path as needed), then call logger.info(), logger.warn(), logger.error(), or logger.debug()
Always use safeReply(), safeSend(), or safeEditReply() instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully

Files:

  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
  • src/api/routes/community.js
src/modules/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Create feature modules in src/modules/ and add corresponding config sections to config.json

Files:

  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
**/*.{js,ts,tsx,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx,mjs}: Use ESM syntax (import/export) — CommonJS is not allowed
Use single quotes for strings — double quotes only allowed in JSON files
Always include semicolons at end of statements
Use 2-space indentation for all code
Use Winston logger from src/logger.js — never use console.* methods

Files:

  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
  • tests/api/routes/community.test.js
  • web/src/app/dashboard/moderation/page.tsx
  • src/api/routes/community.js
{src/commands/**/*.{js,ts},src/modules/**/*.{js,ts},src/index.js}

📄 CodeRabbit inference engine (AGENTS.md)

Use safeReply(), safeSend(), or safeEditReply() for Discord messages — never send unsafe messages directly

Files:

  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
tests/**/*.test.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

tests/**/*.test.js: Write bot tests using Vitest 4 with the node environment, matching the src/ structure in the tests/ directory
Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead

Files:

  • tests/api/routes/community.test.js
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Never use console.* methods in web dashboard code; use appropriate logging mechanisms for React applications

Files:

  • web/src/app/dashboard/moderation/page.tsx
web/src/app/dashboard/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

For dashboard routes, add a matcher entry to dashboardTitleMatchers in web/src/lib/page-titles.ts: use exact equality for leaf routes (pathname === '/dashboard/my-route') and subtree checks (pathname.startsWith('/dashboard/my-route/')); export metadata using createPageMetadata(title) for SSR entry points

Files:

  • web/src/app/dashboard/moderation/page.tsx
web/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

web/src/app/**/*.{ts,tsx}: Export metadata using createPageMetadata() from web/src/lib/page-titles.ts in SSR entry points for dashboard pages
Use DashboardTitleSync component and getDashboardDocumentTitle() for client-side navigation title updates in the dashboard

Files:

  • web/src/app/dashboard/moderation/page.tsx
src/api/routes/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Create API route handlers in src/api/routes/ and mount them in src/api/server.js; add auth middleware if the endpoint requires authentication

Files:

  • src/api/routes/community.js
🧠 Learnings (14)
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/commands/**/*.js : Create slash command definitions in `src/commands/`, exporting a slash command builder and an `execute` function

Applied to files:

  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/**/*.js : Never use `console.*` methods; use the Winston logger instead via `import logger from '../logger.js'` (adjust path as needed), then call `logger.info()`, `logger.warn()`, `logger.error()`, or `logger.debug()`

Applied to files:

  • src/modules/events/clientReady.js
📚 Learning: 2026-03-12T02:03:52.709Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.709Z
Learning: Applies to **/*.{js,ts,tsx,mjs} : Use Winston logger from `src/logger.js` — never use `console.*` methods

Applied to files:

  • src/modules/events/clientReady.js
📚 Learning: 2026-03-11T17:18:17.626Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T17:18:17.626Z
Learning: Applies to src/**/{startup,command-register,reload}*.{js,ts} : Remove process.env.GUILD_ID runtime reads from bot startup and reload command registration

Applied to files:

  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to src/commands/**/*.js : Guild owners should be explicitly exempted from role hierarchy restrictions in command handlers to match Discord permission model expectations

Applied to files:

  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/**/*.js : Always use `safeReply()`, `safeSend()`, or `safeEditReply()` instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully

Applied to files:

  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-12T02:03:52.709Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.709Z
Learning: Applies to {src/commands/**/*.{js,ts},src/modules/**/*.{js,ts},src/index.js} : Use `safeReply()`, `safeSend()`, or `safeEditReply()` for Discord messages — never send unsafe messages directly

Applied to files:

  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to **/*.{js,ts,tsx} : Maintain a maximum line width of 100 characters

Applied to files:

  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-12T02:03:52.709Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.709Z
Learning: Applies to web/src/app/**/*.{ts,tsx} : Use `DashboardTitleSync` component and `getDashboardDocumentTitle()` for client-side navigation title updates in the dashboard

Applied to files:

  • web/src/app/dashboard/moderation/page.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/app/dashboard/**/*.tsx : For dashboard routes, add a matcher entry to `dashboardTitleMatchers` in `web/src/lib/page-titles.ts`: use exact equality for leaf routes (`pathname === '/dashboard/my-route'`) and subtree checks (`pathname.startsWith('/dashboard/my-route/')`); export `metadata` using `createPageMetadata(title)` for SSR entry points

Applied to files:

  • web/src/app/dashboard/moderation/page.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'

Applied to files:

  • web/src/app/dashboard/moderation/page.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to src/api/routes/*.js : Add adaptDeleteGuildIdParam in API route handlers to enforce guild moderation checks on DELETE requests without losing record id

Applied to files:

  • web/src/app/dashboard/moderation/page.tsx
  • src/api/routes/community.js
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to web/src/pages/dashboard/**/*.{ts,tsx} : Use shared title helpers from web/src/lib/page-titles.ts for setting browser titles in dashboard pages

Applied to files:

  • web/src/app/dashboard/moderation/page.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to web/src/app/api/**/route.ts : Include guildId in signed WebSocket ticket payload when issuing tickets from dashboard endpoints

Applied to files:

  • src/api/routes/community.js
🪛 GitHub Actions: CI
web/src/app/dashboard/moderation/page.tsx

[error] 85-89: Formatter would have printed the following content: code formatting diff detected by formatter.

🪛 GitHub Check: SonarCloud Code Analysis
src/modules/events/commandInteraction.js

[failure] 18-18: Refactor this function to reduce its Cognitive Complexity from 19 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZz9qNesde4KftT3hj72&open=AZz9qNesde4KftT3hj72&pullRequest=306

🔇 Additional comments (6)
src/modules/events/clientReady.js (1)

7-7: Use the shared logger instance and include stack metadata on failures.

Line 7 imports a named helper instead of the shared logger instance, and lines 20-22 drop err.stack, which weakens production debugging.

Proposed fix
-import { error } from '../../logger.js';
+import logger from '../../logger.js';
     } catch (err) {
-      error('Command registration failed', {
+      logger.error('Command registration failed', {
         error: err instanceof Error ? err.message : String(err),
+        stack: err instanceof Error ? err.stack : undefined,
       });
     }

As per coding guidelines src/**/*.js: "use the Winston logger instead via import logger ..., then call logger.info()/warn()/error()/debug()."

Also applies to: 20-22

src/modules/events/commandInteraction.js (3)

17-111: Consider extracting helpers to reduce cognitive complexity.

SonarCloud flags cognitive complexity at 19 (limit: 15). The handler manages autocomplete, permission checks, command lookup, execution, and error recovery—each adding nesting depth. Extracting focused helpers like handleAutocomplete(client, interaction) and handleChatInputCommand(client, interaction) would improve readability and testability.


20-35: LGTM on autocomplete handling.

The autocomplete flow now correctly responds with an empty array when the command doesn't support autocomplete (line 23) and also catches errors to respond gracefully (lines 33). This ensures Discord's autocomplete interaction is always acknowledged within the required timeframe.


77-84: LGTM on fire-and-forget analytics.

The logCommandUsage call now includes a .catch() handler that logs failures, making the fire-and-forget intent explicit and ensuring promise rejections don't go unhandled.

src/api/routes/community.js (2)

421-423: OpenAPI contract update is clear and aligned with runtime behavior.

Good clarification for totalMessagesSent semantics and safe-number clamping.


472-475: Stats aggregation + numeric clamping fix looks correct.

Casting aggregate to bigint and clamping response to Number.MAX_SAFE_INTEGER is a solid compatibility/safety fix for the integer response contract.

Also applies to: 532-535

Comment thread web/src/app/dashboard/moderation/page.tsx Outdated
Comment thread web/src/app/dashboard/moderation/page.tsx Outdated
Comment thread web/src/app/dashboard/moderation/page.tsx
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 tackles deferred tech debt across the Discord bot and the Next.js dashboard by extracting event handlers, improving client-side logging, centralizing dashboard state into Zustand stores, and adding UX resiliency (error boundaries, skeletons, toasts), plus some API hardening and middleware maintainability work.

Changes:

  • Extracted bot clientReady / slash command interaction handling into src/modules/events/* with dedicated tests, simplifying src/index.js.
  • Introduced/expanded dashboard UX primitives (ErrorBoundary + skeletons) and moved members/moderation data fetching into Zustand stores with accompanying web tests.
  • Added defense-in-depth validation for the XP proxy route and improved operational tooling (rate limiter sweep/size helpers; config validation constraints; bigint-safe community stats).

Reviewed changes

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

Show a summary per file
File Description
web/tests/stores/moderation-store.test.ts Adds store-level tests for moderation state + fetch flows.
web/tests/stores/members-store.test.ts Adds store-level tests for members state + fetch flows.
web/tests/components/dashboard/skeletons.test.tsx Tests new dashboard skeleton components.
web/tests/components/dashboard/error-boundary.test.tsx Tests new ErrorBoundary behavior and recovery.
web/tests/api/xp-route.test.ts Tests XP proxy route validation/sanitization behavior.
web/src/stores/moderation-store.ts New moderation Zustand store with fetchers for stats/cases/history.
web/src/stores/members-store.ts Extends members store with request-sequenced fetchMembers action.
web/src/lib/logger.ts Implements browser logger shim (level-aware, structured prefix) + improved error formatting.
web/src/hooks/use-user-history.ts Removes hook (migrated to store-based fetching).
web/src/hooks/use-moderation-stats.ts Removes hook (migrated to store-based fetching).
web/src/hooks/use-moderation-cases.ts Removes hook (migrated to store-based fetching).
web/src/components/ui/error-boundary.tsx Updates ErrorBoundary to use logger + toast reporting.
web/src/components/dashboard/skeletons.tsx Adds reusable dashboard skeleton UI components.
web/src/components/dashboard/performance-dashboard.tsx Adds toast feedback for threshold save success/failure.
web/src/components/dashboard/config-workspace/config-categories.ts Replaces console.warn with logger and improves fallback category selection.
web/src/app/dashboard/tickets/page.tsx Wraps tickets list in ErrorBoundary + improves responsive filters/table container.
web/src/app/dashboard/tickets/[ticketId]/page.tsx Adds ErrorBoundary + skeleton loading UI + no-guild empty state.
web/src/app/dashboard/temp-roles/page.tsx Adds ErrorBoundary, toasts, revoke confirmation dialog, and responsive table adjustments.
web/src/app/dashboard/performance/page.tsx Wraps performance dashboard with ErrorBoundary.
web/src/app/dashboard/moderation/page.tsx Switches moderation page to store-based fetch flows + ErrorBoundary.
web/src/app/dashboard/members/page.tsx Switches members page to store-based fetching + ErrorBoundary + responsive filter bar.
web/src/app/dashboard/members/[userId]/page.tsx Wraps member detail in ErrorBoundary.
web/src/app/dashboard/logs/page.tsx Wraps logs page in ErrorBoundary.
web/src/app/dashboard/conversations/page.tsx Adds ErrorBoundary, improves responsive filters/table, and adds keyboard-accessible rows.
web/src/app/dashboard/conversations/[conversationId]/page.tsx Adds ErrorBoundary + skeleton loading UI + guards for missing guild/conversationId.
web/src/app/dashboard/config/page.tsx Wraps config editor in ErrorBoundary.
web/src/app/dashboard/audit-log/page.tsx Adds ErrorBoundary, improves responsive filters/table container.
web/src/app/api/moderation/cases/route.ts Allows forwarding order query param to bot API.
web/src/app/api/guilds/[guildId]/members/[userId]/xp/route.ts Adds strict payload validation + bounds + unknown-field stripping.
tests/modules/events/errors.test.js New unit tests for extracted bot error/shard disconnect handlers.
tests/modules/events/commandInteraction.test.js New unit tests for extracted slash command/autocomplete dispatcher.
tests/modules/events/clientReady.test.js New unit tests for extracted clientReady command registration.
tests/index.test.js Removes event-handler tests now covered by new module-level tests.
tests/api/utils/configValidation.test.js Adds tests for new config validation constraints (min/max/maxLength/openProperties).
tests/api/routes/community.test.js Updates expectation to match all-time message sum semantics.
tests/api/middleware/rateLimit.test.js Adds tests for new sweep()/size()/destroy() middleware helpers.
src/modules/events/errors.js Enhances Discord error logging + shard disconnect warning handler.
src/modules/events/commandInteraction.js New module for interactionCreate slash command/autocomplete handling.
src/modules/events/clientReady.js New module for registering slash commands on client ready.
src/modules/events.js Adds dispatcher wiring for new extracted event modules.
src/index.js Removes inline handlers; relies on registerEventHandlers() modules.
src/api/utils/configValidation.js Adds schema constraints and enforces min/max/maxLength in validation.
src/api/routes/community.js Switches message sum to bigint + clamps safely and documents semantics.
src/api/middleware/rateLimit.js Adds sweep()/size() and refactors cleanup for testability/ops.
migrations/README.md Documents migration numbering anomaly and placeholder migration purpose.
migrations/012_placeholder.cjs Improves placeholder migration documentation (sequence alignment).

You can also share your feedback on Copilot code review. Take the survey.

Comment thread web/src/stores/members-store.ts
Comment thread web/src/stores/moderation-store.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web/src/app/dashboard/tickets/[ticketId]/page.tsx (1)

54-87: ⚠️ Potential issue | 🟠 Major

Prevent stale/out-of-order fetch responses from overwriting current ticket state.

Line 54-Line 87 can race when route params change quickly; an older response can win and render mismatched detail.

Suggested fix
-import { useCallback, useEffect, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
@@
 export default function TicketDetailPage() {
@@
+  const requestSeqRef = useRef(0);
@@
   const fetchDetail = useCallback(async () => {
+    const requestSeq = ++requestSeqRef.current;
+
     if (!guildId || !ticketId) {
+      setData(null);
+      setError(null);
       setLoading(false);
       return;
     }
 
     setLoading(true);
     setError(null);
+    setData(null);
@@
       const result = (await res.json()) as TicketDetail;
+      if (requestSeq !== requestSeqRef.current) {
+        return;
+      }
+      if (result.guild_id !== guildId || String(result.id) !== ticketId) {
+        setError('Ticket data does not match the selected context');
+        return;
+      }
       setData(result);
     } catch (err) {
+      if (requestSeq !== requestSeqRef.current) {
+        return;
+      }
       setError(err instanceof Error ? err.message : 'Failed to fetch ticket');
     } finally {
-      setLoading(false);
+      if (requestSeq === requestSeqRef.current) {
+        setLoading(false);
+      }
     }
   }, [guildId, ticketId, router]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/app/dashboard/tickets/`[ticketId]/page.tsx around lines 54 - 87, The
fetchDetail callback can be raced by rapid guildId/ticketId changes causing
older responses to overwrite state; fix by wiring an AbortController: create a
persistent ref (e.g., fetchAbortRef) and inside fetchDetail abort any prior
controller before creating a new AbortController, pass the new controller.signal
into fetch, and on cleanup/route change abort it; when handling the response,
avoid calling setData/setError if the request was aborted (or swallow the
AbortError by checking err.name === 'AbortError') so only the latest,
non-aborted fetch can update state. Use the existing fetchDetail, setData,
setError, guildId, ticketId, and router identifiers to locate where to add the
ref/abort logic.
tests/api/routes/community.test.js (1)

244-267: ⚠️ Potential issue | 🟡 Minor

Add a clamp regression test for totalMessagesSent.

This test only covers a small value. Please add one case where DB returns a value above
Number.MAX_SAFE_INTEGER (e.g., bigint string) and assert the response is clamped.

As per coding guidelines tests/**/*.test.js: “never lower thresholds—add tests to cover new code instead”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/api/routes/community.test.js` around lines 244 - 267, Add a new test
that verifies totalMessagesSent is clamped when the DB returns a value larger
than Number.MAX_SAFE_INTEGER: create an `it` (e.g., "clamps totalMessagesSent
when DB returns > MAX_SAFE_INTEGER") that uses the same `mockPool.query`
sequence as the existing test but have the second `mockResolvedValueOnce` return
rows: [{ total: '9007199254740993' }] (a bigint string >
Number.MAX_SAFE_INTEGER); call
`request(app).get(`/api/v1/community/${GUILD_ID}/stats`)` and assert the
response `totalMessagesSent` equals Number.MAX_SAFE_INTEGER while the other
fields (memberCount, activeProjects, challengesCompleted, topContributors) are
asserted as in the existing test.
♻️ Duplicate comments (1)
src/modules/events/commandInteraction.js (1)

29-37: ⚠️ Potential issue | 🟡 Minor

Guard fallback autocomplete response to avoid double-ack rejections.

If command.autocomplete() already responded and then throws, the fallback respond([]) can
reject. Guard with interaction.responded before fallback response.

Suggested patch
   try {
     await command.autocomplete(interaction);
   } catch (err) {
-    error('Autocomplete error', {
+    logger.error('Autocomplete error', {
       command: interaction.commandName,
       error: getErrorMessage(err),
     });
-    await interaction.respond([]);
+    if (!interaction.responded) {
+      await interaction.respond([]).catch(() => {});
+    }
   }
 }
For discord.js v14 AutocompleteInteraction: does calling `respond()` after an interaction is already responded reject, and is `interaction.responded` the supported guard?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/events/commandInteraction.js` around lines 29 - 37, The fallback
should avoid calling interaction.respond([]) if the AutocompleteInteraction has
already been acknowledged; inside the catch for
command.autocomplete(interaction) check interaction.responded (the
AutocompleteInteraction property) before calling interaction.respond([]) to
prevent double-ack rejections. Keep the existing error(...) log and
getErrorMessage(err) call, but wrap the fallback respond in a conditional that
only calls interaction.respond([]) when interaction.responded is false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/api/routes/moderation.js`:
- Line 153: The route now supports a query parameter named "order" (see const
order = req.query.order === 'asc' ? 'ASC' : 'DESC' in moderation.js) but the
OpenAPI documentation block for this endpoint is missing that parameter; update
the OpenAPI parameters array for this moderation route to include a new query
parameter "order" (type: string, enum: ["asc","desc"], default: "desc",
description: "Sort order for results") so the spec matches the implemented
behavior and validation.

In `@src/modules/events/commandInteraction.js`:
- Line 7: Replace the named imports { debug, error, info, warn } with the
repository-standard default logger import (import logger from '../../logger.js')
in src/modules/events/commandInteraction.js, then update all calls that
currently use debug(...), info(...), warn(...), error(...) to use
logger.debug(...), logger.info(...), logger.warn(...), logger.error(...)
respectively (ensure every occurrence in the file is changed, e.g., inside the
command interaction handler functions and any helper functions in this module).

In `@web/src/app/dashboard/tickets/`[ticketId]/page.tsx:
- Line 236: The transcript row key currently uses only msg.timestamp which can
collide; update the key on the transcript row (the div with key={msg.timestamp})
to a more collision-resistant identifier such as msg.id or a composite like
`${msg.timestamp}-${msg.id}` (or fallback to the map index only if no stable id
exists) so each rendered message has a unique, stable key for reconciliation.

In `@web/src/stores/members-store.ts`:
- Around line 98-158: fetchMembers is over the Sonar complexity limit due to
separate branches calling set for append vs replace; refactor to compute the new
state values first and call set exactly once. Preserve existing checks
(requestId vs latestMembersRequestId, 401 handling, AbortError) and the returned
strings, but instead of the if (opts.append) { set({ members: [...state.members,
...data.members], ... }) } else { set({ members: data.members, ... }) } pattern,
derive membersArray = opts.append ? [...state.members, ...data.members] :
data.members (use the updater form to access previous state when needed) and
then call set({ members: membersArray, nextAfter: data.nextAfter, total:
data.total, filteredTotal: data.filteredTotal ?? null, loading: false }) once to
reduce cyclomatic complexity.

In `@web/tests/components/dashboard/error-boundary.test.tsx`:
- Around line 12-19: The test mutates console.error (originalConsoleError,
beforeEach, afterEach) which violates the rule; replace direct console.* usage
by importing the project's Winston logger (e.g., from src/logger.js) and
spy/mock its error method with vi.fn() in the beforeEach and restore it in
afterEach (also preserve originalNodeEnv logic as needed). Update assertions to
check logger.error calls instead of console.error, and ensure the original
logger.error is restored after each test to avoid global side effects.
- Around line 44-52: In the "renders default error UI when child throws" test
add an assertion that the mocked logger.error was invoked when ErrorBoundary
catches the error from ThrowingComponent: after rendering and asserting the UI,
assert something like expect(logger.error).toHaveBeenCalled() (or
.toHaveBeenCalledTimes(1) / .toHaveBeenCalledWith(...) depending on existing
mock setup) so the test locks in the logger.error integration; reference the
mocked logger import (logger.error) and the test block containing ErrorBoundary
and ThrowingComponent.

In `@web/tests/stores/members-store.test.ts`:
- Around line 276-278: Replace the redundant type assertions "as
Promise<Response>" by using generic promise syntax so the promises are typed
explicitly; specifically, change constructions like the one assigning
rejectFirst (the expression currently written as new Promise((_, reject) => {
rejectFirst = reject; }) as Promise<Response>) to use new Promise<Response>((_,
reject) => { rejectFirst = reject; }) and make the analogous change to the other
promise at the second occurrence (around line 318) so both use new
Promise<Response>(...) instead of asserting with "as".

---

Outside diff comments:
In `@tests/api/routes/community.test.js`:
- Around line 244-267: Add a new test that verifies totalMessagesSent is clamped
when the DB returns a value larger than Number.MAX_SAFE_INTEGER: create an `it`
(e.g., "clamps totalMessagesSent when DB returns > MAX_SAFE_INTEGER") that uses
the same `mockPool.query` sequence as the existing test but have the second
`mockResolvedValueOnce` return rows: [{ total: '9007199254740993' }] (a bigint
string > Number.MAX_SAFE_INTEGER); call
`request(app).get(`/api/v1/community/${GUILD_ID}/stats`)` and assert the
response `totalMessagesSent` equals Number.MAX_SAFE_INTEGER while the other
fields (memberCount, activeProjects, challengesCompleted, topContributors) are
asserted as in the existing test.

In `@web/src/app/dashboard/tickets/`[ticketId]/page.tsx:
- Around line 54-87: The fetchDetail callback can be raced by rapid
guildId/ticketId changes causing older responses to overwrite state; fix by
wiring an AbortController: create a persistent ref (e.g., fetchAbortRef) and
inside fetchDetail abort any prior controller before creating a new
AbortController, pass the new controller.signal into fetch, and on cleanup/route
change abort it; when handling the response, avoid calling setData/setError if
the request was aborted (or swallow the AbortError by checking err.name ===
'AbortError') so only the latest, non-aborted fetch can update state. Use the
existing fetchDetail, setData, setError, guildId, ticketId, and router
identifiers to locate where to add the ref/abort logic.

---

Duplicate comments:
In `@src/modules/events/commandInteraction.js`:
- Around line 29-37: The fallback should avoid calling interaction.respond([])
if the AutocompleteInteraction has already been acknowledged; inside the catch
for command.autocomplete(interaction) check interaction.responded (the
AutocompleteInteraction property) before calling interaction.respond([]) to
prevent double-ack rejections. Keep the existing error(...) log and
getErrorMessage(err) call, but wrap the fallback respond in a conditional that
only calls interaction.respond([]) when interaction.responded is false.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 77ccb9b1-8d42-4f4d-a7b9-45f5816714c2

📥 Commits

Reviewing files that changed from the base of the PR and between 8b6a32e and 9164f96.

📒 Files selected for processing (14)
  • src/api/middleware/rateLimit.js
  • src/api/routes/community.js
  • src/api/routes/moderation.js
  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
  • tests/api/routes/community.test.js
  • tests/api/routes/moderation.test.js
  • tests/modules/events/clientReady.test.js
  • web/src/app/dashboard/members/[userId]/page.tsx
  • web/src/app/dashboard/moderation/page.tsx
  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • web/src/stores/members-store.ts
  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/tests/stores/members-store.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (12)
**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ESM-only syntax: import/export, never require()/module.exports

Files:

  • src/modules/events/clientReady.js
  • src/api/routes/moderation.js
  • tests/modules/events/clientReady.test.js
  • src/api/routes/community.js
  • tests/api/routes/community.test.js
  • src/api/middleware/rateLimit.js
  • src/modules/events/commandInteraction.js
  • tests/api/routes/moderation.test.js
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes
Always include semicolons at the end of statements
Use 2-space indentation (spaces, not tabs)
Always include trailing commas in multi-line arrays, objects, and function parameters
Maintain a maximum line width of 100 characters

Files:

  • src/modules/events/clientReady.js
  • src/api/routes/moderation.js
  • web/tests/components/dashboard/error-boundary.test.tsx
  • tests/modules/events/clientReady.test.js
  • web/src/stores/members-store.ts
  • web/tests/stores/members-store.test.ts
  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • src/api/routes/community.js
  • tests/api/routes/community.test.js
  • src/api/middleware/rateLimit.js
  • web/src/app/dashboard/members/[userId]/page.tsx
  • web/src/app/dashboard/moderation/page.tsx
  • src/modules/events/commandInteraction.js
  • tests/api/routes/moderation.test.js
src/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.js: Never use console.* methods; use the Winston logger instead via import logger from '../logger.js' (adjust path as needed), then call logger.info(), logger.warn(), logger.error(), or logger.debug()
Always use safeReply(), safeSend(), or safeEditReply() instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully

Files:

  • src/modules/events/clientReady.js
  • src/api/routes/moderation.js
  • src/api/routes/community.js
  • src/api/middleware/rateLimit.js
  • src/modules/events/commandInteraction.js
src/modules/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Create feature modules in src/modules/ and add corresponding config sections to config.json

Files:

  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
**/*.{js,ts,tsx,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx,mjs}: Use ESM syntax (import/export) — CommonJS is not allowed
Use single quotes for strings — double quotes only allowed in JSON files
Always include semicolons at end of statements
Use 2-space indentation for all code
Use Winston logger from src/logger.js — never use console.* methods

Files:

  • src/modules/events/clientReady.js
  • src/api/routes/moderation.js
  • web/tests/components/dashboard/error-boundary.test.tsx
  • tests/modules/events/clientReady.test.js
  • web/src/stores/members-store.ts
  • web/tests/stores/members-store.test.ts
  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • src/api/routes/community.js
  • tests/api/routes/community.test.js
  • src/api/middleware/rateLimit.js
  • web/src/app/dashboard/members/[userId]/page.tsx
  • web/src/app/dashboard/moderation/page.tsx
  • src/modules/events/commandInteraction.js
  • tests/api/routes/moderation.test.js
{src/commands/**/*.{js,ts},src/modules/**/*.{js,ts},src/index.js}

📄 CodeRabbit inference engine (AGENTS.md)

Use safeReply(), safeSend(), or safeEditReply() for Discord messages — never send unsafe messages directly

Files:

  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
src/api/routes/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Create API route handlers in src/api/routes/ and mount them in src/api/server.js; add auth middleware if the endpoint requires authentication

Files:

  • src/api/routes/moderation.js
  • src/api/routes/community.js
web/tests/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with the jsdom environment and React Testing Library, matching the web/src/ structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Files:

  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/tests/stores/members-store.test.ts
tests/**/*.test.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

tests/**/*.test.js: Write bot tests using Vitest 4 with the node environment, matching the src/ structure in the tests/ directory
Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead

Files:

  • tests/modules/events/clientReady.test.js
  • tests/api/routes/community.test.js
  • tests/api/routes/moderation.test.js
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Never use console.* methods in web dashboard code; use appropriate logging mechanisms for React applications

Files:

  • web/src/stores/members-store.ts
  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • web/src/app/dashboard/members/[userId]/page.tsx
  • web/src/app/dashboard/moderation/page.tsx
web/src/app/dashboard/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

For dashboard routes, add a matcher entry to dashboardTitleMatchers in web/src/lib/page-titles.ts: use exact equality for leaf routes (pathname === '/dashboard/my-route') and subtree checks (pathname.startsWith('/dashboard/my-route/')); export metadata using createPageMetadata(title) for SSR entry points

Files:

  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • web/src/app/dashboard/members/[userId]/page.tsx
  • web/src/app/dashboard/moderation/page.tsx
web/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

web/src/app/**/*.{ts,tsx}: Export metadata using createPageMetadata() from web/src/lib/page-titles.ts in SSR entry points for dashboard pages
Use DashboardTitleSync component and getDashboardDocumentTitle() for client-side navigation title updates in the dashboard

Files:

  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • web/src/app/dashboard/members/[userId]/page.tsx
  • web/src/app/dashboard/moderation/page.tsx
🧠 Learnings (23)
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/**/*.js : Never use `console.*` methods; use the Winston logger instead via `import logger from '../logger.js'` (adjust path as needed), then call `logger.info()`, `logger.warn()`, `logger.error()`, or `logger.debug()`

Applied to files:

  • src/modules/events/clientReady.js
📚 Learning: 2026-03-12T02:03:52.709Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.709Z
Learning: Applies to **/*.{js,ts,tsx,mjs} : Use Winston logger from `src/logger.js` — never use `console.*` methods

Applied to files:

  • src/modules/events/clientReady.js
📚 Learning: 2026-03-11T17:18:17.626Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T17:18:17.626Z
Learning: Applies to src/**/{startup,command-register,reload}*.{js,ts} : Remove process.env.GUILD_ID runtime reads from bot startup and reload command registration

Applied to files:

  • src/modules/events/clientReady.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/commands/**/*.js : Create slash command definitions in `src/commands/`, exporting a slash command builder and an `execute` function

Applied to files:

  • src/modules/events/clientReady.js
  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-10T23:29:51.063Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:29:51.063Z
Learning: Applies to src/api/routes/*.js : Apply requireGuildModerator authorization check to DELETE endpoints that modify guild-scoped resources

Applied to files:

  • src/api/routes/moderation.js
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to src/api/routes/*.js : Add adaptDeleteGuildIdParam in API route handlers to enforce guild moderation checks on DELETE requests without losing record id

Applied to files:

  • src/api/routes/moderation.js
  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • src/api/routes/community.js
  • web/src/app/dashboard/moderation/page.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:52.709Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.709Z
Learning: Applies to **/*.{js,ts,tsx,mjs} : Use single quotes for strings — double quotes only allowed in JSON files

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/src/app/dashboard/members/[userId]/page.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to **/*.{js,ts,tsx} : Use single quotes for strings (except in JSON files); no double quotes

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/src/app/dashboard/members/[userId]/page.tsx
📚 Learning: 2026-03-12T02:03:52.709Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.709Z
Learning: Applies to web/src/app/**/*.{ts,tsx} : Use `DashboardTitleSync` component and `getDashboardDocumentTitle()` for client-side navigation title updates in the dashboard

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/src/app/dashboard/members/[userId]/page.tsx
  • web/src/app/dashboard/moderation/page.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/app/dashboard/**/*.tsx : For dashboard routes, add a matcher entry to `dashboardTitleMatchers` in `web/src/lib/page-titles.ts`: use exact equality for leaf routes (`pathname === '/dashboard/my-route'`) and subtree checks (`pathname.startsWith('/dashboard/my-route/')`); export `metadata` using `createPageMetadata(title)` for SSR entry points

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/src/app/dashboard/members/[userId]/page.tsx
  • web/src/app/dashboard/moderation/page.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/**/*.{ts,tsx} : Never use `console.*` methods in web dashboard code; use appropriate logging mechanisms for React applications

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • web/src/app/dashboard/members/[userId]/page.tsx
  • web/src/app/dashboard/moderation/page.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to web/src/pages/dashboard/**/*.{ts,tsx} : Use shared title helpers from web/src/lib/page-titles.ts for setting browser titles in dashboard pages

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/src/app/dashboard/members/[userId]/page.tsx
  • web/src/app/dashboard/moderation/page.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to web/src/app/api/**/route.ts : Include guildId in signed WebSocket ticket payload when issuing tickets from dashboard endpoints

Applied to files:

  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • src/api/routes/community.js
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to src/api/ws/*.js : Bind audit-log WebSocket auth tickets to guild context by including nonce.expiry.guildId.hmac in ticket validation

Applied to files:

  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to **/*.json : Use double quotes in JSON files (standard JSON format); this exception applies only to JSON files, not JavaScript/TypeScript

Applied to files:

  • web/src/app/dashboard/members/[userId]/page.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to **/*.{js,ts,tsx} : Maintain a maximum line width of 100 characters

Applied to files:

  • web/src/app/dashboard/moderation/page.tsx
  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/**/*.js : Always use `safeReply()`, `safeSend()`, or `safeEditReply()` instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully

Applied to files:

  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-12T02:03:52.709Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.709Z
Learning: Applies to {src/commands/**/*.{js,ts},src/modules/**/*.{js,ts},src/index.js} : Use `safeReply()`, `safeSend()`, or `safeEditReply()` for Discord messages — never send unsafe messages directly

Applied to files:

  • src/modules/events/commandInteraction.js
🪛 GitHub Check: SonarCloud Code Analysis
web/src/stores/members-store.ts

[failure] 98-98: Refactor this function to reduce its Cognitive Complexity from 16 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZz94AJFuktEfKwL9SAY&open=AZz94AJFuktEfKwL9SAY&pullRequest=306

web/tests/stores/members-store.test.ts

[warning] 316-318: This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZz94AMOuktEfKwL9SAa&open=AZz94AMOuktEfKwL9SAa&pullRequest=306


[warning] 276-278: This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZz94AMOuktEfKwL9SAZ&open=AZz94AMOuktEfKwL9SAZ&pullRequest=306

web/src/app/dashboard/tickets/[ticketId]/page.tsx

[warning] 124-124: Do not use Array index in keys

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZzyqhCUYIscLWHB68XV&open=AZzyqhCUYIscLWHB68XV&pullRequest=306


[warning] 139-139: Do not use Array index in keys

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZzyqhCUYIscLWHB68XW&open=AZzyqhCUYIscLWHB68XW&pullRequest=306

🔇 Additional comments (21)
src/api/middleware/rateLimit.js (3)

16-23: JSDoc return contract is now accurate and maintainable.

Good update documenting all exposed lifecycle helpers (destroy, sweep, size) in a readable multi-line form.


36-53: Shared stale-entry sweeper is a strong cleanup refactor.

Centralizing stale-client eviction in sweepStaleEntries() avoids logic drift between periodic cleanup and manual sweeping.


86-95: sweep() and size() middleware helpers are well exposed and documented.

This improves observability/test ergonomics while keeping behavior straightforward.

src/api/routes/moderation.js (1)

193-193: Implementation is safe from SQL injection.

The order variable is derived from a strict ternary check (req.query.order === 'asc' ? 'ASC' : 'DESC'), ensuring only the literal strings 'ASC' or 'DESC' can be interpolated into the query. This is a safe pattern for dynamic ORDER BY clauses.

tests/api/routes/moderation.test.js (1)

148-160: Good test coverage for the new ordering feature.

The test correctly verifies that passing order=asc results in ORDER BY created_at ASC in the generated SQL. The assertion on mockPool.query.mock.calls[0][0] appropriately validates the query string rather than just the response, ensuring the implementation detail is correct.

web/src/app/dashboard/members/[userId]/page.tsx (4)

23-23: ErrorBoundary import is now correctly used.

Nice cleanup—this import is now actively used by the page wrapper, removing prior dead-import risk.


331-331: Deferred object URL revocation is a good reliability fix.

This reduces the chance of premature URL cleanup interfering with file download start.


399-607: ErrorBoundary wrapping is a solid UX hardening change.

Good extraction point: wrapping the full detail surface improves failure containment for this route.


400-401: Use single-quoted string props for ErrorBoundary.

These two JSX string literals still use double quotes; switch to single quotes to match repository style.

🔧 Suggested fix
-      title="Member details failed to load"
-      description="There was a problem loading this member&apos;s details. Try again or refresh the page."
+      title='Member details failed to load'
+      description='There was a problem loading this member&apos;s details. Try again or refresh the page.'

As per coding guidelines: **/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes.

web/src/app/dashboard/moderation/page.tsx (5)

57-90: Excellent implementation of per-resource AbortControllers.

Each fetch operation now has its own scoped AbortController, properly preventing the race condition where later effects would cancel earlier ones. The cleanup functions correctly abort on unmount or dependency changes, and the store functions handle AbortError gracefully.


92-118: Clean manual refresh implementation.

The handleRefresh callback correctly handles concurrent fetching with proper unauthorized checks. Not using an AbortController here is appropriate since user-initiated refreshes should complete unless the component unmounts.


204-204: Line exceeds 100-character limit.

This line is approximately 112 characters. Break the JSX element across multiple lines for guideline compliance. As per coding guidelines: "Maintain a maximum line width of 100 characters."

Suggested fix
-                  <Search className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
+                  <Search
+                    className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"
+                  />

253-267: Clean read-only CaseTable configuration.

The user history CaseTable correctly uses fixed values and no-op handlers since filtering/sorting is not applicable to the lookup results. This prevents unnecessary complexity while reusing the same component.


1-14: Dashboard title matcher is properly configured.

The matcher entry for /dashboard/moderation is already present in dashboardTitleMatchers with correct pattern matching (pathname === '/dashboard/moderation' || pathname.startsWith('/dashboard/moderation/')). As a client component, this page correctly avoids direct metadata export; title updates are handled via DashboardTitleSync at the layout level.

web/src/app/dashboard/tickets/[ticketId]/page.tsx (1)

110-156: Good UX improvement for loading and missing-guild states.

Skeleton-first loading plus explicit no-guild rendering closes the previous blank-state gap cleanly.

web/tests/components/dashboard/error-boundary.test.tsx (1)

105-131: Nice coverage for environment-specific toast behavior.

The explicit development vs production assertions are solid and align with the component’s branching logic.

src/modules/events/clientReady.js (1)

14-25: Clean extraction and solid failure logging.

Ready-handler registration and command-registration error handling look correct.

src/api/routes/community.js (1)

472-475: Stats aggregation change is consistent and safer.

All-time aggregation plus explicit MAX_SAFE_INTEGER clamping is a good contract-preserving fix.

Also applies to: 532-535

tests/modules/events/clientReady.test.js (1)

42-68: Good behavioral coverage for the extracted ready handler.

The suite validates listener wiring, registration invocation, and error logging path effectively.

web/src/stores/members-store.ts (1)

68-97: Good request invalidation pattern on reset.

Incrementing latestMembersRequestId in resetAll() cleanly prevents late in-flight writes from mutating fresh state.

web/tests/stores/members-store.test.ts (1)

269-342: Great coverage for stale/superseded request behavior.

These two tests directly protect the latestMembersRequestId invalidation contract and reduce regression risk in concurrent fetch scenarios.

Comment thread src/api/routes/moderation.js
Comment thread src/modules/events/commandInteraction.js Outdated
Comment thread web/src/app/dashboard/tickets/[ticketId]/page.tsx Outdated
Comment thread web/src/stores/members-store.ts
Comment thread web/tests/components/dashboard/error-boundary.test.tsx Outdated
Comment thread web/tests/components/dashboard/error-boundary.test.tsx
Comment thread web/tests/stores/members-store.test.ts
Copilot AI review requested due to automatic review settings March 19, 2026 18:51
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 tackles deferred tech-debt and review follow-ups across the bot + dashboard, focusing on extracting Discord event handlers, improving dashboard UX (error boundaries, skeletons, toasts), and tightening validation/observability.

Changes:

  • Extracted Discord clientReady / slash-command / error handlers into dedicated modules with targeted tests.
  • Added/expanded dashboard infrastructure: browser logger shim, reusable ErrorBoundary, skeleton components, and Zustand stores for members/moderation (with new tests).
  • Strengthened validation and safety: XP proxy payload validation, config schema range/length constraints, moderation cases sort order, and rate-limit lifecycle helpers.

Reviewed changes

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

Show a summary per file
File Description
web/tests/stores/moderation-store.test.ts Adds unit tests for the new moderation Zustand store actions + fetch behavior.
web/tests/stores/members-store.test.ts Adds unit tests for members Zustand store fetching, paging, abort, and stale-request handling.
web/tests/components/dashboard/skeletons.test.tsx Tests newly introduced dashboard skeleton components.
web/tests/components/dashboard/error-boundary.test.tsx Tests ErrorBoundary behavior (logging + toast messaging + reset).
web/tests/api/xp-route.test.ts Adds tests for XP proxy route validation and body sanitization.
web/src/stores/moderation-store.ts Moves moderation data fetching/state into a centralized Zustand store.
web/src/stores/members-store.ts Adds store-owned fetchMembers with stale-request protection via request id.
web/src/lib/logger.ts Implements a browser-capable, level-aware logger shim (warn/error always; debug/info dev-only).
web/src/hooks/use-user-history.ts Removes hook (logic moved into store + page effects).
web/src/hooks/use-moderation-stats.ts Removes hook (logic moved into store + page effects).
web/src/hooks/use-moderation-cases.ts Removes hook (logic moved into store + page effects, server-side ordering).
web/src/components/ui/error-boundary.tsx Introduces reusable error boundary with logging + toast + retry UI.
web/src/components/dashboard/skeletons.tsx Adds reusable skeleton components for dashboard loading states.
web/src/components/dashboard/performance-dashboard.tsx Adds toast feedback for threshold save success/failure.
web/src/components/dashboard/config-workspace/config-categories.ts Replaces console.warn with structured logger and improves fallback selection.
web/src/app/dashboard/tickets/page.tsx Wraps page in ErrorBoundary and improves responsive layout (overflow + filter layout).
web/src/app/dashboard/tickets/[ticketId]/page.tsx Adds ErrorBoundary and replaces spinner with skeleton loading UI.
web/src/app/dashboard/temp-roles/page.tsx Replaces confirm/alert with dialog + toasts; adds boundary and responsive table columns.
web/src/app/dashboard/performance/page.tsx Wraps performance dashboard with ErrorBoundary.
web/src/app/dashboard/moderation/page.tsx Migrates moderation page to Zustand store and effect-driven fetching; adds boundary wrapper.
web/src/app/dashboard/members/page.tsx Migrates members page to store-owned fetch and adds ErrorBoundary.
web/src/app/dashboard/members/[userId]/page.tsx Adds ErrorBoundary and makes CSV URL revocation more robust.
web/src/app/dashboard/logs/page.tsx Wraps logs page with ErrorBoundary.
web/src/app/dashboard/conversations/page.tsx Adds ErrorBoundary, improves responsive filters/table, and keyboard accessibility on rows.
web/src/app/dashboard/conversations/[conversationId]/page.tsx Adds ErrorBoundary + skeleton loading and improves missing-guild handling.
web/src/app/dashboard/config/page.tsx Wraps config editor with ErrorBoundary.
web/src/app/dashboard/audit-log/page.tsx Adds ErrorBoundary and improves responsive filter/table layout.
web/src/app/api/moderation/cases/route.ts Allows forwarding order query param to bot API.
web/src/app/api/guilds/[guildId]/members/[userId]/xp/route.ts Adds defense-in-depth validation (bounds, non-zero, reason length) and strips unknown fields.
tests/modules/events/errors.test.js Adds tests for Discord error + shard disconnect logging.
tests/modules/events/commandInteraction.test.js Adds focused tests for extracted slash command handler module.
tests/modules/events/clientReady.test.js Adds focused tests for extracted clientReady handler module.
tests/index.test.js Removes event-handler tests now covered by dedicated event module tests.
tests/api/utils/configValidation.test.js Adds tests for new range/maxLength/enum/openProperties validation behavior.
tests/api/routes/moderation.test.js Adds test ensuring order=asc produces ascending ORDER BY.
tests/api/routes/community.test.js Updates expectation/comment to align with all-time message count semantics.
tests/api/middleware/rateLimit.test.js Adds tests for new size(), sweep(), and destroy() helpers.
src/modules/events/errors.js Enhances Discord error logging and adds shard disconnect handling + process-level handlers.
src/modules/events/commandInteraction.js New module: slash command dispatch + autocomplete + permission checks + safe error replies.
src/modules/events/clientReady.js New module: registers slash commands on client ready.
src/modules/events.js Becomes a dispatcher that wires together event modules (including the new ones).
src/index.js Removes inline event handlers; relies on registerEventHandlers() after config loads.
src/api/utils/configValidation.js Adds schema constraints and implements min/max and maxLength validation.
src/api/routes/moderation.js Adds order query param for mod case sorting.
src/api/routes/community.js Changes message count query to all-time SUM(bigint) and clamps to Number.MAX_SAFE_INTEGER.
src/api/middleware/rateLimit.js Adds sweep() + size() helpers and refactors cleanup logic to prevent unbounded growth.
migrations/README.md Documents migration numbering anomaly and explains placeholder strategy.
migrations/012_placeholder.cjs Improves placeholder migration documentation (still a no-op).

Comment thread web/src/stores/moderation-store.ts
Comment thread web/src/app/dashboard/moderation/page.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (4)
web/tests/components/dashboard/error-boundary.test.tsx (1)

44-53: 🧹 Nitpick | 🔵 Trivial

Consider more specific logger.error assertion.

The assertion expect(logger.error).toHaveBeenCalled() verifies the call happens but doesn't lock in the expected parameters. A more specific assertion would strengthen the test.

Suggested enhancement
     expect(screen.getByText('Something went wrong')).toBeInTheDocument();
     expect(screen.getByRole('button', { name: /try again/i })).toBeInTheDocument();
-    expect(logger.error).toHaveBeenCalled();
+    expect(logger.error).toHaveBeenCalledWith(
+      expect.stringContaining('Caught error'),
+      expect.any(Error),
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/tests/components/dashboard/error-boundary.test.tsx` around lines 44 - 53,
The test currently only checks that logger.error was called; update the
assertion to verify the expected arguments from ErrorBoundary by asserting
logger.error was called with the error and metadata (e.g., an Error or message
and a component stack) and/or that it was called exactly once. Locate the test
in error-boundary.test.tsx where ErrorBoundary and ThrowingComponent are
rendered and replace expect(logger.error).toHaveBeenCalled() with a more
specific assertion such as using toHaveBeenCalledWith or toHaveBeenCalledTimes
combined with expect.objectContaining or expect.any(Error) to confirm the logger
received the expected error payload.
tests/modules/events/commandInteraction.test.js (1)

57-60: ⚠️ Potential issue | 🟡 Minor

Reset getPermissionError implementation in afterEach to avoid cross-test leakage.

At Line 57, vi.clearAllMocks() does not reset overridden mock implementations. Since Line 127 overrides getPermissionError, that implementation can persist into later tests.

Suggested fix
  afterEach(() => {
    vi.clearAllMocks();
    hasPermission.mockReturnValue(true);
+   getPermissionError.mockReturnValue('Permission denied');
  });
In Vitest 4, does vi.clearAllMocks() reset vi.fn() mock implementations, or only call history?

Also applies to: 125-128

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/modules/events/commandInteraction.test.js` around lines 57 - 60, The
tests override getPermissionError in some cases and vi.clearAllMocks() only
clears call history, so update the afterEach to also reset the mocked
implementation of getPermissionError (in addition to hasPermission) to prevent
leakage; specifically, add a reset call for getPermissionError (e.g.,
getPermissionError.mockReset() or restore its default implementation) inside the
afterEach block that currently calls vi.clearAllMocks() and
hasPermission.mockReturnValue(true).
src/modules/events/commandInteraction.js (1)

24-37: ⚠️ Potential issue | 🟠 Major

Guard autocomplete fallback responses to prevent unhandled rejections.

At Line 25 and Line 36, interaction.respond([]) is unguarded. If it rejects (e.g., already acknowledged or transport error), the promise can escape the autocomplete path (Lines 75-77) and create an unhandled rejection.

Suggested fix
 async function handleAutocomplete(client, interaction) {
   const command = client.commands.get(interaction.commandName);
   if (!command?.autocomplete) {
-    await interaction.respond([]);
+    if (!interaction.responded) {
+      await interaction.respond([]).catch(() => {});
+    }
     return;
   }

   try {
     await command.autocomplete(interaction);
   } catch (err) {
     logger.error('Autocomplete error', {
       command: interaction.commandName,
       error: getErrorMessage(err),
     });
-    await interaction.respond([]);
+    if (!interaction.responded) {
+      await interaction.respond([]).catch((respondErr) => {
+        logger.debug('Failed to send autocomplete fallback', {
+          command: interaction.commandName,
+          error: getErrorMessage(respondErr),
+        });
+      });
+    }
   }
 }
In discord.js v14, can AutocompleteInteraction.respond(...) reject when already responded, and is checking interaction.responded recommended before fallback responses?

Also applies to: 74-78

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/events/commandInteraction.js` around lines 24 - 37, The unguarded
calls to interaction.respond([]) can reject (e.g., if already acknowledged);
update both places where you call interaction.respond([]) (the early-return when
command?.autocomplete is false and the catch fallback after
command.autocomplete) to avoid unhandled rejections by first checking
interaction.responded && skipping respond if true, or by wrapping the respond
call in its own try/catch and swallowing/logging the error; specifically modify
the autocomplete handling around command.autocomplete and the fallback respond
(also the similar block at lines ~74-78) so interaction.respond([]) is only
awaited inside a guarded conditional or protected try/catch, keeping
logger.error/getErrorMessage usage unchanged for the original catch block.
web/tests/stores/members-store.test.ts (1)

273-279: 🧹 Nitpick | 🔵 Trivial

Replace the redundant Promise casts with generic syntax.

new Promise(...) as Promise<Response> does not change the type and keeps the current Sonar warning open. Use new Promise<Response>(...) in both places instead.

Suggested cleanup
     fetchSpy
       .mockImplementationOnce(
         () =>
-          new Promise((_, reject) => {
+          new Promise<Response>((_, reject) => {
             rejectFirst.mockImplementation(reject);
-          }) as Promise<Response>,
+          }),
       )
       .mockResolvedValueOnce({
         ok: true,
         status: 200,
         json: () => Promise.resolve({ members: [], nextAfter: null, total: 0 }),
@@
     vi.spyOn(globalThis, 'fetch').mockImplementationOnce(
       () =>
-        new Promise((resolve) => {
+        new Promise<Response>((resolve) => {
           resolveFirst = resolve;
-        }) as Promise<Response>,
+        }),
     );

Quick check: after the cleanup, this should return no matches.

#!/bin/bash
rg -n --type=ts 'as\s+Promise<Response>' web/tests/stores/members-store.test.ts

Also applies to: 314-319

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/tests/stores/members-store.test.ts` around lines 273 - 279, The test uses
redundant type assertions like "new Promise(... ) as Promise<Response>" which
keeps a Sonar warning; update both occurrences (the
fetchSpy.mockImplementationOnce block that constructs the promise and the
similar block near the rejectFirst usage) to use generic Promise syntax by
replacing "new Promise(... ) as Promise<Response>" with "new
Promise<Response>(...)" so the promises are typed directly without a cast;
locate the instances via the variables fetchSpy and rejectFirst and make the
substitution in each place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/app/dashboard/tickets/`[ticketId]/page.tsx:
- Around line 269-275: The transcript block only handles the open-ticket case
and skips rendering when !data.transcript && data.status !== 'open'; update the
JSX around the existing {!data.transcript && data.status === 'open' && (...)}
check to add a second branch for when !data.transcript && data.status !== 'open'
(or specifically === 'closed') and render a clear placeholder (e.g., "No
transcript available for this ticket") in the same styled container so legacy or
errored closed tickets show a message; reference the data.transcript and
data.status checks in page.tsx to locate and modify the conditional rendering.

In `@web/tests/stores/members-store.test.ts`:
- Around line 251-267: The test should assert that the AbortSignal passed into
fetchMembers is forwarded to global fetch; update the test to create an
AbortController, pass its signal into the fetchMembers call (e.g., include
signal: controller.signal in the options passed to
useMembersStore.getState().fetchMembers), and add an assertion that
globalThis.fetch was called with an init object containing that exact signal
(use expect.objectContaining({ signal: controller.signal }) or equivalent) so
the contract that fetch receives the provided signal is enforced.

---

Duplicate comments:
In `@src/modules/events/commandInteraction.js`:
- Around line 24-37: The unguarded calls to interaction.respond([]) can reject
(e.g., if already acknowledged); update both places where you call
interaction.respond([]) (the early-return when command?.autocomplete is false
and the catch fallback after command.autocomplete) to avoid unhandled rejections
by first checking interaction.responded && skipping respond if true, or by
wrapping the respond call in its own try/catch and swallowing/logging the error;
specifically modify the autocomplete handling around command.autocomplete and
the fallback respond (also the similar block at lines ~74-78) so
interaction.respond([]) is only awaited inside a guarded conditional or
protected try/catch, keeping logger.error/getErrorMessage usage unchanged for
the original catch block.

In `@tests/modules/events/commandInteraction.test.js`:
- Around line 57-60: The tests override getPermissionError in some cases and
vi.clearAllMocks() only clears call history, so update the afterEach to also
reset the mocked implementation of getPermissionError (in addition to
hasPermission) to prevent leakage; specifically, add a reset call for
getPermissionError (e.g., getPermissionError.mockReset() or restore its default
implementation) inside the afterEach block that currently calls
vi.clearAllMocks() and hasPermission.mockReturnValue(true).

In `@web/tests/components/dashboard/error-boundary.test.tsx`:
- Around line 44-53: The test currently only checks that logger.error was
called; update the assertion to verify the expected arguments from ErrorBoundary
by asserting logger.error was called with the error and metadata (e.g., an Error
or message and a component stack) and/or that it was called exactly once. Locate
the test in error-boundary.test.tsx where ErrorBoundary and ThrowingComponent
are rendered and replace expect(logger.error).toHaveBeenCalled() with a more
specific assertion such as using toHaveBeenCalledWith or toHaveBeenCalledTimes
combined with expect.objectContaining or expect.any(Error) to confirm the logger
received the expected error payload.

In `@web/tests/stores/members-store.test.ts`:
- Around line 273-279: The test uses redundant type assertions like "new
Promise(... ) as Promise<Response>" which keeps a Sonar warning; update both
occurrences (the fetchSpy.mockImplementationOnce block that constructs the
promise and the similar block near the rejectFirst usage) to use generic Promise
syntax by replacing "new Promise(... ) as Promise<Response>" with "new
Promise<Response>(...)" so the promises are typed directly without a cast;
locate the instances via the variables fetchSpy and rejectFirst and make the
substitution in each place.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 28779842-9aee-4bda-abe8-a841ae760309

📥 Commits

Reviewing files that changed from the base of the PR and between 9164f96 and c200040.

📒 Files selected for processing (7)
  • src/api/routes/moderation.js
  • src/modules/events/commandInteraction.js
  • tests/modules/events/commandInteraction.test.js
  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • web/src/stores/members-store.ts
  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/tests/stores/members-store.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Agent
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (12)
**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ESM-only syntax: import/export, never require()/module.exports

Files:

  • src/api/routes/moderation.js
  • tests/modules/events/commandInteraction.test.js
  • src/modules/events/commandInteraction.js
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes
Always include semicolons at the end of statements
Use 2-space indentation (spaces, not tabs)
Always include trailing commas in multi-line arrays, objects, and function parameters
Maintain a maximum line width of 100 characters

Files:

  • src/api/routes/moderation.js
  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • tests/modules/events/commandInteraction.test.js
  • src/modules/events/commandInteraction.js
  • web/src/stores/members-store.ts
  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/tests/stores/members-store.test.ts
src/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.js: Never use console.* methods; use the Winston logger instead via import logger from '../logger.js' (adjust path as needed), then call logger.info(), logger.warn(), logger.error(), or logger.debug()
Always use safeReply(), safeSend(), or safeEditReply() instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully

Files:

  • src/api/routes/moderation.js
  • src/modules/events/commandInteraction.js
src/api/routes/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Create API route handlers in src/api/routes/ and mount them in src/api/server.js; add auth middleware if the endpoint requires authentication

Files:

  • src/api/routes/moderation.js
**/*.{js,ts,tsx,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx,mjs}: Use ESM syntax (import/export) — CommonJS is not allowed
Use single quotes for strings — double quotes only allowed in JSON files
Always include semicolons at end of statements
Use 2-space indentation for all code
Use Winston logger from src/logger.js — never use console.* methods

Files:

  • src/api/routes/moderation.js
  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • tests/modules/events/commandInteraction.test.js
  • src/modules/events/commandInteraction.js
  • web/src/stores/members-store.ts
  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/tests/stores/members-store.test.ts
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Never use console.* methods in web dashboard code; use appropriate logging mechanisms for React applications

Files:

  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • web/src/stores/members-store.ts
web/src/app/dashboard/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

For dashboard routes, add a matcher entry to dashboardTitleMatchers in web/src/lib/page-titles.ts: use exact equality for leaf routes (pathname === '/dashboard/my-route') and subtree checks (pathname.startsWith('/dashboard/my-route/')); export metadata using createPageMetadata(title) for SSR entry points

Files:

  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
web/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

web/src/app/**/*.{ts,tsx}: Export metadata using createPageMetadata() from web/src/lib/page-titles.ts in SSR entry points for dashboard pages
Use DashboardTitleSync component and getDashboardDocumentTitle() for client-side navigation title updates in the dashboard

Files:

  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
tests/**/*.test.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

tests/**/*.test.js: Write bot tests using Vitest 4 with the node environment, matching the src/ structure in the tests/ directory
Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead

Files:

  • tests/modules/events/commandInteraction.test.js
src/modules/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Create feature modules in src/modules/ and add corresponding config sections to config.json

Files:

  • src/modules/events/commandInteraction.js
{src/commands/**/*.{js,ts},src/modules/**/*.{js,ts},src/index.js}

📄 CodeRabbit inference engine (AGENTS.md)

Use safeReply(), safeSend(), or safeEditReply() for Discord messages — never send unsafe messages directly

Files:

  • src/modules/events/commandInteraction.js
web/tests/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with the jsdom environment and React Testing Library, matching the web/src/ structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Files:

  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/tests/stores/members-store.test.ts
🧠 Learnings (21)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/**/*.js : Always use `safeReply()`, `safeSend()`, or `safeEditReply()` instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to src/api/routes/*.js : Add adaptDeleteGuildIdParam in API route handlers to enforce guild moderation checks on DELETE requests without losing record id

Applied to files:

  • src/api/routes/moderation.js
  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'

Applied to files:

  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to web/src/app/api/**/route.ts : Include guildId in signed WebSocket ticket payload when issuing tickets from dashboard endpoints

Applied to files:

  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to src/api/ws/*.js : Bind audit-log WebSocket auth tickets to guild context by including nonce.expiry.guildId.hmac in ticket validation

Applied to files:

  • web/src/app/dashboard/tickets/[ticketId]/page.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory

Applied to files:

  • tests/modules/events/commandInteraction.test.js
  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure

Applied to files:

  • tests/modules/events/commandInteraction.test.js
  • web/tests/components/dashboard/error-boundary.test.tsx
  • web/tests/stores/members-store.test.ts
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/commands/**/*.js : Create slash command definitions in `src/commands/`, exporting a slash command builder and an `execute` function

Applied to files:

  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/**/*.js : Always use `safeReply()`, `safeSend()`, or `safeEditReply()` instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully

Applied to files:

  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-12T02:03:52.709Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.709Z
Learning: Applies to {src/commands/**/*.{js,ts},src/modules/**/*.{js,ts},src/index.js} : Use `safeReply()`, `safeSend()`, or `safeEditReply()` for Discord messages — never send unsafe messages directly

Applied to files:

  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to **/*.{js,ts,tsx} : Maintain a maximum line width of 100 characters

Applied to files:

  • src/modules/events/commandInteraction.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/**/*.js : Never use `console.*` methods; use the Winston logger instead via `import logger from '../logger.js'` (adjust path as needed), then call `logger.info()`, `logger.warn()`, `logger.error()`, or `logger.debug()`

Applied to files:

  • src/modules/events/commandInteraction.js
  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:52.709Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.709Z
Learning: Applies to **/*.{js,ts,tsx,mjs} : Use Winston logger from `src/logger.js` — never use `console.*` methods

Applied to files:

  • src/modules/events/commandInteraction.js
  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/**/*.{ts,tsx} : Never use `console.*` methods in web dashboard code; use appropriate logging mechanisms for React applications

Applied to files:

  • src/modules/events/commandInteraction.js
  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/app/dashboard/**/*.tsx : For dashboard routes, add a matcher entry to `dashboardTitleMatchers` in `web/src/lib/page-titles.ts`: use exact equality for leaf routes (`pathname === '/dashboard/my-route'`) and subtree checks (`pathname.startsWith('/dashboard/my-route/')`); export `metadata` using `createPageMetadata(title)` for SSR entry points

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:52.709Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.709Z
Learning: Applies to **/*.{js,ts,tsx,mjs} : Use single quotes for strings — double quotes only allowed in JSON files

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to **/*.{js,ts,tsx} : Use single quotes for strings (except in JSON files); no double quotes

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-12T02:03:52.709Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.709Z
Learning: Applies to web/src/app/**/*.{ts,tsx} : Use `DashboardTitleSync` component and `getDashboardDocumentTitle()` for client-side navigation title updates in the dashboard

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to web/src/pages/dashboard/**/*.{ts,tsx} : Use shared title helpers from web/src/lib/page-titles.ts for setting browser titles in dashboard pages

Applied to files:

  • web/tests/components/dashboard/error-boundary.test.tsx
🪛 GitHub Check: SonarCloud Code Analysis
web/src/app/dashboard/tickets/[ticketId]/page.tsx

[warning] 139-139: Do not use Array index in keys

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZzyqhCUYIscLWHB68XW&open=AZzyqhCUYIscLWHB68XW&pullRequest=306


[warning] 124-124: Do not use Array index in keys

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZzyqhCUYIscLWHB68XV&open=AZzyqhCUYIscLWHB68XV&pullRequest=306

web/tests/stores/members-store.test.ts

[warning] 316-318: This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZz94AMOuktEfKwL9SAa&open=AZz94AMOuktEfKwL9SAa&pullRequest=306


[warning] 276-278: This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZz94AMOuktEfKwL9SAZ&open=AZz94AMOuktEfKwL9SAZ&pullRequest=306

🔇 Additional comments (14)
src/api/routes/moderation.js (2)

78-84: LGTM — OpenAPI documentation now matches the implementation.

The order parameter is properly documented with the correct type, enum values, and default. This resolves the previously flagged documentation gap.


160-160: LGTM — Safe handling of the sort direction.

The ternary on line 160 constrains order to exactly 'ASC' or 'DESC', so the string interpolation on line 200 is not vulnerable to SQL injection. This is the correct pattern when parameterized placeholders cannot be used for ORDER BY direction.

Also applies to: 200-200

web/tests/components/dashboard/error-boundary.test.tsx (4)

1-8: LGTM!

Imports and mock setup follow Vitest best practices. The mocks for sonner and @/lib/logger are properly configured for testing error boundary behavior.


12-25: Console suppression now uses the correct pattern.

The previous approach mutated console.error directly. The current implementation uses vi.spyOn(console, 'error').mockImplementation() which is the proper Vitest pattern for suppressing React's error boundary output during tests. The teardown correctly restores mocks and handles NODE_ENV restoration.


27-32: LGTM!

The ThrowingComponent helper correctly uses Readonly for props (addressing the previous SonarCloud finding) and provides a clean way to trigger errors in tests.


106-132: LGTM!

The environment-specific toast tests properly use vi.stubEnv and verify the exact toast payloads for development (detailed error message) and production (generic user-friendly message). This addresses the previous review feedback about missing coverage for this behavior.

web/src/app/dashboard/tickets/[ticketId]/page.tsx (5)

9-10: LGTM!

Good addition of ErrorBoundary and Skeleton imports to support the new loading states and error handling patterns.


54-91: LGTM!

The fetch logic handles error states and authentication redirects properly. The stale data concern from previous reviews is mitigated by the tightened render condition at line 169.


110-147: LGTM!

The loading skeleton provides good UX feedback. The SonarCloud warnings about array index keys at lines 124 and 139 are false positives—skeleton placeholders have no stable identity and are replaced wholesale, making index keys acceptable here. The biome-ignore comments correctly document this rationale.


149-166: LGTM!

The no-guild and error states are well-implemented. The role="alert" attribute on the error block improves accessibility.


43-48: The title matcher entry already exists and correctly handles this dynamic route with pathname.startsWith('/dashboard/tickets/'), which matches all ticket detail pages regardless of the specific ticket ID.

tests/modules/events/commandInteraction.test.js (1)

66-243: Strong branch coverage for the new interaction dispatcher tests.

This suite validates the critical flows (autocomplete success/failure, permission denial, missing command, execute success, reply vs follow-up error paths) and aligns well with the extracted handler responsibilities.

src/modules/events/commandInteraction.js (1)

116-125: Nice hardening of fire-and-forget usage logging.

Using void logCommandUsage(...).catch(...) makes intent explicit and captures failures locally without impacting command execution.

web/src/stores/members-store.ts (1)

91-96: Good stale-response guard.

The request-id checks after both fetch() and res.json(), plus the bump in resetAll(), close the late-response window cleanly.

Also applies to: 114-147

Comment thread web/src/app/dashboard/tickets/[ticketId]/page.tsx
Comment thread web/tests/stores/members-store.test.ts Outdated
@railway-app railway-app Bot temporarily deployed to volvox-bot / volvox-bot-pr-306 March 19, 2026 19:20 Destroyed
@BillChirico BillChirico enabled auto-merge (squash) March 19, 2026 19:30
Copilot AI review requested due to automatic review settings March 19, 2026 19:32
@railway-app railway-app Bot temporarily deployed to volvox-bot / volvox-bot-pr-306 March 19, 2026 19:32 Destroyed
@BillChirico BillChirico disabled auto-merge March 19, 2026 19:32
@BillChirico BillChirico merged commit b4bdb68 into main Mar 19, 2026
9 of 16 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in Volvox.Bot Mar 19, 2026
@BillChirico BillChirico deleted the chore/issue-144-tech-debt branch March 19, 2026 19:33
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Environment Cleaned Up

The Railway preview environment for this PR has been removed.

Environment: pr-306

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
3 Security Hotspots

See analysis details on SonarQube Cloud

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 tackles a set of deferred tech-debt items across the bot and the web dashboard: extracting Discord event handlers into dedicated modules, improving client-side logging and error handling UX, and strengthening API/validation behavior with accompanying tests.

Changes:

  • Extracted bot clientReady / slash command dispatch / error handling into src/modules/events/* with new focused tests.
  • Introduced/expanded dashboard robustness: ErrorBoundary usage, skeleton loading components, toast notifications, and Zustand stores for members/moderation pages.
  • Hardened API behavior: XP proxy payload validation, moderation case ordering support, config schema range/length validation, rate limit map sweeping helpers, and migration numbering documentation.

Reviewed changes

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

Show a summary per file
File Description
web/tests/stores/moderation-store.test.ts Adds unit tests for moderation Zustand store actions + fetch flows.
web/tests/stores/members-store.test.ts Adds unit tests for members Zustand store fetch sequencing/abort behavior.
web/tests/components/dashboard/skeletons.test.tsx Tests new dashboard skeleton components.
web/tests/components/dashboard/error-boundary.test.tsx Tests ErrorBoundary logging + toast behavior and reset.
web/tests/api/xp-route.test.ts Tests defense-in-depth validation for XP proxy route.
web/src/stores/moderation-store.ts Moves moderation fetching/state into a Zustand store with request de-staling.
web/src/stores/members-store.ts Adds fetchMembers to the store with request de-staling + abort support.
web/src/lib/logger.ts Implements a browser logger shim (structured prefix + level gating).
web/src/hooks/use-user-history.ts Removes legacy hook (migrated to store-based approach).
web/src/hooks/use-moderation-stats.ts Removes legacy hook (migrated to store-based approach).
web/src/hooks/use-moderation-cases.ts Removes legacy hook (migrated to store-based approach).
web/src/components/ui/error-boundary.tsx ErrorBoundary now logs via logger + emits toast notifications.
web/src/components/dashboard/skeletons.tsx Adds reusable skeleton building blocks for dashboard pages.
web/src/components/dashboard/performance-dashboard.tsx Adds toast feedback for threshold save success/failure.
web/src/components/dashboard/config-workspace/config-categories.ts Replaces console warning with logger + safer default fallback.
web/src/app/dashboard/tickets/page.tsx Wraps in ErrorBoundary and improves responsive layout/table overflow.
web/src/app/dashboard/tickets/[ticketId]/page.tsx Adds ErrorBoundary + skeleton loading + additional empty states.
web/src/app/dashboard/temp-roles/page.tsx Replaces confirm/alert with dialog + toast feedback; improves responsive table.
web/src/app/dashboard/performance/page.tsx Wraps dashboard in ErrorBoundary with custom message.
web/src/app/dashboard/moderation/page.tsx Switches from local hooks to moderation Zustand store + abortable refresh.
web/src/app/dashboard/members/page.tsx Switches to members Zustand store fetch method + adds ErrorBoundary.
web/src/app/dashboard/members/[userId]/page.tsx Adds ErrorBoundary and tweaks CSV export URL cleanup timing.
web/src/app/dashboard/logs/page.tsx Wraps logs view in ErrorBoundary.
web/src/app/dashboard/conversations/page.tsx Wraps in ErrorBoundary; improves responsive filters/table; adds keyboard nav.
web/src/app/dashboard/conversations/[conversationId]/page.tsx Adds ErrorBoundary + skeleton loading; improves “no guild” behavior.
web/src/app/dashboard/audit-log/page.tsx Wraps in ErrorBoundary; improves responsive filters/table overflow.
web/src/app/api/moderation/cases/route.ts Allows order query param to be proxied upstream.
web/src/app/api/guilds/[guildId]/members/[userId]/xp/route.ts Adds strict body validation + stripping unknown fields before proxying.
tests/modules/events/errors.test.js New focused tests for Discord error + shard disconnect handlers.
tests/modules/events/commandInteraction.test.js New focused tests for slash command dispatch + autocomplete behavior.
tests/modules/events/clientReady.test.js New focused tests for command registration on ClientReady.
tests/index.test.js Removes tests that were moved to the new event-module test files.
tests/api/utils/configValidation.test.js Adds tests for min/max and maxLength/enum/openProperties validation.
tests/api/routes/moderation.test.js Adds coverage for order=asc sorting.
tests/api/routes/community.test.js Updates expectation/comment for all-time totalMessagesSent.
tests/api/middleware/rateLimit.test.js Adds coverage for size(), sweep(), and destroy() helpers.
src/modules/events/errors.js Improves structured error logging + adds shard disconnect warning behavior.
src/modules/events/commandInteraction.js New extracted interactionCreate handler (slash commands + autocomplete).
src/modules/events/clientReady.js New extracted ClientReady handler for command registration.
src/modules/events.js Registers new extracted handlers and reorganizes registration flow.
src/index.js Removes inline event handlers; delegates to registerEventHandlers.
src/api/utils/configValidation.js Adds min/max and maxLength schema enforcement.
src/api/routes/moderation.js Adds order query param and uses it in ORDER BY.
src/api/routes/community.js Makes totalMessagesSent all-time, uses bigint, clamps to MAX_SAFE_INTEGER.
src/api/middleware/rateLimit.js Adds sweeping + size helpers and documents lifecycle methods.
migrations/README.md Documents migration numbering anomalies and how to add new migrations safely.
migrations/012_placeholder.cjs Expands placeholder migration documentation; remains a no-op.

Comment thread web/src/lib/logger.ts
Comment on lines +60 to +67
const isDev = typeof process !== 'undefined' ? process.env.NODE_ENV === 'development' : false;

const noop = (..._args: unknown[]) => {};

function makeBrowserLogger(level: LogLevel): (...args: unknown[]) => void {
// In production, suppress noisy debug/info — only surface warnings and errors.
if (!isDev && level !== 'warn' && level !== 'error') {
return noop;
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

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

isDev is computed via typeof process !== 'undefined' ? process.env.NODE_ENV === 'development' : false. In Next.js client bundles process may be undefined even though process.env.NODE_ENV is compile-time replaced, which would make isDev always false in the browser and suppress debug/info even during local development (contradicting the header comment and how other client code uses process.env.NODE_ENV). Consider deriving this from process.env.NODE_ENV directly (or globalThis.process?.env?.NODE_ENV) so dev logging actually enables in the browser.

Copilot uses AI. Check for mistakes.

const fetchDetail = useCallback(async () => {
if (!guildId || !conversationId) return;
if (!guildId || !conversationId) {
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

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

When guildId or conversationId is missing, fetchDetail sets loading to false and returns, but it does not clear data or error. If the search params change while staying on this route (e.g., navigating between conversations or dropping guildId), the page can render stale conversation details even though the current URL is invalid. Consider resetting data/error in this branch (and/or in an effect that runs when guildId/conversationId changes).

Suggested change
if (!guildId || !conversationId) {
if (!guildId || !conversationId) {
setData(null);
setError(null);

Copilot uses AI. Check for mistakes.
Comment on lines +395 to +398
role="button"
aria-label={`View conversation in ${convo.channelName}`}
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

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

This row navigates to a new page (handleRowClick) but is marked role="button". For accessibility semantics (and consistency with MemberTable, which uses role="link" for navigational rows), this should be exposed as a link-like control (e.g., role="link" and corresponding label) rather than a button.

Suggested change
role="button"
aria-label={`View conversation in ${convo.channelName}`}
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
role="link"
aria-label={`View conversation in ${convo.channelName}`}
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Enter') {

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

chore: deferred tech debt and reviewer-flagged improvements

2 participants