feat(xp): add dashboard editor for level-up actions#443
feat(xp): add dashboard editor for level-up actions#443MohsinCoding wants to merge 12 commits intomainfrom
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 structured XP action schemas and validator enhancements, normalizes legacy XP action payloads, extends embed rendering (fields, footer, image, timestamp, truncation warnings), integrates normalization into level-up action resolution, introduces a dashboard XP actions editor and types, and adds tests for validation and rendering. 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)
⚔️ Resolve merge conflicts
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/api/utils/configValidation.js | Adds rich XP action/embed schemas, anyOf validation, and integer enforcement — but levelActions is missing uniqueBy: 'level', allowing silent action loss on duplicate levels. |
| src/modules/actions/buildPayload.js | Adds rich embed support (fields, image, timestamp, footer with zero-width-space fallback). Footer empty-string and length edge cases are handled correctly. |
| src/modules/actions/normalizeAction.js | New normalization layer; correctly aliases message→template, thumbnailType→thumbnail token, footerText/footerIconUrl→footer object, and imageUrl→image. Idempotent and well-tested. |
| src/modules/levelUpActions.js | Integrates normalizeXpAction at both the defaultActions loop and getLevelUpDmAction path; no double-normalization issue since dmAction result is pushed directly without re-wrapping. |
| web/src/components/dashboard/xp-level-actions-editor.tsx | Well-structured editor with stable IDs, memoization, duplicate-level prevention, and correct controlled-input handling. Correctly syncs both message and template fields on change. |
Sequence Diagram
sequenceDiagram
participant Dashboard as XpLevelActionsEditor
participant API as Express API
participant Validator as configValidation.js
participant Runtime as levelUpActions.js
participant Normalizer as normalizeAction.js
participant Builder as buildPayload.js
participant Discord as Discord.js
Dashboard->>API: "POST /config { xp.levelActions, xp.defaultActions }"
API->>Validator: validateSingleValue(path, value)
Validator-->>API: errors[] or []
API-->>Dashboard: "400 errors | 200 OK"
Note over Runtime: User levels up
Runtime->>Normalizer: normalizeXpAction(action)
Normalizer-->>Runtime: "normalized { template, embed, ... }"
Runtime->>Builder: buildPayload(action, templateContext)
Builder->>Discord: EmbedBuilder.setColor / addFields / setFooter / setImage / setTimestamp
Builder-->>Runtime: "{ content, embeds }"
Runtime->>Discord: channel.send(payload) / member.send(payload)
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/api/utils/configValidation.js
Line: 101-110
Comment:
**Missing `uniqueBy: 'level'` on `levelActions` array**
`levelUpDm.messages` already carries `uniqueBy: 'level'` to prevent duplicate level entries. `levelActions` does not. If a caller POSTs two entries with the same level, `resolveActions` silently overwrites the first in `levelActionsMap`:
```js
for (const entry of config.levelActions ?? []) {
levelActionsMap.set(entry.level, entry.actions ?? []); // second entry stomps the first
}
```
All actions from the earlier entry are silently dropped — no error is returned and no warning is logged.
```suggestion
const XP_LEVEL_ACTION_ENTRY_SCHEMA = {
type: 'object',
required: ['level', 'actions'],
properties: {
id: { type: 'string', nullable: true },
level: { type: 'number', integer: true, min: 1, max: 1000 },
actions: {
type: 'array',
items: XP_ACTION_ITEM_SCHEMA,
},
},
uniqueBy: 'level',
};
```
Note: `uniqueBy` is already supported by `validateValue`; adding it here is purely a schema-level guard.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/api/utils/configValidation.js
Line: 82
Comment:
**`xpBonus` `amount` should enforce integer validation**
`min: 1` prevents zero-or-below values but allows floats like `1.5`. The dashboard input uses `parseInt` to coerce values, but there is no server-side integer guard, so a direct API POST with `amount: 1.5` passes validation. The parallel `level` fields in this PR both specify `integer: true`.
```suggestion
amount: { type: 'number', min: 1, integer: true, nullable: true },
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (9): Last reviewed commit: "fix(web): harden xp level actions editor" | Re-trigger Greptile
There was a problem hiding this comment.
Pull request overview
Adds a rich dashboard editor for XP level-up action pipelines (default + per-level) and updates the bot runtime to accept/normalize the richer action payload shape, including richer embed support.
Changes:
- Adds
XpLevelActionsEditorUI with action creation, editing (markdown + embed builder), and reordering for default/per-level XP actions. - Introduces runtime normalization (
normalizeXpAction) to bridge legacy config shapes to the richer dashboard shape (message/template aliases, nickSuffix support, embed-builder aliases). - Extends payload generation for announcements/DMs to support embed fields, footer icons, images, and timestamps; adds regression tests.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| web/src/types/config.ts | Expands XP action/embed config types to represent the richer dashboard shape. |
| web/src/components/dashboard/xp-level-actions-editor.tsx | New dashboard editor for default + per-level XP action pipelines. |
| web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx | Mounts the new editor in the XP settings card. |
| src/modules/actions/normalizeAction.js | Adds runtime normalization for XP actions (legacy ↔ dashboard payload bridging). |
| src/modules/levelUpActions.js | Normalizes resolved actions before executing them (and normalizes legacy levelUpDm). |
| src/modules/actions/buildPayload.js | Enhances embed payload building (fields/footer icon/image/timestamp). |
| src/api/utils/configValidation.js | Extends XP action validation (action type enum + richer action properties). |
| tests/modules/actions/normalizeAction.test.js | Tests XP action normalization behavior. |
| tests/modules/actions/buildPayload.test.js | Tests rich embed payload building behavior. |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/api/utils/configValidation.js`:
- Around line 313-343: Create a single shared schema constant (e.g.,
XP_ACTION_ITEM_SCHEMA) that contains the action item type enum and all its
properties (type enum:
'grantRole','removeRole','sendDm','announce','xpBonus','addReaction','nickPrefix','nickSuffix','webhook'
plus roleId, message, template, format, channelMode, channelId, emoji, amount,
prefix, suffix, url, payload, embed with the same nullable/openProperties
settings) and replace the duplicated inline definitions currently used for
levelActions[].actions[] and defaultActions[] with references to that constant
(or spread it into those schema locations) so both places validate against the
single source of truth.
In `@src/modules/actions/buildPayload.js`:
- Around line 34-43: The embed.fields array can exceed Discord's 25-field limit
causing addFields() to fail; before calling embed.addFields(...) in
buildPayload.js, limit embedConfig.fields to the first 25 entries (e.g., take
embedConfig.fields.slice(0, 25)) and then map/render only those items; update
the call that references embedConfig.fields and embed.addFields to use the
capped array so only up to 25 rendered fields are passed to addFields().
In `@src/modules/actions/normalizeAction.js`:
- Around line 6-65: The normalizeEmbed function is doing multiple migrations and
should be split into focused helpers; extract and implement small functions like
normalizeThumbnail(embed) (handles embed.thumbnail, embed.thumbnailType values
'user_avatar'/'server_icon' and embed.thumbnailUrl), normalizeFooter(embed)
(handles string footer, object footer with text/iconURL, and legacy
footerText/footerIconUrl), normalizeFields(embed) (maps/filter embed.fields
preserving name/value/inline logic), and normalizeImageTimestamp(embed,
normalized) (sets normalized.image from imageUrl when missing and sets
normalized.timestamp from showTimestamp), then update normalizeEmbed to call
these helpers while keeping existing behavior and returning the same normalized
object; refer to normalizeEmbed, embed.thumbnailType, embed.thumbnailUrl,
embed.footerText, embed.footerIconUrl, and the current fields mapping logic to
ensure parity.
In `@web/src/components/dashboard/xp-level-actions-editor.tsx`:
- Around line 502-511: Make the ActionGroup component props immutable: update
the ActionGroupProps interface so every property is readonly (e.g., readonly
title: string; readonly description: string; readonly guildId: string; readonly
saving: boolean; readonly onChange: (actions: XpLevelAction[]) => void;) and
change actions to a readonly array type (readonly actions:
ReadonlyArray<XpLevelAction> or readonly actions: readonly XpLevelAction[]);
optionally annotate the component param as Readonly<ActionGroupProps> in the
ActionGroup(...) signature to enforce immutability at the call site.
- Around line 551-564: The list uses actionIndex in the React key which breaks
identity when reordering; change the model to give each action a stable id
(update the XpLevelAction type to include an optional id and ensure creation
paths assign a unique id, e.g., via UUID/nanoid), migrate/assign ids for
existing actions on load, and update the render in the actions.map to use
action.id (falling back to a stable derived value only if necessary) instead of
`${action.type}-${actionIndex}`; adjust any helpers that create actions (where
updateAction, removeAction, moveAction are used) to preserve and operate on the
stable id.
- Around line 569-581: The props for XpLevelActionsEditor should be immutable:
mark XpLevelActionsEditorProps or the function parameter as readonly so callers
can't accidentally mutate props. Update the interface or change the function
signature to accept Readonly<XpLevelActionsEditorProps> (or prefix each prop
with readonly) and ensure the updateDraftConfig type remains correct; adjust
references to XpLevelActionsEditorProps and the XpLevelActionsEditor function
signature accordingly.
- Around line 641-643: The map over levelEntries in the XpLevelActionsEditor
component uses the array index (entryIndex) as the React key which breaks
reconciliation when entries are reordered; update the XpLevelActionEntry type to
include an optional stable id field (e.g., id: string), ensure the code that
creates/adds new entries (the addLevel/insert handlers) generates a stable
unique id for each new entry, and change the mapping key to use entry.id
(falling back to a generated stable id only if necessary) instead of entryIndex
so keys remain stable across reorders.
- Around line 231-253: The props for ActionCard are mutable; mark them read-only
to prevent accidental mutation by either making the interface properties
readonly (add readonly to each property in ActionCardProps) or change the
component signature to accept a Readonly wrapper (use function ActionCard(props:
Readonly<ActionCardProps>) or function ActionCard({ ... }:
Readonly<ActionCardProps>)); update references to ActionCardProps and the
ActionCard function to use the chosen read-only approach so TypeScript enforces
immutability.
- Around line 719-733: Extract the nested delete logic from the Button onClick
into a top-level handler (e.g., handleDeleteLevelAction) that takes the
entryIndex and calls updateDraftConfig to remove the item from xp.levelActions,
then replace the inline arrow in the Button onClick with a call to that handler;
do the same extraction for the two reorder handlers currently inlined (the
move-up/move-down logic that manipulates prev.xp.levelActions) into named
functions (e.g., handleMoveLevelActionUp and handleMoveLevelActionDown) and
invoke those from the Button onClick props to reduce nesting depth while
preserving use of updateDraftConfig and the xp.levelActions update behavior.
- Around line 161-206: The toBuilderConfig function has high cognitive
complexity and nested ternaries; refactor by extracting the footer resolution
and image resolution into small helper functions (e.g., resolveFooter(embed): {
text: string; iconUrl: string } and resolveImageUrl(embed): string) and move the
footerText/footerIconUrl/imageUrl logic into those helpers, then replace the
nested ternaries in toBuilderConfig with calls to resolveFooter and
resolveImageUrl; also simplify thumbnailType and fields handling with clear
conditional branches (or small helpers like resolveThumbnailType) to reduce
nesting and keep toBuilderConfig focused on assembling the final EmbedConfig.
🪄 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: 28681e4c-00b7-49f6-956b-f7e8e4f0daec
📒 Files selected for processing (9)
src/api/utils/configValidation.jssrc/modules/actions/buildPayload.jssrc/modules/actions/normalizeAction.jssrc/modules/levelUpActions.jstests/modules/actions/buildPayload.test.jstests/modules/actions/normalizeAction.test.jsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/xp-level-actions-editor.tsxweb/src/types/config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Agent
- 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 (9)
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
tests/modules/actions/buildPayload.test.jstests/modules/actions/normalizeAction.test.jssrc/modules/levelUpActions.jssrc/modules/actions/buildPayload.jssrc/modules/actions/normalizeAction.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/buildPayload.test.jsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxtests/modules/actions/normalizeAction.test.jssrc/modules/levelUpActions.jssrc/modules/actions/buildPayload.jssrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.tsxsrc/api/utils/configValidation.jsweb/src/types/config.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/buildPayload.test.jstests/modules/actions/normalizeAction.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/buildPayload.test.jsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxtests/modules/actions/normalizeAction.test.jssrc/modules/levelUpActions.jssrc/modules/actions/buildPayload.jssrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.tsxsrc/api/utils/configValidation.jsweb/src/types/config.ts
**/*.{js,ts}
📄 CodeRabbit inference engine (AGENTS.md)
Use parameterized SQL only; do not use string concatenation for SQL queries
Files:
tests/modules/actions/buildPayload.test.jstests/modules/actions/normalizeAction.test.jssrc/modules/levelUpActions.jssrc/modules/actions/buildPayload.jssrc/modules/actions/normalizeAction.jssrc/api/utils/configValidation.jsweb/src/types/config.ts
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/components/dashboard/xp-level-actions-editor.tsxweb/src/types/config.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/modules/levelUpActions.jssrc/modules/actions/buildPayload.jssrc/modules/actions/normalizeAction.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/buildPayload.jssrc/modules/actions/normalizeAction.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/buildPayload.jssrc/modules/actions/normalizeAction.jssrc/api/utils/configValidation.js
🧠 Learnings (5)
📚 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:
tests/modules/actions/buildPayload.test.jstests/modules/actions/normalizeAction.test.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 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/modules/actions/buildPayload.test.jstests/modules/actions/normalizeAction.test.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/src/components/dashboard/xp-level-actions-editor.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
📚 Learning: 2026-03-11T05:32:46.325Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T05:32:46.325Z
Learning: Applies to web/src/app/**/*.{ts,tsx} : Apply static metadata to server-rendered dashboard entry pages and use title template format for root app metadata
Applied to files:
web/src/components/dashboard/xp-level-actions-editor.tsx
🪛 GitHub Check: SonarCloud Code Analysis
src/modules/actions/normalizeAction.js
[failure] 6-6: Refactor this function to reduce its Cognitive Complexity from 25 to the 15 allowed.
web/src/components/dashboard/xp-level-actions-editor.tsx
[warning] 186-190: Extract this nested ternary operation into an independent statement.
[warning] 576-581: Mark the props of the component as read-only.
[warning] 511-511: Mark the props of the component as read-only.
[warning] 243-253: Mark the props of the component as read-only.
[warning] 194-196: Extract this nested ternary operation into an independent statement.
[warning] 643-643: Do not use Array index in keys
[warning] 200-202: Extract this nested ternary operation into an independent statement.
[failure] 729-729: Refactor this code to not nest functions more than 4 levels deep.
[warning] 188-190: Extract this nested ternary operation into an independent statement.
[failure] 161-161: Refactor this function to reduce its Cognitive Complexity from 19 to the 15 allowed.
🔇 Additional comments (7)
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx (1)
673-678: Clean integration into the existing settings-card flow.This keeps the new editor on the same
draftConfig/saving/updateDraftConfigpath as the rest of the community dashboard.Based on learnings: Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks).
src/modules/levelUpActions.js (1)
61-75: Good normalization boundary.Resolving every level-up action through
normalizeXpAction()before dispatch keeps the legacy DM path and dashboard-authored actions on the same runtime shape.Also applies to: 105-105
tests/modules/actions/buildPayload.test.js (1)
6-45: Good regression for the rich embed path.This locks in the newly added fields/footer/image/timestamp behavior in one high-value case.
tests/modules/actions/normalizeAction.test.js (1)
6-64: Good normalization regression coverage.These assertions pin the template aliasing and embed-builder mapping that the runtime now depends on.
src/modules/actions/normalizeAction.js (1)
30-37: Placeholder aliases correctly matchbuildTemplateContext()keys.The aliases
{{avatar}}and{{serverIcon}}are properly defined inbuildTemplateContext()(lines 135 and 143 insrc/utils/templateEngine.js), so the embed thumbnail mapping in lines 30–37 will render correctly.web/src/components/dashboard/xp-level-actions-editor.tsx (2)
1-26: LGTM!Imports are well-organized and type aliases are appropriately defined for the component's scope.
27-81: LGTM!Constants are well-defined with comprehensive template variables and realistic sample values for the preview functionality.
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 `@biome.json`:
- Line 2: The $schema entry was changed from 2.4.10 to 2.4.9 unexpectedly;
confirm whether this downgrade is intentional and either revert to
"https://biomejs.dev/schemas/2.4.10/schema.json" or document the reason for the
change inline (e.g., a brief JSON comment in the PR description or commit
message) so reviewers understand why " $schema" was altered; reference the
$schema key in biome.json when making the update or adding the explanatory note.
In `@web/src/components/dashboard/xp-level-actions-editor.tsx`:
- Line 565: Replace the use of title.toLowerCase().replace(/\s+/g, '-') in the
JSX key generation with String.prototype.replaceAll to make the intent clearer;
update the expression in the key template (the line that builds
`${title.toLowerCase().replace(...)}-${action.type}-...`) to use
title.toLowerCase().replaceAll(/\s+/g, '-') so whitespace sequences are still
collapsed to a single '-' while using replaceAll.
- Around line 78-85: getNextUnusedLevel currently returns 1000 even when that
level is already taken; change getNextUnusedLevel(entries) so it returns null
(or undefined) when no unused level in 1–1000 exists instead of
Math.min(candidate,1000), and keep returning a numeric level when available;
then update the UI logic that triggers adding a level (the "Add Level" button
and its handler in the same component) to check for a null result and disable
the button (and avoid calling the add handler) when getNextUnusedLevel(...)
returns null. Use the function name getNextUnusedLevel and the Add Level
button/handler identifiers in xp-level-actions-editor.tsx to locate and update
the code.
🪄 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: b6c89bb0-5131-4a22-8c65-f8f79029a719
📒 Files selected for processing (4)
biome.jsonsrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/xp-level-actions-editor.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (9)
**/*.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:
biome.json
**/*.{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/CommunitySettingsSection.tsxsrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.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/CommunitySettingsSection.tsxweb/src/components/dashboard/xp-level-actions-editor.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/CommunitySettingsSection.tsxsrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
src/modules/actions/normalizeAction.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/modules/actions/normalizeAction.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/actions/normalizeAction.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/actions/normalizeAction.js
**/*.{js,ts}
📄 CodeRabbit inference engine (AGENTS.md)
Use parameterized SQL only; do not use string concatenation for SQL queries
Files:
src/modules/actions/normalizeAction.js
🧠 Learnings (4)
📚 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 **/*.js : Use ESM-only syntax: `import`/`export`, never `require()`/`module.exports`
Applied to files:
biome.json
📚 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 **/*.{js,ts,jsx,tsx} : ESM only - do not use CommonJS modules
Applied to files:
biome.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/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/xp-level-actions-editor.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
src/modules/actions/normalizeAction.js
[failure] 6-6: Refactor this function to reduce its Cognitive Complexity from 25 to the 15 allowed.
web/src/components/dashboard/xp-level-actions-editor.tsx
[warning] 190-194: Extract this nested ternary operation into an independent statement.
[warning] 565-565: Prefer String#replaceAll() over String#replace().
[failure] 743-743: Refactor this code to not nest functions more than 4 levels deep.
[warning] 589-594: Mark the props of the component as read-only.
[failure] 162-162: Refactor this function to reduce its Cognitive Complexity from 19 to the 15 allowed.
[warning] 572-572: Prefer String#replaceAll() over String#replace().
[warning] 192-194: Extract this nested ternary operation into an independent statement.
[warning] 250-261: Mark the props of the component as read-only.
[warning] 523-523: Mark the props of the component as read-only.
[warning] 206-208: Extract this nested ternary operation into an independent statement.
[warning] 198-202: Extract this nested ternary operation into an independent statement.
🔇 Additional comments (13)
src/modules/actions/normalizeAction.js (2)
6-63: Cognitive complexity exceeds threshold (25 > 15).This concern was already raised in a previous review. The function mixes thumbnail, footer, field, image, and timestamp migrations. Consider extracting focused helpers like
normalizeThumbnail(embed),normalizeFooter(embed), etc.
71-103: LGTM!The
normalizeXpActionfunction correctly handles:
- Pass-through for non-object inputs
- Template derivation from legacy
message,prefix, orsuffixfields based on action type- Embed normalization delegation
The logic aligns with the documented normalization requirements.
web/src/components/dashboard/xp-level-actions-editor.tsx (10)
162-212: Cognitive complexity and nested ternaries intoBuilderConfig.This was flagged in a previous review. Extract helper functions for footer, footer icon URL, and image URL resolution to reduce nesting and improve readability.
237-248: Mark component props as read-only.This was suggested in a previous review. Mark the
ActionCardPropsinterface properties asreadonlyto prevent accidental mutations.
514-521: Mark component props as read-only.This was suggested in a previous review.
563-577: Array index in key may cause issues during reordering.This was flagged in a previous review. Consider adding stable IDs to actions.
582-587: Mark component props as read-only.This was suggested in a previous review.
654-656: Array index in key for level entries.This was flagged in a previous review. Consider adding stable IDs to level entries.
733-752: Extract delete handler to reduce function nesting depth.This was flagged in a previous review. Static analysis reports nesting exceeds 4 levels. Extract the delete logic into a named handler function.
1-76: LGTM!Constants and imports are well-organized:
ACTION_TYPE_OPTIONScovers all action types per the PR objectivesTEMPLATE_VARIABLESprovides the ~20 documented template variablesTEMPLATE_SAMPLESprovides sensible preview values
250-512: LGTM!The
ActionCardcomponent correctly renders type-specific inputs for all 9 action types:
- Role grant/remove with
RoleSelector- DM/announcement with format selection, markdown editor, and embed builder
- XP bonus with numeric input
- Reaction with emoji input
- Nickname prefix/suffix with template editor and character limit guidance
- Webhook with URL and payload inputs
The reorder and delete controls are properly disabled when appropriate.
589-778: Well-structured editor component.The
XpLevelActionsEditorprovides a comprehensive UI for:
- Default actions that run on every level-up
- Per-level action pipelines with level number editing
- Reordering and deletion of both levels and actions
- Proper
savingstate propagation to disable controls during persistenceThe integration with
updateDraftConfigmaintains immutability correctly.web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx (1)
14-14: LGTM!The
XpLevelActionsEditoris correctly integrated:
- Import added appropriately
- Props match the component's interface (
draftConfig,guildId,saving,updateDraftConfig)- Placement at the top of
advancedContentfollows the pattern for advanced feature settings- Feature visibility is properly gated via
showFeature('xp-level-actions')Also applies to: 673-678
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
src/api/utils/configValidation.js:468
validateValue()processesschema.anyOfbefore thevalue === nullcheck, which breaks nullable anyOf schemas (e.g.XP_EMBED_FOOTER_SCHEMAwithnullable: true). A config likefooter: nullwill currently fail validation because each anyOf branch rejects null. Handlenull(andundefined) beforeanyOf, or make theanyOfwrapper explicitly accept null whenschema.nullableis true.
if (schema.anyOf) {
const results = schema.anyOf.map((candidate) => validateValue(value, candidate, path));
const success = results.find((candidateErrors) => candidateErrors.length === 0);
if (success) {
return success;
}
return results.flat();
}
if (value === null) {
if (!schema.nullable) {
errors.push(`${path}: must not be null`);
}
return errors;
}
| const levelEntries = (draftConfig.xp?.levelActions ?? []).map(normalizeDraftEntry); | ||
| const defaultActions = (draftConfig.xp?.defaultActions ?? []).map(normalizeDraftAction); | ||
| const nextUnusedLevel = getNextUnusedLevel(levelEntries); |
There was a problem hiding this comment.
levelEntries / defaultActions are recomputed with normalizeDraftEntry / normalizeDraftAction on every render. For configs that don't yet have ids, this generates fresh UUIDs each render, making React keys unstable and causing child editors to remount (cursor loss / wiped internal state). Consider assigning missing IDs once (e.g., useEffect that patches draftConfig, or memoizing normalized arrays) so IDs remain stable across renders.
db79f8f to
a57ef1b
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
src/api/utils/configValidation.js:467
validateValue()processesschema.anyOfbefore handlingvalue === null. This means schemas markednullable: truebut implemented viaanyOf(e.g.XP_EMBED_FOOTER_SCHEMA) will still rejectnull, because the candidate schemas don’t includenullable. Consider moving the null/undefined short-circuit before theanyOfhandling (or teachinganyOfto respectschema.nullable) sonullable: trueworks consistently.
if (schema.anyOf) {
const results = schema.anyOf.map((candidate) => validateValue(value, candidate, path));
const success = results.find((candidateErrors) => candidateErrors.length === 0);
if (success) {
return success;
}
return results.flat();
}
if (value === null) {
if (!schema.nullable) {
errors.push(`${path}: must not be null`);
}
return errors;
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/api/utils/configValidation.js`:
- Around line 454-461: Before executing the schema.anyOf branch inside
validateValue, short-circuit null handling: if the value is null (or undefined
if you prefer) and schema.nullable is true, return an empty error list so
nullable schemas with anyOf (e.g., XP_EMBED_FOOTER_SCHEMA) accept null; update
the validateValue function to check schema.nullable prior to the schema.anyOf
block and only fall through to the anyOf logic when the nullability check does
not apply.
In `@src/modules/actions/normalizeAction.js`:
- Around line 109-115: normalizeXpAction currently treats arrays as objects so
the spread ({ ...action }) converts arrays into plain objects; add an explicit
guard to return the original array unchanged by checking Array.isArray(action)
before cloning (i.e., in the beginning of normalizeXpAction), ensuring arrays
are left untouched and only plain object actions are shallow-cloned into the
normalized variable.
In `@web/src/components/dashboard/xp-level-actions-editor.tsx`:
- Around line 774-781: Validate and prevent duplicate milestone levels when
updating an entry: in the onChange handler that calls updateLevelEntry (and/or
inside updateLevelEntry implementation), after computing the new level for
currentEntry (the level field), check other entries in the levels array for an
existing entry.level === newLevel (excluding the current entry via entryIndex)
and if a duplicate exists either reject the change (do nothing) or pick the
nearest available level within bounds and set that instead; ensure the UI
provides a clear signal (e.g., keep the previous value) and that
updateLevelEntry, currentEntry and entryIndex are the referenced symbols used to
locate and enforce this de-duplication logic.
- Around line 247-267: The code treats any string in embed.thumbnail as a legacy
custom URL (legacyThumbnail) which forces thumbnailType: 'custom' and loses
semantic tokens like '{{avatar}}' or '{{serverIcon}}'; update to only treat
plain URL strings as legacy custom thumbnails by checking embed.thumbnail is a
string AND not one of the special token values (e.g., '{{avatar}}',
'{{serverIcon}}') or by delegating that detection to resolveThumbnailType, then
set thumbnailType to the semantic type when the token is present and only use
legacyThumbnail/thumbnailUrl when it's truly a custom URL; adjust the
legacyThumbnail assignment and the thumbnailType/thumbnailUrl logic in
toBuilderConfig accordingly so special modes are preserved (referencing
toBuilderConfig, legacyThumbnail, embed.thumbnail, resolveThumbnailType,
thumbnailType, thumbnailUrl).
🪄 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: 10ffb838-00f8-4254-884b-e3ffdf04db57
📒 Files selected for processing (10)
src/api/utils/configValidation.jssrc/modules/actions/buildPayload.jssrc/modules/actions/normalizeAction.jssrc/modules/levelUpActions.jstests/api/utils/configValidation.test.jstests/modules/actions/buildPayload.test.jstests/modules/actions/normalizeAction.test.jsweb/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/components/dashboard/xp-level-actions-editor.tsxweb/src/types/config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Greptile Review
- GitHub Check: Test
- GitHub Check: E2E Tests (2/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:
src/modules/levelUpActions.jstests/api/utils/configValidation.test.jstests/modules/actions/buildPayload.test.jssrc/modules/actions/buildPayload.jssrc/api/utils/configValidation.jstests/modules/actions/normalizeAction.test.jssrc/modules/actions/normalizeAction.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/modules/levelUpActions.jstests/api/utils/configValidation.test.jsweb/src/components/dashboard/config-categories/onboarding-growth.tsxtests/modules/actions/buildPayload.test.jssrc/modules/actions/buildPayload.jssrc/api/utils/configValidation.jstests/modules/actions/normalizeAction.test.jsweb/src/types/config.tssrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.tsx
src/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/**/*.js: Never useconsole.*methods; use the Winston logger instead viaimport logger from '../logger.js'(adjust path as needed), then calllogger.info(),logger.warn(),logger.error(), orlogger.debug()
Always usesafeReply(),safeSend(), orsafeEditReply()instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully
Files:
src/modules/levelUpActions.jssrc/modules/actions/buildPayload.jssrc/api/utils/configValidation.jssrc/modules/actions/normalizeAction.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/buildPayload.jssrc/modules/actions/normalizeAction.js
**/*.{js,ts,jsx,tsx,mjs,mts}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only; do not use CommonJS
Files:
src/modules/levelUpActions.jstests/api/utils/configValidation.test.jsweb/src/components/dashboard/config-categories/onboarding-growth.tsxtests/modules/actions/buildPayload.test.jssrc/modules/actions/buildPayload.jssrc/api/utils/configValidation.jstests/modules/actions/normalizeAction.test.jsweb/src/types/config.tssrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use src/logger.js; do not use console.*
Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Use parameterized SQL only; do not use string concatenation for SQL queries
Community features should be gated behind config..enabled; moderation commands are the exception
Files:
src/modules/levelUpActions.jstests/api/utils/configValidation.test.jsweb/src/components/dashboard/config-categories/onboarding-growth.tsxtests/modules/actions/buildPayload.test.jssrc/modules/actions/buildPayload.jssrc/api/utils/configValidation.jstests/modules/actions/normalizeAction.test.jsweb/src/types/config.tssrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.{js,ts,jsx,tsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Welcome-message variables use double braces only, like {{user}}; single braces are plain text and should not be documented, inserted, or parsed as variables
Files:
src/modules/levelUpActions.jstests/api/utils/configValidation.test.jsweb/src/components/dashboard/config-categories/onboarding-growth.tsxtests/modules/actions/buildPayload.test.jssrc/modules/actions/buildPayload.jssrc/api/utils/configValidation.jstests/modules/actions/normalizeAction.test.jsweb/src/types/config.tssrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.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/api/utils/configValidation.test.jstests/modules/actions/buildPayload.test.jstests/modules/actions/normalizeAction.test.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
web/src/**/*.{ts,tsx}: New dashboard routes need title wiring in web/src/lib/page-titles.ts; use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation
Dashboard clients that need the guild list should consume GuildDirectoryProvider; do not stack extra /api/guilds fetch loops in leaf components
Recharts dashboard views should use web/src/components/ui/stable-responsive-container.tsx; raw ResponsiveContainer mounts can spam width(-1)/height(-1) warnings when panels render before layout settles
Any visual dashboard or landing page change must be verified with Chrome DevTools MCP before calling it done; take a screenshot after the change
Check both light and dark themes if colors or theming changed
Check responsive behavior on mobile, tablet, and desktop if layout changed
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/types/config.tsweb/src/components/dashboard/xp-level-actions-editor.tsx
web/src/**/*.{ts,tsx,css,scss}
📄 CodeRabbit inference engine (AGENTS.md)
Follow the design system when making changes to the UI/UX; see DESIGN.md for the design system and color palette
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/types/config.tsweb/src/components/dashboard/xp-level-actions-editor.tsx
🧠 Learnings (7)
📚 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-categories/onboarding-growth.tsxweb/src/components/dashboard/xp-level-actions-editor.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/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-categories/onboarding-growth.tsxweb/src/components/dashboard/xp-level-actions-editor.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to web/src/**/*.{ts,tsx} : 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/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/components/dashboard/xp-level-actions-editor.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Community features should be gated behind config.<feature>.enabled; moderation commands are the exception
Applied to files:
web/src/components/dashboard/config-categories/onboarding-growth.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:
tests/modules/actions/buildPayload.test.jstests/modules/actions/normalizeAction.test.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 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/modules/actions/normalizeAction.test.js
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to web/src/**/*.{ts,tsx} : Any visual dashboard or landing page change must be verified with Chrome DevTools MCP before calling it done; take a screenshot after the change
Applied to files:
web/src/components/dashboard/xp-level-actions-editor.tsx
🪛 GitHub Check: SonarCloud Code Analysis
src/modules/actions/normalizeAction.js
[warning] 94-94: Unexpected negated condition.
web/src/components/dashboard/xp-level-actions-editor.tsx
[warning] 640-640: Refactor this code to not use nested template literals.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/api/utils/configValidation.js`:
- Around line 404-406: The xp.levelActions array currently allows duplicate
entries with the same level which get silently overwritten when loaded into the
Map in loadLevelActions (see the runtime code that keys by level); update the
schema that defines levelActions to reject duplicate level values by adding a
uniqueness constraint (e.g. add "uniqueItemProperties": ["level"] on the
levelActions array that references XP_LEVEL_ACTION_ENTRY_SCHEMA) and ensure the
JSON Schema validator is configured with the ajv-keywords/uniqueItemProperties
support (or add an equivalent custom keyword/validation that rejects duplicate
xp.levelActions[].level values).
In `@web/src/components/dashboard/xp-level-actions-editor.tsx`:
- Around line 151-159: normalizeDraftAction is overwriting legacy saved values
because createAction(type) seeds message/prefix/suffix before ...action is
spread, and legacy configs may only have a `template` property; update
normalizeDraftAction to detect action?.template and, for types
'sendDm'|'announce'|'nickPrefix'|'nickSuffix', copy that template value into the
appropriate field (message for sendDm/announce, prefix for nickPrefix, suffix
for nickSuffix) when building the returned XpLevelAction so the editor hydrates
legacy `template` into the editor model before spreading ...action and
normalizeDraftEmbed.
🪄 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: a4bc40be-c2cd-4a58-86d1-ecf2afb38412
📒 Files selected for processing (3)
src/api/utils/configValidation.jssrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.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 (2/2)
- GitHub Check: Test
- GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
src/api/utils/configValidation.jssrc/modules/actions/normalizeAction.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/api/utils/configValidation.jssrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.tsx
src/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/**/*.js: Never useconsole.*methods; use the Winston logger instead viaimport logger from '../logger.js'(adjust path as needed), then calllogger.info(),logger.warn(),logger.error(), orlogger.debug()
Always usesafeReply(),safeSend(), orsafeEditReply()instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully
Files:
src/api/utils/configValidation.jssrc/modules/actions/normalizeAction.js
**/*.{js,ts,jsx,tsx,mjs,mts}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only; do not use CommonJS
Files:
src/api/utils/configValidation.jssrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use src/logger.js; do not use console.*
Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Use parameterized SQL only; do not use string concatenation for SQL queries
Community features should be gated behind config..enabled; moderation commands are the exception
Files:
src/api/utils/configValidation.jssrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.{js,ts,jsx,tsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Welcome-message variables use double braces only, like {{user}}; single braces are plain text and should not be documented, inserted, or parsed as variables
Files:
src/api/utils/configValidation.jssrc/modules/actions/normalizeAction.jsweb/src/components/dashboard/xp-level-actions-editor.tsx
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/actions/normalizeAction.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
web/src/**/*.{ts,tsx}: New dashboard routes need title wiring in web/src/lib/page-titles.ts; use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation
Dashboard clients that need the guild list should consume GuildDirectoryProvider; do not stack extra /api/guilds fetch loops in leaf components
Recharts dashboard views should use web/src/components/ui/stable-responsive-container.tsx; raw ResponsiveContainer mounts can spam width(-1)/height(-1) warnings when panels render before layout settles
Any visual dashboard or landing page change must be verified with Chrome DevTools MCP before calling it done; take a screenshot after the change
Check both light and dark themes if colors or theming changed
Check responsive behavior on mobile, tablet, and desktop if layout changed
Files:
web/src/components/dashboard/xp-level-actions-editor.tsx
web/src/**/*.{ts,tsx,css,scss}
📄 CodeRabbit inference engine (AGENTS.md)
Follow the design system when making changes to the UI/UX; see DESIGN.md for the design system and color palette
Files:
web/src/components/dashboard/xp-level-actions-editor.tsx
🧠 Learnings (3)
📚 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/xp-level-actions-editor.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to web/src/**/*.{ts,tsx} : 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/src/components/dashboard/xp-level-actions-editor.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to web/src/**/*.{ts,tsx} : Any visual dashboard or landing page change must be verified with Chrome DevTools MCP before calling it done; take a screenshot after the change
Applied to files:
web/src/components/dashboard/xp-level-actions-editor.tsx
🪛 GitHub Check: SonarCloud Code Analysis
src/modules/actions/normalizeAction.js
[warning] 94-94: Unexpected negated condition.
web/src/components/dashboard/xp-level-actions-editor.tsx
[warning] 271-273: Extract this nested ternary operation into an independent statement.
[warning] 256-260: Extract this nested ternary operation into an independent statement.
[warning] 258-260: Extract this nested ternary operation into an independent statement.
[warning] 653-653: Refactor this code to not use nested template literals.
[warning] 270-270: Extract this nested ternary operation into an independent statement.
…ibility, color normalization - normalizeAction: only accept numeric color values - xp-level-actions-editor: use generateId() from config-editor-utils - xp-level-actions-editor: memoize normalizeDraftEntry/normalizeDraftAction with useMemo - xp-level-actions-editor: handle legacy embed.thumbnail string in toBuilderConfig - xp-level-actions-editor: add htmlFor/id to Label/select and Label/Input associations - onboarding-growth: integrate XpLevelActionsEditor in new tab-based config-workspace layout
…il tokens, duplicate levels
d7fdf40 to
ebc6aca
Compare
| function fromSingleSelection(values: string[]): string { | ||
| return values[0] ?? ''; |
There was a problem hiding this comment.
fromSingleSelection() returns an empty string when nothing is selected. That value is then persisted into action roleId/channelId, which passes validation but results in noisy runtime warnings (e.g., role/channel not found) and can leave channelMode: "specific" with an unusable channel. Prefer returning null/undefined for “no selection” and only setting the id field when a real selection exists (or adjust the schema to reject empty strings).
| function fromSingleSelection(values: string[]): string { | |
| return values[0] ?? ''; | |
| /** | |
| * Converts a single-selection array back to an optional identifier. | |
| * | |
| * @param values - The selected values from a single-select control. | |
| * @returns The selected identifier, or `undefined` when nothing is selected. | |
| */ | |
| function fromSingleSelection(values: string[]): string | undefined { | |
| return values[0]; |
| }, | ||
| channelId: { type: 'string', nullable: true }, | ||
| emoji: { type: 'string', nullable: true }, | ||
| amount: { type: 'number', nullable: true }, |
There was a problem hiding this comment.
xpBonus.amount is validated as a generic number but the handler treats <= 0 as invalid and skips the action. Consider adding a minimum bound (e.g. min: 1) so invalid bonus XP values are rejected at save time instead of being silently ignored at runtime.
| amount: { type: 'number', nullable: true }, | |
| amount: { type: 'number', min: 1, nullable: true }, |
| /** XP level-up action definition. */ | ||
| export interface XpActionEmbedField { | ||
| id?: string; | ||
| name: string; | ||
| value: string; | ||
| inline: boolean; | ||
| } | ||
|
|
||
| export type XpActionEmbedThumbnailType = 'none' | 'user_avatar' | 'server_icon' | 'custom'; | ||
|
|
||
| export interface XpActionEmbedConfig { | ||
| color?: string; | ||
| title?: string; | ||
| description?: string; | ||
| thumbnail?: string; | ||
| thumbnailType?: XpActionEmbedThumbnailType; | ||
| thumbnailUrl?: string; | ||
| fields?: XpActionEmbedField[]; | ||
| footer?: string | { text?: string; iconURL?: string }; | ||
| footerText?: string; | ||
| footerIconUrl?: string; | ||
| image?: string; | ||
| imageUrl?: string; | ||
| timestamp?: boolean; | ||
| showTimestamp?: boolean; | ||
| } |
There was a problem hiding this comment.
XpActionEmbedConfig includes several alias/legacy fields (thumbnail vs thumbnailType/thumbnailUrl, footer vs footerText/footerIconUrl, image vs imageUrl, timestamp vs showTimestamp). Adding a short note (like other config types in this file do for legacy fields) would make it clearer which fields the dashboard emits vs which the runtime consumes.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/src/types/config.ts (1)
313-338:⚠️ Potential issue | 🟡 MinorAllow
embed: nullinXpLevelAction.
src/api/utils/configValidation.js:73-96explicitly allowsxp.*Actions[].embed = null, andweb/src/components/dashboard/xp-level-actions-editor.tsx:134-145already guards for a null embed during normalization. Keeping this property non-null here misstates the API contract and hides a real runtime state from the compiler.🛠️ Proposed fix
- embed?: XpActionEmbedConfig; + embed?: XpActionEmbedConfig | null;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/types/config.ts` around lines 313 - 338, The XpLevelAction interface currently types embed as XpActionEmbedConfig, but the runtime/API and normalization code allow embed to be null; update the XpLevelAction declaration (and any related usages) so that embed is typed as optional and nullable (e.g., embed?: XpActionEmbedConfig | null) to reflect the actual contract and prevent compiler errors in places like xp-level-actions-editor.tsx and config validation consumers.
♻️ Duplicate comments (2)
src/api/utils/configValidation.js (1)
404-406:⚠️ Potential issue | 🟠 MajorDisallow duplicate
xp.levelActions[].levelvalues.At runtime
src/modules/levelUpActions.js:93-96loads these entries into aMap, so a later duplicate silently overwrites the earlier one. Add the sameuniqueBy: 'level'guard you already use forxp.levelUpDm.messages, plus a regression test.🐛 Proposed fix
levelActions: { type: 'array', + uniqueBy: 'level', items: XP_LEVEL_ACTION_ENTRY_SCHEMA, },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/api/utils/configValidation.js` around lines 404 - 406, The xp.levelActions array allows duplicate entries with the same level which later get silently overwritten when loaded into the Map in levelUpActions (see levelUpActions load logic); update the validation schema for levelActions (where XP_LEVEL_ACTION_ENTRY_SCHEMA is used) to include uniqueBy: 'level' (same style as xp.levelUpDm.messages) so duplicate level values are rejected at config validation, and add a regression test that asserts validation fails when two levelActions entries share the same level.web/src/components/dashboard/xp-level-actions-editor.tsx (1)
151-159:⚠️ Potential issue | 🟠 MajorHydrate
templatebefore the default seed wins.
createAction(type)prepopulatesmessage/prefix/suffix, so actions that only
persisttemplaterender those canned defaults instead of the saved value. Since
src/modules/actions/normalizeAction.jsusestemplateas the canonical runtime field for
these action types, opening and saving them here can overwrite existing configs.💡 Suggested fix
function normalizeDraftAction(action?: DeepPartial<XpLevelAction> | null): XpLevelAction { const type = action?.type ?? 'grantRole'; - return { + const normalized = { ...createAction(type), ...action, id: action?.id ?? createStableId(), embed: normalizeDraftEmbed(action?.embed), type, }; + + if (typeof action?.template === 'string') { + if ( + (type === 'sendDm' || type === 'announce') && + typeof action?.message !== 'string' + ) { + normalized.message = action.template; + } + if (type === 'nickPrefix' && typeof action?.prefix !== 'string') { + normalized.prefix = action.template; + } + if (type === 'nickSuffix' && typeof action?.suffix !== 'string') { + normalized.suffix = action.template; + } + } + + return normalized; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/xp-level-actions-editor.tsx` around lines 151 - 159, normalizeDraftAction currently lets createAction(type) seed canned message/prefix/suffix that win over an action that only provides template; fix by hydrating the template into the returned action before the defaults win: call createAction(type) into a base (or keep current call) but when constructing the return ensure you set message (and prefix/suffix if applicable) from action?.template first (e.g. message: action?.template ?? base.message), then spread ...action and the rest; update normalizeDraftAction to prefer action.template for message/prefix/suffix so saved templates are preserved when merging with createAction(type) and keep id/embed/type handling as-is (references: normalizeDraftAction, createAction, normalizeDraftEmbed, template, message, prefix, suffix).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/api/utils/configValidation.js`:
- Around line 128-139: Change the level fields to require integers instead of
generic numbers: update XP_LEVEL_ACTION_ENTRY_SCHEMA to use an integer
constraint (e.g., "type": "integer" or "multipleOf": 1) while keeping the
existing min/max bounds, and apply the same integer-only rule to the schema that
validates xp.levelUpDm.messages[].level so fractional levels like 5.5 are
rejected; ensure you modify the properties.field named "level" in both
XP_LEVEL_ACTION_ENTRY_SCHEMA and the schema that covers
xp.levelUpDm.messages[].level (preserving min/max).
In `@src/modules/actions/buildPayload.js`:
- Around line 12-35: The renderFooter function currently passes rendered footer
text through unchanged; clamp footerConfig.text to Discord's 2048-character
limit before returning to avoid embed rejections (similar to how field
names/values are clamped). In renderFooter (and where footerConfig is composed
using renderTemplate), truncate footerConfig.text to at most 2048 characters
(ensure trimming/empty-checks still behave the same and preserve the
zero-width-space fallback used when text is absent) so that when
embed.setFooter() later consumes the value it will never exceed Discord's limit.
In `@web/src/components/dashboard/config-categories/onboarding-growth.tsx`:
- Around line 197-203: The XP toggle is inconsistent with the reputation toggle:
make the XP tab use the same coupled state by reading both flags for
isCurrentFeatureEnabled and updating both when toggled. In the activeTab ===
'xp-level-actions' branch adjust isCurrentFeatureEnabled to consider
draftConfig.xp?.enabled || draftConfig.reputation?.enabled, and change
handleToggleCurrentFeature (the updateDraftConfig call) to set both xp.enabled
and reputation.enabled to the new value v so xp and reputation remain in sync.
In `@web/src/components/dashboard/xp-level-actions-editor.tsx`:
- Around line 676-680: draftConfig.xp.levelActions and xp.defaultActions must be
validated as arrays before mapping: instead of directly mapping
rawLevelActions/rawDefaultActions, guard with
Array.isArray(draftConfig.xp?.levelActions) ? draftConfig.xp.levelActions : []
and likewise for defaultActions so normalizeDraftEntry and normalizeDraftAction
only receive valid array elements; update the computations of levelEntries and
defaultActions to use these guarded arrays (apply the same Array.isArray guard
to any other mappings around normalizeDraftEntry/normalizeDraftAction).
---
Outside diff comments:
In `@web/src/types/config.ts`:
- Around line 313-338: The XpLevelAction interface currently types embed as
XpActionEmbedConfig, but the runtime/API and normalization code allow embed to
be null; update the XpLevelAction declaration (and any related usages) so that
embed is typed as optional and nullable (e.g., embed?: XpActionEmbedConfig |
null) to reflect the actual contract and prevent compiler errors in places like
xp-level-actions-editor.tsx and config validation consumers.
---
Duplicate comments:
In `@src/api/utils/configValidation.js`:
- Around line 404-406: The xp.levelActions array allows duplicate entries with
the same level which later get silently overwritten when loaded into the Map in
levelUpActions (see levelUpActions load logic); update the validation schema for
levelActions (where XP_LEVEL_ACTION_ENTRY_SCHEMA is used) to include uniqueBy:
'level' (same style as xp.levelUpDm.messages) so duplicate level values are
rejected at config validation, and add a regression test that asserts validation
fails when two levelActions entries share the same level.
In `@web/src/components/dashboard/xp-level-actions-editor.tsx`:
- Around line 151-159: normalizeDraftAction currently lets createAction(type)
seed canned message/prefix/suffix that win over an action that only provides
template; fix by hydrating the template into the returned action before the
defaults win: call createAction(type) into a base (or keep current call) but
when constructing the return ensure you set message (and prefix/suffix if
applicable) from action?.template first (e.g. message: action?.template ??
base.message), then spread ...action and the rest; update normalizeDraftAction
to prefer action.template for message/prefix/suffix so saved templates are
preserved when merging with createAction(type) and keep id/embed/type handling
as-is (references: normalizeDraftAction, createAction, normalizeDraftEmbed,
template, message, prefix, suffix).
🪄 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: c1a6bd90-880c-49a1-92ce-41cbf4a7f74e
📒 Files selected for processing (11)
src/api/utils/configValidation.jssrc/modules/actions/buildPayload.jssrc/modules/actions/normalizeAction.jssrc/modules/levelUpActions.jstests/api/utils/configValidation.test.jstests/modules/actions/buildPayload.test.jstests/modules/actions/normalizeAction.test.jsweb/src/app/dashboard/logs/page.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/components/dashboard/xp-level-actions-editor.tsxweb/src/types/config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Greptile Review
- GitHub Check: Analyze (actions)
- GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{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-categories/onboarding-growth.tsxweb/src/app/dashboard/logs/page.tsxsrc/modules/levelUpActions.jssrc/api/utils/configValidation.jstests/modules/actions/buildPayload.test.jstests/api/utils/configValidation.test.jssrc/modules/actions/buildPayload.jsweb/src/types/config.tssrc/modules/actions/normalizeAction.jstests/modules/actions/normalizeAction.test.jsweb/src/components/dashboard/xp-level-actions-editor.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
web/src/**/*.{ts,tsx}: New dashboard routes need title wiring in web/src/lib/page-titles.ts; use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation
Dashboard clients that need the guild list should consume GuildDirectoryProvider; do not stack extra /api/guilds fetch loops in leaf components
Recharts dashboard views should use web/src/components/ui/stable-responsive-container.tsx; raw ResponsiveContainer mounts can spam width(-1)/height(-1) warnings when panels render before layout settles
Any visual dashboard or landing page change must be verified with Chrome DevTools MCP before calling it done; take a screenshot after the change
Check both light and dark themes if colors or theming changed
Check responsive behavior on mobile, tablet, and desktop if layout changed
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/app/dashboard/logs/page.tsxweb/src/types/config.tsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.{js,ts,jsx,tsx,mjs,mts}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only; do not use CommonJS
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/app/dashboard/logs/page.tsxsrc/modules/levelUpActions.jssrc/api/utils/configValidation.jstests/modules/actions/buildPayload.test.jstests/api/utils/configValidation.test.jssrc/modules/actions/buildPayload.jsweb/src/types/config.tssrc/modules/actions/normalizeAction.jstests/modules/actions/normalizeAction.test.jsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use src/logger.js; do not use console.*
Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Use parameterized SQL only; do not use string concatenation for SQL queries
Community features should be gated behind config..enabled; moderation commands are the exception
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/app/dashboard/logs/page.tsxsrc/modules/levelUpActions.jssrc/api/utils/configValidation.jstests/modules/actions/buildPayload.test.jstests/api/utils/configValidation.test.jssrc/modules/actions/buildPayload.jsweb/src/types/config.tssrc/modules/actions/normalizeAction.jstests/modules/actions/normalizeAction.test.jsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.{js,ts,jsx,tsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Welcome-message variables use double braces only, like {{user}}; single braces are plain text and should not be documented, inserted, or parsed as variables
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/app/dashboard/logs/page.tsxsrc/modules/levelUpActions.jssrc/api/utils/configValidation.jstests/modules/actions/buildPayload.test.jstests/api/utils/configValidation.test.jssrc/modules/actions/buildPayload.jsweb/src/types/config.tssrc/modules/actions/normalizeAction.jstests/modules/actions/normalizeAction.test.jsweb/src/components/dashboard/xp-level-actions-editor.tsx
web/src/**/*.{ts,tsx,css,scss}
📄 CodeRabbit inference engine (AGENTS.md)
Follow the design system when making changes to the UI/UX; see DESIGN.md for the design system and color palette
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/app/dashboard/logs/page.tsxweb/src/types/config.tsweb/src/components/dashboard/xp-level-actions-editor.tsx
web/src/app/dashboard/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
For dashboard routes, add a matcher entry to
dashboardTitleMatchersinweb/src/lib/page-titles.ts: use exact equality for leaf routes (pathname === '/dashboard/my-route') and subtree checks (pathname.startsWith('/dashboard/my-route/')); exportmetadatausingcreatePageMetadata(title)for SSR entry points
Files:
web/src/app/dashboard/logs/page.tsx
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
src/modules/levelUpActions.jssrc/api/utils/configValidation.jstests/modules/actions/buildPayload.test.jstests/api/utils/configValidation.test.jssrc/modules/actions/buildPayload.jssrc/modules/actions/normalizeAction.jstests/modules/actions/normalizeAction.test.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/modules/levelUpActions.jssrc/api/utils/configValidation.jssrc/modules/actions/buildPayload.jssrc/modules/actions/normalizeAction.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/buildPayload.jssrc/modules/actions/normalizeAction.js
tests/**/*.test.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
tests/**/*.test.js: Write bot tests using Vitest 4 with thenodeenvironment, matching thesrc/structure in thetests/directory
Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead
Files:
tests/modules/actions/buildPayload.test.jstests/api/utils/configValidation.test.jstests/modules/actions/normalizeAction.test.js
🧠 Learnings (14)
📚 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-categories/onboarding-growth.tsxweb/src/components/dashboard/xp-level-actions-editor.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/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-categories/onboarding-growth.tsxweb/src/app/dashboard/logs/page.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to web/src/**/*.{ts,tsx} : 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/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/app/dashboard/logs/page.tsx
📚 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 web/src/pages/dashboard/**/*.{ts,tsx} : Use shared title helpers from web/src/lib/page-titles.ts for setting browser titles in dashboard pages
Applied to files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/app/dashboard/logs/page.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Community features should be gated behind config.<feature>.enabled; moderation commands are the exception
Applied to files:
web/src/components/dashboard/config-categories/onboarding-growth.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/src/app/dashboard/logs/page.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/**/*.{ts,tsx} : Never use `console.*` methods in web dashboard code; use appropriate logging mechanisms for React applications
Applied to files:
web/src/app/dashboard/logs/page.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to web/src/**/*.{ts,tsx} : Recharts dashboard views should use web/src/components/ui/stable-responsive-container.tsx; raw ResponsiveContainer mounts can spam width(-1)/height(-1) warnings when panels render before layout settles
Applied to files:
web/src/app/dashboard/logs/page.tsx
📚 Learning: 2026-03-11T05:32:46.325Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T05:32:46.325Z
Learning: Applies to web/src/app/**/*.{ts,tsx} : Apply static metadata to server-rendered dashboard entry pages and use title template format for root app metadata
Applied to files:
web/src/app/dashboard/logs/page.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Applied to files:
web/src/app/dashboard/logs/page.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to web/src/**/*.{ts,tsx} : Dashboard clients that need the guild list should consume GuildDirectoryProvider; do not stack extra /api/guilds fetch loops in leaf components
Applied to files:
web/src/app/dashboard/logs/page.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:
tests/modules/actions/buildPayload.test.jstests/modules/actions/normalizeAction.test.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 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/modules/actions/normalizeAction.test.js
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to web/src/**/*.{ts,tsx} : Any visual dashboard or landing page change must be verified with Chrome DevTools MCP before calling it done; take a screenshot after the change
Applied to files:
web/src/components/dashboard/xp-level-actions-editor.tsx
🔇 Additional comments (4)
web/src/app/dashboard/logs/page.tsx (2)
8-8: Good resilience improvement withErrorBoundary.
This is a solid safeguard for rendering failures in the logs dashboard subtree.
12-12: Nice type-safety upgrade withDiscordChannel.
Using a typed channel model here makes the local channel-state flow clearer and safer.src/modules/actions/normalizeAction.js (1)
54-68: Footer normalization lines up with the runtime consumer.Converging the legacy footer variants to
{ text, iconURL }keeps
src/modules/actions/buildPayload.jssimple and backward-compatible.web/src/components/dashboard/xp-level-actions-editor.tsx (1)
731-856: Please attach the required dashboard verification artifacts.This is a substantial new dashboard surface, so we should still see the Chrome DevTools
screenshots/checks for light/dark and mobile/tablet/desktop before merge.As per coding guidelines, 'Any visual dashboard or landing page change must be verified with Chrome DevTools MCP before calling it done; take a screenshot after the change', 'Check both light and dark themes if colors or theming changed', and 'Check responsive behavior on mobile, tablet, and desktop if layout changed'.
|
| levelActions: { | ||
| type: 'array', | ||
| items: { | ||
| type: 'object', | ||
| required: ['level', 'actions'], | ||
| properties: { | ||
| level: { type: 'number', min: 1, max: 1000 }, | ||
| actions: { | ||
| type: 'array', | ||
| items: { | ||
| type: 'object', | ||
| required: ['type'], | ||
| properties: { | ||
| type: { type: 'string' }, | ||
| roleId: { type: 'string', nullable: true }, | ||
| }, | ||
| openProperties: true, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| items: XP_LEVEL_ACTION_ENTRY_SCHEMA, | ||
| }, | ||
| defaultActions: { | ||
| type: 'array', | ||
| items: { | ||
| type: 'object', | ||
| required: ['type'], | ||
| properties: { | ||
| type: { type: 'string' }, | ||
| roleId: { type: 'string', nullable: true }, | ||
| }, | ||
| openProperties: true, | ||
| }, | ||
| items: XP_ACTION_ITEM_SCHEMA, | ||
| }, |
There was a problem hiding this comment.
xp.levelActions currently doesn't enforce unique milestone levels. Since resolveActions() builds a Map keyed by entry.level, duplicate levels will silently overwrite earlier entries, which can lead to lost/ignored actions. Consider adding uniqueBy: 'level' to the xp.levelActions array schema (or otherwise validating uniqueness) so duplicates are rejected at save time.
| {/* Level-Up Actions Layout */} | ||
| {activeTab === 'xp-level-actions' && ( | ||
| <div className="space-y-6"> | ||
| <div className="p-6 rounded-[24px] border border-border/40 bg-muted/20 backdrop-blur-xl space-y-6"> | ||
| <XpLevelActionsEditor | ||
| draftConfig={draftConfig} | ||
| guildId={guildId} | ||
| saving={saving} | ||
| updateDraftConfig={updateDraftConfig} | ||
| /> | ||
| </div> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
The new xp-level-actions dashboard tab mounts XpLevelActionsEditor, but there are existing tests for OnboardingGrowthCategory and none currently cover this new tab/editor path. Please add/update dashboard component tests to verify the Level-Up Actions tab renders, toggles enabled state correctly, and mounts the editor without crashing (at least a smoke test).
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/api/utils/configValidation.js`:
- Around line 98-126: The XP_ACTION_ITEM_SCHEMA's amount property currently only
enforces min: 1 and lacks an upper bound, risking runaway XP values; update the
amount validator inside XP_ACTION_ITEM_SCHEMA to include a reasonable max (for
example max: 100000) so the schema enforces both min and max constraints on
`amount` and prevents misconfiguration in functions that rely on this schema
(look for the `XP_ACTION_ITEM_SCHEMA` and its `amount` property).
In `@web/src/components/dashboard/xp-level-actions-editor.tsx`:
- Around line 151-160: normalizeDraftAction currently seeds a default message
via createAction(type) then spreads the legacy action which may only have
template set, so the saved template never becomes message on first load; to fix,
inside normalizeDraftAction (before spreading/return), detect when action exists
and action.template is a string but action.message is falsy, and copy template
into message (e.g., action = { ...action, message: action.template }) so the
returned XpLevelAction uses the legacy template as message for sendDm/announce
actions.
🪄 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: b2f29632-eaf4-4499-af71-41088bf59923
📒 Files selected for processing (7)
src/api/utils/configValidation.jssrc/modules/actions/buildPayload.jstests/api/utils/configValidation.test.jstests/modules/actions/buildPayload.test.jsweb/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/components/dashboard/xp-level-actions-editor.tsxweb/src/types/config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Agent
- GitHub Check: Greptile Review
- GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{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-categories/onboarding-growth.tsxtests/api/utils/configValidation.test.jstests/modules/actions/buildPayload.test.jssrc/modules/actions/buildPayload.jssrc/api/utils/configValidation.jsweb/src/types/config.tsweb/src/components/dashboard/xp-level-actions-editor.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
web/src/**/*.{ts,tsx}: New dashboard routes need title wiring in web/src/lib/page-titles.ts; use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation
Dashboard clients that need the guild list should consume GuildDirectoryProvider; do not stack extra /api/guilds fetch loops in leaf components
Recharts dashboard views should use web/src/components/ui/stable-responsive-container.tsx; raw ResponsiveContainer mounts can spam width(-1)/height(-1) warnings when panels render before layout settles
Any visual dashboard or landing page change must be verified with Chrome DevTools MCP before calling it done; take a screenshot after the change
Check both light and dark themes if colors or theming changed
Check responsive behavior on mobile, tablet, and desktop if layout changed
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/types/config.tsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.{js,ts,jsx,tsx,mjs,mts}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only; do not use CommonJS
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxtests/api/utils/configValidation.test.jstests/modules/actions/buildPayload.test.jssrc/modules/actions/buildPayload.jssrc/api/utils/configValidation.jsweb/src/types/config.tsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use src/logger.js; do not use console.*
Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Use parameterized SQL only; do not use string concatenation for SQL queries
Community features should be gated behind config..enabled; moderation commands are the exception
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxtests/api/utils/configValidation.test.jstests/modules/actions/buildPayload.test.jssrc/modules/actions/buildPayload.jssrc/api/utils/configValidation.jsweb/src/types/config.tsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.{js,ts,jsx,tsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Welcome-message variables use double braces only, like {{user}}; single braces are plain text and should not be documented, inserted, or parsed as variables
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxtests/api/utils/configValidation.test.jstests/modules/actions/buildPayload.test.jssrc/modules/actions/buildPayload.jssrc/api/utils/configValidation.jsweb/src/types/config.tsweb/src/components/dashboard/xp-level-actions-editor.tsx
web/src/**/*.{ts,tsx,css,scss}
📄 CodeRabbit inference engine (AGENTS.md)
Follow the design system when making changes to the UI/UX; see DESIGN.md for the design system and color palette
Files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/types/config.tsweb/src/components/dashboard/xp-level-actions-editor.tsx
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
tests/api/utils/configValidation.test.jstests/modules/actions/buildPayload.test.jssrc/modules/actions/buildPayload.jssrc/api/utils/configValidation.js
tests/**/*.test.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
tests/**/*.test.js: Write bot tests using Vitest 4 with thenodeenvironment, matching thesrc/structure in thetests/directory
Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead
Files:
tests/api/utils/configValidation.test.jstests/modules/actions/buildPayload.test.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/modules/actions/buildPayload.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/actions/buildPayload.js
🧠 Learnings (11)
📚 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-categories/onboarding-growth.tsxweb/src/components/dashboard/xp-level-actions-editor.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/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-categories/onboarding-growth.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to web/src/**/*.{ts,tsx} : 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/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/components/dashboard/xp-level-actions-editor.tsx
📚 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 web/src/pages/dashboard/**/*.{ts,tsx} : Use shared title helpers from web/src/lib/page-titles.ts for setting browser titles in dashboard pages
Applied to files:
web/src/components/dashboard/config-categories/onboarding-growth.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/src/components/dashboard/config-categories/onboarding-growth.tsx
📚 Learning: 2026-04-11T22:42:46.710Z
Learnt from: BillChirico
Repo: VolvoxLLC/volvox-bot PR: 477
File: web/src/components/layout/server-selector.tsx:136-153
Timestamp: 2026-04-11T22:42:46.710Z
Learning: In `web/src/components/layout/server-selector.tsx`, the community feature is intentionally NOT gated behind a global config flag. The `ServerSelector` renders a full guild directory across all guilds a user belongs to; community eligibility is per-guild, so gating each community entry would require N extra config fetches (one per guild). Per-guild community gating is handled instead in the selected-guild config surface (e.g. ConfigProvider), where the selected guild's config is already available.
Applied to files:
web/src/components/dashboard/config-categories/onboarding-growth.tsx
📚 Learning: 2026-03-11T05:32:46.325Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T05:32:46.325Z
Learning: Applies to web/src/app/**/*.{ts,tsx} : Apply static metadata to server-rendered dashboard entry pages and use title template format for root app metadata
Applied to files:
web/src/components/dashboard/config-categories/onboarding-growth.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to web/src/**/*.{ts,tsx} : Any visual dashboard or landing page change must be verified with Chrome DevTools MCP before calling it done; take a screenshot after the change
Applied to files:
web/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/components/dashboard/xp-level-actions-editor.tsx
📚 Learning: 2026-04-09T20:31:03.263Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-09T20:31:03.263Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Community features should be gated behind config.<feature>.enabled; moderation commands are the exception
Applied to files:
web/src/components/dashboard/config-categories/onboarding-growth.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-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:
tests/modules/actions/buildPayload.test.js
🔇 Additional comments (15)
web/src/components/dashboard/config-categories/onboarding-growth.tsx (2)
197-205: Toggle consistency is now properly maintained.The
xp-level-actionstab now correctly mirrors thereputationtab's toggle logic—both read from and write toxp.enabledandreputation.enabledtogether. This ensures the two related tabs stay in sync.
746-758: LGTM!The
XpLevelActionsEditorintegration is clean and correctly passes all required props (draftConfig,guildId,saving,updateDraftConfig).src/api/utils/configValidation.js (2)
37-47: LGTM!The centralized
XP_ACTION_TYPESconstant ensures consistency between validation and runtime action handling.
465-472: LGTM!The
anyOfhandling correctly short-circuits on the first successful candidate and flattens errors when all candidates fail. The null/undefined checks before this block (lines 454-463) ensure nullable schemas withanyOfwork correctly.src/modules/actions/buildPayload.js (2)
13-39: LGTM!The
renderFooterhelper properly handles both string and object footer formats, clamps text to Discord's 2048-character limit, and correctly uses a zero-width space when only an icon URL is present (required by Discord's API).
66-94: LGTM!The embed construction correctly:
- Caps fields at 25 and logs a warning when truncating
- Clamps field names to 256 and values to 1024 characters
- Uses zero-width space fallbacks for empty field text
- Conditionally renders footer, image, and timestamp
tests/modules/actions/buildPayload.test.js (1)
1-134: LGTM!Comprehensive test coverage for
buildPayload:
- Rich embed rendering with all fields
- Field truncation at 25 with warning verification
- Zero-width space footer fallback
- Footer text truncation to 2048 characters
- Empty footer omission
The mock setup using
vi.hoistedensures the logger mock is available before the module import.tests/api/utils/configValidation.test.js (2)
399-480: LGTM!Good test coverage additions for the new XP schema validations:
- Fractional level rejection (integer enforcement)
- Non-positive
xpBonusamount rejection- Structured embed schema acceptance with all field types
- Level action entry
idfield support- Unknown embed key rejection (closed schema)
512-517: LGTM!Correctly verifies that
levelUpDm.messages[].levelalso enforces integer-only values, mirroring thelevelActions[].levelconstraint.web/src/types/config.ts (2)
287-313: LGTM!The
XpActionEmbedConfigtype correctly models:
- Dashboard-native fields (
thumbnailType,footerText,imageUrl,showTimestamp)- Legacy/runtime aliases (
thumbnail,footer,image,timestamp)- Union type for footer supporting both string and structured object formats
This aligns with the backend's
XP_EMBED_SCHEMAandnormalizeEmbedbehavior.
315-340: LGTM!The
XpLevelActionupdates correctly add:
- Optional
idfor stable React keys and config persistence'nickSuffix'discriminant matchingXP_ACTION_TYPESinconfigValidation.js- Optional
templatefield for runtime normalization compatibility- Strongly-typed
embedfield replacing the previousRecord<string, unknown>web/src/components/dashboard/xp-level-actions-editor.tsx (4)
676-686: LGTM!The
Array.isArrayguards correctly handle malformed config data, preventing runtime errors whenlevelActionsordefaultActionsaren't arrays. TheuseMemowrapping ensures normalization only runs when the underlying arrays change.
795-809: LGTM!Duplicate level blocking is correctly implemented—the change is silently rejected if another entry already uses the target level. This prevents ambiguous per-level overrides.
247-281: LGTM!The
toBuilderConfigfunction correctly preserves semantic thumbnail modes (user_avatar,server_icon) from legacy{{avatar}}/{{serverIcon}}template tokens, addressing the previous review concern about losing special modes when loading normalized actions.
88-95: LGTM!
getNextUnusedLevelnow correctly returnsnullwhen all levels 1–1000 are exhausted, and the "Add Level" button at line 768 disables whennextUnusedLevel == null.
| const XP_ACTION_ITEM_SCHEMA = { | ||
| type: 'object', | ||
| required: ['type'], | ||
| properties: { | ||
| id: { type: 'string', nullable: true }, | ||
| type: { | ||
| type: 'string', | ||
| enum: XP_ACTION_TYPES, | ||
| }, | ||
| roleId: { type: 'string', nullable: true }, | ||
| message: { type: 'string', nullable: true }, | ||
| template: { type: 'string', nullable: true }, | ||
| format: { type: 'string', enum: ['text', 'embed', 'both'], nullable: true }, | ||
| channelMode: { | ||
| type: 'string', | ||
| enum: ['current', 'specific', 'none'], | ||
| nullable: true, | ||
| }, | ||
| channelId: { type: 'string', nullable: true }, | ||
| emoji: { type: 'string', nullable: true }, | ||
| amount: { type: 'number', min: 1, nullable: true }, | ||
| prefix: { type: 'string', nullable: true }, | ||
| suffix: { type: 'string', nullable: true }, | ||
| url: { type: 'string', nullable: true }, | ||
| payload: { type: 'string', nullable: true }, | ||
| embed: XP_EMBED_SCHEMA, | ||
| }, | ||
| openProperties: true, | ||
| }; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider adding a maximum constraint for xpBonus amount.
The amount field validates min: 1 but has no upper bound. A misconfigured guild could grant millions of XP per level-up, potentially causing leaderboard issues or storage bloat. Consider adding a reasonable max (e.g., 100000) to match Discord-scale expectations.
♻️ Suggested constraint
amount: { type: 'number', min: 1, nullable: true },
+ // Consider:
+ // amount: { type: 'number', min: 1, max: 100000, nullable: true },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/api/utils/configValidation.js` around lines 98 - 126, The
XP_ACTION_ITEM_SCHEMA's amount property currently only enforces min: 1 and lacks
an upper bound, risking runaway XP values; update the amount validator inside
XP_ACTION_ITEM_SCHEMA to include a reasonable max (for example max: 100000) so
the schema enforces both min and max constraints on `amount` and prevents
misconfiguration in functions that rely on this schema (look for the
`XP_ACTION_ITEM_SCHEMA` and its `amount` property).
| function normalizeDraftAction(action?: DeepPartial<XpLevelAction> | null): XpLevelAction { | ||
| const type = action?.type ?? 'grantRole'; | ||
| return { | ||
| ...createAction(type), | ||
| ...action, | ||
| id: action?.id ?? createStableId(), | ||
| embed: normalizeDraftEmbed(action?.embed), | ||
| type, | ||
| }; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if there's any code that copies template to message during normalization
rg -n "template.*message|message.*template" src/modules/actions/normalizeAction.js web/src/components/dashboard/xp-level-actions-editor.tsxRepository: VolvoxLLC/volvox-bot
Length of output: 545
🏁 Script executed:
#!/bin/bash
# Check how actions are loaded/fetched from backend
rg -n "normalizeDraftAction|XpLevelAction" web/src/components/dashboard/xp-level-actions-editor.tsx | head -20
# Check if normalizeAction is part of the response pipeline
rg -n "normalizeXpAction|normalizeAction" src/modules/actions/ --type jsRepository: VolvoxLLC/volvox-bot
Length of output: 1410
🏁 Script executed:
#!/bin/bash
# Check createAction implementation
rg -n "function createAction|const createAction" web/src/components/dashboard/xp-level-actions-editor.tsx -A 15Repository: VolvoxLLC/volvox-bot
Length of output: 529
🏁 Script executed:
#!/bin/bash
# Check where normalizeDraftAction is called - is it for new actions or loaded configs?
rg -n "normalizeDraftAction" web/src/components/dashboard/xp-level-actions-editor.tsx -B 2 -A 2Repository: VolvoxLLC/volvox-bot
Length of output: 1077
🏁 Script executed:
#!/bin/bash
# Find the API endpoint that returns xp config/actions
rg -n "getGuildConfig|xp.*action|action.*config" src/routes/ src/api/ --type js | head -30Repository: VolvoxLLC/volvox-bot
Length of output: 225
🏁 Script executed:
#!/bin/bash
# Check if normalizeXpAction is called in the response pipeline
rg -n "normalizeXpAction|normalizeXpConfig" src/ --type js -B 2 -A 2Repository: VolvoxLLC/volvox-bot
Length of output: 1891
🏁 Script executed:
#!/bin/bash
# Check the API response that sends xp actions to frontend
rg -n "res\.json|res\.send" src/routes/ src/api/ --type js -l | xargs rg -l "xp.*action|action" | head -5Repository: VolvoxLLC/volvox-bot
Length of output: 267
🏁 Script executed:
#!/bin/bash
# Find guild config endpoint that returns to frontend
fd -e js src/api | xargs rg -l "guild.*config|getGuildConfig|GET.*config" | head -5Repository: VolvoxLLC/volvox-bot
Length of output: 694
🏁 Script executed:
#!/bin/bash
# Check the guilds API route
cat src/api/routes/guilds.js | head -100Repository: VolvoxLLC/volvox-bot
Length of output: 3544
🏁 Script executed:
#!/bin/bash
# Search for where xp config is returned in API
rg -n "\.xp\s*:|config\.xp|return.*xp" src/api/ --type js | head -20Repository: VolvoxLLC/volvox-bot
Length of output: 1846
🏁 Script executed:
#!/bin/bash
# Search for the xp config endpoint in guilds.js
rg -n "GET|xp|config" src/api/routes/guilds.js | head -30Repository: VolvoxLLC/volvox-bot
Length of output: 1948
🏁 Script executed:
#!/bin/bash
# Check the configAllowlist to see if xp is included
cat src/api/utils/configAllowlist.jsRepository: VolvoxLLC/volvox-bot
Length of output: 2974
🏁 Script executed:
#!/bin/bash
# Find the exact endpoint that returns guild config with xp
rg -n "router\.get\(|READABLE_CONFIG" src/api/routes/guilds.js -A 10 | head -50Repository: VolvoxLLC/volvox-bot
Length of output: 1863
🏁 Script executed:
#!/bin/bash
# Get the full implementation of GET /:id/config endpoint
sed -n '828,860p' src/api/routes/guilds.jsRepository: VolvoxLLC/volvox-bot
Length of output: 877
🏁 Script executed:
#!/bin/bash
# Check what happens after the config is built - any normalization?
sed -n '828,880p' src/api/routes/guilds.jsRepository: VolvoxLLC/volvox-bot
Length of output: 1442
🏁 Script executed:
#!/bin/bash
# Check if there's any normalization on config write (PATCH)
rg -n "setConfigValue|validateConfigPatchBody" src/api/routes/guilds.js -A 20 | head -50Repository: VolvoxLLC/volvox-bot
Length of output: 2144
🏁 Script executed:
#!/bin/bash
# Check the config write validation
cat src/api/utils/validateConfigPatch.js | head -80Repository: VolvoxLLC/volvox-bot
Length of output: 3461
🏁 Script executed:
#!/bin/bash
# Check if xp actions are normalized when saved to database
rg -n "normalizeXpAction|normalizeXpConfig" src/modules/config.jsRepository: VolvoxLLC/volvox-bot
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Check if there's any data migration that normalizes xp actions in database
fd -type f -name "*.js" src/ | xargs rg -l "migration|migrate" | head -10Repository: VolvoxLLC/volvox-bot
Length of output: 494
🏁 Script executed:
#!/bin/bash
# Verify the exact behavior: check if createAction always populates message for sendDm/announce
sed -n '97,125p' web/src/components/dashboard/xp-level-actions-editor.tsxRepository: VolvoxLLC/volvox-bot
Length of output: 848
🏁 Script executed:
#!/bin/bash
# Check if there's any post-fetch normalization of actions in the dashboard
rg -n "rawLevelActions|rawDefaultActions" web/src/components/dashboard/xp-level-actions-editor.tsx -B 3 -A 3Repository: VolvoxLLC/volvox-bot
Length of output: 703
🏁 Script executed:
#!/bin/bash
# Check migrations directory for xp action normalization
find src -name "*migrat*" -type fRepository: VolvoxLLC/volvox-bot
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Check if there's database schema initialization that normalizes xp
rg -n "xp|template|message" src/db.js | head -20Repository: VolvoxLLC/volvox-bot
Length of output: 995
🏁 Script executed:
#!/bin/bash
# Most importantly: verify the onChange behavior at line 475
sed -n '470,480p' web/src/components/dashboard/xp-level-actions-editor.tsxRepository: VolvoxLLC/volvox-bot
Length of output: 680
🏁 Script executed:
#!/bin/bash
# Check if normalizeDraftAction or normalizeDraftEmbed has any special handling for template
sed -n '151,170p' web/src/components/dashboard/xp-level-actions-editor.tsxRepository: VolvoxLLC/volvox-bot
Length of output: 696
🏁 Script executed:
#!/bin/bash
# Verify if there's any preprocessing before normalizeDraftAction is called
rg -n "normalizeDraftAction|draftConfig.xp" web/src/components/dashboard/xp-level-actions-editor.tsx -B 5 | head -40Repository: VolvoxLLC/volvox-bot
Length of output: 1522
Fix legacy template field hydration for sendDm/announce actions.
When loading legacy configs that only have a template field (no message), normalizeDraftAction spreads createAction(type) first, which seeds a default message. The subsequent spread of the loaded action adds template but doesn't override the default message. This causes the UI (line 474) to display the default message instead of the saved template on initial load.
The onChange handler (line 475) keeps both fields in sync going forward, so the issue only manifests on first load of legacy data.
Solution: Before spreading the action, check if it has template but lacks message, and copy the template value to message:
function normalizeDraftAction(action?: DeepPartial<XpLevelAction> | null): XpLevelAction {
const type = action?.type ?? 'grantRole';
// Restore template to message for legacy configs
if (action && typeof action.template === 'string' && !action.message) {
action = { ...action, message: action.template };
}
return {
...createAction(type),
...action,
id: action?.id ?? createStableId(),
embed: normalizeDraftEmbed(action?.embed),
type,
};
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/dashboard/xp-level-actions-editor.tsx` around lines 151 -
160, normalizeDraftAction currently seeds a default message via
createAction(type) then spreads the legacy action which may only have template
set, so the saved template never becomes message on first load; to fix, inside
normalizeDraftAction (before spreading/return), detect when action exists and
action.template is a string but action.message is falsy, and copy template into
message (e.g., action = { ...action, message: action.template }) so the returned
XpLevelAction uses the legacy template as message for sendDm/announce actions.



Summary
nickSuffix, and embed-builder outputDetails
XpLevelActionsEditorin the Level-Up Actions dashboard card with role/channel pickers, markdown editing, embed editing, and action reorderinglevelUpDmflow intact for compatibility while enabling the broader action pipeline editorCloses #364