chore: deferred tech debt and reviewer-flagged improvements (#144)#306
chore: deferred tech debt and reviewer-flagged improvements (#144)#306BillChirico merged 69 commits intomainfrom
Conversation
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)
…config validation, logger shim)
…o-op server logger
- 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
8b6a32e to
ea60e6e
Compare
There was a problem hiding this comment.
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 | 🟡 MinorAdd a regression test for
Number.MAX_SAFE_INTEGERclamping.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 | 🟡 MinorInconsistent error handling for
replyErr.Lines 101 and 107 access
.messagedirectly onreplyErr, but elsewhere in this file (lines 31, 83, 88) the patternerr instanceof Error ? err.message : String(err)is used. If a non-Error value is thrown, this logsundefinedinstead 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 | 🟡 MinorAdd an accessible name to the clear-history button.
This icon-only button relies on
title, which is not a reliable accessible name. Addaria-labelso 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
📒 Files selected for processing (5)
src/api/routes/community.jssrc/modules/events/clientReady.jssrc/modules/events/commandInteraction.jstests/api/routes/community.test.jsweb/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, neverrequire()/module.exports
Files:
src/modules/events/clientReady.jssrc/modules/events/commandInteraction.jstests/api/routes/community.test.jssrc/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.jssrc/modules/events/commandInteraction.jstests/api/routes/community.test.jsweb/src/app/dashboard/moderation/page.tsxsrc/api/routes/community.js
src/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/**/*.js: Never useconsole.*methods; use the Winston logger instead viaimport logger from '../logger.js'(adjust path as needed), then calllogger.info(),logger.warn(),logger.error(), orlogger.debug()
Always usesafeReply(),safeSend(), orsafeEditReply()instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully
Files:
src/modules/events/clientReady.jssrc/modules/events/commandInteraction.jssrc/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 toconfig.json
Files:
src/modules/events/clientReady.jssrc/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 fromsrc/logger.js— never useconsole.*methods
Files:
src/modules/events/clientReady.jssrc/modules/events/commandInteraction.jstests/api/routes/community.test.jsweb/src/app/dashboard/moderation/page.tsxsrc/api/routes/community.js
{src/commands/**/*.{js,ts},src/modules/**/*.{js,ts},src/index.js}
📄 CodeRabbit inference engine (AGENTS.md)
Use
safeReply(),safeSend(), orsafeEditReply()for Discord messages — never send unsafe messages directly
Files:
src/modules/events/clientReady.jssrc/modules/events/commandInteraction.js
tests/**/*.test.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
tests/**/*.test.js: Write bot tests using Vitest 4 with thenodeenvironment, matching thesrc/structure in thetests/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
dashboardTitleMatchersinweb/src/lib/page-titles.ts: use exact equality for leaf routes (pathname === '/dashboard/my-route') and subtree checks (pathname.startsWith('/dashboard/my-route/')); exportmetadatausingcreatePageMetadata(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}: ExportmetadatausingcreatePageMetadata()fromweb/src/lib/page-titles.tsin SSR entry points for dashboard pages
UseDashboardTitleSynccomponent andgetDashboardDocumentTitle()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 insrc/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.jssrc/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.jssrc/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.tsxsrc/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.
🔇 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
loggerinstance, and lines 20-22 droperr.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 viaimport logger ..., then calllogger.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)andhandleChatInputCommand(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
logCommandUsagecall 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
totalMessagesSentsemantics and safe-number clamping.
472-475: Stats aggregation + numeric clamping fix looks correct.Casting aggregate to
bigintand clamping response toNumber.MAX_SAFE_INTEGERis a solid compatibility/safety fix for the integer response contract.Also applies to: 532-535
There was a problem hiding this comment.
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 intosrc/modules/events/*with dedicated tests, simplifyingsrc/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.
There was a problem hiding this comment.
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 | 🟠 MajorPrevent 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 | 🟡 MinorAdd 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 | 🟡 MinorGuard fallback autocomplete response to avoid double-ack rejections.
If
command.autocomplete()already responded and then throws, the fallbackrespond([])can
reject. Guard withinteraction.respondedbefore 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
📒 Files selected for processing (14)
src/api/middleware/rateLimit.jssrc/api/routes/community.jssrc/api/routes/moderation.jssrc/modules/events/clientReady.jssrc/modules/events/commandInteraction.jstests/api/routes/community.test.jstests/api/routes/moderation.test.jstests/modules/events/clientReady.test.jsweb/src/app/dashboard/members/[userId]/page.tsxweb/src/app/dashboard/moderation/page.tsxweb/src/app/dashboard/tickets/[ticketId]/page.tsxweb/src/stores/members-store.tsweb/tests/components/dashboard/error-boundary.test.tsxweb/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, neverrequire()/module.exports
Files:
src/modules/events/clientReady.jssrc/api/routes/moderation.jstests/modules/events/clientReady.test.jssrc/api/routes/community.jstests/api/routes/community.test.jssrc/api/middleware/rateLimit.jssrc/modules/events/commandInteraction.jstests/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.jssrc/api/routes/moderation.jsweb/tests/components/dashboard/error-boundary.test.tsxtests/modules/events/clientReady.test.jsweb/src/stores/members-store.tsweb/tests/stores/members-store.test.tsweb/src/app/dashboard/tickets/[ticketId]/page.tsxsrc/api/routes/community.jstests/api/routes/community.test.jssrc/api/middleware/rateLimit.jsweb/src/app/dashboard/members/[userId]/page.tsxweb/src/app/dashboard/moderation/page.tsxsrc/modules/events/commandInteraction.jstests/api/routes/moderation.test.js
src/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/**/*.js: Never useconsole.*methods; use the Winston logger instead viaimport logger from '../logger.js'(adjust path as needed), then calllogger.info(),logger.warn(),logger.error(), orlogger.debug()
Always usesafeReply(),safeSend(), orsafeEditReply()instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully
Files:
src/modules/events/clientReady.jssrc/api/routes/moderation.jssrc/api/routes/community.jssrc/api/middleware/rateLimit.jssrc/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 toconfig.json
Files:
src/modules/events/clientReady.jssrc/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 fromsrc/logger.js— never useconsole.*methods
Files:
src/modules/events/clientReady.jssrc/api/routes/moderation.jsweb/tests/components/dashboard/error-boundary.test.tsxtests/modules/events/clientReady.test.jsweb/src/stores/members-store.tsweb/tests/stores/members-store.test.tsweb/src/app/dashboard/tickets/[ticketId]/page.tsxsrc/api/routes/community.jstests/api/routes/community.test.jssrc/api/middleware/rateLimit.jsweb/src/app/dashboard/members/[userId]/page.tsxweb/src/app/dashboard/moderation/page.tsxsrc/modules/events/commandInteraction.jstests/api/routes/moderation.test.js
{src/commands/**/*.{js,ts},src/modules/**/*.{js,ts},src/index.js}
📄 CodeRabbit inference engine (AGENTS.md)
Use
safeReply(),safeSend(), orsafeEditReply()for Discord messages — never send unsafe messages directly
Files:
src/modules/events/clientReady.jssrc/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 insrc/api/server.js; add auth middleware if the endpoint requires authentication
Files:
src/api/routes/moderation.jssrc/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 thejsdomenvironment and React Testing Library, matching theweb/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.tsxweb/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 thenodeenvironment, matching thesrc/structure in thetests/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.jstests/api/routes/community.test.jstests/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.tsweb/src/app/dashboard/tickets/[ticketId]/page.tsxweb/src/app/dashboard/members/[userId]/page.tsxweb/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
dashboardTitleMatchersinweb/src/lib/page-titles.ts: use exact equality for leaf routes (pathname === '/dashboard/my-route') and subtree checks (pathname.startsWith('/dashboard/my-route/')); exportmetadatausingcreatePageMetadata(title)for SSR entry points
Files:
web/src/app/dashboard/tickets/[ticketId]/page.tsxweb/src/app/dashboard/members/[userId]/page.tsxweb/src/app/dashboard/moderation/page.tsx
web/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
web/src/app/**/*.{ts,tsx}: ExportmetadatausingcreatePageMetadata()fromweb/src/lib/page-titles.tsin SSR entry points for dashboard pages
UseDashboardTitleSynccomponent andgetDashboardDocumentTitle()for client-side navigation title updates in the dashboard
Files:
web/src/app/dashboard/tickets/[ticketId]/page.tsxweb/src/app/dashboard/members/[userId]/page.tsxweb/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.jssrc/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.jsweb/src/app/dashboard/tickets/[ticketId]/page.tsxsrc/api/routes/community.jsweb/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.tsxweb/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.tsxweb/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.tsxweb/src/app/dashboard/members/[userId]/page.tsxweb/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.tsxweb/src/app/dashboard/members/[userId]/page.tsxweb/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.tsxweb/src/app/dashboard/tickets/[ticketId]/page.tsxweb/src/app/dashboard/members/[userId]/page.tsxweb/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.tsxweb/src/app/dashboard/members/[userId]/page.tsxweb/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.tsxsrc/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.tsxsrc/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.
web/tests/stores/members-store.test.ts
[warning] 316-318: This assertion is unnecessary since it does not change the type of the expression.
[warning] 276-278: This assertion is unnecessary since it does not change the type of the expression.
web/src/app/dashboard/tickets/[ticketId]/page.tsx
[warning] 124-124: Do not use Array index in keys
[warning] 139-139: Do not use Array index in keys
🔇 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()andsize()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
ordervariable 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=ascresults inORDER BY created_at ASCin the generated SQL. The assertion onmockPool.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's details. Try again or refresh the page." + title='Member details failed to load' + description='There was a problem loading this member'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 handleAbortErrorgracefully.
92-118: Clean manual refresh implementation.The
handleRefreshcallback 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
CaseTablecorrectly 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/moderationis already present indashboardTitleMatcherswith 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 viaDashboardTitleSyncat 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
latestMembersRequestIdinresetAll()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
latestMembersRequestIdinvalidation contract and reduce regression risk in concurrent fetch scenarios.
There was a problem hiding this comment.
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). |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (4)
web/tests/components/dashboard/error-boundary.test.tsx (1)
44-53: 🧹 Nitpick | 🔵 TrivialConsider more specific
logger.errorassertion.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 | 🟡 MinorReset
getPermissionErrorimplementation inafterEachto avoid cross-test leakage.At Line 57,
vi.clearAllMocks()does not reset overridden mock implementations. Since Line 127 overridesgetPermissionError, 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 | 🟠 MajorGuard 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 | 🔵 TrivialReplace the redundant
Promisecasts with generic syntax.
new Promise(...) as Promise<Response>does not change the type and keeps the current Sonar warning open. Usenew 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.tsAlso 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
📒 Files selected for processing (7)
src/api/routes/moderation.jssrc/modules/events/commandInteraction.jstests/modules/events/commandInteraction.test.jsweb/src/app/dashboard/tickets/[ticketId]/page.tsxweb/src/stores/members-store.tsweb/tests/components/dashboard/error-boundary.test.tsxweb/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, neverrequire()/module.exports
Files:
src/api/routes/moderation.jstests/modules/events/commandInteraction.test.jssrc/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.jsweb/src/app/dashboard/tickets/[ticketId]/page.tsxtests/modules/events/commandInteraction.test.jssrc/modules/events/commandInteraction.jsweb/src/stores/members-store.tsweb/tests/components/dashboard/error-boundary.test.tsxweb/tests/stores/members-store.test.ts
src/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/**/*.js: Never useconsole.*methods; use the Winston logger instead viaimport logger from '../logger.js'(adjust path as needed), then calllogger.info(),logger.warn(),logger.error(), orlogger.debug()
Always usesafeReply(),safeSend(), orsafeEditReply()instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully
Files:
src/api/routes/moderation.jssrc/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 insrc/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 fromsrc/logger.js— never useconsole.*methods
Files:
src/api/routes/moderation.jsweb/src/app/dashboard/tickets/[ticketId]/page.tsxtests/modules/events/commandInteraction.test.jssrc/modules/events/commandInteraction.jsweb/src/stores/members-store.tsweb/tests/components/dashboard/error-boundary.test.tsxweb/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.tsxweb/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
dashboardTitleMatchersinweb/src/lib/page-titles.ts: use exact equality for leaf routes (pathname === '/dashboard/my-route') and subtree checks (pathname.startsWith('/dashboard/my-route/')); exportmetadatausingcreatePageMetadata(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}: ExportmetadatausingcreatePageMetadata()fromweb/src/lib/page-titles.tsin SSR entry points for dashboard pages
UseDashboardTitleSynccomponent andgetDashboardDocumentTitle()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 thenodeenvironment, matching thesrc/structure in thetests/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 toconfig.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(), orsafeEditReply()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 thejsdomenvironment and React Testing Library, matching theweb/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.tsxweb/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.jsweb/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.tsxweb/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.jsweb/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.jsweb/tests/components/dashboard/error-boundary.test.tsxweb/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.jsweb/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.jsweb/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.jsweb/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
[warning] 124-124: Do not use Array index in keys
web/tests/stores/members-store.test.ts
[warning] 316-318: This assertion is unnecessary since it does not change the type of the expression.
[warning] 276-278: This assertion is unnecessary since it does not change the type of the expression.
🔇 Additional comments (14)
src/api/routes/moderation.js (2)
78-84: LGTM — OpenAPI documentation now matches the implementation.The
orderparameter 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
orderto 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 forORDER BYdirection.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
sonnerand@/lib/loggerare properly configured for testing error boundary behavior.
12-25: Console suppression now uses the correct pattern.The previous approach mutated
console.errordirectly. The current implementation usesvi.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 handlesNODE_ENVrestoration.
27-32: LGTM!The
ThrowingComponenthelper correctly usesReadonlyfor 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.stubEnvand 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 withpathname.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()andres.json(), plus the bump inresetAll(), close the late-response window cleanly.Also applies to: 114-147
🧹 Preview Environment Cleaned UpThe Railway preview environment for this PR has been removed. Environment: |
|
There was a problem hiding this comment.
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 intosrc/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. |
| 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; |
There was a problem hiding this comment.
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.
|
|
||
| const fetchDetail = useCallback(async () => { | ||
| if (!guildId || !conversationId) return; | ||
| if (!guildId || !conversationId) { |
There was a problem hiding this comment.
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).
| if (!guildId || !conversationId) { | |
| if (!guildId || !conversationId) { | |
| setData(null); | |
| setError(null); |
| role="button" | ||
| aria-label={`View conversation in ${convo.channelName}`} | ||
| onKeyDown={(e: React.KeyboardEvent) => { | ||
| if (e.key === 'Enter' || e.key === ' ') { |
There was a problem hiding this comment.
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.
| 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') { |


Addresses items from #144 — deferred tech debt collected from PR reviews, code audits, and session notes.
Workstreams
✅ Code Quality
events.jshandlers — Pulled inline handlers (clientReady, commandInteraction) into separate modules undersrc/modules/events/. Main events file is now a clean dispatcher.web/src/lib/logger.tswith proper client-side logging (structured, level-aware)console.errorcleanup — Replaced raw console calls in dashboard components with logger🔄 In Progress (will merge as subagents complete)
Not Included
Tests
Closes #144