Skip to content

feat(xp): add dashboard editor for level-up actions#443

Open
MohsinCoding wants to merge 12 commits intomainfrom
mohsin/xp-actions
Open

feat(xp): add dashboard editor for level-up actions#443
MohsinCoding wants to merge 12 commits intomainfrom
mohsin/xp-actions

Conversation

@MohsinCoding
Copy link
Copy Markdown
Contributor

Summary

  • add a level-up action editor to the dashboard for default actions and per-level pipelines
  • normalize XP action payloads so the runtime accepts the richer dashboard action shape, including message/template aliases, nickSuffix, and embed-builder output
  • extend message payload building and validation to support richer embed content for level-up actions
  • add regression coverage for XP action normalization and rich embed payload generation

Details

  • mounts a new XpLevelActionsEditor in the Level-Up Actions dashboard card with role/channel pickers, markdown editing, embed editing, and action reordering
  • updates the XP config types and server-side validation schema to cover the supported level-up action fields
  • keeps the existing levelUpDm flow intact for compatibility while enabling the broader action pipeline editor

Closes #364

@railway-app
Copy link
Copy Markdown

railway-app bot commented Apr 7, 2026

This PR was not deployed automatically as @MohsinCoding does not have access to the Railway project.

In order to get automatic PR deploys, please add @MohsinCoding to your workspace on Railway.

Copilot AI review requested due to automatic review settings April 7, 2026 03:31
@github-project-automation github-project-automation bot moved this to Backlog in Volvox.Bot Apr 7, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 7, 2026

⚠️ No Changeset found

Latest commit: 943358c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • XP level actions editor UI for managing guild automation
    • Expanded embed support with configurable fields, rich footers, images, and timestamps
  • Improvements

    • Tighter XP configuration validation (required integers, stricter embed schema)
    • Better normalization of legacy XP action payloads
    • Added error boundary to logs page
  • Tests

    • New tests covering embed rendering, action normalization, and XP validation

Walkthrough

Adds 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

Cohort / File(s) Summary
Config validation & tests
src/api/utils/configValidation.js, tests/api/utils/configValidation.test.js
Introduce reusable XP schema fragments and level-action entry schema; constrain xp.levelActions/xp.defaultActions action shapes (fixed type, structured embed), add integer: true handling, implement anyOf validation and integer enforcement, and add tests for integer level, xpBonus bounds, embed whitelist, ids, and unknown-key rejection.
Action normalization & payload rendering
src/modules/actions/normalizeAction.js, src/modules/actions/buildPayload.js, tests/modules/actions/normalizeAction.test.js, tests/modules/actions/buildPayload.test.js
Add normalizeXpAction(action) to canonicalize legacy action shapes (template/prefix/suffix, embed/footer/fields/thumbnail/image/timestamp normalization). Enhance buildPayload to render embed fields (truncate to 25 with warning), truncate field title/value limits, render footer (string/object, zero-width fallback, truncate), support image and timestamp, and add tests covering serialization and warnings.
Level-up pipeline integration
src/modules/levelUpActions.js
Apply normalizeXpAction to DM and non-DM actions when resolving levelActions and defaultActions, ensuring pipeline receives normalized action objects.
Dashboard types & editor UI
web/src/types/config.ts, web/src/components/dashboard/xp-level-actions-editor.tsx, web/src/components/dashboard/config-categories/onboarding-growth.tsx
Add typed embed config (XpActionEmbedField, XpActionEmbedConfig, XpActionEmbedThumbnailType), add id and template to XpLevelAction/XpLevelActionEntry, add nickSuffix action type, implement XpLevelActionsEditor component, and wire it into onboarding-growth UI with toggle handling.
Logs page safety
web/src/app/dashboard/logs/page.tsx
Wrap LogsPage output in an ErrorBoundary and explicitly type channel state as DiscordChannel[].
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(xp): add dashboard editor for level-up actions' directly and clearly summarizes the main change — adding a dashboard editor component for XP level-up actions.
Description check ✅ Passed The PR description clearly relates to the changeset, detailing the addition of an XpLevelActionsEditor, normalization of XP action payloads, schema updates, and test coverage for the new functionality.
Linked Issues check ✅ Passed The PR implements core requirements from issue #364: adds the action pipeline system, supports multiple action types, provides normalization and validation, includes tests, and adds the dashboard editor component with configuration support.
Out of Scope Changes check ✅ Passed All code changes align with the PR objectives of adding the XP level-up actions editor, normalizing payloads, and extending validation; one unrelated change (ErrorBoundary in logs page) is minimal and defensive.

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

📋 Issue Planner

Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).

View plan for ticket: #364

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mohsin/xp-actions
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch mohsin/xp-actions
  • 🛠️ Publish Changes: Commit on current branch
  • 🛠️ Publish Changes: Create PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 7, 2026

Greptile Summary

This PR adds a full XP level-up action editor to the dashboard, normalizes the action payload shape between the dashboard editor and the runtime pipeline, and extends buildPayload with rich embed support (fields, image, timestamp, footer hardening). The normalizeXpAction layer correctly bridges legacy configs and the new editor shape, and previous concerns around empty footer text and string color drops are resolved in this revision.

  • P1 — silent action loss on duplicate levels: XP_LEVEL_ACTION_ENTRY_SCHEMA is missing uniqueBy: 'level'. The parallel levelUpDm.messages array already carries this constraint. Without it, a direct API POST with two entries at the same level is accepted; resolveActions silently overwrites the first in levelActionsMap, dropping all of its actions.

Confidence Score: 4/5

  • Safe to merge after adding uniqueBy: 'level' to the levelActions schema — normal dashboard usage is unaffected but the gap allows silent action loss via direct API calls.
  • All prior review concerns (empty footer crash, string color drop) are resolved. The normalization layer is correct and idempotent, embed building is well-hardened, and test coverage is solid. One P1 remains: the missing uniqueBy constraint on levelActions means duplicate-level entries accepted by the API silently cause action loss in resolveActions, unlike the parallel levelUpDm.messages array which already enforces uniqueness.
  • src/api/utils/configValidation.js — XP_LEVEL_ACTION_ENTRY_SCHEMA needs uniqueBy: 'level'

Important Files Changed

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

@coveralls
Copy link
Copy Markdown

coveralls commented Apr 7, 2026

Coverage Status

coverage: 90.55% (-0.08%) from 90.633% — mohsin/xp-actions into main

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds 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 XpLevelActionsEditor UI 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.

Comment thread src/modules/actions/buildPayload.js
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx Outdated
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx
Comment thread src/api/utils/configValidation.js Outdated
Comment thread src/api/utils/configValidation.js Outdated
Comment thread src/modules/actions/normalizeAction.js Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between d130899 and 26039e9.

📒 Files selected for processing (9)
  • src/api/utils/configValidation.js
  • src/modules/actions/buildPayload.js
  • src/modules/actions/normalizeAction.js
  • src/modules/levelUpActions.js
  • tests/modules/actions/buildPayload.test.js
  • tests/modules/actions/normalizeAction.test.js
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/src/components/dashboard/xp-level-actions-editor.tsx
  • web/src/types/config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (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, never require()/module.exports

Files:

  • tests/modules/actions/buildPayload.test.js
  • tests/modules/actions/normalizeAction.test.js
  • src/modules/levelUpActions.js
  • src/modules/actions/buildPayload.js
  • src/modules/actions/normalizeAction.js
  • src/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.js
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • tests/modules/actions/normalizeAction.test.js
  • src/modules/levelUpActions.js
  • src/modules/actions/buildPayload.js
  • src/modules/actions/normalizeAction.js
  • web/src/components/dashboard/xp-level-actions-editor.tsx
  • src/api/utils/configValidation.js
  • web/src/types/config.ts
tests/**/*.test.js

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

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

Files:

  • tests/modules/actions/buildPayload.test.js
  • tests/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.js
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • tests/modules/actions/normalizeAction.test.js
  • src/modules/levelUpActions.js
  • src/modules/actions/buildPayload.js
  • src/modules/actions/normalizeAction.js
  • web/src/components/dashboard/xp-level-actions-editor.tsx
  • src/api/utils/configValidation.js
  • web/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.js
  • tests/modules/actions/normalizeAction.test.js
  • src/modules/levelUpActions.js
  • src/modules/actions/buildPayload.js
  • src/modules/actions/normalizeAction.js
  • src/api/utils/configValidation.js
  • web/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.tsx
  • web/src/components/dashboard/xp-level-actions-editor.tsx
  • web/src/types/config.ts
src/**/*.js

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

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

Files:

  • src/modules/levelUpActions.js
  • src/modules/actions/buildPayload.js
  • src/modules/actions/normalizeAction.js
  • src/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 to config.json

Files:

  • src/modules/levelUpActions.js
  • src/modules/actions/buildPayload.js
  • 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/levelUpActions.js
  • src/modules/actions/buildPayload.js
  • src/modules/actions/normalizeAction.js
  • src/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.js
  • tests/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.js
  • tests/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.tsx
  • web/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.

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

web/src/components/dashboard/xp-level-actions-editor.tsx

[warning] 186-190: Extract this nested ternary operation into an independent statement.

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


[warning] 576-581: Mark the props of the component as read-only.

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


[warning] 511-511: Mark the props of the component as read-only.

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


[warning] 243-253: Mark the props of the component as read-only.

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


[warning] 194-196: Extract this nested ternary operation into an independent statement.

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


[warning] 643-643: Do not use Array index in keys

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


[warning] 200-202: Extract this nested ternary operation into an independent statement.

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


[failure] 729-729: Refactor this code to not nest functions more than 4 levels deep.

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


[warning] 188-190: Extract this nested ternary operation into an independent statement.

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


[failure] 161-161: Refactor this function to reduce its Cognitive Complexity from 19 to the 15 allowed.

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

🔇 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 / updateDraftConfig path 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 match buildTemplateContext() keys.

The aliases {{avatar}} and {{serverIcon}} are properly defined in buildTemplateContext() (lines 135 and 143 in src/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.

Comment thread src/api/utils/configValidation.js Outdated
Comment thread src/modules/actions/buildPayload.js
Comment thread src/modules/actions/normalizeAction.js Outdated
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx Outdated
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx Outdated
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx Outdated
@github-project-automation github-project-automation bot moved this from Backlog to In Review in Volvox.Bot Apr 7, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 26039e9 and a2f8be6.

📒 Files selected for processing (4)
  • biome.json
  • src/modules/actions/normalizeAction.js
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/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.tsx
  • src/modules/actions/normalizeAction.js
  • web/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.tsx
  • web/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.tsx
  • src/modules/actions/normalizeAction.js
  • web/src/components/dashboard/xp-level-actions-editor.tsx
**/*.js

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

Use ESM-only syntax: import/export, never require()/module.exports

Files:

  • src/modules/actions/normalizeAction.js
src/**/*.js

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

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

Files:

  • src/modules/actions/normalizeAction.js
src/modules/**/*.js

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

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

Files:

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

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

web/src/components/dashboard/xp-level-actions-editor.tsx

[warning] 190-194: Extract this nested ternary operation into an independent statement.

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


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

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


[failure] 743-743: Refactor this code to not nest functions more than 4 levels deep.

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


[warning] 589-594: Mark the props of the component as read-only.

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


[failure] 162-162: Refactor this function to reduce its Cognitive Complexity from 19 to the 15 allowed.

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


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

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


[warning] 192-194: Extract this nested ternary operation into an independent statement.

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


[warning] 250-261: Mark the props of the component as read-only.

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


[warning] 523-523: Mark the props of the component as read-only.

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


[warning] 206-208: Extract this nested ternary operation into an independent statement.

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


[warning] 198-202: Extract this nested ternary operation into an independent statement.

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

🔇 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 normalizeXpAction function correctly handles:

  • Pass-through for non-object inputs
  • Template derivation from legacy message, prefix, or suffix fields 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 in toBuilderConfig.

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 ActionCardProps interface properties as readonly to 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_OPTIONS covers all action types per the PR objectives
  • TEMPLATE_VARIABLES provides the ~20 documented template variables
  • TEMPLATE_SAMPLES provides sensible preview values

250-512: LGTM!

The ActionCard component 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 XpLevelActionsEditor provides 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 saving state propagation to disable controls during persistence

The integration with updateDraftConfig maintains immutability correctly.

web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx (1)

14-14: LGTM!

The XpLevelActionsEditor is correctly integrated:

  • Import added appropriately
  • Props match the component's interface (draftConfig, guildId, saving, updateDraftConfig)
  • Placement at the top of advancedContent follows the pattern for advanced feature settings
  • Feature visibility is properly gated via showFeature('xp-level-actions')

Also applies to: 673-678

Comment thread biome.json Outdated
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx Outdated
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx Outdated
@BillChirico BillChirico added backend javascript Pull requests that update javascript code labels Apr 7, 2026
Copilot AI review requested due to automatic review settings April 8, 2026 19:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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() processes schema.anyOf before the value === null check, which breaks nullable anyOf schemas (e.g. XP_EMBED_FOOTER_SCHEMA with nullable: true). A config like footer: null will currently fail validation because each anyOf branch rejects null. Handle null (and undefined) before anyOf, or make the anyOf wrapper explicitly accept null when schema.nullable is 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;
  }

Comment thread src/modules/actions/normalizeAction.js
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx
Comment on lines +653 to +655
const levelEntries = (draftConfig.xp?.levelActions ?? []).map(normalizeDraftEntry);
const defaultActions = (draftConfig.xp?.defaultActions ?? []).map(normalizeDraftAction);
const nextUnusedLevel = getNextUnusedLevel(levelEntries);
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx
coderabbitai[bot]
coderabbitai bot previously approved these changes Apr 10, 2026
Comment thread src/modules/actions/normalizeAction.js Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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() processes schema.anyOf before handling value === null. This means schemas marked nullable: true but implemented via anyOf (e.g. XP_EMBED_FOOTER_SCHEMA) will still reject null, because the candidate schemas don’t include nullable. Consider moving the null/undefined short-circuit before the anyOf handling (or teaching anyOf to respect schema.nullable) so nullable: true works 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;

Comment thread src/modules/actions/normalizeAction.js
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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

Inline comments:
In `@src/api/utils/configValidation.js`:
- Around line 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

📥 Commits

Reviewing files that changed from the base of the PR and between a2f8be6 and 9b34445.

📒 Files selected for processing (10)
  • src/api/utils/configValidation.js
  • src/modules/actions/buildPayload.js
  • src/modules/actions/normalizeAction.js
  • src/modules/levelUpActions.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/buildPayload.test.js
  • tests/modules/actions/normalizeAction.test.js
  • web/src/components/dashboard/config-categories/onboarding-growth.tsx
  • web/src/components/dashboard/xp-level-actions-editor.tsx
  • web/src/types/config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (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, never require()/module.exports

Files:

  • src/modules/levelUpActions.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/buildPayload.test.js
  • src/modules/actions/buildPayload.js
  • src/api/utils/configValidation.js
  • tests/modules/actions/normalizeAction.test.js
  • src/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.js
  • tests/api/utils/configValidation.test.js
  • web/src/components/dashboard/config-categories/onboarding-growth.tsx
  • tests/modules/actions/buildPayload.test.js
  • src/modules/actions/buildPayload.js
  • src/api/utils/configValidation.js
  • tests/modules/actions/normalizeAction.test.js
  • web/src/types/config.ts
  • src/modules/actions/normalizeAction.js
  • web/src/components/dashboard/xp-level-actions-editor.tsx
src/**/*.js

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

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

Files:

  • src/modules/levelUpActions.js
  • src/modules/actions/buildPayload.js
  • src/api/utils/configValidation.js
  • 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 to config.json

Files:

  • src/modules/levelUpActions.js
  • src/modules/actions/buildPayload.js
  • src/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.js
  • tests/api/utils/configValidation.test.js
  • web/src/components/dashboard/config-categories/onboarding-growth.tsx
  • tests/modules/actions/buildPayload.test.js
  • src/modules/actions/buildPayload.js
  • src/api/utils/configValidation.js
  • tests/modules/actions/normalizeAction.test.js
  • web/src/types/config.ts
  • src/modules/actions/normalizeAction.js
  • web/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.js
  • tests/api/utils/configValidation.test.js
  • web/src/components/dashboard/config-categories/onboarding-growth.tsx
  • tests/modules/actions/buildPayload.test.js
  • src/modules/actions/buildPayload.js
  • src/api/utils/configValidation.js
  • tests/modules/actions/normalizeAction.test.js
  • web/src/types/config.ts
  • src/modules/actions/normalizeAction.js
  • web/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.js
  • tests/api/utils/configValidation.test.js
  • web/src/components/dashboard/config-categories/onboarding-growth.tsx
  • tests/modules/actions/buildPayload.test.js
  • src/modules/actions/buildPayload.js
  • src/api/utils/configValidation.js
  • tests/modules/actions/normalizeAction.test.js
  • web/src/types/config.ts
  • src/modules/actions/normalizeAction.js
  • web/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 the node environment, matching the src/ structure in the tests/ directory
Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead

Files:

  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/buildPayload.test.js
  • tests/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.tsx
  • web/src/types/config.ts
  • 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/config-categories/onboarding-growth.tsx
  • web/src/types/config.ts
  • web/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.tsx
  • web/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
  • 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/config-categories/onboarding-growth.tsx
  • 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 **/*.{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.js
  • tests/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.

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

web/src/components/dashboard/xp-level-actions-editor.tsx

[warning] 640-640: Refactor this code to not use nested template literals.

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

Comment thread src/api/utils/configValidation.js Outdated
Comment thread src/modules/actions/normalizeAction.js
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx Outdated
Copilot AI review requested due to automatic review settings April 10, 2026 14:28
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@src/api/utils/configValidation.js`:
- Around line 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9b34445 and d7fdf40.

📒 Files selected for processing (3)
  • src/api/utils/configValidation.js
  • src/modules/actions/normalizeAction.js
  • web/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, never require()/module.exports

Files:

  • src/api/utils/configValidation.js
  • src/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.js
  • src/modules/actions/normalizeAction.js
  • web/src/components/dashboard/xp-level-actions-editor.tsx
src/**/*.js

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

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

Files:

  • src/api/utils/configValidation.js
  • src/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.js
  • src/modules/actions/normalizeAction.js
  • web/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.js
  • src/modules/actions/normalizeAction.js
  • web/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.js
  • src/modules/actions/normalizeAction.js
  • web/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 to config.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.

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

web/src/components/dashboard/xp-level-actions-editor.tsx

[warning] 271-273: Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZ13y-0IlvW5v9mPUWj0&open=AZ13y-0IlvW5v9mPUWj0&pullRequest=443


[warning] 256-260: Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZ13y-0IlvW5v9mPUWjx&open=AZ13y-0IlvW5v9mPUWjx&pullRequest=443


[warning] 258-260: Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZ13y-0IlvW5v9mPUWjy&open=AZ13y-0IlvW5v9mPUWjy&pullRequest=443


[warning] 653-653: Refactor this code to not use nested template literals.

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


[warning] 270-270: Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=VolvoxLLC_volvox-bot&issues=AZ13y-0IlvW5v9mPUWjz&open=AZ13y-0IlvW5v9mPUWjz&pullRequest=443

Comment thread src/api/utils/configValidation.js
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx
MohsinCoding and others added 7 commits April 10, 2026 10:32
…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
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Comment on lines +174 to +175
function fromSingleSelection(values: string[]): string {
return values[0] ?? '';
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

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

Suggested change
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];

Copilot uses AI. Check for mistakes.
Comment thread src/api/utils/configValidation.js Outdated
},
channelId: { type: 'string', nullable: true },
emoji: { type: 'string', nullable: true },
amount: { type: 'number', nullable: true },
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
amount: { type: 'number', nullable: true },
amount: { type: 'number', min: 1, nullable: true },

Copilot uses AI. Check for mistakes.
Comment thread web/src/types/config.ts
Comment on lines 286 to +311
/** 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;
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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 | 🟡 Minor

Allow embed: null in XpLevelAction.

src/api/utils/configValidation.js:73-96 explicitly allows xp.*Actions[].embed = null, and web/src/components/dashboard/xp-level-actions-editor.tsx:134-145 already 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 | 🟠 Major

Disallow duplicate xp.levelActions[].level values.

At runtime src/modules/levelUpActions.js:93-96 loads these entries into a Map, so a later duplicate silently overwrites the earlier one. Add the same uniqueBy: 'level' guard you already use for xp.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 | 🟠 Major

Hydrate template before the default seed wins.

createAction(type) prepopulates message / prefix / suffix, so actions that only
persist template render those canned defaults instead of the saved value. Since
src/modules/actions/normalizeAction.js uses template as 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

📥 Commits

Reviewing files that changed from the base of the PR and between d7fdf40 and 7e4328b.

📒 Files selected for processing (11)
  • src/api/utils/configValidation.js
  • src/modules/actions/buildPayload.js
  • src/modules/actions/normalizeAction.js
  • src/modules/levelUpActions.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/buildPayload.test.js
  • tests/modules/actions/normalizeAction.test.js
  • web/src/app/dashboard/logs/page.tsx
  • web/src/components/dashboard/config-categories/onboarding-growth.tsx
  • web/src/components/dashboard/xp-level-actions-editor.tsx
  • web/src/types/config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (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.tsx
  • web/src/app/dashboard/logs/page.tsx
  • src/modules/levelUpActions.js
  • src/api/utils/configValidation.js
  • tests/modules/actions/buildPayload.test.js
  • tests/api/utils/configValidation.test.js
  • src/modules/actions/buildPayload.js
  • web/src/types/config.ts
  • src/modules/actions/normalizeAction.js
  • tests/modules/actions/normalizeAction.test.js
  • web/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.tsx
  • web/src/app/dashboard/logs/page.tsx
  • web/src/types/config.ts
  • web/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.tsx
  • web/src/app/dashboard/logs/page.tsx
  • src/modules/levelUpActions.js
  • src/api/utils/configValidation.js
  • tests/modules/actions/buildPayload.test.js
  • tests/api/utils/configValidation.test.js
  • src/modules/actions/buildPayload.js
  • web/src/types/config.ts
  • src/modules/actions/normalizeAction.js
  • tests/modules/actions/normalizeAction.test.js
  • web/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.tsx
  • web/src/app/dashboard/logs/page.tsx
  • src/modules/levelUpActions.js
  • src/api/utils/configValidation.js
  • tests/modules/actions/buildPayload.test.js
  • tests/api/utils/configValidation.test.js
  • src/modules/actions/buildPayload.js
  • web/src/types/config.ts
  • src/modules/actions/normalizeAction.js
  • tests/modules/actions/normalizeAction.test.js
  • web/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.tsx
  • web/src/app/dashboard/logs/page.tsx
  • src/modules/levelUpActions.js
  • src/api/utils/configValidation.js
  • tests/modules/actions/buildPayload.test.js
  • tests/api/utils/configValidation.test.js
  • src/modules/actions/buildPayload.js
  • web/src/types/config.ts
  • src/modules/actions/normalizeAction.js
  • tests/modules/actions/normalizeAction.test.js
  • 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/config-categories/onboarding-growth.tsx
  • web/src/app/dashboard/logs/page.tsx
  • web/src/types/config.ts
  • web/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 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

Files:

  • web/src/app/dashboard/logs/page.tsx
**/*.js

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

Use ESM-only syntax: import/export, never require()/module.exports

Files:

  • src/modules/levelUpActions.js
  • src/api/utils/configValidation.js
  • tests/modules/actions/buildPayload.test.js
  • tests/api/utils/configValidation.test.js
  • src/modules/actions/buildPayload.js
  • src/modules/actions/normalizeAction.js
  • tests/modules/actions/normalizeAction.test.js
src/**/*.js

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

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

Files:

  • src/modules/levelUpActions.js
  • src/api/utils/configValidation.js
  • src/modules/actions/buildPayload.js
  • 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 to config.json

Files:

  • src/modules/levelUpActions.js
  • src/modules/actions/buildPayload.js
  • src/modules/actions/normalizeAction.js
tests/**/*.test.js

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

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

Files:

  • tests/modules/actions/buildPayload.test.js
  • tests/api/utils/configValidation.test.js
  • tests/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.tsx
  • web/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
  • 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} : 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.tsx
  • web/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.tsx
  • 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} : 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.js
  • tests/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 with ErrorBoundary.
This is a solid safeguard for rendering failures in the logs dashboard subtree.


12-12: Nice type-safety upgrade with DiscordChannel.
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.js simple 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'.

Comment thread src/api/utils/configValidation.js
Comment thread src/modules/actions/buildPayload.js
Comment thread web/src/components/dashboard/xp-level-actions-editor.tsx Outdated
Copilot AI review requested due to automatic review settings April 14, 2026 17:40
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment on lines 404 to 411
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,
},
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +746 to +758
{/* 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>
)}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

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

Copilot generated this review using guidance from organization custom instructions.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@src/api/utils/configValidation.js`:
- Around line 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7e4328b and 943358c.

📒 Files selected for processing (7)
  • src/api/utils/configValidation.js
  • src/modules/actions/buildPayload.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/buildPayload.test.js
  • web/src/components/dashboard/config-categories/onboarding-growth.tsx
  • web/src/components/dashboard/xp-level-actions-editor.tsx
  • web/src/types/config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (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.tsx
  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/buildPayload.test.js
  • src/modules/actions/buildPayload.js
  • src/api/utils/configValidation.js
  • web/src/types/config.ts
  • web/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.tsx
  • web/src/types/config.ts
  • web/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.tsx
  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/buildPayload.test.js
  • src/modules/actions/buildPayload.js
  • src/api/utils/configValidation.js
  • web/src/types/config.ts
  • web/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.tsx
  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/buildPayload.test.js
  • src/modules/actions/buildPayload.js
  • src/api/utils/configValidation.js
  • web/src/types/config.ts
  • web/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.tsx
  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/buildPayload.test.js
  • src/modules/actions/buildPayload.js
  • src/api/utils/configValidation.js
  • web/src/types/config.ts
  • 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/config-categories/onboarding-growth.tsx
  • web/src/types/config.ts
  • web/src/components/dashboard/xp-level-actions-editor.tsx
**/*.js

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

Use ESM-only syntax: import/export, never require()/module.exports

Files:

  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/buildPayload.test.js
  • src/modules/actions/buildPayload.js
  • src/api/utils/configValidation.js
tests/**/*.test.js

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

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

Files:

  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/buildPayload.test.js
src/**/*.js

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

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

Files:

  • src/modules/actions/buildPayload.js
  • src/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 to config.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.tsx
  • web/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.tsx
  • web/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.tsx
  • 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 **/*.{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-actions tab now correctly mirrors the reputation tab's toggle logic—both read from and write to xp.enabled and reputation.enabled together. This ensures the two related tabs stay in sync.


746-758: LGTM!

The XpLevelActionsEditor integration 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_TYPES constant ensures consistency between validation and runtime action handling.


465-472: LGTM!

The anyOf handling 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 with anyOf work correctly.

src/modules/actions/buildPayload.js (2)

13-39: LGTM!

The renderFooter helper 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.hoisted ensures 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 xpBonus amount rejection
  • Structured embed schema acceptance with all field types
  • Level action entry id field support
  • Unknown embed key rejection (closed schema)

512-517: LGTM!

Correctly verifies that levelUpDm.messages[].level also enforces integer-only values, mirroring the levelActions[].level constraint.

web/src/types/config.ts (2)

287-313: LGTM!

The XpActionEmbedConfig type 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_SCHEMA and normalizeEmbed behavior.


315-340: LGTM!

The XpLevelAction updates correctly add:

  • Optional id for stable React keys and config persistence
  • 'nickSuffix' discriminant matching XP_ACTION_TYPES in configValidation.js
  • Optional template field for runtime normalization compatibility
  • Strongly-typed embed field replacing the previous Record<string, unknown>
web/src/components/dashboard/xp-level-actions-editor.tsx (4)

676-686: LGTM!

The Array.isArray guards correctly handle malformed config data, preventing runtime errors when levelActions or defaultActions aren't arrays. The useMemo wrapping 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 toBuilderConfig function 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!

getNextUnusedLevel now correctly returns null when all levels 1–1000 are exhausted, and the "Add Level" button at line 768 disables when nextUnusedLevel == null.

Comment on lines +98 to +126
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,
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment on lines +151 to +160
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,
};
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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.tsx

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

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

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

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

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

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

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

Repository: VolvoxLLC/volvox-bot

Length of output: 694


🏁 Script executed:

#!/bin/bash
# Check the guilds API route
cat src/api/routes/guilds.js | head -100

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

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

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

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

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

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

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

Repository: VolvoxLLC/volvox-bot

Length of output: 2144


🏁 Script executed:

#!/bin/bash
# Check the config write validation
cat src/api/utils/validateConfigPatch.js | head -80

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

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

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

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

Repository: VolvoxLLC/volvox-bot

Length of output: 703


🏁 Script executed:

#!/bin/bash
# Check migrations directory for xp action normalization
find src -name "*migrat*" -type f

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

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

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

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

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

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

Labels

backend javascript Pull requests that update javascript code size:XL tests

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

EPIC(xp): auto-assign roles on level-up with dashboard configuration

4 participants