feat(xp): add configurable level-up dms#399
Conversation
|
This PR was not deployed automatically as @MohsinCoding does not have access to the Railway project. In order to get automatic PR deploys, please add @MohsinCoding to your workspace on Railway. |
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a configurable XP level-up DM feature: new config schema and defaults, backend logic to generate/send per-level or default DMs (with scoped rate-limiting), dashboard UI/editor for templates and overrides, TypeScript types, config utils updates, and tests across validation, actions, rate-limiting, and UI flows. Changes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📋 Issue PlannerLet us write the prompt for your AI agent so you can ship faster (with fewer bugs). View plan for ticket: ✨ Finishing Touches📝 Generate docstrings
🧪 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 |
|
| Filename | Overview |
|---|---|
| src/modules/levelUpActions.js | Introduces getLevelUpDmAction with per-level rateLimitScope; wires levelUpDm sendDm into resolveActions after configured actions for each crossed level |
| src/modules/actions/sendDm.js | Extends checkDmRateLimit/recordDmSend with optional scope parameter and getDmLimitKey helper; handleSendDm reads action.rateLimitScope defaulting to 'default' |
| src/api/utils/configValidation.js | Adds levelUpDm sub-schema to xp.properties and uniqueBy uniqueness enforcement for messages array items |
| src/modules/reputation.js | Adds levelUpDm deep-merge block in getXpConfig alongside roleRewards; preserves messages array via null-coalescing |
| src/modules/xpDefaults.js | Adds levelUpDm defaults block matching config.json and dashboard constant |
| web/src/lib/config-utils.ts | Fixes computePatches for newly added plain objects via flattenObjectToLeafPatches; adds isPlainObject/hasPlainObjectChild helpers |
| web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx | Adds Level-Up DMs UI with stable originalIndex-keyed override rows, useEffect draft sync, live preview, and duplicate-level protection |
| web/src/types/config.ts | Adds XpLevelUpDmMessage and XpLevelUpDmConfig interfaces; extends XpConfig with non-optional levelUpDm field |
| config.json | Adds levelUpDm block to xp section with values exactly matching XP_DEFAULTS |
| src/utils/loadCommands.js | Cosmetic: extracts pathToFileURL(filePath).href to named fileUrl variable; no behavioral change |
Sequence Diagram
sequenceDiagram
participant Discord
participant Reputation as reputation.js
participant Pipeline as executeLevelUpPipeline
participant Resolver as resolveActions
participant LUD as getLevelUpDmAction
participant DM as handleSendDm
participant RateLimit as dmLimits Map
Discord->>Reputation: message received
Reputation->>Reputation: gainXp() detects level-up
Reputation->>Pipeline: executeLevelUpPipeline({member, guild, previousLevel, newLevel, config})
Pipeline->>Resolver: resolveActions(previousLevel, newLevel, config)
loop for each crossed level
Resolver->>Resolver: collect levelActions / defaultActions
Resolver->>LUD: getLevelUpDmAction(level, config)
alt levelUpDm.enabled AND (override.message for level OR sendOnEveryLevel)
LUD-->>Resolver: {type:sendDm, rateLimitScope:levelUpDm:level}
else disabled or no match
LUD-->>Resolver: null
end
end
Resolver-->>Pipeline: ordered [{level, action}] list
loop for each action
Pipeline->>DM: handleSendDm(action, context)
DM->>RateLimit: checkDmRateLimit(guildId, userId, rateLimitScope)
alt within 60s rate window for this scope
RateLimit-->>DM: false (blocked)
else allowed
RateLimit-->>DM: true
DM->>Discord: member.user.send(payload)
DM->>RateLimit: recordDmSend(guildId, userId, rateLimitScope)
end
end
Reviews (12): Last reviewed commit: "fix(web): stabilize embed field fallback..." | Re-trigger Greptile
There was a problem hiding this comment.
Pull request overview
Adds a new configurable xp.levelUpDm config section to send level-up milestone DMs via the existing level-up action/template pipeline, with full bot + API + dashboard wiring and associated fixes in the dashboard config editor.
Changes:
- Introduces
xp.levelUpDmdefaults, schema validation (incl. unique per-level overrides), and action derivation into the level-up pipeline. - Updates the dashboard XP editor to manage level-up DM enablement, templates, overrides, search, preview, and correct patch generation for newly-added nested objects.
- Fixes Windows ESM command loading by importing local command modules via
file://URLs.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| web/tests/lib/config-utils.test.ts | Adds coverage for dotted leaf patch generation when nested objects are newly added. |
| web/tests/components/dashboard/config-editor-autosave.test.tsx | Expands integration tests for searching/editing/saving level-up DM settings and dotted patch behavior. |
| web/src/types/config.ts | Extends the web config contract with XpLevelUpDmConfig / XpLevelUpDmMessage and adds xp.levelUpDm. |
| web/src/lib/config-utils.ts | Enhances computePatches to flatten newly-added nested objects into dotted leaf patches. |
| web/src/components/dashboard/config-workspace/config-categories.ts | Adds a search index entry for “Level-Up DMs”. |
| web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx | Adds UI controls, overrides, and preview rendering for level-up DM configuration. |
| tests/modules/levelUpActions.test.js | Adds bot tests validating derived sendDm actions from xp.levelUpDm. |
| tests/api/utils/configValidation.test.js | Adds API validation tests for xp.levelUpDm and uniqueness/length constraints. |
| src/utils/loadCommands.js | Fixes dynamic command imports on Windows via pathToFileURL(...).href. |
| src/modules/xpDefaults.js | Defines new XP defaults for levelUpDm. |
| src/modules/reputation.js | Deep-merges levelUpDm config into resolved XP config (similar to roleRewards merge). |
| src/modules/levelUpActions.js | Derives and appends sendDm actions for crossed levels based on xp.levelUpDm. |
| src/api/utils/configValidation.js | Adds schema validation for xp.levelUpDm and introduces generic uniqueBy array validation. |
| config.json | Adds xp.levelUpDm to the default config contract. |
| sendOnEveryLevel: { type: 'boolean' }, | ||
| defaultMessage: { type: 'string', maxLength: 2000 }, | ||
| messages: { | ||
| type: 'array', | ||
| uniqueBy: 'level', | ||
| items: { | ||
| type: 'object', | ||
| required: ['level', 'message'], | ||
| properties: { | ||
| level: { type: 'number', min: 1, max: 1000 }, | ||
| message: { type: 'string', minLength: 1, maxLength: 2000 }, | ||
| }, |
There was a problem hiding this comment.
xp.levelUpDm.defaultMessage currently allows empty/whitespace-only strings (only maxLength is enforced). Since sendDm ultimately renders this into payload.content, an empty template can lead to Discord API errors when the action fires. Consider enforcing minLength: 1 and a non-whitespace pattern (e.g. \S) for defaultMessage (and likely messages[].message for consistency, since it currently allows ' ' due to missing pattern).
|
|
||
| function renderLevelUpDmPreview(template: string) { | ||
| return template.replace(/\{\{(\w+)\}\}/g, (match, key: string) => { | ||
| return key in LEVEL_UP_DM_PREVIEW_CONTEXT ? LEVEL_UP_DM_PREVIEW_CONTEXT[key] : match; |
There was a problem hiding this comment.
renderLevelUpDmPreview uses key in LEVEL_UP_DM_PREVIEW_CONTEXT, which also matches properties on the prototype chain (e.g. {{toString}}, {{constructor}}) and can produce unexpected preview output. Use an own-property check (e.g. Object.hasOwn(...)) to match the server-side template engine behavior.
| return key in LEVEL_UP_DM_PREVIEW_CONTEXT ? LEVEL_UP_DM_PREVIEW_CONTEXT[key] : match; | |
| return Object.hasOwn(LEVEL_UP_DM_PREVIEW_CONTEXT, key) | |
| ? LEVEL_UP_DM_PREVIEW_CONTEXT[key] | |
| : match; |
| xp: { | ||
| ...prev.xp, | ||
| levelUpDm: { | ||
| enabled: value, | ||
| sendOnEveryLevel: prev.xp?.levelUpDm?.sendOnEveryLevel ?? false, | ||
| defaultMessage: | ||
| prev.xp?.levelUpDm?.defaultMessage ?? | ||
| '🎉 You reached **Level {{level}}** in **{{server}}**! Keep chatting!', | ||
| messages: prev.xp?.levelUpDm?.messages ?? [], |
There was a problem hiding this comment.
The levelUpDm update blocks repeat the same default message literal and reconstruct the same object shape in multiple handlers. This duplication increases the risk of drift (e.g. updating the default in one place but not others). Consider extracting a shared DEFAULT_LEVEL_UP_DM_MESSAGE constant and/or a small helper that merges/initializes xp.levelUpDm so each handler only specifies the field it is changing.
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 `@tests/modules/levelUpActions.test.js`:
- Around line 104-172: Add a test exercising a skipped-level DM branch for
resolveActions: simulate leveling from 4 to 6 with levelUpDm enabled,
sendOnEveryLevel true/false as appropriate, and messages containing an override
for level 5 and a default for level 6; assert that resolveActions(4, 6, config)
returns two derived sendDm actions—one for level 5 using the override template
and one for level 6 using the default template—so the resolver emits
per-crossed-level DMs rather than only the final DM or incorrect combinations.
In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 732-749: The onClick handler that calls updateDraftConfig is
creating a new level override with a hardcoded level: 1 which can collide with
existing xp.levelUpDm.messages and cause server validation errors; change the
logic in the add-override code path (the updateDraftConfig call that modifies
xp.levelUpDm.messages) to compute a non-colliding level (e.g., find the max
existing messages[].level and use max+1 or scan for the first unused positive
integer) and append that leveled object, and also optionally guard by
filtering/validating duplicates before set (ensure xp.levelUpDm.messages
contains unique level values) so that the local form never produces duplicate
messages[].level that the backend will reject.
- Around line 52-55: Replace the prototype-inclusive check in
renderLevelUpDmPreview so only own properties from LEVEL_UP_DM_PREVIEW_CONTEXT
are used: in the replacer callback for template.replace, change the condition
that currently uses "key in LEVEL_UP_DM_PREVIEW_CONTEXT" to use
Object.hasOwn(LEVEL_UP_DM_PREVIEW_CONTEXT, key) (or
Object.prototype.hasOwnProperty.call(...) if needed) so placeholders like
{{toString}} are not substituted from the prototype.
In `@web/src/lib/config-utils.ts`:
- Around line 10-26: flattenObjectToLeafPatches currently omits patches for
empty plain objects ({}), causing transitions like undefined -> {} or {} ->
undefined to be lost; modify flattenObjectToLeafPatches so that when
isPlainObject(value) is true but Object.keys(value).length === 0 you push a leaf
patch { path: fullPath, value: {} } instead of recursing (i.e., treat empty
objects as leaves), and apply the same change to any other function/block with
the same plain-object recursion logic to ensure empty-object diffs are
preserved.
In `@web/tests/components/dashboard/config-editor-autosave.test.tsx`:
- Around line 263-306: After adding an override row the test must type into the
override textarea and assert focus/value persist across a rerender: locate the
newly added override textarea after clicking the "Add Override" button (use the
same DOM helpers around levelUpCard/advancedButton and the button with name 'Add
Override' to find the new row), use user.type to enter several characters into
that textarea, trigger a rerender (e.g., toggle advancedButton off then on or
perform an action that causes the component to re-render), then assert that
document.activeElement is the same textarea and its value still contains the
typed characters to verify the focus-retention/key-stability regression is
covered.
🪄 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: 4cda93bf-245a-4f62-b47e-87598b1549dd
📒 Files selected for processing (14)
config.jsonsrc/api/utils/configValidation.jssrc/modules/levelUpActions.jssrc/modules/reputation.jssrc/modules/xpDefaults.jssrc/utils/loadCommands.jstests/api/utils/configValidation.test.jstests/modules/levelUpActions.test.jsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/lib/config-utils.tsweb/src/types/config.tsweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/tests/lib/config-utils.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Agent
- GitHub Check: Greptile Review
- GitHub Check: Test
- GitHub Check: E2E Tests (2/2)
- GitHub Check: E2E Tests (1/2)
🧰 Additional context used
📓 Path-based instructions (13)
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
src/utils/loadCommands.jssrc/modules/reputation.jssrc/modules/xpDefaults.jssrc/modules/levelUpActions.jstests/modules/levelUpActions.test.jstests/api/utils/configValidation.test.jssrc/api/utils/configValidation.js
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes
Always include semicolons at the end of statements
Use 2-space indentation (spaces, not tabs)
Always include trailing commas in multi-line arrays, objects, and function parameters
Maintain a maximum line width of 100 characters
Files:
src/utils/loadCommands.jssrc/modules/reputation.jssrc/modules/xpDefaults.jsweb/tests/lib/config-utils.test.tssrc/modules/levelUpActions.jsweb/src/components/dashboard/config-workspace/config-categories.tstests/modules/levelUpActions.test.jstests/api/utils/configValidation.test.jssrc/api/utils/configValidation.jsweb/src/types/config.tsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/lib/config-utils.ts
src/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/**/*.js: Never useconsole.*methods; use the Winston logger instead viaimport logger from '../logger.js'(adjust path as needed), then calllogger.info(),logger.warn(),logger.error(), orlogger.debug()
Always usesafeReply(),safeSend(), orsafeEditReply()instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully
Files:
src/utils/loadCommands.jssrc/modules/reputation.jssrc/modules/xpDefaults.jssrc/modules/levelUpActions.jssrc/api/utils/configValidation.js
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*
Files:
src/utils/loadCommands.jssrc/modules/reputation.jssrc/modules/xpDefaults.jsweb/tests/lib/config-utils.test.tssrc/modules/levelUpActions.jsweb/src/components/dashboard/config-workspace/config-categories.tstests/modules/levelUpActions.test.jstests/api/utils/configValidation.test.jssrc/api/utils/configValidation.jsweb/src/types/config.tsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/lib/config-utils.ts
src/**/*.{js,ts}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,ts}: Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Community features should be gated behind config..enabled. Moderation commands are the exception.
Files:
src/utils/loadCommands.jssrc/modules/reputation.jssrc/modules/xpDefaults.jssrc/modules/levelUpActions.jssrc/api/utils/configValidation.js
**/*.{js,ts}
📄 CodeRabbit inference engine (AGENTS.md)
Use parameterized SQL only; do not use string concatenation for SQL queries
Files:
src/utils/loadCommands.jssrc/modules/reputation.jssrc/modules/xpDefaults.jsweb/tests/lib/config-utils.test.tssrc/modules/levelUpActions.jsweb/src/components/dashboard/config-workspace/config-categories.tstests/modules/levelUpActions.test.jstests/api/utils/configValidation.test.jssrc/api/utils/configValidation.jsweb/src/types/config.tsweb/src/lib/config-utils.ts
src/modules/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Create feature modules in
src/modules/and add corresponding config sections toconfig.json
Files:
src/modules/reputation.jssrc/modules/xpDefaults.jssrc/modules/levelUpActions.js
web/tests/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with thejsdomenvironment and React Testing Library, matching theweb/src/structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Files:
web/tests/lib/config-utils.test.tsweb/tests/components/dashboard/config-editor-autosave.test.tsx
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/config-categories.tsweb/src/types/config.tsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/lib/config-utils.ts
tests/**/*.test.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
tests/**/*.test.js: Write bot tests using Vitest 4 with thenodeenvironment, matching thesrc/structure in thetests/directory
Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead
Files:
tests/modules/levelUpActions.test.jstests/api/utils/configValidation.test.js
**/*.json
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use double quotes in JSON files (standard JSON format); this exception applies only to JSON files, not JavaScript/TypeScript
Files:
config.json
config.json
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
All community features in
config.jsonshould be gated behind aconfig.<feature>.enabledflag; moderation commands are always available regardless of config settings
Files:
config.json
{config.json,src/api/utils/configAllowlist.js}
📄 CodeRabbit inference engine (AGENTS.md)
Config-backed features must be added to config.json and src/api/utils/configAllowlist.js. If a key is missing from SAFE_CONFIG_KEYS, the dashboard cannot save it.
Files:
config.json
🧠 Learnings (10)
📚 Learning: 2026-03-26T00:04:14.673Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.673Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use src/logger.js; do not use console.*
Applied to files:
src/utils/loadCommands.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/commands/**/*.js : Create slash command definitions in `src/commands/`, exporting a slash command builder and an `execute` function
Applied to files:
src/utils/loadCommands.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure
Applied to files:
web/tests/lib/config-utils.test.tsweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory
Applied to files:
web/tests/lib/config-utils.test.ts
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Applied to files:
web/tests/lib/config-utils.test.tsweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.673Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.673Z
Learning: Applies to src/**/*.{js,ts} : Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Applied to files:
src/modules/levelUpActions.js
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to 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/config-categories.tsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead
Applied to files:
tests/api/utils/configValidation.test.js
📚 Learning: 2026-03-10T23:29:51.063Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:29:51.063Z
Learning: Config editor save contract: maintain global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.673Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.673Z
Learning: Applies to {web/src/lib/page-titles.ts,web/src/**/+page.ts,web/src/**/+page.svelte} : New dashboard routes need title wiring in web/src/lib/page-titles.ts: use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation.
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
[warning] 53-53: Prefer String#replaceAll() over String#replace().
[failure] 831-831: Refactor this code to not nest functions more than 4 levels deep.
web/src/lib/config-utils.ts
[warning] 79-79: This assertion is unnecessary since it does not change the type of the expression.
[warning] 79-79: This assertion is unnecessary since it does not change the type of the expression.
🔇 Additional comments (8)
src/utils/loadCommands.js (1)
3-3: Cross-platform dynamic import fix looks correct.Switching to
pathToFileURL(...).hrefforimport()is a solid Windows-safe ESM approach.Also applies to: 33-33, 36-36
src/modules/reputation.js (1)
55-61:levelUpDmdeep-merge behavior is implemented cleanly.Default-preserving merge plus
messagesfallback is consistent with the intended config contract.config.json (1)
291-296: Defaultxp.levelUpDmconfig block is well-structured.The section includes proper feature gating and sensible defaults for rollout.
web/tests/lib/config-utils.test.ts (1)
1-33: Great regression coverage for nested-object patch flattening.This test precisely locks in the dotted leaf patch behavior for newly introduced config subtrees.
src/modules/xpDefaults.js (1)
11-16:XP_DEFAULTSextension is clean and consistent.Adding
levelUpDmhere keeps the module’s single-source-of-truth pattern intact.tests/api/utils/configValidation.test.js (1)
414-417: Validation tests forxp.levelUpDmare comprehensive for core constraints.Good coverage on acceptance path plus duplicate-level, blank-message, and length-rejection paths.
Also applies to: 425-457
web/src/components/dashboard/config-workspace/config-categories.ts (1)
218-226: Search metadata addition for Level-Up DMs is solid.The new item is well-tagged and mapped to the existing XP Level Actions feature surface.
src/modules/levelUpActions.js (1)
50-64: DM action derivation and injection into the level-crossing loop are implemented correctly.This cleanly reuses the existing action pipeline while preserving per-level override and default fallback behavior.
Also applies to: 93-96
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/modules/actions/sendDm.js (1)
88-99:⚠️ Potential issue | 🟠 MajorDon't let persisted actions choose their own DM rate-limit bucket.
xp.levelActionsandxp.defaultActionsstill accept arbitrary action properties, so a configuredsendDmcan now setrateLimitScopeand sidestep the intended throttle by picking a fresh bucket. Keep this scope in trusted pipeline metadata only, or ignore it for user-authored actions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/actions/sendDm.js` around lines 88 - 99, The code currently allows persisted/user actions to set rateLimitScope via action.rateLimitScope; change sendDm to ignore action.rateLimitScope for untrusted actions and derive the scope only from trusted pipeline metadata (e.g., a trusted parameter on templateContext or an explicit trusted arg) with fallback to 'default'; update the line that sets rateLimitScope and ensure checkDmRateLimit(guild.id, userId, rateLimitScope) and recordDmSend(guild.id, userId, rateLimitScope) use this trusted scope (leave safeSend, buildPayload, member.user unchanged), and do not accept action.rateLimitScope coming from xp.levelActions or xp.defaultActions.
♻️ Duplicate comments (1)
web/tests/components/dashboard/config-editor-autosave.test.tsx (1)
304-324:⚠️ Potential issue | 🟡 MinorForce a rerender before asserting focus retention.
This still checks the textarea immediately after typing, so the unstable-key bug can pass even if the row remounts on the next render. Trigger one rerender after Line 320 and then reassert that the same textarea keeps focus and value.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/tests/components/dashboard/config-editor-autosave.test.tsx` around lines 304 - 324, After typing into firstOverrideTextarea, force a rerender (e.g., await waitFor(() => {} ) or await act(() => Promise.resolve())) and then re-query the textarea (use the same selection logic that produced firstOverrideTextarea) and reassert that document.activeElement is that textarea and it still has value 'Override text'; update the test around firstOverrideTextarea, the await user.type call, and the subsequent assertions to include this forced rerender + re-check to ensure focus retention across a remount.
🤖 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/levelUpActions.js`:
- Around line 50-74: getLevelUpDmAction currently returns a separate sendDm
action per level which, combined with handleSendDm's single rateLimitScope
('levelUpDm'), causes skipped-level DMs to be dropped; change the behavior to
collapse skipped-level notifications into a single DM by making
getLevelUpDmAction produce one action for a multi-level jump (e.g., accept the
final level or an array/range of levels and choose the highest-level override or
a single default message when sendOnEveryLevel is true) so only one sendDm is
created per pipeline, or alternatively move the limiter application out of
per-action rateLimitScope into the pipeline-level send step (handleSendDm) so
the limiter is applied once; update getLevelUpDmAction and the caller
consistently (or update handleSendDm) so rateLimitScope usage no longer causes
intermediate sends to be lost.
In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 747-765: The Add Override handler creates a new override with
message: '' which backend now rejects; in the onClick updater
(updateDraftConfig) where buildLevelUpDmConfig is called, seed the new messages
entry with a non-empty template instead of an empty string (use
getNextLevelUpDmOverrideLevel(existingMessages) to interpolate a placeholder
like "Congrats on reaching level {level}!" or similar) so the autosave won't
produce a server-rejected draft; update the object created in that block (the
message property of the new array element) to a valid template string.
In `@web/src/lib/config-utils.ts`:
- Around line 85-92: This branch currently emits leaf-null patches
(patches.push(..., value: null)) which leaves nested keys set to null; instead
emit deletion-style patches that match the backend prune/delete path — replace
value: null with value: undefined (or the project's canonical delete marker if
one exists) and when origVal is an object push a single patch for the parent
subtree (patches.push({ path: fullPath, value: undefined })) rather than
flattening to leaf-null entries; update the branch around
modHasKey/modVal/origVal and usages of flattenObjectToLeafPatches to produce the
deletion marker so setNestedValue on the backend can remove the subtree.
---
Outside diff comments:
In `@src/modules/actions/sendDm.js`:
- Around line 88-99: The code currently allows persisted/user actions to set
rateLimitScope via action.rateLimitScope; change sendDm to ignore
action.rateLimitScope for untrusted actions and derive the scope only from
trusted pipeline metadata (e.g., a trusted parameter on templateContext or an
explicit trusted arg) with fallback to 'default'; update the line that sets
rateLimitScope and ensure checkDmRateLimit(guild.id, userId, rateLimitScope) and
recordDmSend(guild.id, userId, rateLimitScope) use this trusted scope (leave
safeSend, buildPayload, member.user unchanged), and do not accept
action.rateLimitScope coming from xp.levelActions or xp.defaultActions.
---
Duplicate comments:
In `@web/tests/components/dashboard/config-editor-autosave.test.tsx`:
- Around line 304-324: After typing into firstOverrideTextarea, force a rerender
(e.g., await waitFor(() => {} ) or await act(() => Promise.resolve())) and then
re-query the textarea (use the same selection logic that produced
firstOverrideTextarea) and reassert that document.activeElement is that textarea
and it still has value 'Override text'; update the test around
firstOverrideTextarea, the await user.type call, and the subsequent assertions
to include this forced rerender + re-check to ensure focus retention across a
remount.
🪄 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: bb59eef2-c445-434c-98da-5664fb939fb9
📒 Files selected for processing (10)
src/api/utils/configValidation.jssrc/modules/actions/sendDm.jssrc/modules/levelUpActions.jstests/api/utils/configValidation.test.jstests/modules/actions/sendDm.test.jstests/modules/levelUpActions.test.jsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/lib/config-utils.tsweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/tests/lib/config-utils.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Greptile Review
- GitHub Check: E2E Tests (2/2)
- GitHub Check: Test
- GitHub Check: E2E Tests (1/2)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
tests/modules/actions/sendDm.test.jssrc/modules/levelUpActions.jstests/api/utils/configValidation.test.jstests/modules/levelUpActions.test.jssrc/modules/actions/sendDm.jssrc/api/utils/configValidation.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/modules/actions/sendDm.test.jsweb/tests/lib/config-utils.test.tssrc/modules/levelUpActions.jstests/api/utils/configValidation.test.jstests/modules/levelUpActions.test.jssrc/modules/actions/sendDm.jssrc/api/utils/configValidation.jsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/lib/config-utils.ts
tests/**/*.test.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
tests/**/*.test.js: Write bot tests using Vitest 4 with thenodeenvironment, matching thesrc/structure in thetests/directory
Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead
Files:
tests/modules/actions/sendDm.test.jstests/api/utils/configValidation.test.jstests/modules/levelUpActions.test.js
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*
Files:
tests/modules/actions/sendDm.test.jsweb/tests/lib/config-utils.test.tssrc/modules/levelUpActions.jstests/api/utils/configValidation.test.jstests/modules/levelUpActions.test.jssrc/modules/actions/sendDm.jssrc/api/utils/configValidation.jsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/lib/config-utils.ts
**/*.{js,ts}
📄 CodeRabbit inference engine (AGENTS.md)
Use parameterized SQL only; do not use string concatenation for SQL queries
Files:
tests/modules/actions/sendDm.test.jsweb/tests/lib/config-utils.test.tssrc/modules/levelUpActions.jstests/api/utils/configValidation.test.jstests/modules/levelUpActions.test.jssrc/modules/actions/sendDm.jssrc/api/utils/configValidation.jsweb/src/lib/config-utils.ts
web/tests/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with thejsdomenvironment and React Testing Library, matching theweb/src/structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Files:
web/tests/lib/config-utils.test.tsweb/tests/components/dashboard/config-editor-autosave.test.tsx
src/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/**/*.js: Never useconsole.*methods; use the Winston logger instead viaimport logger from '../logger.js'(adjust path as needed), then calllogger.info(),logger.warn(),logger.error(), orlogger.debug()
Always usesafeReply(),safeSend(), orsafeEditReply()instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully
Files:
src/modules/levelUpActions.jssrc/modules/actions/sendDm.jssrc/api/utils/configValidation.js
src/modules/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Create feature modules in
src/modules/and add corresponding config sections toconfig.json
Files:
src/modules/levelUpActions.jssrc/modules/actions/sendDm.js
src/**/*.{js,ts}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,ts}: Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Community features should be gated behind config..enabled. Moderation commands are the exception.
Files:
src/modules/levelUpActions.jssrc/modules/actions/sendDm.jssrc/api/utils/configValidation.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/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/lib/config-utils.ts
🧠 Learnings (8)
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure
Applied to files:
web/tests/lib/config-utils.test.tsweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory
Applied to files:
web/tests/lib/config-utils.test.ts
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Applied to files:
web/tests/lib/config-utils.test.tsweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead
Applied to files:
web/tests/lib/config-utils.test.ts
📚 Learning: 2026-03-26T00:04:14.673Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.673Z
Learning: Applies to src/**/*.{js,ts} : Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Applied to files:
src/modules/levelUpActions.js
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to 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-sections/CommunitySettingsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.673Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.673Z
Learning: Applies to src/**/*.{js,ts} : Community features should be gated behind config.<feature>.enabled. Moderation commands are the exception.
Applied to files:
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
📚 Learning: 2026-03-10T23:29:51.063Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:29:51.063Z
Learning: Config editor save contract: maintain global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
🪛 GitHub Check: SonarCloud Code Analysis
src/api/utils/configValidation.js
[warning] 339-339: String.raw should be used to avoid escaping \.
[warning] 348-348: String.raw should be used to avoid escaping \.
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
[warning] 58-58: Prefer String#replaceAll() over String#replace().
[failure] 842-842: Refactor this code to not nest functions more than 4 levels deep.
[failure] 803-803: Refactor this code to not nest functions more than 4 levels deep.
web/src/lib/config-utils.ts
[warning] 84-84: This assertion is unnecessary since it does not change the type of the expression.
[warning] 84-84: This assertion is unnecessary since it does not change the type of the expression.
🔇 Additional comments (4)
web/tests/components/dashboard/config-editor-autosave.test.tsx (3)
94-130: Good fixture setup for both seeded and missingxp.levelUpDmstates.This keeps the new onboarding tests focused and gives the autosave regression a realistic “missing subtree” baseline.
231-261: Nice search regression coverage.This exercises the discoverability path for the new Level-Up Actions card without coupling the test to unrelated onboarding sections.
327-389: Good coverage for dotted leaf patches on newly added nested config.The assertion against
path === 'xp'makes the nested diff regression much harder to reintroduce.web/src/lib/config-utils.ts (1)
10-17: Nice fix for empty-object diffs.Treating
{}as a leaf patch closes the droppedundefined -> {}and{}->undefinedcases.
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
@greptile review |
| * Check whether a DM is allowed under the rate limit. | ||
| * | ||
| * @param {string} guildId | ||
| * @param {string} userId | ||
| * @returns {boolean} | ||
| */ | ||
| export function checkDmRateLimit(guildId, userId) { | ||
| const key = `${guildId}:${userId}`; | ||
| export function checkDmRateLimit(guildId, userId, scope = 'default') { |
There was a problem hiding this comment.
checkDmRateLimit/recordDmSend now accept a scope parameter, but the JSDoc only documents guildId and userId. Please update the JSDoc to include scope (and clarify whether rate limiting is intended to be per-scope or global per user).
| }, [levelUpDmDefaultMessage]); | ||
|
|
||
| useEffect(() => { | ||
| setLevelUpDmOverrideDrafts(Object.fromEntries(levelUpDmOverrideDraftEntries)); |
There was a problem hiding this comment.
This effect fully resets levelUpDmOverrideDrafts from draftConfig whenever levelUpDmMessages changes. Because override messages are only persisted to draftConfig onBlur, any change that reorders/updates levelUpDmMessages (e.g. changing the level number, adding/removing an override) can wipe in-progress textarea edits. Consider merging new entries into the existing drafts state instead of overwriting it wholesale, or only initializing drafts for indices that don’t already exist.
| setLevelUpDmOverrideDrafts(Object.fromEntries(levelUpDmOverrideDraftEntries)); | |
| setLevelUpDmOverrideDrafts((previousDrafts) => { | |
| const nextDrafts: Record<number, string> = {}; | |
| for (const [originalIndex, message] of levelUpDmOverrideDraftEntries) { | |
| nextDrafts[originalIndex] = | |
| previousDrafts[originalIndex] !== undefined | |
| ? previousDrafts[originalIndex] | |
| : message; | |
| } | |
| return nextDrafts; | |
| }); |
|
|
||
| return ( | ||
| <div | ||
| key={field.id ?? `field-${i}`} | ||
| key={field.id ?? `field-${field.name}-${field.value}-${field.inline}`} | ||
| className={cn(field.inline ? 'col-span-1' : 'col-span-3')} |
There was a problem hiding this comment.
The fallback React key for rendered fields is derived from mutable content (field.name, field.value, field.inline). If field.id is missing (e.g. initial hydration before ensureFieldIds runs), typing will change the key and can cause React to remount nodes (losing selection/focus). Prefer a stable fallback (e.g. index) until IDs are guaranteed, or ensure IDs exist before first render.
| {value.fields.map((field, i) => ( | ||
| <div | ||
| key={field.id ?? `field-editor-${i}`} | ||
| key={field.id ?? `field-editor-${field.name}-${field.value}-${field.inline}`} |
There was a problem hiding this comment.
Same unstable-key issue in the editor list: key={field.id ?? field-editor-${field.name}-${field.value}-${field.inline}} can remount a field editor while the user types if field.id is absent, causing focus loss. Use a stable fallback key until IDs are hydrated (or hydrate IDs before rendering the list).
| key={field.id ?? `field-editor-${field.name}-${field.value}-${field.inline}`} | |
| key={field.id ?? `field-editor-${i}`} |
| */ | ||
| const dmLimits = new Map(); | ||
|
|
||
| function getDmLimitKey(guildId, userId, scope = 'default') { | ||
| return `${guildId}:${userId}:${scope}`; | ||
| } |
There was a problem hiding this comment.
The rate-limiter doc comment still describes the Map key as ${guildId}:${userId}, but the implementation now includes a scope segment via getDmLimitKey(...). Please update the comment/JSDoc to reflect the new key format so future debugging matches actual behavior.
| if (isPlainObject(origVal) && isPlainObject(modVal)) { | ||
| if (Object.keys(modVal).length === 0 && !hasPlainObjectChild(origVal)) { | ||
| patches.push({ path: fullPath, value: {} }); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
When modVal is an explicit empty object ({}) but origVal contains nested objects, this code recurses instead of emitting a single { path: fullPath, value: {} }. That recursion will generate child patches like ...enabled: null for removed primitives, which is likely invalid for the PATCH validator (non-nullable booleans) and can break “reset section to empty/defaults” flows. Consider treating any explicit empty-object replacement as a parent-level {} patch regardless of origVal contents.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
tests/modules/actions/sendDm.test.js (1)
169-173:⚠️ Potential issue | 🟡 MinorUse runtime-accurate
levelUpDm:<level>scope in this test.Line 172 should assert against a production-format scope (e.g.,
'levelUpDm:5') instead of'levelUpDm', so this test validates the same key shape used by level-up actions.Diff suggestion
it('should track rate limits independently by scope', () => { recordDmSend('g1', 'u1', 'default'); expect(checkDmRateLimit('g1', 'u1', 'default')).toBe(false); - expect(checkDmRateLimit('g1', 'u1', 'levelUpDm')).toBe(true); + expect(checkDmRateLimit('g1', 'u1', 'levelUpDm:5')).toBe(true); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/modules/actions/sendDm.test.js` around lines 169 - 173, The test uses a non-production scope string; update the assertion to use a runtime-accurate scope like 'levelUpDm:5' so it matches the real key shape used by level-up actions: modify the second expect call that currently checks checkDmRateLimit('g1','u1','levelUpDm') to instead check checkDmRateLimit('g1','u1','levelUpDm:5') while leaving the recordDmSend('g1','u1','default') and the first assertion unchanged to ensure scopes are tracked independently (functions involved: recordDmSend, checkDmRateLimit).web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx (1)
730-780:⚠️ Potential issue | 🟠 MajorUse the shared markdown editor here instead of raw textareas.
These blocks still only show a static token list and a plain-text preview, so admins cannot insert variables from a picker and
**markdown**is displayed literally instead of as the DM will render. That leaves the level-up DM UI short of the stated feature requirements and duplicates functionality already implemented inweb/src/components/ui/discord-markdown-editor.tsx. Wire that editor in for the default template and each override, passing the variable names and preview context you already build.Also applies to: 908-960
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx` around lines 730 - 780, Replace the raw textarea inputs used for level-up DM templates with the shared markdown editor component (the one exported from web/src/components/ui/discord-markdown-editor.tsx) for the default template (currently using levelUpDmDefaultDraft, isValidLevelUpDmTemplate, updateDraftConfig, buildLevelUpDmConfig) and for each override block (the other levelUpDm* drafts between the other mentioned ranges); pass LEVEL_UP_DM_TEMPLATE_VARS as the variable/picker list and provide the same preview context by using renderLevelUpDmPreview for live rendering, preserve disabled={saving}, rows/maxLength behavior, and keep the onBlur/updateDraftConfig flow so validation via isValidLevelUpDmTemplate and buildLevelUpDmConfig still runs when editing finishes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 156-179: levelUpDmMessages is recreated every render which causes
levelUpDmOverrideDraftEntries (and the effect that resets
levelUpDmOverrideDrafts) to receive a new reference and wipe in-progress edits;
wrap the construction of levelUpDmMessages in useMemo so it only recomputes when
the underlying messages change (e.g. depend on levelUpDm?.messages), keeping the
mapping, originalIndex injection and sort logic intact; this stabilizes
levelUpDmMessages and prevents setLevelUpDmOverrideDrafts from running on
unrelated renders.
In `@web/src/components/ui/embed-builder.tsx`:
- Around line 473-480: The current key for fields in the config.fields.map uses
a fallback composed of mutable content (field.name/field.value/field.inline)
which can change and cause remounts/collisions when field.id is missing; update
the key expression in the map callback to use field.id as the primary key and a
deterministic non-content fallback based on the iteration index (e.g.
key={field.id ?? `field-${index}`}), and apply the same change to the other
fields map occurrence that currently builds fallback keys from name/value/inline
so keys remain stable across edits and hydration.
- Around line 16-17: The Image components in EmbedBuilder (rendering
thumbnailUrl, imageUrl, footerIconUrl) may receive partial/invalid user-typed
URLs and will trigger Next.js remotePatterns validation errors; update the
render logic to validate the URL before passing it to <Image> (e.g., add an
isValidRemoteImage(url) helper that parses the URL, ensures a valid
protocol/hostname and matches allowed hostnames like "cdn.discordapp.com"), and
if invalid render a safe fallback (placeholder image or null) or wrap the Image
render in an ErrorBoundary component to prevent the editor from crashing; locate
validation around the variables thumbnailUrl, imageUrl, footerIconUrl and the
JSX blocks that render them to apply this change.
---
Duplicate comments:
In `@tests/modules/actions/sendDm.test.js`:
- Around line 169-173: The test uses a non-production scope string; update the
assertion to use a runtime-accurate scope like 'levelUpDm:5' so it matches the
real key shape used by level-up actions: modify the second expect call that
currently checks checkDmRateLimit('g1','u1','levelUpDm') to instead check
checkDmRateLimit('g1','u1','levelUpDm:5') while leaving the
recordDmSend('g1','u1','default') and the first assertion unchanged to ensure
scopes are tracked independently (functions involved: recordDmSend,
checkDmRateLimit).
In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 730-780: Replace the raw textarea inputs used for level-up DM
templates with the shared markdown editor component (the one exported from
web/src/components/ui/discord-markdown-editor.tsx) for the default template
(currently using levelUpDmDefaultDraft, isValidLevelUpDmTemplate,
updateDraftConfig, buildLevelUpDmConfig) and for each override block (the other
levelUpDm* drafts between the other mentioned ranges); pass
LEVEL_UP_DM_TEMPLATE_VARS as the variable/picker list and provide the same
preview context by using renderLevelUpDmPreview for live rendering, preserve
disabled={saving}, rows/maxLength behavior, and keep the
onBlur/updateDraftConfig flow so validation via isValidLevelUpDmTemplate and
buildLevelUpDmConfig still runs when editing finishes.
🪄 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: c2e09381-3d16-4f52-b08c-3039cd2cc4db
📒 Files selected for processing (9)
tests/commands.test.jstests/modules/actions/sendDm.test.jstests/modules/cli-process.test.jsweb/package.jsonweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/ui/discord-markdown-editor.tsxweb/src/components/ui/embed-builder.tsxweb/src/lib/discord-markdown.tsweb/tests/components/dashboard/config-editor-autosave.test.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Agent
- GitHub Check: Greptile Review
- GitHub Check: E2E Tests (1/2)
- GitHub Check: E2E Tests (2/2)
- GitHub Check: Test
🧰 Additional context used
📓 Path-based instructions (8)
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
tests/commands.test.jstests/modules/actions/sendDm.test.jstests/modules/cli-process.test.js
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes
Always include semicolons at the end of statements
Use 2-space indentation (spaces, not tabs)
Always include trailing commas in multi-line arrays, objects, and function parameters
Maintain a maximum line width of 100 characters
Files:
tests/commands.test.jstests/modules/actions/sendDm.test.jstests/modules/cli-process.test.jsweb/src/lib/discord-markdown.tsweb/src/components/ui/embed-builder.tsxweb/src/components/ui/discord-markdown-editor.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
tests/**/*.test.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
tests/**/*.test.js: Write bot tests using Vitest 4 with thenodeenvironment, matching thesrc/structure in thetests/directory
Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead
Files:
tests/commands.test.jstests/modules/actions/sendDm.test.jstests/modules/cli-process.test.js
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*
Files:
tests/commands.test.jstests/modules/actions/sendDm.test.jstests/modules/cli-process.test.jsweb/src/lib/discord-markdown.tsweb/src/components/ui/embed-builder.tsxweb/src/components/ui/discord-markdown-editor.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
**/*.{js,ts}
📄 CodeRabbit inference engine (AGENTS.md)
Use parameterized SQL only; do not use string concatenation for SQL queries
Files:
tests/commands.test.jstests/modules/actions/sendDm.test.jstests/modules/cli-process.test.jsweb/src/lib/discord-markdown.ts
**/*.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:
web/package.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/lib/discord-markdown.tsweb/src/components/ui/embed-builder.tsxweb/src/components/ui/discord-markdown-editor.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
web/tests/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with thejsdomenvironment and React Testing Library, matching theweb/src/structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
🧠 Learnings (11)
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory
Applied to files:
tests/commands.test.jsweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Run the narrowest checks that actually prove the change. Use repo-level commands when the blast radius is real: pnpm mono:lint, pnpm mono:test, pnpm mono:typecheck, pnpm mono:build, pnpm mono:test:coverage.
Applied to files:
web/package.json
📚 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/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to {web/src/lib/page-titles.ts,web/src/**/+page.ts,web/src/**/+page.svelte} : New dashboard routes need title wiring in web/src/lib/page-titles.ts: use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation.
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-10T23:29:51.063Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:29:51.063Z
Learning: Config editor save contract: maintain global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/app/dashboard/**/*.tsx : For dashboard routes, add a matcher entry to `dashboardTitleMatchers` in `web/src/lib/page-titles.ts`: use exact equality for leaf routes (`pathname === '/dashboard/my-route'`) and subtree checks (`pathname.startsWith('/dashboard/my-route/')`); export `metadata` using `createPageMetadata(title)` for SSR entry points
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to src/**/*.{js,ts} : Community features should be gated behind config.<feature>.enabled. Moderation commands are the exception.
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to 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:
web/tests/components/dashboard/config-editor-autosave.test.tsx
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/ui/discord-markdown-editor.tsx
[warning] 317-317: Prefer String#replaceAll() over String#replace().
[warning] 317-317: Prefer String#replaceAll() over String#replace().
[warning] 317-317: Prefer String#replaceAll() over String#replace().
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
[warning] 73-73: Prefer String#replaceAll() over String#replace().
[failure] 896-896: Refactor this code to not nest functions more than 4 levels deep.
[failure] 857-857: Refactor this code to not nest functions more than 4 levels deep.
🔇 Additional comments (3)
tests/commands.test.js (1)
25-25: Explicit hook timeout is a good stability guard.Setting
beforeAll(..., 30_000)here is a solid change for slower module initialization paths and keeps test behavior deterministic across environments.tests/modules/cli-process.test.js (1)
653-653: Good synchronization fix before advancing fake timers.Using
await Promise.resolve();at Line 653 is a solid way to flush microtasks before
advanceTimersByTimeAsync, which helps keep this timeout test deterministic.web/src/components/ui/embed-builder.tsx (1)
257-275: Offset-based keys inrenderVariablePrevieware a solid improvement.This avoids index-only key churn and keeps token/text nodes stable across edits.
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/routes/members.js`:
- Around line 329-331: The code constructs memberList via
Array.from(discordPage.values()) then re-creates that same array to compute
lastMember; reuse memberList instead to avoid duplicate conversion. Replace the
second Array.from call with retrieving the last element from memberList (e.g.,
memberList[memberList.length - 1]) and leave paginationCursor = lastMember ?
lastMember.id : null; reference: memberList, discordPage, lastMember,
paginationCursor.
In `@web/src/components/dashboard/config-sections/EngagementSection.tsx`:
- Around line 42-45: The current key for each badge uses mutable value-based
fields (`badge.days` and `badge.label`) which causes React to remount rows on
edit and allows duplicate keys; update the rendering in EngagementSection where
the badge row is created (the element using key={`${badge.days ??
0}-${badge.label ?? 'badge'}`}) to use a stable identifier instead—either use
the array index as the key (acceptable because badges are not reorderable) or
generate and store a unique id on badge creation (e.g., add id to the badge
object and use that id as the key) so that editing `days`/`label` does not
change the key and inputs retain focus.
🪄 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: 9c87abe1-35b7-47f8-a3b6-0db522262111
📒 Files selected for processing (5)
src/api/routes/guilds.jssrc/api/routes/members.jssrc/api/routes/tickets.jsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{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:
web/src/components/dashboard/config-sections/EngagementSection.tsxsrc/api/routes/guilds.jssrc/api/routes/tickets.jssrc/api/routes/members.jsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
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-sections/EngagementSection.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*
Files:
web/src/components/dashboard/config-sections/EngagementSection.tsxsrc/api/routes/guilds.jssrc/api/routes/tickets.jssrc/api/routes/members.jsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
src/api/routes/guilds.jssrc/api/routes/tickets.jssrc/api/routes/members.js
src/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/**/*.js: Never useconsole.*methods; use the Winston logger instead viaimport logger from '../logger.js'(adjust path as needed), then calllogger.info(),logger.warn(),logger.error(), orlogger.debug()
Always usesafeReply(),safeSend(), orsafeEditReply()instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully
Files:
src/api/routes/guilds.jssrc/api/routes/tickets.jssrc/api/routes/members.js
src/api/routes/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Create API route handlers in
src/api/routes/and mount them insrc/api/server.js; add auth middleware if the endpoint requires authentication
Files:
src/api/routes/guilds.jssrc/api/routes/tickets.jssrc/api/routes/members.js
src/**/*.{js,ts}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,ts}: Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Community features should be gated behind config..enabled. Moderation commands are the exception.
Files:
src/api/routes/guilds.jssrc/api/routes/tickets.jssrc/api/routes/members.js
**/*.{js,ts}
📄 CodeRabbit inference engine (AGENTS.md)
Use parameterized SQL only; do not use string concatenation for SQL queries
Files:
src/api/routes/guilds.jssrc/api/routes/tickets.jssrc/api/routes/members.js
🧠 Learnings (5)
📚 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/dashboard/config-sections/EngagementSection.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to src/api/routes/*.js : Add adaptDeleteGuildIdParam in API route handlers to enforce guild moderation checks on DELETE requests without losing record id
Applied to files:
src/api/routes/guilds.jssrc/api/routes/members.js
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to src/api/ws/logStream.js : Implement backward-compatible WebSocket ticket parsing to accept both legacy 3-part and new 4-part tickets for maintaining existing log stream behavior
Applied to files:
src/api/routes/tickets.js
📚 Learning: 2026-03-10T23:29:51.063Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:29:51.063Z
Learning: Applies to src/api/routes/*.js : Apply requireGuildModerator authorization check to DELETE endpoints that modify guild-scoped resources
Applied to files:
src/api/routes/members.js
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to src/**/*.{js,ts} : Community features should be gated behind config.<feature>.enabled. Moderation commands are the exception.
Applied to files:
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
🪛 GitHub Check: CodeQL
src/api/routes/tickets.js
[failure] 340-340: Missing rate limiting
This route handler performs authorization, but is not rate-limited.
src/api/routes/members.js
[failure] 292-292: Missing rate limiting
This route handler performs authorization, but is not rate-limited.
[failure] 294-473: Missing rate limiting
This route handler performs a database access, but is not rate-limited.
This route handler performs a database access, but is not rate-limited.
This route handler performs a database access, but is not rate-limited.
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
[warning] 73-73: Prefer String#replaceAll() over String#replace().
[failure] 902-902: Refactor this code to not nest functions more than 4 levels deep.
[failure] 863-863: Refactor this code to not nest functions more than 4 levels deep.
🔇 Additional comments (8)
src/api/routes/tickets.js (1)
337-394: Rate limiting and parameterized filtering look correct here.This route now clearly applies
ticketRateLimitand keeps SQL inputs parameterized while supporting optional filters. No issues in this changed segment.src/api/routes/guilds.js (1)
496-496: LGTM—Formatting improvement.The indentation adjustment aligns the code properly within the callback block and follows the 2-space indentation guideline.
src/api/routes/members.js (1)
289-474: LGTM — Clean reformatting with proper middleware chain.The route handler reformatting improves readability while maintaining all functionality. The middleware chain correctly applies rate limiting (
membersRateLimit), authorization (requireGuildModerator), and validation (validateGuild) before the handler executes.Note: The CodeQL warnings flagged in static analysis are false positives — rate limiting is properly applied at line 291.
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx (5)
73-75: ConsiderreplaceAllfor explicit intent (nitpick).Static analysis suggests using
String#replaceAll()instead ofString#replace()with a global regex. Both behave identically here, butreplaceAllmakes the global replacement intent more explicit.
856-888: Extract level-change handler to reduce nesting (flagged by static analysis).SonarCloud flags this callback for exceeding 4 levels of nesting. Consider extracting the level-change logic into a named helper function for improved readability and testability.
895-911: Extract removal handler to reduce nesting (flagged by static analysis).Similar to the level-input handler, this callback also exceeds the 4-level nesting threshold. Extracting this would maintain consistency with any level-input refactor.
156-167: Memoization correctly stabilizes override row identity.The
useMemoforlevelUpDmMessageswith[levelUpDm?.messages]dependency ensures the array reference only changes when the underlying messages change, preventing unnecessary re-renders and draft state resets. Combined with the stable keyxp-level-dm-${entry.originalIndex}at line 841, this properly addresses the focus-loss issue for override textareas.
78-91: Level-cap and template validation properly aligned with backend schema.
getNextLevelUpDmOverrideLevelcorrectly caps at 1000 and returnsundefinedwhen exhausted, which disables the "Add Override" button (line 827).isValidLevelUpDmTemplateusestrim().length > 0, matching the backend'spattern: '\\S'constraint. The onBlur handlers at lines 742-745 and 924-928 prevent blank templates from being persisted, avoiding autosave of schema-invalid states.Also applies to: 93-95
….tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: MohsinCoding <91380732+MohsinCoding@users.noreply.github.com>
|
@greptile review |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 33 out of 34 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
web/src/components/ui/embed-builder.tsx:504
- The fallback React key for preview fields is derived from mutable field contents (
name,value,inline). Iffield.idis missing (e.g. legacy configs), multiple empty fields will produce duplicate keys and edits can cause remounts. Prefer a stable unique fallback (e.g. index for preview-only rendering) or ensure IDs are always present before rendering.
{config.fields.length > 0 && (
<div className="grid grid-cols-3 gap-2" data-testid="embed-preview-fields">
{config.fields.map((field) => {
const renderedName = renderVariablePreview(field.name);
const renderedValue = renderDiscordMarkdown(field.value);
return (
<div
key={field.id ?? `field-${field.name}-${field.value}-${field.inline}`}
className={cn(field.inline ? 'col-span-1' : 'col-span-3')}
>
<div className="text-xs font-semibold text-white">
{renderedName.length > 0 ? renderedName : '\u200b'}
</div>
<div className="text-xs">
{renderedValue.length > 0 ? renderedValue : '\u200b'}
</div>
</div>
);
})}
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 171-185: The effect is replacing the entire
levelUpDmOverrideDrafts map on every change, which clobbers in-progress edits
and leaves the first render empty; change initialization of
levelUpDmOverrideDrafts to use a lazy initializer based on
Object.fromEntries(levelUpDmOverrideDraftEntries) so existing entries are
present on first paint, and update the effect that currently calls
setLevelUpDmOverrideDrafts(Object.fromEntries(levelUpDmOverrideDraftEntries)) to
instead merge diffs: call setLevelUpDmOverrideDrafts(prev => { create a new map
starting from prev, add/update keys from levelUpDmOverrideDraftEntries, and
remove keys that no longer exist in levelUpDmOverrideDraftEntries }) so edits in
other rows are preserved while syncing persisted changes; reference state/setter
names levelUpDmOverrideDrafts and setLevelUpDmOverrideDrafts and the dependency
levelUpDmOverrideDraftEntries.
- Around line 29-40: The dashboard duplicates template tokens and preview
rendering (LEVEL_UP_DM_TEMPLATE_VARS, LEVEL_UP_DM_PREVIEW_CONTEXT, and
renderLevelUpDmPreview) instead of reusing the runtime template builder; replace
the dashboard's manual context/token logic by importing and calling
buildTemplateContext from src/utils/templateEngine.js to construct the preview
context and use the shared rendering behavior so variables (e.g., xpToNext) stay
in sync with runtime, or alternatively add a short code comment documenting why
the dashboard needs separate logic if you intentionally keep it.
In `@web/tests/components/dashboard/community-settings-section.test.tsx`:
- Around line 1-21: The current test only exercises the helper
buildLevelUpDmConfig; add a new React Testing Library-based test that mounts the
CommunitySettingsSection component to cover the editor UI behaviors: render the
DM editor, assert blur-only saves (edit a field and blur, then verify prop/state
or saved output), test adding and removing override messages via the UI controls
(click "add override", populate level/message, then remove and assert list
updates), verify duplicate-level rejection by attempting to add an override with
an existing level and asserting the validation error appears, and check preview
rendering updates when editing defaultMessage or an override; keep the original
buildLevelUpDmConfig unit test small in a util test file and use Vitest with the
jsdom environment and RTL queries (render, fireEvent/userEvent, screen) to drive
interactions against CommunitySettingsSection.
🪄 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: f721a716-a23f-490a-80f3-11d1f6f9d244
📒 Files selected for processing (4)
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/ui/embed-builder.tsxweb/tests/components/dashboard/community-settings-section.test.tsxweb/tests/components/ui/embed-builder.test.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: CI
- GitHub Check: Agent
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{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:
web/tests/components/ui/embed-builder.test.tsxweb/tests/components/dashboard/community-settings-section.test.tsxweb/src/components/ui/embed-builder.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
web/tests/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with thejsdomenvironment and React Testing Library, matching theweb/src/structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Files:
web/tests/components/ui/embed-builder.test.tsxweb/tests/components/dashboard/community-settings-section.test.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*
Files:
web/tests/components/ui/embed-builder.test.tsxweb/tests/components/dashboard/community-settings-section.test.tsxweb/src/components/ui/embed-builder.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
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/ui/embed-builder.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
🧠 Learnings (6)
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure
Applied to files:
web/tests/components/dashboard/community-settings-section.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Applied to files:
web/tests/components/dashboard/community-settings-section.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead
Applied to files:
web/tests/components/dashboard/community-settings-section.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory
Applied to files:
web/tests/components/dashboard/community-settings-section.test.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/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-sections/CommunitySettingsSection.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to src/**/*.{js,ts} : Community features should be gated behind config.<feature>.enabled. Moderation commands are the exception.
Applied to files:
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/ui/embed-builder.tsx
[warning] 353-365: Mark the props of the component as read-only.
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
[warning] 73-73: Prefer String#replaceAll() over String#replace().
[failure] 902-902: Refactor this code to not nest functions more than 4 levels deep.
[failure] 863-863: Refactor this code to not nest functions more than 4 levels deep.
🔇 Additional comments (4)
web/src/components/ui/embed-builder.tsx (3)
487-494: Fallback keys are still content-derived and unstable whenfield.idis missing.Line 493 and Line 851 still use
${field.name}-${field.value}-${field.inline}as fallback key material. Those values change while typing, so React can remount rows and cause focus/cursor jumps for id-less legacy payloads.💡 Proposed patch
- {config.fields.map((field) => { + {config.fields.map((field, i) => { @@ - key={field.id ?? `field-${field.name}-${field.value}-${field.inline}`} + key={field.id ?? `field-preview-${i}`} @@ - {value.fields.map((field, i) => ( + {value.fields.map((field, i) => ( <div - key={field.id ?? `field-editor-${field.name}-${field.value}-${field.inline}`} + key={field.id ?? `field-editor-${i}`} className="space-y-2 rounded-md border border-border bg-muted/30 p-3" >Also applies to: 849-852
256-274: Stable segment-based keys in variable preview look correct.Using
segmentStartfor keys avoids index drift and keeps rendered token identity stable during edits.
353-367:PreviewImageextraction is a good simplification.Centralizing preview image rendering in one local component improves maintainability and keeps sizing explicit.
Also applies to: 468-474, 511-517, 528-534
web/tests/components/ui/embed-builder.test.tsx (1)
500-522: Good regression coverage for custom preview images.This test cleanly validates that all three custom image slots render with the expected
srcvalues.
|
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (5)
web/tests/components/dashboard/config-editor-autosave.test.tsx (1)
162-186: 🧹 Nitpick | 🔵 TrivialConsider documenting or reducing high test timeouts.
Several tests use 15000-30000ms timeouts. If these are needed for CI, a brief comment explaining the requirement would help future maintainers. If tests consistently complete faster, consider reducing to improve feedback speed.
💡 Suggested comment
it('renders category navigation and AI features', async () => { + // 30s timeout: CI environments may be slower with dynamic imports and fetch mocks const fetchMock = vi.fn().mockResolvedValue({🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/tests/components/dashboard/config-editor-autosave.test.tsx` around lines 162 - 186, This test uses an explicit 30000ms timeout at the end of the "renders category navigation and AI features" test; either reduce the timeout to a lower value that still passes locally/CI (e.g., 5000-10000ms) or add a brief inline comment above the test explaining why the extended timeout is required for CI stability; reference the test block that renders ConfigLayoutShell and AiAutomationCategory so reviewers can locate and update the timeout or add the justification comment.web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx (4)
917-933: 🧹 Nitpick | 🔵 TrivialStatic analysis flags deep nesting in removal handler.
Similar to the level-change handler, the removal callback at line 924 exceeds the 4-level nesting threshold. Extracting this would maintain consistency.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx` around lines 917 - 933, The removal onClick handler is too deeply nested—extract the callback into a named function to reduce nesting; create a helper like handleRemoveLevelUpMessage that calls updateDraftConfig and uses buildLevelUpDmConfig to set xp.levelUpDm.messages to the filtered array excluding entry.originalIndex, then pass handleRemoveLevelUpMessage to the Button's onClick and keep disabled={saving}; reference updateDraftConfig, buildLevelUpDmConfig, xp.levelUpDm, and entry.originalIndex when implementing.
755-797: 🧹 Nitpick | 🔵 TrivialVariable picker from issue requirements not implemented.
Issue
#361explicitly requests a "variable picker" UI that inserts{{variable}}tokens into the focused textarea. The current implementation only displays a comma-separated reference list (LEVEL_UP_DM_TEMPLATE_VARS.join(', ')), requiring admins to type tokens manually.Consider adding insertable chips or a dropdown that writes the selected token into the textarea at cursor position, similar to the
VariablePalettecomponent used inembed-builder.tsx.Do you want me to sketch a shared token-insertion helper or open an issue to track this enhancement?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx` around lines 755 - 797, The UI currently only shows LEVEL_UP_DM_TEMPLATE_VARS as a comma-separated list instead of providing a variable picker that inserts tokens into the textarea; replace or augment that list with a VariablePalette-like component (as used in embed-builder.tsx) that renders clickable chips/dropdown for each variable and, when clicked, inserts the selected `{{variable}}` at the current cursor position of levelUpDmDefaultDraft (the textarea controlled by levelUpDmDefaultDraft state), preserving cursor/focus, and keeping validation via isValidLevelUpDmTemplate before calling updateDraftConfig and buildLevelUpDmConfig on blur/save; consider factoring insertion logic into a shared helper so other places can reuse it.
72-76: 🧹 Nitpick | 🔵 TrivialConsider using
replaceAllfor consistency with static analysis.Static analysis suggests
replaceAlloverreplacewith a global regex. While functionally equivalent,replaceAllis more explicit.♻️ Optional fix
function renderLevelUpDmPreview(template: string, context = LEVEL_UP_DM_PREVIEW_CONTEXT) { - return template.replace(/\{\{(\w+)\}\}/g, (match, key: string) => { + return template.replaceAll(/\{\{(\w+)\}\}/g, (match, key: string) => { return Object.hasOwn(context, key) ? context[key] : match; }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx` around lines 72 - 76, In renderLevelUpDmPreview, replace the use of template.replace(/\{\{(\w+)\}\}/g, ...) with template.replaceAll(/\{\{(\w+)\}\}/g, ...) to satisfy the static analysis rule and make the global-replacement intent explicit; keep the same replacer function and retain LEVEL_UP_DM_PREVIEW_CONTEXT usage for fallback lookups (no logic changes required).
876-910: 🧹 Nitpick | 🔵 TrivialStatic analysis flags deep nesting in level-change handler.
The inline
onChangecallback exceeds 4 levels of nesting. Consider extracting to a named handler for improved readability and testability.♻️ Suggested extraction
// Define near other helpers or at component scope const handleLevelChange = ( targetIndex: number, value: number | undefined, ) => { if (value === undefined) return; const normalizedValue = Math.floor(value); updateDraftConfig((prev) => { const messages = [...(prev.xp?.levelUpDm?.messages ?? [])]; const hasDuplicateLevel = messages.some( (candidate, index) => index !== targetIndex && candidate?.level === normalizedValue, ); if (hasDuplicateLevel) return prev; messages[targetIndex] = { ...messages[targetIndex], level: normalizedValue }; return { ...prev, xp: { ...prev.xp, levelUpDm: buildLevelUpDmConfig(prev.xp?.levelUpDm, { messages }), }, }; }); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx` around lines 876 - 910, Extract the inline onChange logic into a named handler (e.g., handleLevelChange) to reduce nesting and improve testability: create a function that accepts targetIndex (entry.originalIndex) and the raw input string or parsed number, call parseNumberInput inside the input onChange to get a number (or forward the raw string and parse inside the handler), return early if undefined, normalize with Math.floor, then call updateDraftConfig exactly as before (copying messages from prev.xp?.levelUpDm?.messages, checking duplicates with messages.some, early-returning prev on duplicate, updating messages[targetIndex], and building the new xp.levelUpDm via buildLevelUpDmConfig). Replace the inline onChange with a call to handleLevelChange(targetIndex, /* value or event.target.value */) and keep disabled={saving} and className unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/src/components/ui/embed-builder.tsx`:
- Around line 353-367: The prop object for PreviewImage should be marked
read-only to satisfy TypeScript immutability rules; update the parameter type
for the PreviewImage function (the inline props type) to use readonly properties
(e.g., make each prop readonly or wrap the object in Readonly<...>) so src, alt,
width, height, and className are immutable in the function signature.
---
Duplicate comments:
In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 917-933: The removal onClick handler is too deeply nested—extract
the callback into a named function to reduce nesting; create a helper like
handleRemoveLevelUpMessage that calls updateDraftConfig and uses
buildLevelUpDmConfig to set xp.levelUpDm.messages to the filtered array
excluding entry.originalIndex, then pass handleRemoveLevelUpMessage to the
Button's onClick and keep disabled={saving}; reference updateDraftConfig,
buildLevelUpDmConfig, xp.levelUpDm, and entry.originalIndex when implementing.
- Around line 755-797: The UI currently only shows LEVEL_UP_DM_TEMPLATE_VARS as
a comma-separated list instead of providing a variable picker that inserts
tokens into the textarea; replace or augment that list with a
VariablePalette-like component (as used in embed-builder.tsx) that renders
clickable chips/dropdown for each variable and, when clicked, inserts the
selected `{{variable}}` at the current cursor position of levelUpDmDefaultDraft
(the textarea controlled by levelUpDmDefaultDraft state), preserving
cursor/focus, and keeping validation via isValidLevelUpDmTemplate before calling
updateDraftConfig and buildLevelUpDmConfig on blur/save; consider factoring
insertion logic into a shared helper so other places can reuse it.
- Around line 72-76: In renderLevelUpDmPreview, replace the use of
template.replace(/\{\{(\w+)\}\}/g, ...) with
template.replaceAll(/\{\{(\w+)\}\}/g, ...) to satisfy the static analysis rule
and make the global-replacement intent explicit; keep the same replacer function
and retain LEVEL_UP_DM_PREVIEW_CONTEXT usage for fallback lookups (no logic
changes required).
- Around line 876-910: Extract the inline onChange logic into a named handler
(e.g., handleLevelChange) to reduce nesting and improve testability: create a
function that accepts targetIndex (entry.originalIndex) and the raw input string
or parsed number, call parseNumberInput inside the input onChange to get a
number (or forward the raw string and parse inside the handler), return early if
undefined, normalize with Math.floor, then call updateDraftConfig exactly as
before (copying messages from prev.xp?.levelUpDm?.messages, checking duplicates
with messages.some, early-returning prev on duplicate, updating
messages[targetIndex], and building the new xp.levelUpDm via
buildLevelUpDmConfig). Replace the inline onChange with a call to
handleLevelChange(targetIndex, /* value or event.target.value */) and keep
disabled={saving} and className unchanged.
In `@web/tests/components/dashboard/config-editor-autosave.test.tsx`:
- Around line 162-186: This test uses an explicit 30000ms timeout at the end of
the "renders category navigation and AI features" test; either reduce the
timeout to a lower value that still passes locally/CI (e.g., 5000-10000ms) or
add a brief inline comment above the test explaining why the extended timeout is
required for CI stability; reference the test block that renders
ConfigLayoutShell and AiAutomationCategory so reviewers can locate and update
the timeout or add the justification comment.
🪄 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: f0805ce9-7978-495c-8f62-4066bdb966f6
📒 Files selected for processing (3)
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/ui/embed-builder.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Greptile Review
- GitHub Check: E2E Tests (2/2)
- GitHub Check: Test
- GitHub Check: E2E Tests (1/2)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{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:
web/src/components/ui/embed-builder.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsx
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/ui/embed-builder.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*
Files:
web/src/components/ui/embed-builder.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsx
web/tests/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with thejsdomenvironment and React Testing Library, matching theweb/src/structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
🧠 Learnings (10)
📚 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-sections/CommunitySettingsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to src/**/*.{js,ts} : Community features should be gated behind config.<feature>.enabled. Moderation commands are the exception.
Applied to files:
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'
Applied to files:
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-10T23:29:51.063Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:29:51.063Z
Learning: Config editor save contract: maintain global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to {web/src/lib/page-titles.ts,web/src/**/+page.ts,web/src/**/+page.svelte} : New dashboard routes need title wiring in web/src/lib/page-titles.ts: use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation.
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/app/dashboard/**/*.tsx : For dashboard routes, add a matcher entry to `dashboardTitleMatchers` in `web/src/lib/page-titles.ts`: use exact equality for leaf routes (`pathname === '/dashboard/my-route'`) and subtree checks (`pathname.startsWith('/dashboard/my-route/')`); export `metadata` using `createPageMetadata(title)` for SSR entry points
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to 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:
web/tests/components/dashboard/config-editor-autosave.test.tsx
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/ui/embed-builder.tsx
[warning] 353-365: Mark the props of the component as read-only.
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
[warning] 73-73: Prefer String#replaceAll() over String#replace().
[failure] 924-924: Refactor this code to not nest functions more than 4 levels deep.
[failure] 884-884: Refactor this code to not nest functions more than 4 levels deep.
🔇 Additional comments (12)
web/src/components/ui/embed-builder.tsx (2)
256-274: LGTM! Stable key generation using cumulative offset.Using a running
offsetcounter to generate unique keys (var-${segmentStart}-${varName}andtext-${segmentStart}) provides stable identity across variable tokenization shifts, preventing unnecessary remounts when template content changes.
487-493: LGTM! Stable key fallback for field preview.Using
field.id ?? \field-${index}`provides a deterministic non-content fallback while keepingfield.id` as the primary key, which addresses the previous focus-loss concern for older configs without ids.web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx (6)
3-3: LGTM! Required React hooks imported.The import of
useEffect,useMemo, anduseStateproperly supports the new local draft state management and memoization for Level-Up DMs.
58-70: LGTM! Dynamic preview context for per-level overrides.
buildLevelUpDmPreviewContextcorrectly deriveslevel,previousLevel, andnextLevelfrom the override's actual level, ensuring preview accuracy for each row. TheNumber.isFiniteguard and floor/max clamping handle edge cases appropriately.
78-91: LGTM! Bounded level generation with upper limit.
getNextLevelUpDmOverrideLevelcorrectly caps at 1000 (matching the backend schema constraint atsrc/api/utils/configValidation.js:347) and returnsundefinedwhen exhausted, enabling the UI to disable "Add Override" appropriately.
97-108: LGTM! CentralizedbuildLevelUpDmConfighelper.This helper consolidates defaults and merges updates, reducing duplication across handlers and ensuring consistent fallback behavior.
156-167: LGTM! Properly memoizedlevelUpDmMessages.Wrapping the mapping and sorting in
useMemowith[levelUpDm?.messages]as the dependency stabilizes the reference and prevents the downstream effect from resetting drafts on unrelated renders.
183-204: LGTM! Merge-based draft synchronization preserves in-progress edits.The effect correctly merges new entries and prunes removed keys rather than replacing the whole map, preventing clobbering of edits in other rows when one row persists.
web/tests/components/dashboard/config-editor-autosave.test.tsx (4)
1-10: LGTM! Updated mock setup with sharedmockPush.Extracting
mockPushas a module-levelvi.fn()and clearing it inbeforeEachenables proper router interaction testing across the suite.
86-147: LGTM! Comprehensive config fixtures for xp.levelUpDm testing.
minimalConfignow includes the fullxp.levelUpDmstructure, andminimalConfigWithoutLevelUpDmenables testing the newly-added object PATCH behavior. These fixtures align with the TypeScript types inweb/src/types/config.ts.
280-368: LGTM! Focus-retention regression test properly exercises key stability.The test now:
- Adds override rows and types into textareas
- Asserts textarea identity persists (
refreshedFirstOverrideTextarea === firstOverrideTextarea)- Verifies value retention after rerender
- Tests decimal level input normalization and preview update
This addresses the prior concern about not exercising the key-stability bug fix.
370-434: LGTM! Dotted patch assertion validates nested config diffing fix.The test correctly verifies that:
- No PATCH is sent before "Confirm Save"
- After confirm, patches use dotted paths like
xp.levelUpDm.enabled- Top-level
path === 'xp'never appearsThis aligns with
computePatchesbehavior inweb/src/lib/config-utils.ts.


Summary
xp.levelUpDmconfig with validation, defaults, runtime wiring, and dashboard supportWhat Changed
xp.levelUpDmto the config contract, defaults, allowlist-backed save path, and validationsendDmactions per crossed level fromxp.levelUpDmxpfile://URLsVerification
pnpm exec vitest run tests/api/utils/configValidation.test.js tests/modules/levelUpActions.test.jspnpm exec vitest run tests/utils/loadCommands.test.js tests/commands.test.jspnpm test -- web/tests/lib/config-utils.test.ts web/tests/components/dashboard/config-editor-autosave.test.tsxpnpm typecheckinwebpnpm exec biome checkon touched filesCloses #361