Conversation
|
This PR was not deployed automatically as @MohsinCoding does not have access to the Railway project. In order to get automatic PR deploys, please add @MohsinCoding to your workspace on Railway. |
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a new top-level Changes
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Pull request overview
Adds configurable rotating Discord bot presence (“botStatus”) with template interpolation, new config schema paths, startup/shutdown lifecycle wiring, and dashboard controls, with expanded test coverage to validate behavior.
Changes:
- Implement rotating presence messages with new
botStatus.rotation.*config shape (plus legacy compatibility) and template variables ({guildCount},{memberCount},{commandCount},{uptime},{version},{botName}). - Wire bot status lifecycle into bot startup/shutdown and hot-reload via config listeners.
- Add dashboard UI + web types for bot presence/rotation controls and extend API/schema + tests for new config paths.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| web/src/types/config.ts | Adds botStatus and rotation message types to the web config model. |
| web/src/components/dashboard/config-workspace/types.ts | Adds bot-status feature id for dashboard workspace typing. |
| web/src/components/dashboard/config-workspace/config-categories.ts | Exposes “Bot Presence” in categories, labels, and search. |
| web/src/components/dashboard/config-editor.tsx | Adds UI controls for bot presence status + rotation toggle/interval. |
| tests/modules/botStatus.test.js | Expands tests for interpolation variables and rotation config behavior. |
| tests/config-listeners.test.js | Updates expectations for new botStatus rotation listener keys/count. |
| tests/api/utils/validateConfigPatch.test.js | Allows botStatus as a writable top-level config section in test allowlist. |
| tests/api/utils/configValidation.test.js | Adds schema coverage assertions + botStatus validation tests. |
| tests/api/utils/configAllowlist.test.js | Ensures botStatus is included in readable/writable allowlists. |
| src/modules/botStatus.js | Implements new rotation config parsing, interpolation variables, scheduler behavior. |
| src/index.js | Starts/stops bot status module in startup and graceful shutdown. |
| src/config-listeners.js | Adds hot-reload listeners for botStatus.rotation.* config paths. |
| src/api/utils/configValidation.js | Adds botStatus schema definition for API validation. |
| config.json | Adds default botStatus.rotation configuration to seed defaults. |
| return getRotationMessages(cfg).map((entry) => entry.text); | ||
| } | ||
|
|
||
| /** | ||
| * Resolve rotation interval in milliseconds with Discord-safe minimum. | ||
| * | ||
| * @param {Object} cfg - botStatus config section | ||
| * @returns {number} | ||
| */ | ||
| export function resolveRotationIntervalMs(cfg) { | ||
| if (typeof cfg?.rotation?.intervalMinutes === 'number' && cfg.rotation.intervalMinutes > 0) { | ||
| return Math.round(cfg.rotation.intervalMinutes * 60_000); | ||
| } | ||
|
|
| type: 'object', | ||
| required: ['text'], |
| {activeCategoryId === 'community-tools' && visibleFeatureIds.has('bot-status') && ( | ||
| <SettingsFeatureCard | ||
| featureId="bot-status" | ||
| title="Bot Presence" | ||
| description="Set bot presence and rotate status messages." | ||
| enabled={draftConfig.botStatus?.enabled ?? true} | ||
| onEnabledChange={(v) => updateBotStatusField('enabled', v)} | ||
| disabled={saving} | ||
| basicContent={ | ||
| <div className="space-y-4"> | ||
| <label htmlFor="bot-status-value" className="space-y-2 block"> | ||
| <span className="text-sm font-medium">Presence Status</span> | ||
| <select | ||
| id="bot-status-value" | ||
| value={draftConfig.botStatus?.status ?? 'online'} | ||
| onChange={(e) => updateBotStatusField('status', e.target.value)} | ||
| disabled={saving} |
|
| Filename | Overview |
|---|---|
| src/modules/botStatus.js | Core rewrite: adds rotation config, template variables ({commandCount}, {uptime}, {version}), Custom activity support, and legacy backward compatibility. Missing minimum interval enforcement despite JSDoc claim. |
| src/config-listeners.js | Adds hot-reload listeners for new rotation config paths (rotation.enabled, rotation.intervalMinutes, rotation.messages). Clean integration with existing pattern. |
| src/index.js | Wires startBotStatus into startup and stopBotStatus into graceful shutdown. Clean, minimal changes. |
| src/api/utils/configValidation.js | Adds botStatus schema with rotation sub-object, activity type enum, and status enum validation. Message items lack properties definition and intervalMinutes has no minimum. |
| config.json | Adds default botStatus rotation config with 3 sample messages and 5-minute interval. Well-structured defaults. |
| web/src/components/dashboard/config-editor.tsx | Adds Bot Presence feature card with status selector, rotation toggle, and interval input. UI enforces min=1 for interval. Clean integration. |
| web/src/types/config.ts | Adds BotStatusConfig, BotStatusRotationConfig, and BotStatusRotationMessage interfaces. Clean TypeScript types with proper union literals. |
| tests/modules/botStatus.test.js | Good test coverage for new rotation messages, interval resolution, template variables, and new config shape rotation. Tests both legacy and new formats. |
| tests/config-listeners.test.js | Updates listener count from 18 to 22 and verifies new rotation config paths are registered. Correctly tracks the 4 new listeners. |
| tests/api/utils/configValidation.test.js | Adds botStatus schema validation tests covering valid rotation settings, invalid status values, and missing required text field. Good coverage. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[startup - index.js] -->|startBotStatus| B[botStatus.js]
B --> C{cfg.enabled?}
C -->|No| D[Skip - log disabled]
C -->|Yes| E[applyPresence - set initial status]
E --> F{isRotationEnabled?}
F -->|No| G[Static status - no rotation]
F -->|Yes| H[Start setInterval]
H --> I[rotate - advance index]
I --> J[getRotationMessages]
J --> K{rotation.messages exists?}
K -->|Yes| L[Use new format - per-message type]
K -->|No| M{legacy activities exists?}
M -->|Yes| N[Use legacy format - shared activityType]
M -->|No| O[Default: Playing with Discord]
L --> P[interpolateActivity]
N --> P
O --> P
P --> Q[buildActivityPayload]
Q --> R[client.user.setPresence]
S[config-listeners.js] -->|onConfigChange botStatus.*| T[reloadBotStatus]
T --> U[stopBotStatus]
U --> B
V[gracefulShutdown] -->|stopBotStatus| W[clearInterval + null client]
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/modules/botStatus.js
Line: 238-251
Comment:
**Missing minimum interval enforcement despite JSDoc claim**
The JSDoc says "with Discord-safe minimum" but no minimum is actually enforced. While the dashboard UI uses `min={1}`, the config API has no such guard — a direct API patch with `intervalMinutes: 0.001` or `rotateIntervalMs: 100` would be accepted, potentially hammering the Discord API and triggering rate limits.
Consider clamping to a reasonable floor (e.g. 1 minute / 60000ms):
```suggestion
export function resolveRotationIntervalMs(cfg) {
const MIN_INTERVAL_MS = 60_000; // 1 minute — Discord rate-limit safety floor
if (typeof cfg?.rotation?.intervalMinutes === 'number' && cfg.rotation.intervalMinutes > 0) {
return Math.max(MIN_INTERVAL_MS, Math.round(cfg.rotation.intervalMinutes * 60_000));
}
if (typeof cfg?.rotateIntervalMs === 'number' && cfg.rotateIntervalMs > 0) {
return Math.max(MIN_INTERVAL_MS, cfg.rotateIntervalMs);
}
if (cfg?.rotation) {
return DEFAULT_ROTATE_INTERVAL_MINUTES * 60_000;
}
return DEFAULT_LEGACY_ROTATE_INTERVAL_MS;
}
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/api/utils/configValidation.js
Line: 195
Comment:
**No minimum constraint on `intervalMinutes`**
The schema accepts any positive number (e.g. `0.001`) for `intervalMinutes`. Without a `min` constraint here, the API allows values that could cause aggressive Discord API polling. Consider adding a `min` property if the schema validator supports it, or validate in `resolveRotationIntervalMs` (see related comment on `botStatus.js`).
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: e987db7
| export function resolveRotationIntervalMs(cfg) { | ||
| if (typeof cfg?.rotation?.intervalMinutes === 'number' && cfg.rotation.intervalMinutes > 0) { | ||
| return Math.round(cfg.rotation.intervalMinutes * 60_000); | ||
| } | ||
|
|
||
| if (typeof cfg?.rotateIntervalMs === 'number' && cfg.rotateIntervalMs > 0) { | ||
| return cfg.rotateIntervalMs; | ||
| } | ||
| return ['with Discord']; | ||
|
|
||
| if (cfg?.rotation) { | ||
| return DEFAULT_ROTATE_INTERVAL_MINUTES * 60_000; | ||
| } | ||
|
|
||
| return DEFAULT_LEGACY_ROTATE_INTERVAL_MS; |
There was a problem hiding this comment.
Missing minimum interval enforcement despite JSDoc claim
The JSDoc says "with Discord-safe minimum" but no minimum is actually enforced. While the dashboard UI uses min={1}, the config API has no such guard — a direct API patch with intervalMinutes: 0.001 or rotateIntervalMs: 100 would be accepted, potentially hammering the Discord API and triggering rate limits.
Consider clamping to a reasonable floor (e.g. 1 minute / 60000ms):
| export function resolveRotationIntervalMs(cfg) { | |
| if (typeof cfg?.rotation?.intervalMinutes === 'number' && cfg.rotation.intervalMinutes > 0) { | |
| return Math.round(cfg.rotation.intervalMinutes * 60_000); | |
| } | |
| if (typeof cfg?.rotateIntervalMs === 'number' && cfg.rotateIntervalMs > 0) { | |
| return cfg.rotateIntervalMs; | |
| } | |
| return ['with Discord']; | |
| if (cfg?.rotation) { | |
| return DEFAULT_ROTATE_INTERVAL_MINUTES * 60_000; | |
| } | |
| return DEFAULT_LEGACY_ROTATE_INTERVAL_MS; | |
| export function resolveRotationIntervalMs(cfg) { | |
| const MIN_INTERVAL_MS = 60_000; // 1 minute — Discord rate-limit safety floor | |
| if (typeof cfg?.rotation?.intervalMinutes === 'number' && cfg.rotation.intervalMinutes > 0) { | |
| return Math.max(MIN_INTERVAL_MS, Math.round(cfg.rotation.intervalMinutes * 60_000)); | |
| } | |
| if (typeof cfg?.rotateIntervalMs === 'number' && cfg.rotateIntervalMs > 0) { | |
| return Math.max(MIN_INTERVAL_MS, cfg.rotateIntervalMs); | |
| } | |
| if (cfg?.rotation) { | |
| return DEFAULT_ROTATE_INTERVAL_MINUTES * 60_000; | |
| } | |
| return DEFAULT_LEGACY_ROTATE_INTERVAL_MS; | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/modules/botStatus.js
Line: 238-251
Comment:
**Missing minimum interval enforcement despite JSDoc claim**
The JSDoc says "with Discord-safe minimum" but no minimum is actually enforced. While the dashboard UI uses `min={1}`, the config API has no such guard — a direct API patch with `intervalMinutes: 0.001` or `rotateIntervalMs: 100` would be accepted, potentially hammering the Discord API and triggering rate limits.
Consider clamping to a reasonable floor (e.g. 1 minute / 60000ms):
```suggestion
export function resolveRotationIntervalMs(cfg) {
const MIN_INTERVAL_MS = 60_000; // 1 minute — Discord rate-limit safety floor
if (typeof cfg?.rotation?.intervalMinutes === 'number' && cfg.rotation.intervalMinutes > 0) {
return Math.max(MIN_INTERVAL_MS, Math.round(cfg.rotation.intervalMinutes * 60_000));
}
if (typeof cfg?.rotateIntervalMs === 'number' && cfg.rotateIntervalMs > 0) {
return Math.max(MIN_INTERVAL_MS, cfg.rotateIntervalMs);
}
if (cfg?.rotation) {
return DEFAULT_ROTATE_INTERVAL_MINUTES * 60_000;
}
return DEFAULT_LEGACY_ROTATE_INTERVAL_MS;
}
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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/utils/configValidation.js`:
- Around line 196-201: The rotation.messages[] JSON schema currently only
requires the presence of a text key; tighten it by making the message item
schema validate shapes: in the schema for rotation.messages.items (the object
validated today), change required/items to explicitly define properties: set
properties.text: { type: 'string', minLength: 1 }, optionally add
properties.type: { type: 'string', enum: ['text','markdown','html'] } (or your
allowed types), set additionalProperties: false, and keep required: ['text'];
this ensures non-string text or unexpected keys are rejected by the validator.
In `@src/index.js`:
- Around line 487-488: The call to startBotStatus(client) runs before the bot is
logged in so applyPresence(client) returns early and the initial presence is
delayed; move the startBotStatus(client) invocation to after the
client.login(token) (or explicitly call applyPresence(client) immediately after
client.login) so the initial presence is applied right away; update any code in
src/index.js where startBotStatus or applyPresence is invoked to run post-login
(referencing startBotStatus, applyPresence, and client.login(token)).
In `@src/modules/botStatus.js`:
- Around line 235-242: resolveRotationIntervalMs currently returns whatever the
config supplies (cfg.rotation.intervalMinutes or cfg.rotateIntervalMs) without
enforcing the Discord-safe minimum; update it to clamp the computed interval to
a minimum of 4000 ms (20s/5 updates) before returning. In practice compute the
interval from cfg.rotation.intervalMinutes
(Math.round(cfg.rotation.intervalMinutes * 60000)) or take cfg.rotateIntervalMs,
then apply Math.max(computedMs, 4000) and return that clamped value so presence
updates cannot exceed Discord rate limits; keep using the existing function name
resolveRotationIntervalMs and the same config keys (cfg.rotation.intervalMinutes
and cfg.rotateIntervalMs).
In `@tests/modules/botStatus.test.js`:
- Around line 117-121: The test for interpolateActivity is too weak; update the
spec so it asserts the placeholder is actually removed and that the string
begins with the 'v' prefix. In the 'replaces {version} from package.json' test
(using makeClient() and interpolateActivity('v{version}', client)), add an
assertion that result does not contain the literal '{version}' (e.g.,
expect(result).not.toContain('{version}')) and keep/strengthen the existing
check that it starts with 'v' and has content after it (e.g.,
expect(result).toMatch(/^v.+/)).
🪄 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: 6490c900-2402-42cb-8c4d-8990bf351ace
📒 Files selected for processing (14)
config.jsonsrc/api/utils/configValidation.jssrc/config-listeners.jssrc/index.jssrc/modules/botStatus.jstests/api/utils/configAllowlist.test.jstests/api/utils/configValidation.test.jstests/api/utils/validateConfigPatch.test.jstests/config-listeners.test.jstests/modules/botStatus.test.jsweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-workspace/types.tsweb/src/types/config.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 (10)
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
tests/api/utils/configAllowlist.test.jssrc/index.jstests/api/utils/validateConfigPatch.test.jstests/modules/botStatus.test.jssrc/config-listeners.jstests/config-listeners.test.jstests/api/utils/configValidation.test.jssrc/api/utils/configValidation.jssrc/modules/botStatus.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:
tests/api/utils/configAllowlist.test.jsweb/src/components/dashboard/config-workspace/types.tsweb/src/types/config.tssrc/index.jstests/api/utils/validateConfigPatch.test.jstests/modules/botStatus.test.jssrc/config-listeners.jstests/config-listeners.test.jstests/api/utils/configValidation.test.jsweb/src/components/dashboard/config-workspace/config-categories.tssrc/api/utils/configValidation.jsweb/src/components/dashboard/config-editor.tsxsrc/modules/botStatus.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/utils/configAllowlist.test.jstests/api/utils/validateConfigPatch.test.jstests/modules/botStatus.test.jstests/config-listeners.test.jstests/api/utils/configValidation.test.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:
tests/api/utils/configAllowlist.test.jsweb/src/components/dashboard/config-workspace/types.tsweb/src/types/config.tssrc/index.jstests/api/utils/validateConfigPatch.test.jstests/modules/botStatus.test.jssrc/config-listeners.jstests/config-listeners.test.jstests/api/utils/configValidation.test.jsweb/src/components/dashboard/config-workspace/config-categories.tssrc/api/utils/configValidation.jsweb/src/components/dashboard/config-editor.tsxsrc/modules/botStatus.js
**/*.json
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use double quotes in JSON files (standard JSON format); this exception applies only to JSON files, not JavaScript/TypeScript
Files:
config.json
config.json
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
All community features in
config.jsonshould be gated behind aconfig.<feature>.enabledflag; moderation commands are always available regardless of config settings
Files:
config.json
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/components/dashboard/config-workspace/types.tsweb/src/types/config.tsweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-editor.tsx
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/index.jssrc/config-listeners.jssrc/api/utils/configValidation.jssrc/modules/botStatus.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/index.jssrc/modules/botStatus.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/botStatus.js
🧠 Learnings (5)
📚 Learning: 2026-03-12T02:03:36.476Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.476Z
Learning: Applies to src/api/utils/configAllowlist.js : When adding a new config section, add it to `SAFE_CONFIG_KEYS` to enable saves via the API/dashboard, and to `READABLE_CONFIG_KEYS` for API read access
Applied to files:
tests/api/utils/configAllowlist.test.jsweb/src/components/dashboard/config-workspace/types.tstests/api/utils/validateConfigPatch.test.js
📚 Learning: 2026-03-12T02:03:52.689Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.689Z
Learning: Add config sections to `SAFE_CONFIG_KEYS` in `src/api/utils/configAllowlist.js` when adding new writable config options
Applied to files:
tests/api/utils/configAllowlist.test.jstests/api/utils/validateConfigPatch.test.js
📚 Learning: 2026-03-12T02:03:36.476Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.476Z
Learning: Applies to config.json : All community features in `config.json` should be gated behind a `config.<feature>.enabled` flag; moderation commands are always available regardless of config settings
Applied to files:
config.jsonweb/src/components/dashboard/config-editor.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/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)
Applied to files:
web/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-10T20:36:29.483Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T20:36:29.483Z
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/index.js
🪛 GitHub Check: SonarCloud Code Analysis
tests/api/utils/configValidation.test.js
[warning] 141-141: Unnecessary escape character: ".
[warning] 141-141: Unnecessary escape character: ".
web/src/components/dashboard/config-editor.tsx
[warning] 961-961: The empty object is useless.
[warning] 962-962: The empty object is useless.
src/modules/botStatus.js
[warning] 125-125: Prefer String#replaceAll() over String#replace().
[warning] 148-148: Unexpected negated condition.
[warning] 124-124: Prefer String#replaceAll() over String#replace().
[warning] 127-127: Prefer String#replaceAll() over String#replace().
[warning] 126-126: Prefer String#replaceAll() over String#replace().
🔇 Additional comments (13)
config.json (1)
153-174:botStatusdefault config is well-structured and feature-gated.The added section has clear enable flags and sane starter rotation defaults.
src/modules/botStatus.js (1)
195-217: Backward-compatible message resolution (new + legacy) is implemented cleanly.The fallback flow from
rotation.messagesto legacyactivitiesis solid and resilient.src/index.js (1)
296-296: Good shutdown lifecycle wiring.Stopping bot status during graceful shutdown is the right cleanup point.
tests/api/utils/configAllowlist.test.js (1)
23-23: Allowlist coverage extension is correct.Good addition of
botStatusassertions for both writable and readable config surfaces.Also applies to: 39-39
src/config-listeners.js (1)
116-119: Rotation-path listener coverage is complete.Adding dedicated listeners for the new
botStatus.rotation.*keys correctly wires hot-reload behavior.web/src/components/dashboard/config-workspace/types.ts (1)
29-30:ConfigFeatureIdupdate is aligned with dashboard feature mapping.The new
'bot-status'union member cleanly supports the added workspace feature.tests/api/utils/validateConfigPatch.test.js (1)
15-15: Test fixture allowlist sync looks good.Including
botStatusin the safe key set matches the new writable config surface.tests/config-listeners.test.js (1)
95-103: Rotation listener coverage update looks correct.These assertions properly extend the registration checks for the new
botStatus.rotation.*paths and keep the total listener count expectation in sync.web/src/components/dashboard/config-workspace/config-categories.ts (1)
38-39: Bot Presence wiring across category metadata is coherent.
sectionKeys,featureIds,FEATURE_LABELS, andCONFIG_SEARCH_ITEMSare all aligned for the newbot-statusfeature, so navigation and search indexing should resolve correctly.Also applies to: 70-71, 353-361
tests/api/utils/configValidation.test.js (1)
107-115: Good schema-test expansion forbotStatus.You added both top-level schema presence checks and targeted validation cases (valid paths + invalid enum/shape), which strengthens regression protection for the new config section.
Also applies to: 120-143
tests/modules/botStatus.test.js (1)
109-115: Rotation test coverage additions are solid.The new tests cover placeholder interpolation, interval resolution, message-source selection, and runtime rotation behavior for the new
rotationconfig shape.Also applies to: 212-232, 405-425
web/src/components/dashboard/config-editor.tsx (1)
119-120: Bot Presence editor integration is consistent with the existing workspace architecture.The new section key recognition, nested draft updaters, and
SettingsFeatureCardimplementation fit cleanly into the current category-driven editor flow.Based on learnings: "Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)".
Also applies to: 944-968, 2218-2275
web/src/types/config.ts (1)
249-264: Type-layer updates forbotStatusare well connected.The new interfaces plus
BotConfig/ConfigSectionextensions provide the expected type plumbing for the feature across editor and workspace code.Also applies to: 368-369, 394-395
| messages: { | ||
| type: 'array', | ||
| items: { | ||
| type: 'object', | ||
| required: ['text'], | ||
| }, |
There was a problem hiding this comment.
Harden rotation.messages[] schema validation.
Line 196–201 only enforces key presence (text), not field shape. Invalid payloads (e.g., non-string text, unsupported type) can pass API validation and silently degrade later.
Proposed schema + validator tightening
rotation: {
type: 'object',
properties: {
enabled: { type: 'boolean' },
intervalMinutes: { type: 'number' },
messages: {
type: 'array',
items: {
type: 'object',
+ properties: {
+ type: {
+ type: 'string',
+ enum: ['Playing', 'Watching', 'Listening', 'Competing', 'Streaming', 'Custom'],
+ },
+ text: { type: 'string' },
+ },
required: ['text'],
},
},
},
}, } else if (schema.items.type === 'object') {
if (typeof item !== 'object' || item === null || Array.isArray(item)) {
errors.push(
`${path}[${i}]: expected object, got ${Array.isArray(item) ? 'array' : item === null ? 'null' : typeof item}`,
);
- } else if (schema.items.required) {
- for (const key of schema.items.required) {
- if (!(key in item)) {
- errors.push(`${path}[${i}]: missing required key "${key}"`);
- }
- }
+ } else {
+ if (schema.items.required) {
+ for (const key of schema.items.required) {
+ if (!(key in item)) {
+ errors.push(`${path}[${i}]: missing required key "${key}"`);
+ }
+ }
+ }
+ if (schema.items.properties) {
+ errors.push(...validateValue(item, schema.items, `${path}[${i}]`));
+ }
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/api/utils/configValidation.js` around lines 196 - 201, The
rotation.messages[] JSON schema currently only requires the presence of a text
key; tighten it by making the message item schema validate shapes: in the schema
for rotation.messages.items (the object validated today), change required/items
to explicitly define properties: set properties.text: { type: 'string',
minLength: 1 }, optionally add properties.type: { type: 'string', enum:
['text','markdown','html'] } (or your allowed types), set additionalProperties:
false, and keep required: ['text']; this ensures non-string text or unexpected
keys are rejected by the validator.
| // Start configurable bot presence rotation | ||
| startBotStatus(client); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n "startBotStatus\\(|client\\.login\\(" src/index.js -C2
rg -n "if \\(!cfg\\?\\.enabled \\|\\| !client\\?\\.user\\) return" src/modules/botStatus.js -C2Repository: VolvoxLLC/volvox-bot
Length of output: 556
🏁 Script executed:
# Get the full startBotStatus function implementation
rg -n "function startBotStatus|export.*startBotStatus|const startBotStatus" src/ -A 20
# Get the applyPresence function
rg -n "function applyPresence|const applyPresence|async.*applyPresence" src/ -A 15
# Check for ClientReady event handlers
rg -n "ClientReady|client\.on.*ready" src/index.js -C 3Repository: VolvoxLLC/volvox-bot
Length of output: 2562
🏁 Script executed:
# Get more context on the ClientReady handler - what happens there
rg -n "ClientReady" src/index.js -A 30
# Check the rotation interval setup
rg -n "rotateInterval|setInterval" src/modules/botStatus.js -B 2 -A 8Repository: VolvoxLLC/volvox-bot
Length of output: 3222
🏁 Script executed:
# Check if there are any event handlers on ready that might depend on startBotStatus
rg -n "startBotStatus|on.*ready|ClientReady" src/index.js -B 2 -A 2
# Verify the rotate function and see if it has the same client.user check
rg -n "function rotate|const rotate" src/modules/botStatus.js -A 10Repository: VolvoxLLC/volvox-bot
Length of output: 1032
Move startBotStatus(client) to after client.login(token) to ensure initial presence applies immediately.
Currently at line 487, startBotStatus() calls applyPresence(client) before the client is logged in, but applyPresence() returns early when !client?.user. This delays the first visible status update until the next rotation interval tick (5+ minutes by default), instead of appearing immediately on startup.
Proposed fix
- // Start configurable bot presence rotation
- startBotStatus(client);
-
// Start tempban scheduler for automatic unbans (DB required)
if (dbPool) {
startTempbanScheduler(client);
startWarningExpiryScheduler();
startScheduler(client);
startGithubFeed(client);
}
// Load commands and login
await loadCommands();
await client.login(token);
+
+ // Start configurable bot presence rotation after client is authenticated
+ startBotStatus(client);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/index.js` around lines 487 - 488, The call to startBotStatus(client) runs
before the bot is logged in so applyPresence(client) returns early and the
initial presence is delayed; move the startBotStatus(client) invocation to after
the client.login(token) (or explicitly call applyPresence(client) immediately
after client.login) so the initial presence is applied right away; update any
code in src/index.js where startBotStatus or applyPresence is invoked to run
post-login (referencing startBotStatus, applyPresence, and client.login(token)).
| export function resolveRotationIntervalMs(cfg) { | ||
| if (typeof cfg?.rotation?.intervalMinutes === 'number' && cfg.rotation.intervalMinutes > 0) { | ||
| return Math.round(cfg.rotation.intervalMinutes * 60_000); | ||
| } | ||
|
|
||
| if (typeof cfg?.rotateIntervalMs === 'number' && cfg.rotateIntervalMs > 0) { | ||
| return cfg.rotateIntervalMs; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is Discord's bot presence update rate limit (status/activity updates), and is a 20-second minimum still recommended?
💡 Result:
- Officially documented (Gateway send rate limit): Discord allows 120 Gateway send events per connection per 60 seconds (~2/sec). This budget includes
Update Presence(opcode 3) packets. (docs.discord.com) - Presence-specific limit (commonly enforced/observed): In practice, presence updates are treated as having an opcode-specific limit of 5 updates per 20 seconds (per session/connection). (docs.discord.food)
- If you hit an opcode-specific limit: the Gateway sends a
Rate Limitedevent with aretry_aftervalue you must respect. (docs.discord.com)
Is a 20-second minimum still recommended?
Yes—spacing presence/activity changes to ≥20 seconds is still a safe, conservative rule (it stays well under the 5/20s limit). (docs.discord.food)
Citations:
- 1: https://docs.discord.com/developers/events/gateway
- 2: https://docs.discord.food/topics/gateway-events?utm_source=openai
- 3: https://docs.discord.com/developers/events/gateway-events
- 4: https://docs.discord.food/topics/gateway-events?utm_source=openai
🏁 Script executed:
# First, check if the file exists and get its size
wc -l src/modules/botStatus.jsRepository: VolvoxLLC/volvox-bot
Length of output: 92
🏁 Script executed:
# Read the specific lines mentioned (235-242) with context
sed -n '230,250p' src/modules/botStatus.js | cat -nRepository: VolvoxLLC/volvox-bot
Length of output: 777
🏁 Script executed:
# Search for any existing MIN/MAX interval constants or rate limit comments
rg -i 'min.*interval|rate.*limit|20.?s|throttl' src/modules/botStatus.js -A 2 -B 2Repository: VolvoxLLC/volvox-bot
Length of output: 278
🏁 Script executed:
# Look at the beginning of the file to find constant definitions
head -40 src/modules/botStatus.jsRepository: VolvoxLLC/volvox-bot
Length of output: 1124
🏁 Script executed:
# Search for usages of resolveRotationIntervalMs
rg 'resolveRotationIntervalMs' src/modules/botStatus.js -B 2 -A 2Repository: VolvoxLLC/volvox-bot
Length of output: 462
🏁 Script executed:
# Search for any existing MIN constant definitions
rg 'const.*MIN.*=' src/modules/botStatus.jsRepository: VolvoxLLC/volvox-bot
Length of output: 106
🏁 Script executed:
# Search for DEFAULT_LEGACY_ROTATE_INTERVAL_MS constant
rg 'DEFAULT_LEGACY_ROTATE_INTERVAL_MS' src/modules/botStatus.jsRepository: VolvoxLLC/volvox-bot
Length of output: 157
🏁 Script executed:
# Get more context around the rotation interval setup
sed -n '200,250p' src/modules/botStatus.js | cat -nRepository: VolvoxLLC/volvox-bot
Length of output: 1841
🏁 Script executed:
# Check what the rotate function does to understand if presence updates are sent
rg -A 15 'async function rotate\(client\)' src/modules/botStatus.jsRepository: VolvoxLLC/volvox-bot
Length of output: 46
🏁 Script executed:
# Search for rotate function with different patterns
rg 'rotate\s*=' src/modules/botStatus.js -A 15Repository: VolvoxLLC/volvox-bot
Length of output: 46
🏁 Script executed:
# Get the full context around line 280-330 to see the rotate function
sed -n '280,330p' src/modules/botStatus.js | cat -nRepository: VolvoxLLC/volvox-bot
Length of output: 1837
Clamp configured rotation interval to a Discord-safe minimum.
The resolveRotationIntervalMs() function accepts unclamped values that can trigger Discord presence update throttling. Although the JSDoc claims "Discord-safe minimum," the implementation does not enforce it. Users can configure very small intervals (e.g., intervalMinutes: 0.1 = 6 seconds) which would exceed Discord's documented rate limit of 5 presence updates per 20 seconds.
Proposed fix
const DEFAULT_LEGACY_ROTATE_INTERVAL_MS = 30_000;
const DEFAULT_ROTATE_INTERVAL_MINUTES = 5;
+const MIN_PRESENCE_INTERVAL_MS = 20_000;
export function resolveRotationIntervalMs(cfg) {
if (typeof cfg?.rotation?.intervalMinutes === 'number' && cfg.rotation.intervalMinutes > 0) {
- return Math.round(cfg.rotation.intervalMinutes * 60_000);
+ return Math.max(Math.round(cfg.rotation.intervalMinutes * 60_000), MIN_PRESENCE_INTERVAL_MS);
}
if (typeof cfg?.rotateIntervalMs === 'number' && cfg.rotateIntervalMs > 0) {
- return cfg.rotateIntervalMs;
+ return Math.max(cfg.rotateIntervalMs, MIN_PRESENCE_INTERVAL_MS);
}
if (cfg?.rotation) {
return DEFAULT_ROTATE_INTERVAL_MINUTES * 60_000;
}
return DEFAULT_LEGACY_ROTATE_INTERVAL_MS;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/modules/botStatus.js` around lines 235 - 242, resolveRotationIntervalMs
currently returns whatever the config supplies (cfg.rotation.intervalMinutes or
cfg.rotateIntervalMs) without enforcing the Discord-safe minimum; update it to
clamp the computed interval to a minimum of 4000 ms (20s/5 updates) before
returning. In practice compute the interval from cfg.rotation.intervalMinutes
(Math.round(cfg.rotation.intervalMinutes * 60000)) or take cfg.rotateIntervalMs,
then apply Math.max(computedMs, 4000) and return that clamped value so presence
updates cannot exceed Discord rate limits; keep using the existing function name
resolveRotationIntervalMs and the same config keys (cfg.rotation.intervalMinutes
and cfg.rotateIntervalMs).
| it('replaces {version} from package.json', () => { | ||
| const client = makeClient(); | ||
| const result = interpolateActivity('v{version}', client); | ||
| expect(result).toMatch(/^v.+/); | ||
| }); |
There was a problem hiding this comment.
Strengthen the {version} interpolation assertion.
expect(result).toMatch(/^v.+/) can still pass when {version} is not replaced. Assert token removal explicitly.
Suggested test assertion tightening
it('replaces {version} from package.json', () => {
const client = makeClient();
const result = interpolateActivity('v{version}', client);
- expect(result).toMatch(/^v.+/);
+ expect(result).not.toContain('{version}');
+ expect(result).toMatch(/^v(unknown|\d+\.\d+\.\d+.*)$/);
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('replaces {version} from package.json', () => { | |
| const client = makeClient(); | |
| const result = interpolateActivity('v{version}', client); | |
| expect(result).toMatch(/^v.+/); | |
| }); | |
| it('replaces {version} from package.json', () => { | |
| const client = makeClient(); | |
| const result = interpolateActivity('v{version}', client); | |
| expect(result).not.toContain('{version}'); | |
| expect(result).toMatch(/^v(unknown|\d+\.\d+\.\d+.*)$/); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/modules/botStatus.test.js` around lines 117 - 121, The test for
interpolateActivity is too weak; update the spec so it asserts the placeholder
is actually removed and that the string begins with the 'v' prefix. In the
'replaces {version} from package.json' test (using makeClient() and
interpolateActivity('v{version}', client)), add an assertion that result does
not contain the literal '{version}' (e.g.,
expect(result).not.toContain('{version}')) and keep/strengthen the existing
check that it starts with 'v' and has content after it (e.g.,
expect(result).toMatch(/^v.+/)).
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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/modules/botStatus.js`:
- Around line 146-151: In resolveActivityType, simplify the negated undefined
check by using a clearer membership or truthy test: replace the ternary
condition that uses ACTIVITY_TYPE_MAP[typeStr] !== undefined with either the
in-operator (typeStr in ACTIVITY_TYPE_MAP) or a nullish fallback
(ACTIVITY_TYPE_MAP[typeStr] ?? ActivityType.Playing) so the function returns
ACTIVITY_TYPE_MAP[typeStr] when present and ActivityType.Playing otherwise;
update the function resolveActivityType accordingly to reference
ACTIVITY_TYPE_MAP and ActivityType.Playing.
- Around line 121-127: The chained global regex replacements in the function
that builds the status text should use String.prototype.replaceAll for clearer
intent and to avoid regex overhead; update the code in the function (the block
returning text.replace(...).replace(...)) to call
text.replaceAll('{memberCount}', String(memberCount)).replaceAll('{guildCount}',
String(guildCount)).replaceAll('{botName}',
botName).replaceAll('{commandCount}',
String(commandCount)).replaceAll('{uptime}', uptime).replaceAll('{version}',
version) ensuring each placeholder is passed as a plain string to replaceAll.
In `@web/src/components/landing/Pricing.tsx`:
- Around line 81-83: The long className expression on the toggle label should be
refactored to respect the 100-char width and improve readability: replace the
negation (!isAnnual) with positive logic (isAnnual ? 'text-muted-foreground' :
'text-foreground') and either compute a small variable (e.g., const
toggleLabelClass = isAnnual ? 'text-muted-foreground' : 'text-foreground') and
use className={`text-sm font-medium transition-colors ${toggleLabelClass}`} in
the span, or break the template literal across multiple concatenated lines;
update the span's className usage (and the analogous one at the other
occurrence) accordingly.
In `@web/src/components/landing/Stats.tsx`:
- Around line 217-225: The fallback object assigned to s (the expression "const
s = stats ?? { ... }") is missing a trailing comma after the last property;
update the object literal used in the stats nullish-coalescing fallback (the
properties servers, members, commandsServed, activeConversations, uptime,
messagesProcessed, cachedAt) to include a trailing comma after cachedAt so it
conforms to the project's multi-line object comma style.
🪄 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: 39606bb3-6e0c-4e66-9864-ee5a5fe8c756
📒 Files selected for processing (13)
src/index.jssrc/modules/botStatus.jssrc/modules/events/messageCreate.jstests/api/routes/warnings.test.jstests/api/utils/configValidation.test.jstests/commands/voice.test.jstests/utils/discordCache.test.jsweb/src/app/page.tsxweb/src/components/landing/FeatureGrid.tsxweb/src/components/landing/Footer.tsxweb/src/components/landing/Hero.tsxweb/src/components/landing/Pricing.tsxweb/src/components/landing/Stats.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (9)
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
tests/commands/voice.test.jstests/api/utils/configValidation.test.jstests/api/routes/warnings.test.jssrc/modules/events/messageCreate.jstests/utils/discordCache.test.jssrc/index.jssrc/modules/botStatus.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:
tests/commands/voice.test.jsweb/src/components/landing/FeatureGrid.tsxweb/src/components/landing/Footer.tsxtests/api/utils/configValidation.test.jsweb/src/components/landing/Stats.tsxtests/api/routes/warnings.test.jssrc/modules/events/messageCreate.jsweb/src/components/landing/Pricing.tsxtests/utils/discordCache.test.jsweb/src/app/page.tsxweb/src/components/landing/Hero.tsxsrc/index.jssrc/modules/botStatus.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/commands/voice.test.jstests/api/utils/configValidation.test.jstests/api/routes/warnings.test.jstests/utils/discordCache.test.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:
tests/commands/voice.test.jsweb/src/components/landing/FeatureGrid.tsxweb/src/components/landing/Footer.tsxtests/api/utils/configValidation.test.jsweb/src/components/landing/Stats.tsxtests/api/routes/warnings.test.jssrc/modules/events/messageCreate.jsweb/src/components/landing/Pricing.tsxtests/utils/discordCache.test.jsweb/src/app/page.tsxweb/src/components/landing/Hero.tsxsrc/index.jssrc/modules/botStatus.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/components/landing/FeatureGrid.tsxweb/src/components/landing/Footer.tsxweb/src/components/landing/Stats.tsxweb/src/components/landing/Pricing.tsxweb/src/app/page.tsxweb/src/components/landing/Hero.tsx
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/messageCreate.jssrc/index.jssrc/modules/botStatus.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/messageCreate.jssrc/modules/botStatus.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/messageCreate.jssrc/index.jssrc/modules/botStatus.js
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/page.tsx
🧠 Learnings (8)
📚 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/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)
Applied to files:
web/src/components/landing/FeatureGrid.tsx
📚 Learning: 2026-03-12T02:03:36.476Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.476Z
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/messageCreate.js
📚 Learning: 2026-03-12T02:03:52.689Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.689Z
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/messageCreate.js
📚 Learning: 2026-03-12T02:03:36.476Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.476Z
Learning: Applies to src/utils/cache.js : Use `src/utils/cache.js` for generic caching with Redis primary + in-memory fallback; use `src/utils/discordCache.js` for Discord API response caching (channels, roles, members); use `src/utils/reputationCache.js` for leaderboard and user reputation data; all caches auto-invalidate when config changes
Applied to files:
tests/utils/discordCache.test.js
📚 Learning: 2026-03-12T02:03:36.476Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.476Z
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/page.tsx
📚 Learning: 2026-03-10T20:36:29.483Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T20:36:29.483Z
Learning: Applies to web/src/app/**/*.{ts,tsx} : Apply static metadata to server-rendered dashboard entry pages and use title template format for root app metadata
Applied to files:
web/src/app/page.tsxweb/src/components/landing/Hero.tsx
📚 Learning: 2026-03-12T02:03:52.689Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-12T02:03:52.689Z
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/page.tsxweb/src/components/landing/Hero.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/components/landing/Hero.tsx
🪛 GitHub Actions: CI
web/src/components/landing/FeatureGrid.tsx
[error] 1-1: Command failed with exit code 1: pnpm --prefix web test (vitest run).
web/src/components/landing/Footer.tsx
[error] 1-1: Command failed with exit code 1: pnpm --prefix web test (vitest run).
web/src/components/landing/Stats.tsx
[error] 1-1: Command failed with exit code 1: pnpm --prefix web test (vitest run).
web/src/components/landing/Pricing.tsx
[error] 1-1: Command failed with exit code 1: pnpm --prefix web test (vitest run).
web/src/app/page.tsx
[error] 1-1: Command failed with exit code 1: pnpm --prefix web test (vitest run).
web/src/components/landing/Hero.tsx
[error] 1-1: Command failed with exit code 1: pnpm --prefix web test (vitest run).
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/landing/Stats.tsx
[warning] 113-121: Mark the props of the component as read-only.
web/src/components/landing/Pricing.tsx
[warning] 82-82: Unexpected negated condition.
src/modules/botStatus.js
[warning] 148-148: Unexpected negated condition.
[warning] 126-126: Prefer String#replaceAll() over String#replace().
[warning] 125-125: Prefer String#replaceAll() over String#replace().
[warning] 127-127: Prefer String#replaceAll() over String#replace().
[warning] 124-124: Prefer String#replaceAll() over String#replace().
🔇 Additional comments (24)
tests/commands/voice.test.js (1)
266-268: Looks good — readable assertion formatting with unchanged behavior.This multiline
toContainassertion keeps the same expected CSV content while improving readability.web/src/components/landing/Footer.tsx (1)
26-26: LGTM — Formatting-only changes.These are purely cosmetic JSX formatting adjustments (inline text wrapping, multiline anchor tag attributes) with no behavioral impact. The code adheres to the coding guidelines.
Also applies to: 72-92, 104-105
tests/api/routes/warnings.test.js (1)
70-72: LGTM: Formatting improvement.The multi-line format improves readability without changing the test behavior. The formatting follows all coding guidelines: single quotes, proper semicolon, and 2-space indentation.
web/src/app/page.tsx (2)
96-101: Formatting-only SVG path updates look good.These edits only reflow attributes and preserve icon path data and rendering behavior.
Also applies to: 106-111
144-147: Anchor tag reformatting is safe and preserves behavior.Docs/GitHub link targets and security attributes remain intact; this is a clean non-functional change.
Also applies to: 150-155
web/src/components/landing/Pricing.tsx (1)
159-161: CTA anchor formatting change looks goodThis is a formatting-only change; behavior and security attributes (
target="_blank"+rel="noopener noreferrer") remain correct.Also applies to: 163-165
web/src/components/landing/Stats.tsx (2)
113-121: MakeStatCardprops read-only to prevent accidental mutationPlease type the component props as
Readonly<StatCardProps>(or readonly fields in the interface) to align with immutable prop conventions and the static analysis warning.💡 Suggested refactor
-function StatCard({ +function StatCard({ icon, color, value, label, formatter = formatNumber, delay, isInView, -}: StatCardProps) { +}: Readonly<StatCardProps>) {[ suggest_recommended_refactor ]
As per coding guidelines, "Maintain a maximum line width of 100 characters" and project lint/static checks should be respected consistently in changed TS/TSX segments.
88-89: Formatting-only updates look goodThese changes are behavior-preserving and keep rendering logic intact.
Also applies to: 228-269, 308-311
web/src/components/landing/FeatureGrid.tsx (2)
4-5: LGTM: Type import separation follows TypeScript best practices.Separating the type import from runtime imports improves code organization and is a common TypeScript convention.
64-65: LGTM: JSX formatting is clean and within line length limits.The single-line formatting for the title and description elements is appropriate given their concise content, and both lines remain well within the 100-character limit.
src/modules/events/messageCreate.js (1)
21-21: LGTM!Import reorganization is clean. The file correctly uses ESM syntax, the Winston logger, and
safeReplyfor Discord messaging throughout.tests/utils/discordCache.test.js (1)
266-270: LGTM!The assertion refactor from multi-line await to inline
expect(await ...)is functionally equivalent and slightly more concise.tests/api/utils/configValidation.test.js (2)
107-117: LGTM!Good expansion of the CONFIG_SCHEMA test to verify that all expected top-level sections including the new
botStatusandauditLogare present.
120-143: LGTM!Solid test coverage for the new
botStatusschema validation:
- Valid rotation settings acceptance
- Invalid status value rejection
- Missing required
textfield rejectionConsider adding edge case tests for
intervalMinutes(e.g., zero or negative values) in a follow-up if the schema enforces minimum bounds.web/src/components/landing/Hero.tsx (3)
48-51: LGTM!Minor JSX formatting improvement for better readability.
132-132: LGTM!Adding parentheses around the arrow function parameter
(prev)is a consistent style improvement.
460-489: LGTM!The Button/Link composition updates using
asChildpattern for the dashboard and GitHub links follow proper Next.js and Radix UI patterns for accessible navigation.src/modules/botStatus.js (4)
238-252: Clamp configured rotation interval to a Discord-safe minimum.The
resolveRotationIntervalMs()function accepts unclamped values that can trigger Discord presence update throttling. Users can configure very small intervals (e.g.,intervalMinutes: 0.1= 6 seconds) which would exceed Discord's documented rate limit of 5 presence updates per 20 seconds.Proposed fix
const DEFAULT_LEGACY_ROTATE_INTERVAL_MS = 30_000; const DEFAULT_ROTATE_INTERVAL_MINUTES = 5; +const MIN_PRESENCE_INTERVAL_MS = 20_000; export function resolveRotationIntervalMs(cfg) { if (typeof cfg?.rotation?.intervalMinutes === 'number' && cfg.rotation.intervalMinutes > 0) { - return Math.round(cfg.rotation.intervalMinutes * 60_000); + return Math.max(Math.round(cfg.rotation.intervalMinutes * 60_000), MIN_PRESENCE_INTERVAL_MS); } if (typeof cfg?.rotateIntervalMs === 'number' && cfg.rotateIntervalMs > 0) { - return cfg.rotateIntervalMs; + return Math.max(cfg.rotateIntervalMs, MIN_PRESENCE_INTERVAL_MS); } if (cfg?.rotation) { return DEFAULT_ROTATE_INTERVAL_MINUTES * 60_000; } return DEFAULT_LEGACY_ROTATE_INTERVAL_MS; }
73-86: LGTM!The
formatUptime()function handles edge cases well (non-finite, negative values) and produces a clean, compact format.
198-220: LGTM!Good design with
getRotationMessages()providing graceful fallback from new format to legacy format to default, ensuring the bot always has at least one status message.
338-368: LGTM!The
startBotStatus()function correctly clears any existing interval before starting a new one, preventing interval leaks on hot-reload.src/index.js (3)
486-488: MovestartBotStatus(client)to afterclient.login(token)to ensure initial presence applies immediately.Currently
startBotStatus()is called beforeclient.login(token)(line 499), butapplyPresence()returns early when!client?.user. This delays the first visible status update until the next rotation interval tick (5+ minutes by default).Proposed fix
// Start triage module (per-channel message classification + response) await startTriage(client, config, healthMonitor); - // Start configurable bot presence rotation - startBotStatus(client); - // Start tempban scheduler for automatic unbans (DB required) if (dbPool) { startTempbanScheduler(client); startWarningExpiryScheduler(); startScheduler(client); startGithubFeed(client); } // Load commands and login await loadCommands(); await client.login(token); + + // Start configurable bot presence rotation after client is authenticated + startBotStatus(client);
46-46: LGTM!Clean import of the new bot status module functions.
295-295: LGTM!Proper integration of
stopBotStatus()in the graceful shutdown sequence, placed appropriately before API server shutdown.
| return text | ||
| .replace(/\{memberCount\}/g, String(memberCount)) | ||
| .replace(/\{guildCount\}/g, String(guildCount)) | ||
| .replace(/\{botName\}/g, botName); | ||
| .replace(/\{botName\}/g, botName) | ||
| .replace(/\{commandCount\}/g, String(commandCount)) | ||
| .replace(/\{uptime\}/g, uptime) | ||
| .replace(/\{version\}/g, version); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Prefer String#replaceAll() over String#replace() with global regex.
Modern JavaScript provides replaceAll() which is clearer for global replacements and avoids the regex overhead.
Suggested refactor
return text
- .replace(/\{memberCount\}/g, String(memberCount))
- .replace(/\{guildCount\}/g, String(guildCount))
- .replace(/\{botName\}/g, botName)
- .replace(/\{commandCount\}/g, String(commandCount))
- .replace(/\{uptime\}/g, uptime)
- .replace(/\{version\}/g, version);
+ .replaceAll('{memberCount}', String(memberCount))
+ .replaceAll('{guildCount}', String(guildCount))
+ .replaceAll('{botName}', botName)
+ .replaceAll('{commandCount}', String(commandCount))
+ .replaceAll('{uptime}', uptime)
+ .replaceAll('{version}', version);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return text | |
| .replace(/\{memberCount\}/g, String(memberCount)) | |
| .replace(/\{guildCount\}/g, String(guildCount)) | |
| .replace(/\{botName\}/g, botName); | |
| .replace(/\{botName\}/g, botName) | |
| .replace(/\{commandCount\}/g, String(commandCount)) | |
| .replace(/\{uptime\}/g, uptime) | |
| .replace(/\{version\}/g, version); | |
| return text | |
| .replaceAll('{memberCount}', String(memberCount)) | |
| .replaceAll('{guildCount}', String(guildCount)) | |
| .replaceAll('{botName}', botName) | |
| .replaceAll('{commandCount}', String(commandCount)) | |
| .replaceAll('{uptime}', uptime) | |
| .replaceAll('{version}', version); |
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis
[warning] 126-126: Prefer String#replaceAll() over String#replace().
[warning] 125-125: Prefer String#replaceAll() over String#replace().
[warning] 127-127: Prefer String#replaceAll() over String#replace().
[warning] 124-124: Prefer String#replaceAll() over String#replace().
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/modules/botStatus.js` around lines 121 - 127, The chained global regex
replacements in the function that builds the status text should use
String.prototype.replaceAll for clearer intent and to avoid regex overhead;
update the code in the function (the block returning
text.replace(...).replace(...)) to call text.replaceAll('{memberCount}',
String(memberCount)).replaceAll('{guildCount}',
String(guildCount)).replaceAll('{botName}',
botName).replaceAll('{commandCount}',
String(commandCount)).replaceAll('{uptime}', uptime).replaceAll('{version}',
version) ensuring each placeholder is passed as a plain string to replaceAll.
| export function resolveActivityType(typeStr) { | ||
| if (!typeStr) return ActivityType.Playing; | ||
| return ACTIVITY_TYPE_MAP[typeStr] !== undefined | ||
| ? ACTIVITY_TYPE_MAP[typeStr] | ||
| : ActivityType.Playing; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Simplify negated condition.
The static analysis flagged the negated condition. Using in operator or direct truthy check is clearer.
Suggested refactor
export function resolveActivityType(typeStr) {
if (!typeStr) return ActivityType.Playing;
- return ACTIVITY_TYPE_MAP[typeStr] !== undefined
- ? ACTIVITY_TYPE_MAP[typeStr]
- : ActivityType.Playing;
+ return typeStr in ACTIVITY_TYPE_MAP
+ ? ACTIVITY_TYPE_MAP[typeStr]
+ : ActivityType.Playing;
}🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis
[warning] 148-148: Unexpected negated condition.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/modules/botStatus.js` around lines 146 - 151, In resolveActivityType,
simplify the negated undefined check by using a clearer membership or truthy
test: replace the ternary condition that uses ACTIVITY_TYPE_MAP[typeStr] !==
undefined with either the in-operator (typeStr in ACTIVITY_TYPE_MAP) or a
nullish fallback (ACTIVITY_TYPE_MAP[typeStr] ?? ActivityType.Playing) so the
function returns ACTIVITY_TYPE_MAP[typeStr] when present and
ActivityType.Playing otherwise; update the function resolveActivityType
accordingly to reference ACTIVITY_TYPE_MAP and ActivityType.Playing.
| <span | ||
| className={`text-sm font-medium transition-colors ${!isAnnual ? 'text-foreground' : 'text-muted-foreground'}`} | ||
| > |
There was a problem hiding this comment.
Wrap toggle label className expressions to satisfy line-width rule
Line 82 and Line 101 exceed the configured 100-character limit. You can also remove the negation for readability.
Proposed diff
- <span
- className={`text-sm font-medium transition-colors ${!isAnnual ? 'text-foreground' : 'text-muted-foreground'}`}
- >
+ <span
+ className={`text-sm font-medium transition-colors ${
+ isAnnual ? 'text-muted-foreground' : 'text-foreground'
+ }`}
+ >
Monthly
</span>
@@
- <span
- className={`text-sm font-medium transition-colors ${isAnnual ? 'text-foreground' : 'text-muted-foreground'}`}
- >
+ <span
+ className={`text-sm font-medium transition-colors ${
+ isAnnual ? 'text-foreground' : 'text-muted-foreground'
+ }`}
+ >
Annual <span className="text-accent font-bold">Save 36%</span>
</span>As per coding guidelines, "Maintain a maximum line width of 100 characters."
Also applies to: 100-102
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis
[warning] 82-82: Unexpected negated condition.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/landing/Pricing.tsx` around lines 81 - 83, The long
className expression on the toggle label should be refactored to respect the
100-char width and improve readability: replace the negation (!isAnnual) with
positive logic (isAnnual ? 'text-muted-foreground' : 'text-foreground') and
either compute a small variable (e.g., const toggleLabelClass = isAnnual ?
'text-muted-foreground' : 'text-foreground') and use className={`text-sm
font-medium transition-colors ${toggleLabelClass}`} in the span, or break the
template literal across multiple concatenated lines; update the span's className
usage (and the analogous one at the other occurrence) accordingly.
| const s = stats ?? { | ||
| servers: 0, members: 0, commandsServed: 0, | ||
| activeConversations: 0, uptime: 0, messagesProcessed: 0, cachedAt: '', | ||
| servers: 0, | ||
| members: 0, | ||
| commandsServed: 0, | ||
| activeConversations: 0, | ||
| uptime: 0, | ||
| messagesProcessed: 0, | ||
| cachedAt: '', | ||
| }; |
There was a problem hiding this comment.
Add trailing comma in fallback stats object
At Line 224, the last property in a multi-line object is missing a trailing comma. This violates the repository style rule and can cause lint/style check failures.
✅ Suggested fix
const s = stats ?? {
servers: 0,
members: 0,
commandsServed: 0,
activeConversations: 0,
uptime: 0,
messagesProcessed: 0,
- cachedAt: '',
+ cachedAt: '',
};As per coding guidelines, "Always include trailing commas in multi-line arrays, objects, and function parameters."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const s = stats ?? { | |
| servers: 0, members: 0, commandsServed: 0, | |
| activeConversations: 0, uptime: 0, messagesProcessed: 0, cachedAt: '', | |
| servers: 0, | |
| members: 0, | |
| commandsServed: 0, | |
| activeConversations: 0, | |
| uptime: 0, | |
| messagesProcessed: 0, | |
| cachedAt: '', | |
| }; | |
| const s = stats ?? { | |
| servers: 0, | |
| members: 0, | |
| commandsServed: 0, | |
| activeConversations: 0, | |
| uptime: 0, | |
| messagesProcessed: 0, | |
| cachedAt: '', | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/landing/Stats.tsx` around lines 217 - 225, The fallback
object assigned to s (the expression "const s = stats ?? { ... }") is missing a
trailing comma after the last property; update the object literal used in the
stats nullish-coalescing fallback (the properties servers, members,
commandsServed, activeConversations, uptime, messagesProcessed, cachedAt) to
include a trailing comma after cachedAt so it conforms to the project's
multi-line object comma style.


Summary
Verification
Closes #228