feat(permissions): support multiple admin and moderator roles#261
feat(permissions): support multiple admin and moderator roles#261BillChirico merged 10 commits intomainfrom
Conversation
- 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
|
Claude finished @BillChirico's task in 3m 0s —— View job Code Review — feat/multi-role-permissions
|
|
🚅 Deployed to the volvox-bot-pr-261 environment in volvox-bot
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
📜 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)
🧰 Additional context used📓 Path-based instructions (4)**/*.{js,ts,tsx,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{js,ts,tsx,jsx,json,md}📄 CodeRabbit inference engine (AGENTS.md)
Files:
tests/**/*.{js,ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
src/**/*.{js,ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (6)📚 Learning: 2026-03-08T02:15:42.321ZApplied to files:
📚 Learning: 2026-03-08T02:15:42.321ZApplied to files:
📚 Learning: 2026-03-08T02:15:42.321ZApplied to files:
📚 Learning: 2026-03-08T02:15:42.321ZApplied to files:
📚 Learning: 2026-03-08T04:37:50.316ZApplied to files:
📚 Learning: 2026-03-08T04:37:50.316ZApplied to files:
🔇 Additional comments (8)
📝 WalkthroughSummary by CodeRabbit
WalkthroughReplace 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
Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Note Docstrings generation - SUCCESS |
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`
There was a problem hiding this comment.
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
📒 Files selected for processing (10)
TASK.mdconfig.jsonsrc/modules/moderation.jssrc/utils/modExempt.jssrc/utils/permissions.jstests/utils/modExempt.test.jstests/utils/permissions.test.jsweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/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.tsxsrc/modules/moderation.jssrc/utils/permissions.jsweb/src/components/dashboard/config-editor.tsxweb/src/types/config.tstests/utils/permissions.test.jssrc/utils/modExempt.jstests/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.tsxweb/src/components/dashboard/config-editor.tsxweb/src/types/config.ts
**/*.{js,cjs,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only with
import/exportsyntax, never CommonJS except in migration files (.cjs)
Files:
src/modules/moderation.jssrc/utils/permissions.jstests/utils/permissions.test.jssrc/utils/modExempt.jstests/utils/modExempt.test.js
src/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,mjs,jsx,ts,tsx}: Usesrc/logger.jsWinston logger singleton, never useconsole.*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.jssrc/utils/permissions.jssrc/utils/modExempt.js
src/modules/**/*.{js,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Create new modules for features with corresponding config sections in
config.jsonand entries inSAFE_CONFIG_KEYS
Files:
src/modules/moderation.js
{.env*,README.md,src/**/!(*.test).{js,ts}}
📄 CodeRabbit inference engine (CLAUDE.md)
Remove
GUILD_IDfrom 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.jssrc/utils/permissions.jssrc/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.jstests/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.tsxweb/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
There was a problem hiding this comment.
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
📒 Files selected for processing (8)
src/api/utils/configValidation.jssrc/modules/moderation.jssrc/utils/modExempt.jssrc/utils/permissions.jstests/utils/modExempt.test.jstests/utils/permissions.test.jsweb/src/components/dashboard/config-editor.tsxweb/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/exportsyntax, never CommonJS except in migration files (.cjs)
Files:
src/api/utils/configValidation.jstests/utils/modExempt.test.jssrc/utils/permissions.jstests/utils/permissions.test.jssrc/modules/moderation.jssrc/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.jstests/utils/modExempt.test.jssrc/utils/permissions.jstests/utils/permissions.test.jssrc/modules/moderation.jsweb/src/types/config.tssrc/utils/modExempt.jsweb/src/components/dashboard/config-editor.tsx
src/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,mjs,jsx,ts,tsx}: Usesrc/logger.jsWinston logger singleton, never useconsole.*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.jssrc/utils/permissions.jssrc/modules/moderation.jssrc/utils/modExempt.js
{.env*,README.md,src/**/!(*.test).{js,ts}}
📄 CodeRabbit inference engine (CLAUDE.md)
Remove
GUILD_IDfrom 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.jssrc/utils/permissions.jssrc/modules/moderation.jssrc/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.jstests/utils/permissions.test.js
src/modules/**/*.{js,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Create new modules for features with corresponding config sections in
config.jsonand entries inSAFE_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.tsweb/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.jsweb/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.jsweb/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.jssrc/utils/modExempt.jsweb/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
…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
…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).
There was a problem hiding this comment.
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
📒 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/exportsyntax, 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
mergeRoleIdsutility 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
adminRoleIdsarray, maintaining proper assertion on role lookup.
564-617: Excellent comprehensive test coverage formergeRoleIds.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
🧹 Preview Environment Cleaned UpThe Railway preview environment for this PR has been removed. Environment: |

Summary
Allow configuring multiple Discord roles for both admin and moderator permissions instead of a single role each.
Changes
adminRoleId: string | null→adminRoleIds: string[], same for moderatorisAdmin()/isModerator()check if member has ANY role in the array using.some()adminRoleId/moderatorRoleIdfor old configsBefore / After
Before: one role ID per permission level
After: any number of roles — member qualifies if they hold at least one