Skip to content

feat(permissions): support multiple admin and moderator roles#261

Merged
BillChirico merged 10 commits intomainfrom
feat/multi-role-permissions
Mar 8, 2026
Merged

feat(permissions): support multiple admin and moderator roles#261
BillChirico merged 10 commits intomainfrom
feat/multi-role-permissions

Conversation

@BillChirico
Copy link
Collaborator

Summary

Allow configuring multiple Discord roles for both admin and moderator permissions instead of a single role each.

Changes

  • Config schema: adminRoleId: string | nulladminRoleIds: string[], same for moderator
  • Backend: isAdmin() / isModerator() check if member has ANY role in the array using .some()
  • Backward compat: Falls back to singular adminRoleId/moderatorRoleId for old configs
  • modExempt.js: Updated to array check
  • moderation.js: Protect-roles spreads full arrays
  • Dashboard: RoleSelectors now true multi-select (no more single-role wrapping/unwrapping)
  • Tests: Updated + new multi-role test cases

Before / After

Before: one role ID per permission level
After: any number of roles — member qualifies if they hold at least one

Bill Chirico added 2 commits March 7, 2026 18:30
- config.json: adminRoleId/moderatorRoleId → adminRoleIds/moderatorRoleIds (arrays)
- web/src/types/config.ts: PermissionsConfig updated to string[] fields
- src/utils/permissions.js: isAdmin/isModerator check arrays; falls back to
  singular field for old configs
- src/utils/modExempt.js: isExempt checks adminRoleIds/moderatorRoleIds arrays
  with backward compat for singular field
- src/modules/moderation.js: protect-roles spreads full arrays instead of
  wrapping a single ID
- web/src/components/dashboard/config-editor.tsx: RoleSelector for admin/
  moderator switched to true multi-select (no maxSelections, no array wrapping)
- tests/utils/permissions.test.js: updated to use array fields; added multi-
  role and backward-compat tests
- tests/utils/modExempt.test.js: updated to use array fields; added multi-
  role and backward-compat tests
- adminRoleId/moderatorRoleId (singular) → adminRoleIds/moderatorRoleIds (arrays)
- isAdmin()/isModerator() check member.roles against any role in array
- Backward compat: falls back to singular field for old configs
- modExempt.js and moderation.js updated to use arrays
- Dashboard RoleSelectors now true multi-select
- Tests updated with multi-role coverage
Copilot AI review requested due to automatic review settings March 7, 2026 23:31
@BillChirico BillChirico added this to the v0.1.0 - "Big Boy MVP" milestone Mar 7, 2026
@BillChirico BillChirico added priority: medium Medium priority type: feature New feature scope: dashboard Web dashboard scope: moderation Moderation features labels Mar 7, 2026
@github-project-automation github-project-automation bot moved this to Backlog in Volvox.Bot Mar 7, 2026
@claude
Copy link

claude bot commented Mar 7, 2026

Claude finished @BillChirico's task in 3m 0s —— View job


Code Review — feat/multi-role-permissions

  • Read changed files
  • Analyze for hard-rule violations and bugs
  • Check backward-compat logic
  • Verify test coverage gaps
  • Submit verdict

@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-261 March 7, 2026 23:31 Destroyed
@railway-app
Copy link

railway-app bot commented Mar 7, 2026

🚅 Deployed to the volvox-bot-pr-261 environment in volvox-bot

Service Status Web Updated (UTC)
bot 🚨 Crashed (View Logs) Web Mar 8, 2026 at 4:45 am
web ✅ Success (View Logs) Mar 8, 2026 at 4:45 am
docs ✅ Success (View Logs) Web Mar 8, 2026 at 4:44 am

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6a74bbc5-9c4c-4261-bae1-eb5030d92b10

📥 Commits

Reviewing files that changed from the base of the PR and between 473ee55 and 4ae0333.

📒 Files selected for processing (3)
  • src/api/utils/configValidation.js
  • tests/utils/permissions.test.js
  • web/src/components/dashboard/config-editor.tsx
📜 Recent 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: Greptile Review
  • GitHub Check: claude-review
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx,jsx}: Use ESM only — Use import/export, no CommonJS
Use single quotes — No double quotes except in JSON
Semicolons are always required

Files:

  • web/src/components/dashboard/config-editor.tsx
  • tests/utils/permissions.test.js
  • src/api/utils/configValidation.js
**/*.{js,ts,tsx,jsx,json,md}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indent — Biome enforced

Files:

  • web/src/components/dashboard/config-editor.tsx
  • tests/utils/permissions.test.js
  • src/api/utils/configValidation.js
tests/**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Tests required with 80% coverage threshold, never lower it

Files:

  • tests/utils/permissions.test.js
src/**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{js,ts,tsx}: Use Winston logger from src/logger.js, NEVER console.*
Use safe Discord message methods — safeReply()/safeSend()/safeEditReply()
Use parameterized SQL — Never string interpolation in queries

Files:

  • src/api/utils/configValidation.js
🧠 Learnings (6)
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with metadata-driven config search and reusable `SettingsFeatureCard` component pattern

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/tests/components/dashboard/**/*.test.{ts,tsx} : Web dashboard config editor tests should cover category switching, search functionality, dirty badges, and explicit manual-save workspace behavior instead of autosave assumptions

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
  • tests/utils/permissions.test.js
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to {.env.example,README.md} : Remove `GUILD_ID` from shared environment variables in deployment configuration; preserve guild-scoped deploy support only via CLI flag `--guild-id` for development

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to src/commands/**/*.js : Command registration startup and reload logic should not read `process.env.GUILD_ID` directly; rely on CLI flags for guild-scoped deployments

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-08T04:37:50.316Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-08T04:37:50.316Z
Learning: All config sections must be added to `SAFE_CONFIG_KEYS` in `src/api/utils/configAllowlist.js` to enable API saves

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
  • src/api/utils/configValidation.js
📚 Learning: 2026-03-08T04:37:50.316Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-08T04:37:50.316Z
Learning: Applies to tests/**/*.{js,ts,tsx} : Tests required with 80% coverage threshold, never lower it

Applied to files:

  • tests/utils/permissions.test.js
🔇 Additional comments (8)
src/api/utils/configValidation.js (2)

293-296: LGTM — openProperties logic correctly allows freeform object keys.

The validation now properly skips the "unknown config key" error when schema.openProperties is true, enabling freeform maps like allowedCommands.


202-217: LGTM — permissions schema correctly defined with plural arrays, legacy fallbacks, and freeform command mapping.

The schema properly includes both new plural fields (adminRoleIds, moderatorRoleIds) and legacy singular fields (adminRoleId, moderatorRoleId) with nullable: true. The openProperties: true on allowedCommands correctly allows freeform command→permission mappings. The permissions key is properly registered in SAFE_CONFIG_KEYS for API saves.

tests/utils/permissions.test.js (4)

19-19: LGTM — mergeRoleIds import added.

The test file now imports mergeRoleIds to enable unit testing of the merge utility.


90-149: Excellent coverage for isAdmin multi-role and backward compatibility scenarios.

The tests now comprehensively cover:

  • Single admin role via adminRoleIds array
  • Any-of matching across multiple admin roles
  • Backward compat with legacy adminRoleId
  • The critical merged-config case (adminRoleIds: [] + adminRoleId) that would break with ?? alone

This addresses the previous review feedback about missing merged-config fixtures.


503-522: Complete merged-config coverage for isModerator.

The isModerator function also uses mergeRoleIds twice for both admin and moderator roles. This demonstrates that the function is used consistently across permission checks to handle the merged-config case.

Lines 503-510 test the moderator merged-config scenario, and lines 512-522 add the admin counterpart (as suggested in the previous review). This ensures isModerator() correctly finds legacy roles when empty arrays are present from defaults.


576-629: Thorough mergeRoleIds test coverage including edge cases and production failure scenarios.

The test suite covers:

  • Normal merge and deduplication
  • Empty/null/undefined array handling
  • Malformed config normalization (string instead of array)
  • Empty string handling
  • The documented production failure case (line 625-629)

This aligns well with the mergeRoleIds implementation that handles null/undefined/empty array inputs and uses a Set for deduplication, normalizing roleIds defensively.

web/src/components/dashboard/config-editor.tsx (2)

875-880: Auto-clearing legacy fields is the right approach for migration hygiene.

Setting adminRoleId = null and moderatorRoleId = null when the plural arrays are written prevents computePatches from generating both old and new forms simultaneously. However, this relies on the backend persisting null rather than deleting the key.

The backend PATCH handler passes the null value directly to setConfigValue, which means if null is sent for permissions.adminRoleId, it will be persisted as-is in the database, not deleted.

This is acceptable because mergeRoleIds checks typeof roleId === 'string' and correctly skips null values since null is not a string. The pattern is safe but creates a coupling between the frontend auto-clear logic and the backend's null-handling.


2003-2043: Multi-select implementation correctly hydrates from both plural and legacy singular fields.

The selected prop logic properly:

  1. Spreads the new adminRoleIds/moderatorRoleIds arrays
  2. Falls back to include the legacy adminRoleId/moderatorRoleId if present and not already included (deduplication)

This addresses the previous review feedback about preserving legacy single-role values. On selection change, updatePermissionsField writes the plural array and auto-clears the legacy field, ensuring a clean migration path.


📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Admin and Moderator roles now support multiple selections (arrays) in config and the dashboard editor.
  • Bug Fixes & Improvements

    • Permission checks updated to honor multi-role arrays while preserving legacy single-role compatibility and de-duplication.
    • Config editor: per-section drafts, batched saves/patches, diff preview, discard/undo and keyboard shortcuts.
    • Validation schema extended to accept multi-role fields with legacy-field support.
  • Tests

    • Expanded tests for multi-role scenarios, legacy compatibility, and merged-role behavior.

Walkthrough

Replace singular adminRoleId/moderatorRoleId with plural adminRoleIds/moderatorRoleIds across config, validation, types, utilities, moderation checks, frontend editor, and tests; retain legacy singular fields for backward compatibility and introduce merge/owner helper utilities.

Changes

Cohort / File(s) Summary
Config & schema
config.json, src/api/utils/configValidation.js, web/src/types/config.ts
Add permissions schema and replace singular adminRoleId/moderatorRoleId with adminRoleIds/moderatorRoleIds (string[]); keep optional deprecated singular fields for compatibility.
Permission utilities
src/utils/permissions.js
Add mergeRoleIds, getBotOwnerIds, isBotOwner; update isAdmin/isModerator to use merged arrays, bot-owner checks, and updated permission logic.
Exemption checks
src/utils/modExempt.js
Switch exemption checks to use merged admin/moderator ID arrays (via mergeRoleIds) while preserving legacy modRoles name/id behavior and Administrator bypass.
Moderation module
src/modules/moderation.js
Update isProtectedTarget and protected-role resolution to derive admin/moderator IDs from merged arrays instead of single-ID fields.
Frontend dashboard & types
web/src/components/dashboard/config-editor.tsx, web/src/components/dashboard/performance-dashboard.tsx, web/src/types/config.ts
Convert Admin/Moderator role inputs to multi-select arrays (adminRoleIds/moderatorRoleIds), update editor wiring and type signatures, clear legacy singular fields when writing plural forms; minor tooltip formatting change.
Tests
tests/utils/permissions.test.js, tests/utils/modExempt.test.js
Expand tests for plural role arrays, multiple-match and negative cases, merged legacy+new scenarios, deduplication, and preserve legacy single-ID behavior.
Miscellaneous
src/utils/dbMaintenance.js
Reordered import placement only; no functional change.

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding support for multiple admin and moderator roles instead of single roles.
Description check ✅ Passed The description is directly related to the changeset, providing a comprehensive summary of the feature, changes made, backward compatibility approach, and before/after comparison.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 feat/multi-role-permissions
  • 🛠️ Publish Changes: Commit on current branch
  • 🛠️ Publish Changes: Create PR

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.

@coveralls
Copy link

coveralls commented Mar 7, 2026

Coverage Status

coverage: 87.561% (+0.006%) from 87.555%
when pulling b2b604a on feat/multi-role-permissions
into f7d7dac on main.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings and committed to branch feat/multi-role-permissions (commit: 5f0a8cdd9cad6ac1c5d62aa164cfc9a4b8d5fc43)

Docstrings generation was requested by @BillChirico.

The following files were modified:

* `src/modules/moderation.js`
* `src/utils/permissions.js`
* `web/src/components/dashboard/config-editor.tsx`
* `web/src/components/dashboard/performance-dashboard.tsx`

These files were kept as they were:
* `src/utils/modExempt.js`

These files were ignored:
* `tests/utils/modExempt.test.js`
* `tests/utils/permissions.test.js`

These file types are not supported:
* `TASK.md`
* `config.json`
@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-261 March 7, 2026 23:37 Destroyed
Copilot AI review requested due to automatic review settings March 7, 2026 23:38
@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-261 March 7, 2026 23:38 Destroyed
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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

🤖 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/moderation.js`:
- Around line 588-598: The merged-config path currently drops legacy singular
role fields because adminRoleIds/moderatorRoleIds may be present as empty
arrays, so protectRoles.includeAdmins/includeModerators end up not protecting
legacy roles; fix by normalizing permissions earlier (or use a shared resolver)
so adminRoleIds and moderatorRoleIds are computed to fall back to
config.permissions.adminRoleId/config.permissions.moderatorRoleId when the array
is missing or empty. Locate the resolution logic around adminRoleIds and
moderatorRoleIds in src/modules/moderation.js and replace the inline compat
checks with a call to a shared resolver (or implement one) that returns the
singular fallback if the array is undefined or has length === 0, then use those
normalized arrays when building protectedRoleIds (and reuse the same resolver in
command permission helpers).

In `@src/utils/modExempt.js`:
- Around line 33-43: The fallback currently treats an empty array from merged
defaults as present, causing legacy singular fields to be ignored; update the
normalization for adminRoleIds and moderatorRoleIds to be migration-safe by
treating an empty plural array as absent and using the legacy singular
(config.permissions.adminRoleId / moderatorRoleId) when the plural is missing or
empty. Concretely, set adminRoleIds =
Array.isArray(config.permissions?.adminRoleIds) &&
config.permissions.adminRoleIds.length ? config.permissions.adminRoleIds :
(config.permissions?.adminRoleId ? [config.permissions.adminRoleId] : []) and do
the same pattern for moderatorRoleIds so the checks using
member.roles.cache.has(id) will honor legacy values.

In `@src/utils/permissions.js`:
- Around line 65-71: The backward-compat fallback is lost because
config.permissions.adminRoleIds/moderatorRoleIds can be present as empty arrays
(from defaults) so the nullish coalescing (??) never falls back to the singular
adminRoleId/moderatorRoleId; update the code to treat an empty plural array as
“unset” by normalizing config.permissions early (e.g., if adminRoleIds is an
empty array and adminRoleId exists then set adminRoleIds = [adminRoleId], same
for moderatorRoleIds/moderatorRoleId) or change the fallback expression to check
for falsy/empty arrays (e.g., use a conditional that checks
Array.isArray(adminRoleIds) && adminRoleIds.length > 0 ? adminRoleIds :
(config.permissions?.adminRoleId ? [config.permissions.adminRoleId] : []));
apply the same normalization/logic everywhere the compat pattern is used
(adminRoleIds/adminRoleId and moderatorRoleIds/moderatorRoleId) so legacy
singular fields are honored even when defaults inject empty arrays.

In `@tests/utils/modExempt.test.js`:
- Around line 87-97: Add tests that mirror the merged legacy shape by passing
config objects where permissions include both the plural array and singular id
(e.g., permissions: { adminRoleIds: [], adminRoleId: 'admin-role-id' } and
permissions: { moderatorRoleIds: [], moderatorRoleId: 'mod-role-id' }), using
the same makeMessage inputs and asserting isExempt(msg, config) returns true;
place these alongside the existing backward-compat tests so the isExempt
function is validated against the real merged legacy shape.

In `@tests/utils/permissions.test.js`:
- Around line 129-137: The backward-compat tests for isAdmin and isModerator
only use singular legacy fields and miss the real failing shape where legacy
scalar fields are present alongside empty new-array fields (e.g., adminRoleIds:
[] + adminRoleId and moderatorRoleIds: [] + moderatorRoleId). Update the tests
(the cases around isAdmin and isModerator) to call the helpers with
merged-config fixtures that include both the new-array key set to an empty array
and the legacy singular key set (e.g., config = { permissions: { adminRoleIds:
[], adminRoleId: '123456' } }) so the runtime merge scenario is exercised for
both isAdmin and isModerator before asserting behavior and spying on
member.roles.cache.has.

In `@web/src/components/dashboard/config-editor.tsx`:
- Around line 1997-2017: The RoleSelector components for admin and moderator
roles currently only use draftConfig.permissions?.adminRoleIds and
moderatorRoleIds and thus lose legacy single-role values; change the selected
prop for both RoleSelector instances to fall back to the singular fields
(draftConfig.permissions?.adminRoleId and
draftConfig.permissions?.moderatorRoleId) when the plural arrays are
empty/undefined so legacy data is preserved until migration—update the selected
expressions near RoleSelector (ids "admin-role-ids" and "moderator-role-ids")
and ensure updatePermissionsField('adminRoleIds' / 'moderatorRoleIds') behavior
remains unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 821fa671-5ec1-407d-a1e0-e2e9e1629867

📥 Commits

Reviewing files that changed from the base of the PR and between a6e76d0 and 6bf104a.

📒 Files selected for processing (10)
  • TASK.md
  • config.json
  • src/modules/moderation.js
  • src/utils/modExempt.js
  • src/utils/permissions.js
  • tests/utils/modExempt.test.js
  • tests/utils/permissions.test.js
  • web/src/components/dashboard/config-editor.tsx
  • web/src/components/dashboard/performance-dashboard.tsx
  • 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). (3)
  • GitHub Check: Greptile Review
  • GitHub Check: Docker Build Validation
  • GitHub Check: claude-review
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,mjs,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,mjs,jsx,ts,tsx}: Use single quotes for strings in code, double quotes only allowed in JSON files
Always end statements with semicolons
Use 2-space indentation, enforced by Biome

Files:

  • web/src/components/dashboard/performance-dashboard.tsx
  • src/modules/moderation.js
  • src/utils/permissions.js
  • web/src/components/dashboard/config-editor.tsx
  • web/src/types/config.ts
  • tests/utils/permissions.test.js
  • src/utils/modExempt.js
  • tests/utils/modExempt.test.js
web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design

Files:

  • web/src/components/dashboard/performance-dashboard.tsx
  • web/src/components/dashboard/config-editor.tsx
  • web/src/types/config.ts
**/*.{js,cjs,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only with import/export syntax, never CommonJS except in migration files (.cjs)

Files:

  • src/modules/moderation.js
  • src/utils/permissions.js
  • tests/utils/permissions.test.js
  • src/utils/modExempt.js
  • tests/utils/modExempt.test.js
src/**/*.{js,mjs,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{js,mjs,jsx,ts,tsx}: Use src/logger.js Winston logger singleton, never use console.* methods
Use safe Discord message methods: safeReply(), safeSend(), safeEditReply() instead of direct Discord.js methods
Use parameterized SQL queries, never string interpolation for database queries

Files:

  • src/modules/moderation.js
  • src/utils/permissions.js
  • src/utils/modExempt.js
src/modules/**/*.{js,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

Create new modules for features with corresponding config sections in config.json and entries in SAFE_CONFIG_KEYS

Files:

  • src/modules/moderation.js
{.env*,README.md,src/**/!(*.test).{js,ts}}

📄 CodeRabbit inference engine (CLAUDE.md)

Remove GUILD_ID from shared environment variables in production/deployment configurations; preserve dev-only guild-scoped deploy support via CLI flag --guild-id <guild_id>

Files:

  • src/modules/moderation.js
  • src/utils/permissions.js
  • src/utils/modExempt.js
tests/**/*.{js,mjs,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Maintain 80% code coverage threshold minimum, never lower this threshold

Files:

  • tests/utils/permissions.test.js
  • tests/utils/modExempt.test.js
🧠 Learnings (5)
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Refactor config feature presentation to use reusable `SettingsFeatureCard` pattern with structure: header + master toggle + Basic/Advanced blocks

Applied to files:

  • web/src/components/dashboard/performance-dashboard.tsx
  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/api/utils/configAllowlist.js : Maintain `SAFE_CONFIG_KEYS` for writable config sections via API and `READABLE_CONFIG_KEYS` for read-only sections, add new config sections to SAFE to enable saves

Applied to files:

  • src/modules/moderation.js
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Config editor save contract must maintain: global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Config editor must implement metadata-driven config search with cross-category quick jump, focus/scroll targeting, and auto-open advanced sections when search hits advanced controls

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Web dashboard config editor must use category workspace navigation with categories: `AI & Automation`, `Onboarding & Growth`, `Moderation & Safety`, `Community Tools`, `Support & Integrations` located in `web/src/components/dashboard/config-workspace/`

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
🪛 markdownlint-cli2 (0.21.0)
TASK.md

[warning] 7-7: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 16-16: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 21-21: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 25-25: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 27-27: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 32-32: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 36-36: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 41-41: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 43-43: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 48-48: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 57-57: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 58-58: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 60-60: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 63-63: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 67-67: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 69-69: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 74-74: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🔇 Additional comments (1)
web/src/components/dashboard/performance-dashboard.tsx (1)

363-363: No concerns with the Tooltip JSX cleanup.

This is a formatting-only change; the formatter behavior and chart props remain unchanged.

Also applies to: 403-403

Copilot AI review requested due to automatic review settings March 7, 2026 23:54
@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-261 March 7, 2026 23:54 Destroyed
@BillChirico BillChirico review requested due to automatic review settings March 7, 2026 23:54
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: 2

🤖 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 202-213: The permissions schema is missing the deprecated singular
fields so PATCH paths like permissions.adminRoleId and
permissions.moderatorRoleId are rejected; update the permissions properties
object to include adminRoleId and moderatorRoleId (allowing null as the
dashboard sends null) — e.g., add properties named adminRoleId and
moderatorRoleId with types that accept string or null so validateSingleValue()
will recognize those dotted paths and legacy guilds won't hit "Unknown config
path".

In `@src/utils/permissions.js`:
- Around line 21-24: mergeRoleIds assumes roleIds is an array and will break or
split strings; defensively validate and normalize roleIds before spreading:
check Array.isArray(roleIds) and use a shallow copy if true; if roleIds is a
string treat it as a single ID (wrap in an array); for any other
non-iterable/malformed types fall back to an empty array, then proceed to push
roleId if provided and not already present. Update the function mergeRoleIds to
perform this normalization so malformed persisted config values cannot cause
runtime errors or char-by-char splitting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fcc5e6ed-4228-4ea5-ad1a-da4150ac8a63

📥 Commits

Reviewing files that changed from the base of the PR and between 10cccf4 and f41423c.

📒 Files selected for processing (8)
  • src/api/utils/configValidation.js
  • src/modules/moderation.js
  • src/utils/modExempt.js
  • src/utils/permissions.js
  • tests/utils/modExempt.test.js
  • tests/utils/permissions.test.js
  • web/src/components/dashboard/config-editor.tsx
  • 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: claude-review
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,cjs,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only with import/export syntax, never CommonJS except in migration files (.cjs)

Files:

  • src/api/utils/configValidation.js
  • tests/utils/modExempt.test.js
  • src/utils/permissions.js
  • tests/utils/permissions.test.js
  • src/modules/moderation.js
  • src/utils/modExempt.js
**/*.{js,mjs,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,mjs,jsx,ts,tsx}: Use single quotes for strings in code, double quotes only allowed in JSON files
Always end statements with semicolons
Use 2-space indentation, enforced by Biome

Files:

  • src/api/utils/configValidation.js
  • tests/utils/modExempt.test.js
  • src/utils/permissions.js
  • tests/utils/permissions.test.js
  • src/modules/moderation.js
  • web/src/types/config.ts
  • src/utils/modExempt.js
  • web/src/components/dashboard/config-editor.tsx
src/**/*.{js,mjs,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{js,mjs,jsx,ts,tsx}: Use src/logger.js Winston logger singleton, never use console.* methods
Use safe Discord message methods: safeReply(), safeSend(), safeEditReply() instead of direct Discord.js methods
Use parameterized SQL queries, never string interpolation for database queries

Files:

  • src/api/utils/configValidation.js
  • src/utils/permissions.js
  • src/modules/moderation.js
  • src/utils/modExempt.js
{.env*,README.md,src/**/!(*.test).{js,ts}}

📄 CodeRabbit inference engine (CLAUDE.md)

Remove GUILD_ID from shared environment variables in production/deployment configurations; preserve dev-only guild-scoped deploy support via CLI flag --guild-id <guild_id>

Files:

  • src/api/utils/configValidation.js
  • src/utils/permissions.js
  • src/modules/moderation.js
  • src/utils/modExempt.js
tests/**/*.{js,mjs,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Maintain 80% code coverage threshold minimum, never lower this threshold

Files:

  • tests/utils/modExempt.test.js
  • tests/utils/permissions.test.js
src/modules/**/*.{js,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

Create new modules for features with corresponding config sections in config.json and entries in SAFE_CONFIG_KEYS

Files:

  • src/modules/moderation.js
web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design

Files:

  • web/src/types/config.ts
  • web/src/components/dashboard/config-editor.tsx
🧠 Learnings (9)
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/api/utils/configAllowlist.js : Maintain `SAFE_CONFIG_KEYS` for writable config sections via API and `READABLE_CONFIG_KEYS` for read-only sections, add new config sections to SAFE to enable saves

Applied to files:

  • src/api/utils/configValidation.js
  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.test.{js,jsx,ts,tsx} : Config editor tests must cover manual-save workspace behavior (not autosave assumptions), category switching, search functionality, and dirty badges

Applied to files:

  • tests/utils/permissions.test.js
  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to {.env*,README.md,src/**/!(*.test).{js,ts}} : Remove `GUILD_ID` from shared environment variables in production/deployment configurations; preserve dev-only guild-scoped deploy support via CLI flag `--guild-id <guild_id>`

Applied to files:

  • src/modules/moderation.js
  • src/utils/modExempt.js
  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/utils/discordCache.js : Cache Discord API responses for channels, roles, and members with auto-invalidation on config changes

Applied to files:

  • src/modules/moderation.js
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/**/*.{js,mjs,jsx,ts,tsx} : Use safe Discord message methods: `safeReply()`, `safeSend()`, `safeEditReply()` instead of direct Discord.js methods

Applied to files:

  • src/modules/moderation.js
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Config editor save contract must maintain: global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Config editor must implement metadata-driven config search with cross-category quick jump, focus/scroll targeting, and auto-open advanced sections when search hits advanced controls

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Web dashboard config editor must use category workspace navigation with categories: `AI & Automation`, `Onboarding & Growth`, `Moderation & Safety`, `Community Tools`, `Support & Integrations` located in `web/src/components/dashboard/config-workspace/`

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Refactor config feature presentation to use reusable `SettingsFeatureCard` pattern with structure: header + master toggle + Basic/Advanced blocks

Applied to files:

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

Copilot AI review requested due to automatic review settings March 8, 2026 00:41
…mergeRoleIds

- Add permissions block to CONFIG_SCHEMA (adminRoleIds, moderatorRoleIds,
  legacy singular fields, botOwners, modRoles) so PATCH validation works
  for existing guilds using the old singular fields
- mergeRoleIds now defensively handles non-array roleIds (string or malformed
  persisted config) using a Set to deduplicate
@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-261 March 8, 2026 00:41 Destroyed
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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 8, 2026
…cases

Covers: array merge, dedup, empty array + singular, null/undefined fallbacks,
malformed string normalization, empty string guards, and the production
merged-config failure scenario (defaults inject [] + legacy guild override).
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/utils/permissions.test.js`:
- Around line 503-510: Add a parallel test to the existing merged-config case
that verifies isModerator correctly recognizes a legacy admin role when
adminRoleIds: [] is present; specifically, create a member stub with
permissions.has returning false and roles.cache.has returning true for the
legacy admin id, pass a config object with permissions: { adminRoleIds: [],
adminRoleId: 'legacy-admin-123' } and assert isModerator(member, config) returns
true, referencing the isModerator function and the adminRoleIds/adminRoleId
config keys to mirror the existing moderator test pattern.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cd896580-f9a5-4db6-9624-b7196f999bc0

📥 Commits

Reviewing files that changed from the base of the PR and between 03d0ae1 and 473ee55.

📒 Files selected for processing (1)
  • tests/utils/permissions.test.js
📜 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). (3)
  • GitHub Check: Greptile Review
  • GitHub Check: Test (Vitest Coverage)
  • GitHub Check: claude-review
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,cjs,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only with import/export syntax, never CommonJS except in migration files (.cjs)

Files:

  • tests/utils/permissions.test.js
**/*.{js,mjs,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,mjs,jsx,ts,tsx}: Use single quotes for strings in code, double quotes only allowed in JSON files
Always end statements with semicolons
Use 2-space indentation, enforced by Biome

Files:

  • tests/utils/permissions.test.js
tests/**/*.{js,mjs,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Maintain 80% code coverage threshold minimum, never lower this threshold

Files:

  • tests/utils/permissions.test.js
🧠 Learnings (1)
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/tests/components/dashboard/**/*.test.{ts,tsx} : Web dashboard config editor tests should cover category switching, search functionality, dirty badges, and explicit manual-save workspace behavior instead of autosave assumptions

Applied to files:

  • tests/utils/permissions.test.js
🔇 Additional comments (4)
tests/utils/permissions.test.js (4)

19-19: LGTM!

Import correctly added for the new mergeRoleIds utility that's tested below.


140-149: Good addition addressing the merged-config scenario.

This test correctly covers the production failure case where getConfig() merges defaults (adminRoleIds: []) with legacy guild overrides (adminRoleId: 'value'). This addresses the previously flagged gap in backward-compat testing.


358-366: LGTM!

Test correctly updated to use adminRoleIds array, maintaining proper assertion on role lookup.


564-617: Excellent comprehensive test coverage for mergeRoleIds.

The 13 test cases thoroughly cover all edge cases:

  • Array/singular merging and deduplication
  • Null/undefined handling
  • Malformed config normalization (string instead of array)
  • Empty string guards
  • The critical production failure scenario (line 613-617)

The tests align well with the implementation logic documented in src/utils/permissions.js:9-36.

- tests: add isModerator merged-config test for legacy adminRoleId (admin-first check)
- configValidation: allowedCommands uses openProperties:true to allow freeform
  command keys without triggering unknown-key validation errors
- config-editor: updatePermissionsField auto-nulls legacy singular fields when
  plural arrays are written, so computePatches stays clean regardless of which
  field the user edits
Copilot AI review requested due to automatic review settings March 8, 2026 04:40
@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-261 March 8, 2026 04:40 Destroyed
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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-261 March 8, 2026 04:44 Destroyed
@BillChirico BillChirico merged commit 1328536 into main Mar 8, 2026
19 of 21 checks passed
@BillChirico BillChirico deleted the feat/multi-role-permissions branch March 8, 2026 04:48
@github-project-automation github-project-automation bot moved this from In Review to Done in Volvox.Bot Mar 8, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 8, 2026

🧹 Preview Environment Cleaned Up

The Railway preview environment for this PR has been removed.

Environment: pr-261

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: medium Medium priority scope: dashboard Web dashboard scope: moderation Moderation features type: feature New feature

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants