Skip to content

feat: custom bot status rotation#304

Open
MohsinCoding wants to merge 2 commits intomainfrom
mohsin/rotating-bot-status
Open

feat: custom bot status rotation#304
MohsinCoding wants to merge 2 commits intomainfrom
mohsin/rotating-bot-status

Conversation

@MohsinCoding
Copy link
Contributor

Summary

  • add configurable rotating bot presence support in �otStatus with backward compatibility for legacy activity config
  • support template variables for dynamic status text: {guildCount}, {memberCount}, {commandCount}, {uptime}, {version} (plus existing {botName})
  • wire bot status lifecycle into startup/shutdown and hot-reload on new �otStatus.rotation.* config paths
  • add �otStatus schema validation updates and default config.json rotation configuration
  • add dashboard config support for bot presence/rotation toggles and interval controls
  • expand test coverage for bot status interpolation, rotation scheduler behavior, and config listener/schema updates

Verification

  • root workspace vitest suite passes locally after dependency install
  • web workspace currently has unrelated pre-existing test failures not introduced by this change

Closes #228

Copilot AI review requested due to automatic review settings March 14, 2026 20:41
@github-project-automation github-project-automation bot moved this to Backlog in Volvox.Bot Mar 14, 2026
@railway-app
Copy link

railway-app bot commented Mar 14, 2026

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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Bot presence rotation: bot can cycle through multiple configurable status messages on a schedule (default 5-minute cadence).
    • Presence options: selectable status (online, idle, dnd, invisible) and activity types (Playing, Watching, Listening, Competing, Streaming).
    • Message templating: status texts may include placeholders (command count, uptime, version).
    • Dashboard: UI to configure bot presence, rotation toggle, and interval.

Walkthrough

Adds a new top-level botStatus configuration and implements a rotation-aware bot presence system: schema and allowlist updates, a new src/modules/botStatus.js module (start/stop/reload, interpolation, rotation scheduler), config listeners, index lifecycle hooks, web dashboard types/UI, and corresponding tests.

Changes

Cohort / File(s) Summary
Top-level config
config.json
Adds new botStatus top-level property with enabled/status/rotation shape.
Config validation & allowlist
src/api/utils/configValidation.js, src/api/utils/configAllowlist.js, tests/api/utils/configValidation.test.js, tests/api/utils/configAllowlist.test.js, tests/api/utils/validateConfigPatch.test.js
Adds botStatus schema to CONFIG_SCHEMA and includes botStatus in SAFE_CONFIG_KEYS/READABLE_CONFIG_KEYS; tests updated to validate schema and allowed keys.
Bot status module & lifecycle
src/modules/botStatus.js, src/config-listeners.js, src/index.js, tests/modules/botStatus.test.js, tests/config-listeners.test.js
Introduces rotation-aware presence module with helpers (formatUptime, resolvePresenceConfig, getRotationMessages, resolveRotationIntervalMs, applyPresence, start/stop/reload), interpolation placeholders ({guildCount, memberCount, commandCount, uptime, version}), rotation scheduler, config change listeners wired to restart presence, and tests for behavior.
Web dashboard types & UI
web/src/types/config.ts, web/src/components/dashboard/config-editor.tsx, web/src/components/dashboard/config-workspace/types.ts, web/src/components/dashboard/config-workspace/config-categories.ts
Adds TypeScript interfaces for botStatus rotation, extends BotConfig and ConfigSection, and wires a Bot Presence SettingsFeatureCard and editor helpers to update botStatus and rotation fields.
Minor formatting/refactor tests & UI
web/src/..., web/src/app/page.tsx, web/src/components/landing/..., tests/** (various small changes)
Multiple non-functional formatting and JSX reflow changes across landing pages, footer, hero, pricing, stats, and small test formatting updates unrelated to feature logic.

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive Most changes are directly scoped to bot status rotation. However, several non-essential formatting changes appear in unrelated landing page components, test files, and event handlers that are not required for the feature. Consider removing purely cosmetic formatting changes in landing page components (Hero, Footer, FeatureGrid, Pricing, Stats) and test files (warnings.test, voice.test) as they are out of scope for the bot status feature.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: custom bot status rotation' is concise, specific, and directly describes the main feature added—configurable rotating bot presence with multiple status messages.
Description check ✅ Passed The description covers the key implementation details including config support, template variables, lifecycle integration, schema validation, dashboard UI, and test coverage—all directly related to the changeset.
Linked Issues check ✅ Passed The PR implements all core requirements from issue #228: rotating bot status with configurable messages, template variable support, Discord ActivityType support, graceful fallback, hot-reload, dashboard controls, and comprehensive test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mohsin/rotating-bot-status
  • 🛠️ Publish Changes: Commit on current branch
  • 🛠️ Publish Changes: Create PR
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot

See analysis details on SonarQube Cloud

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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.

Comment on lines +229 to +242
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);
}

Comment on lines +199 to +200
type: 'object',
required: ['text'],
Comment on lines +2218 to +2234
{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}
@greptile-apps
Copy link

greptile-apps bot commented Mar 14, 2026

Greptile Summary

Adds configurable rotating bot presence with a new botStatus.rotation config format while maintaining backward compatibility with the legacy activities/rotateIntervalMs shape. New template variables ({commandCount}, {uptime}, {version}) extend the existing interpolation system. The dashboard gets a Bot Presence feature card, config listeners are wired for hot-reload, and the startup/shutdown lifecycle is properly integrated.

  • New rotation config: Supports per-message activity types (e.g. Watching, Playing) via rotation.messages[] objects, falling back to legacy string arrays
  • Template variables: Added {commandCount}, {uptime}, {version} alongside existing {memberCount}, {guildCount}, {botName}
  • Dashboard UI: Adds presence status selector, rotation toggle, and interval input under Community Tools
  • Missing rate-limit guard: resolveRotationIntervalMs documents "Discord-safe minimum" but enforces none — the config API can accept sub-second intervals that would trigger Discord rate limits
  • Lint formatting: ~13 files contain Biome auto-format changes (JSX line splitting, import reordering) with no functional impact
  • Test coverage: Good coverage for new functions (getRotationMessages, resolveRotationIntervalMs, new config shape rotation, template variables) and updated listener count assertions

Confidence Score: 4/5

  • This PR is safe to merge with one low-risk issue around missing interval floor enforcement.
  • Well-structured feature with good backward compatibility, proper lifecycle management, and solid test coverage. The only concern is the missing minimum interval enforcement in resolveRotationIntervalMs which could theoretically cause Discord rate limiting if configured via the API (the dashboard UI does enforce min=1 minute). The bulk of the diff is lint auto-formatting with no functional impact.
  • Pay close attention to src/modules/botStatus.js — specifically resolveRotationIntervalMs which lacks the minimum interval it documents.

Important Files Changed

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]
Loading
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

Comment on lines +238 to +251
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;
Copy link

Choose a reason for hiding this comment

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

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):

Suggested change
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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 72ee8f0 and 965a5c6.

📒 Files selected for processing (14)
  • config.json
  • src/api/utils/configValidation.js
  • src/config-listeners.js
  • src/index.js
  • src/modules/botStatus.js
  • tests/api/utils/configAllowlist.test.js
  • tests/api/utils/configValidation.test.js
  • tests/api/utils/validateConfigPatch.test.js
  • tests/config-listeners.test.js
  • tests/modules/botStatus.test.js
  • web/src/components/dashboard/config-editor.tsx
  • web/src/components/dashboard/config-workspace/config-categories.ts
  • web/src/components/dashboard/config-workspace/types.ts
  • web/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, never require()/module.exports

Files:

  • tests/api/utils/configAllowlist.test.js
  • src/index.js
  • tests/api/utils/validateConfigPatch.test.js
  • tests/modules/botStatus.test.js
  • src/config-listeners.js
  • tests/config-listeners.test.js
  • tests/api/utils/configValidation.test.js
  • src/api/utils/configValidation.js
  • src/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.js
  • web/src/components/dashboard/config-workspace/types.ts
  • web/src/types/config.ts
  • src/index.js
  • tests/api/utils/validateConfigPatch.test.js
  • tests/modules/botStatus.test.js
  • src/config-listeners.js
  • tests/config-listeners.test.js
  • tests/api/utils/configValidation.test.js
  • web/src/components/dashboard/config-workspace/config-categories.ts
  • src/api/utils/configValidation.js
  • web/src/components/dashboard/config-editor.tsx
  • src/modules/botStatus.js
tests/**/*.test.js

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

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

Files:

  • tests/api/utils/configAllowlist.test.js
  • tests/api/utils/validateConfigPatch.test.js
  • tests/modules/botStatus.test.js
  • tests/config-listeners.test.js
  • tests/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 from src/logger.js — never use console.* methods

Files:

  • tests/api/utils/configAllowlist.test.js
  • web/src/components/dashboard/config-workspace/types.ts
  • web/src/types/config.ts
  • src/index.js
  • tests/api/utils/validateConfigPatch.test.js
  • tests/modules/botStatus.test.js
  • src/config-listeners.js
  • tests/config-listeners.test.js
  • tests/api/utils/configValidation.test.js
  • web/src/components/dashboard/config-workspace/config-categories.ts
  • src/api/utils/configValidation.js
  • web/src/components/dashboard/config-editor.tsx
  • src/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.json should be gated behind a config.<feature>.enabled flag; 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.ts
  • web/src/types/config.ts
  • web/src/components/dashboard/config-workspace/config-categories.ts
  • web/src/components/dashboard/config-editor.tsx
src/**/*.js

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

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

Files:

  • src/index.js
  • src/config-listeners.js
  • src/api/utils/configValidation.js
  • src/modules/botStatus.js
{src/commands/**/*.{js,ts},src/modules/**/*.{js,ts},src/index.js}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • src/index.js
  • src/modules/botStatus.js
src/modules/**/*.js

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

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

Files:

  • src/modules/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.js
  • web/src/components/dashboard/config-workspace/types.ts
  • tests/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.js
  • tests/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.json
  • web/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.ts
  • web/src/components/dashboard/config-workspace/config-categories.ts
  • web/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: ".

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


[warning] 141-141: Unnecessary escape character: ".

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

web/src/components/dashboard/config-editor.tsx

[warning] 961-961: The empty object is useless.

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


[warning] 962-962: The empty object is useless.

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

src/modules/botStatus.js

[warning] 125-125: Prefer String#replaceAll() over String#replace().

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


[warning] 148-148: Unexpected negated condition.

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


[warning] 124-124: Prefer String#replaceAll() over String#replace().

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


[warning] 127-127: Prefer String#replaceAll() over String#replace().

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


[warning] 126-126: Prefer String#replaceAll() over String#replace().

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

🔇 Additional comments (13)
config.json (1)

153-174: botStatus default 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.messages to legacy activities is 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 botStatus assertions 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: ConfigFeatureId update 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 botStatus in 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, and CONFIG_SEARCH_ITEMS are all aligned for the new bot-status feature, 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 for botStatus.

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 rotation config 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 SettingsFeatureCard implementation 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 for botStatus are well connected.

The new interfaces plus BotConfig/ConfigSection extensions provide the expected type plumbing for the feature across editor and workspace code.

Also applies to: 368-369, 394-395

Comment on lines +196 to +201
messages: {
type: 'array',
items: {
type: 'object',
required: ['text'],
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +487 to +488
// Start configurable bot presence rotation
startBotStatus(client);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -C2

Repository: 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 3

Repository: 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 8

Repository: 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 10

Repository: 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)).

Comment on lines +235 to 242
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 Limited event with a retry_after value 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:


🏁 Script executed:

# First, check if the file exists and get its size
wc -l src/modules/botStatus.js

Repository: 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 -n

Repository: 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 2

Repository: 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.js

Repository: VolvoxLLC/volvox-bot

Length of output: 1124


🏁 Script executed:

# Search for usages of resolveRotationIntervalMs
rg 'resolveRotationIntervalMs' src/modules/botStatus.js -B 2 -A 2

Repository: VolvoxLLC/volvox-bot

Length of output: 462


🏁 Script executed:

# Search for any existing MIN constant definitions
rg 'const.*MIN.*=' src/modules/botStatus.js

Repository: 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.js

Repository: 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 -n

Repository: 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.js

Repository: VolvoxLLC/volvox-bot

Length of output: 46


🏁 Script executed:

# Search for rotate function with different patterns
rg 'rotate\s*=' src/modules/botStatus.js -A 15

Repository: 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 -n

Repository: 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).

Comment on lines +117 to +121
it('replaces {version} from package.json', () => {
const client = makeClient();
const result = interpolateActivity('v{version}', client);
expect(result).toMatch(/^v.+/);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.+/)).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 965a5c6 and e987db7.

📒 Files selected for processing (13)
  • src/index.js
  • src/modules/botStatus.js
  • src/modules/events/messageCreate.js
  • tests/api/routes/warnings.test.js
  • tests/api/utils/configValidation.test.js
  • tests/commands/voice.test.js
  • tests/utils/discordCache.test.js
  • web/src/app/page.tsx
  • web/src/components/landing/FeatureGrid.tsx
  • web/src/components/landing/Footer.tsx
  • web/src/components/landing/Hero.tsx
  • web/src/components/landing/Pricing.tsx
  • web/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, never require()/module.exports

Files:

  • tests/commands/voice.test.js
  • tests/api/utils/configValidation.test.js
  • tests/api/routes/warnings.test.js
  • src/modules/events/messageCreate.js
  • tests/utils/discordCache.test.js
  • src/index.js
  • src/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.js
  • web/src/components/landing/FeatureGrid.tsx
  • web/src/components/landing/Footer.tsx
  • tests/api/utils/configValidation.test.js
  • web/src/components/landing/Stats.tsx
  • tests/api/routes/warnings.test.js
  • src/modules/events/messageCreate.js
  • web/src/components/landing/Pricing.tsx
  • tests/utils/discordCache.test.js
  • web/src/app/page.tsx
  • web/src/components/landing/Hero.tsx
  • src/index.js
  • src/modules/botStatus.js
tests/**/*.test.js

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

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

Files:

  • tests/commands/voice.test.js
  • tests/api/utils/configValidation.test.js
  • tests/api/routes/warnings.test.js
  • tests/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 from src/logger.js — never use console.* methods

Files:

  • tests/commands/voice.test.js
  • web/src/components/landing/FeatureGrid.tsx
  • web/src/components/landing/Footer.tsx
  • tests/api/utils/configValidation.test.js
  • web/src/components/landing/Stats.tsx
  • tests/api/routes/warnings.test.js
  • src/modules/events/messageCreate.js
  • web/src/components/landing/Pricing.tsx
  • tests/utils/discordCache.test.js
  • web/src/app/page.tsx
  • web/src/components/landing/Hero.tsx
  • src/index.js
  • src/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.tsx
  • web/src/components/landing/Footer.tsx
  • web/src/components/landing/Stats.tsx
  • web/src/components/landing/Pricing.tsx
  • web/src/app/page.tsx
  • web/src/components/landing/Hero.tsx
src/**/*.js

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

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

Files:

  • src/modules/events/messageCreate.js
  • src/index.js
  • src/modules/botStatus.js
src/modules/**/*.js

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

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

Files:

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

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • src/modules/events/messageCreate.js
  • src/index.js
  • src/modules/botStatus.js
web/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • web/src/app/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.tsx
  • web/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.tsx
  • web/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.

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

web/src/components/landing/Pricing.tsx

[warning] 82-82: Unexpected negated condition.

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

src/modules/botStatus.js

[warning] 148-148: Unexpected negated condition.

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


[warning] 126-126: Prefer String#replaceAll() over String#replace().

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


[warning] 125-125: Prefer String#replaceAll() over String#replace().

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


[warning] 127-127: Prefer String#replaceAll() over String#replace().

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


[warning] 124-124: Prefer String#replaceAll() over String#replace().

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

🔇 Additional comments (24)
tests/commands/voice.test.js (1)

266-268: Looks good — readable assertion formatting with unchanged behavior.

This multiline toContain assertion 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 good

This 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: Make StatCard props read-only to prevent accidental mutation

Please 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 good

These 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 safeReply for 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 botStatus and auditLog are present.


120-143: LGTM!

Solid test coverage for the new botStatus schema validation:

  • Valid rotation settings acceptance
  • Invalid status value rejection
  • Missing required text field rejection

Consider 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 asChild pattern 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: Move startBotStatus(client) to after client.login(token) to ensure initial presence applies immediately.

Currently startBotStatus() is called before client.login(token) (line 499), but applyPresence() 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.

Comment on lines 121 to +127
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 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.

Suggested change
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().

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


[warning] 125-125: Prefer String#replaceAll() over String#replace().

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


[warning] 127-127: Prefer String#replaceAll() over String#replace().

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


[warning] 124-124: Prefer String#replaceAll() over String#replace().

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

🤖 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.

Comment on lines +146 to +151
export function resolveActivityType(typeStr) {
if (!typeStr) return ActivityType.Playing;
return ACTIVITY_TYPE_MAP[typeStr] !== undefined
? ACTIVITY_TYPE_MAP[typeStr]
: ActivityType.Playing;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 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.

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

🤖 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.

Comment on lines +81 to +83
<span
className={`text-sm font-medium transition-colors ${!isAnnual ? 'text-foreground' : 'text-muted-foreground'}`}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

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

🤖 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.

Comment on lines 217 to 225
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: '',
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

@github-project-automation github-project-automation bot moved this from Backlog to In Review in Volvox.Bot Mar 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

feat: Custom Bot Status Rotation

2 participants