Skip to content

feat(xp): add configurable level-up dms#399

Merged
BillChirico merged 29 commits intomainfrom
mohsin/xp-levelup-dms
Apr 7, 2026
Merged

feat(xp): add configurable level-up dms#399
BillChirico merged 29 commits intomainfrom
mohsin/xp-levelup-dms

Conversation

@MohsinCoding
Copy link
Copy Markdown
Contributor

@MohsinCoding MohsinCoding commented Mar 26, 2026

Summary

  • add dedicated xp.levelUpDm config with validation, defaults, runtime wiring, and dashboard support
  • derive level-up DMs through the existing action/template pipeline instead of adding a separate sender
  • fix the dashboard XP config editor issues found while implementing the feature

What Changed

  • added xp.levelUpDm to the config contract, defaults, allowlist-backed save path, and validation
  • updated the level-up pipeline to derive sendDm actions per crossed level from xp.levelUpDm
  • added dashboard controls under the existing Level-Up Actions section for enablement, default message, per-level overrides, and preview
  • fixed per-level override textarea focus loss by using stable row identity instead of a key tied to the editable message text
  • fixed nested config diffing so newly added objects are saved as dotted leaf patches instead of invalid top-level object patches like xp
  • fixed Windows command loading by importing local command modules through file:// URLs

Verification

  • pnpm exec vitest run tests/api/utils/configValidation.test.js tests/modules/levelUpActions.test.js
  • pnpm exec vitest run tests/utils/loadCommands.test.js tests/commands.test.js
  • pnpm test -- web/tests/lib/config-utils.test.ts web/tests/components/dashboard/config-editor-autosave.test.tsx
  • pnpm typecheck in web
  • pnpm exec biome check on touched files

Closes #361

Copilot AI review requested due to automatic review settings March 26, 2026 23:29
@railway-app
Copy link
Copy Markdown

railway-app Bot commented Mar 26, 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.

@github-project-automation github-project-automation Bot moved this to Backlog in Volvox.Bot Mar 26, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 26, 2026

⚠️ No Changeset found

Latest commit: 802f041

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 Mar 26, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Level-Up DMs: enable automated direct messages with a default template (supports {{level}} and {{server}}), per-level overrides, live preview, and option to send on every level.
  • Bug Fixes / Validation

    • Prevent duplicate override levels and enforce non-empty/length limits for templates.
    • DM sending now respects scoped rate limits to avoid duplicate milestone messages.
  • Tests

    • Added coverage for DM generation, validation, rate-limiting, and UI flows.

Walkthrough

Adds a configurable XP level-up DM feature: new config schema and defaults, backend logic to generate/send per-level or default DMs (with scoped rate-limiting), dashboard UI/editor for templates and overrides, TypeScript types, config utils updates, and tests across validation, actions, rate-limiting, and UI flows.

Changes

Cohort / File(s) Summary
Config & Defaults
config.json, src/modules/xpDefaults.js, src/api/utils/configValidation.js, web/src/types/config.ts
Introduced xp.levelUpDm config and defaults; schema validation for levelUpDm (fields: enabled, sendOnEveryLevel, defaultMessage, messages), message length/pattern constraints, and array uniqueness by level.
Level-Up Action Logic
src/modules/levelUpActions.js, src/modules/reputation.js
Added helpers to build per-level DM actions and per-level rate-limit scope; resolveActions now appends DM actions for each crossed level; getXpConfig deep-merges levelUpDm with defaults.
DM Sending & Rate Limit
src/modules/actions/sendDm.js, tests/modules/actions/sendDm.test.js
Scoped rate-limiting keys introduced (guildId:userId:scope); checkDmRateLimit/recordDmSend accept optional scope. Added test ensuring scope-separated rate limits.
Dashboard UI & Editor
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx, web/src/components/dashboard/config-workspace/config-categories.ts
Added Level-Up DMs UI: toggles, default template editor, per-level override add/edit/remove with duplicate-level prevention, previews, and search entry for the feature. Exported buildLevelUpDmConfig helper.
Config Patch/Serialization Utilities
web/src/lib/config-utils.ts, web/tests/lib/config-utils.test.ts
Changed computePatches to treat/flatten plain objects, emit leaf patches, and preserve empty objects; added tests verifying nested-leaf patching and empty-object semantics.
Command Loader Import Fix
src/utils/loadCommands.js
Switched dynamic imports to use pathToFileURL(filePath).href for module loading.
Type & UI Helpers
web/src/lib/discord-markdown.ts, web/src/components/ui/discord-markdown-editor.tsx, web/src/components/ui/embed-builder.tsx, web/tests/components/ui/embed-builder.test.tsx
Added types for level-up DM, minor formatting/preview improvements, more stable keying and image preview component; added embed preview test.
Tests — Validation & Actions
tests/api/utils/configValidation.test.js, tests/modules/levelUpActions.test.js
Added validation tests for xp.levelUpDm (shape, duplicates, whitespace, length) and multiple resolveActions tests validating DM generation, default fallback, preservation of existing actions, multi-level skips, and disabled behavior.
Dashboard Integration Tests
web/tests/components/dashboard/config-editor-autosave.test.tsx, web/tests/components/dashboard/community-settings-section.test.tsx
Extended workspace integration tests and added unit for buildLevelUpDmConfig; added UI flows for editing level-up DMs, previews, and PATCH-saved dotted path assertions.
Misc. Web & UI Adjustments
web/src/components/dashboard/analytics-dashboard-sections.tsx, web/src/components/dashboard/analytics-dashboard.tsx, web/src/components/ui/discord-markdown-editor.tsx
Removed unused compareMode prop, adjusted imports/props, and small preview/cursor behavior tweaks.
API Route Formatting
src/api/routes/guilds.js, src/api/routes/members.js, src/api/routes/tickets.js
Formatting and handler refactors only; no logic or response shape changes.
Test Timeouts & Async Tuning
tests/commands.test.js, tests/db.test.js, tests/index.test.js, tests/logger.test.js, tests/modules/cli-process.test.js, tests/modules/events-uncaught-exit.test.js, tests/sentry.test.js
Increased several test timeouts to 30s and adjusted async sequencing in one CLI test to avoid flakiness.
Build Script
web/package.json
Updated typecheck script to run next typegen before tsc --noEmit.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.18% 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 configurable level-up dms' directly summarizes the main change in the changeset: adding a new configurable feature for level-up DMs to the XP system.
Description check ✅ Passed The PR description is well-structured and directly related to the changeset, covering the summary of changes, what changed, and verification steps without being off-topic.
Linked Issues check ✅ Passed The PR implements all primary coding objectives from issue #361: config schema validation [config.json, configValidation.js, xpDefaults.js], template interpolation [levelUpActions.js, sendDm.js], DM sending with per-level overrides and fallback [levelUpActions.js, sendDm.js], rate limiting via scoped rate limits [sendDm.js], dashboard UI [CommunitySettingsSection.tsx], and comprehensive tests [multiple test files].
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #361 or supporting infrastructure: xp.levelUpDm config and validation, level-up DM pipeline, dashboard UI, rate limiting, and bug fixes (command loading, config diffing, textarea focus) discovered while implementing the feature.

✏️ 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: #361

✨ 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-levelup-dms
  • 🛠️ Publish Changes: Commit on current branch
  • 🛠️ Publish Changes: Create PR

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

❤️ Share

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

@coveralls
Copy link
Copy Markdown

coveralls commented Mar 26, 2026

Coverage Status

coverage: 90.547% (-0.02%) from 90.57%
when pulling 802f041 on mohsin/xp-levelup-dms
into 4c34f9b on main.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 26, 2026

Greptile Summary

This PR adds a configurable xp.levelUpDm feature to the bot, enabling server admins to send customized DMs to members on level-up. The implementation correctly routes level-up DMs through the existing action/template pipeline with per-level rate-limit scoping (levelUpDm:<level>) to keep them independent of regular sendDm actions. Two dashboard bugs are also fixed: per-level override textareas no longer lose focus (stable originalIndex keys instead of message-text keys), and computePatches now correctly flattens newly added nested config objects into dotted leaf patches instead of emitting invalid top-level object patches.

  • Added xp.levelUpDm config block (enabled, sendOnEveryLevel, defaultMessage, messages) with full schema validation, uniqueBy enforcement on the messages array, and defaults in both xpDefaults.js and config.json
  • Wired getLevelUpDmAction into resolveActions so level-up DMs participate in the same action pipeline as grantRole, announce, etc., with per-level rateLimitScope: 'levelUpDm:<level>' preventing cross-level rate-limit collisions
  • Extended checkDmRateLimit/recordDmSend with an optional scope parameter (backward-compatible, defaults to 'default') so the new feature's DMs don't interfere with existing sendDm actions
  • Fixed computePatches in config-utils.ts to generate leaf-level patches when a new nested config section is first added from the dashboard
  • Added a full Level-Up DMs dashboard UI with enable/disable switch, sendOnEveryLevel toggle, default template editor with live preview, and per-level override management with duplicate-level protection

Confidence Score: 5/5

Safe to merge — implementation is correct, well-tested, and the previous rate-limit concern is resolved by per-level scoping

No P0 or P1 issues found. The core level-up DM pipeline logic, per-scope rate limiting, config validation schema, dashboard patch diffing fix, and UI are all implemented correctly and backed by tests. The previous reviewer concern about rate-limit collision between levelUpDm and existing sendDm actions is resolved by the new per-scope key ('levelUpDm:' vs 'default'). All remaining observations are latent edge cases with no impact on current behaviour.

No files require special attention — all changed files are well-scoped and adequately tested

Important Files Changed

Filename Overview
src/modules/levelUpActions.js Introduces getLevelUpDmAction with per-level rateLimitScope; wires levelUpDm sendDm into resolveActions after configured actions for each crossed level
src/modules/actions/sendDm.js Extends checkDmRateLimit/recordDmSend with optional scope parameter and getDmLimitKey helper; handleSendDm reads action.rateLimitScope defaulting to 'default'
src/api/utils/configValidation.js Adds levelUpDm sub-schema to xp.properties and uniqueBy uniqueness enforcement for messages array items
src/modules/reputation.js Adds levelUpDm deep-merge block in getXpConfig alongside roleRewards; preserves messages array via null-coalescing
src/modules/xpDefaults.js Adds levelUpDm defaults block matching config.json and dashboard constant
web/src/lib/config-utils.ts Fixes computePatches for newly added plain objects via flattenObjectToLeafPatches; adds isPlainObject/hasPlainObjectChild helpers
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx Adds Level-Up DMs UI with stable originalIndex-keyed override rows, useEffect draft sync, live preview, and duplicate-level protection
web/src/types/config.ts Adds XpLevelUpDmMessage and XpLevelUpDmConfig interfaces; extends XpConfig with non-optional levelUpDm field
config.json Adds levelUpDm block to xp section with values exactly matching XP_DEFAULTS
src/utils/loadCommands.js Cosmetic: extracts pathToFileURL(filePath).href to named fileUrl variable; no behavioral change

Sequence Diagram

sequenceDiagram
    participant Discord
    participant Reputation as reputation.js
    participant Pipeline as executeLevelUpPipeline
    participant Resolver as resolveActions
    participant LUD as getLevelUpDmAction
    participant DM as handleSendDm
    participant RateLimit as dmLimits Map

    Discord->>Reputation: message received
    Reputation->>Reputation: gainXp() detects level-up
    Reputation->>Pipeline: executeLevelUpPipeline({member, guild, previousLevel, newLevel, config})
    Pipeline->>Resolver: resolveActions(previousLevel, newLevel, config)
    loop for each crossed level
        Resolver->>Resolver: collect levelActions / defaultActions
        Resolver->>LUD: getLevelUpDmAction(level, config)
        alt levelUpDm.enabled AND (override.message for level OR sendOnEveryLevel)
            LUD-->>Resolver: {type:sendDm, rateLimitScope:levelUpDm:level}
        else disabled or no match
            LUD-->>Resolver: null
        end
    end
    Resolver-->>Pipeline: ordered [{level, action}] list
    loop for each action
        Pipeline->>DM: handleSendDm(action, context)
        DM->>RateLimit: checkDmRateLimit(guildId, userId, rateLimitScope)
        alt within 60s rate window for this scope
            RateLimit-->>DM: false (blocked)
        else allowed
            RateLimit-->>DM: true
            DM->>Discord: member.user.send(payload)
            DM->>RateLimit: recordDmSend(guildId, userId, rateLimitScope)
        end
    end
Loading

Reviews (12): Last reviewed commit: "fix(web): stabilize embed field fallback..." | Re-trigger Greptile

Comment thread src/modules/levelUpActions.js
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 new configurable xp.levelUpDm config section to send level-up milestone DMs via the existing level-up action/template pipeline, with full bot + API + dashboard wiring and associated fixes in the dashboard config editor.

Changes:

  • Introduces xp.levelUpDm defaults, schema validation (incl. unique per-level overrides), and action derivation into the level-up pipeline.
  • Updates the dashboard XP editor to manage level-up DM enablement, templates, overrides, search, preview, and correct patch generation for newly-added nested objects.
  • Fixes Windows ESM command loading by importing local command modules via file:// URLs.

Reviewed changes

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

Show a summary per file
File Description
web/tests/lib/config-utils.test.ts Adds coverage for dotted leaf patch generation when nested objects are newly added.
web/tests/components/dashboard/config-editor-autosave.test.tsx Expands integration tests for searching/editing/saving level-up DM settings and dotted patch behavior.
web/src/types/config.ts Extends the web config contract with XpLevelUpDmConfig / XpLevelUpDmMessage and adds xp.levelUpDm.
web/src/lib/config-utils.ts Enhances computePatches to flatten newly-added nested objects into dotted leaf patches.
web/src/components/dashboard/config-workspace/config-categories.ts Adds a search index entry for “Level-Up DMs”.
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx Adds UI controls, overrides, and preview rendering for level-up DM configuration.
tests/modules/levelUpActions.test.js Adds bot tests validating derived sendDm actions from xp.levelUpDm.
tests/api/utils/configValidation.test.js Adds API validation tests for xp.levelUpDm and uniqueness/length constraints.
src/utils/loadCommands.js Fixes dynamic command imports on Windows via pathToFileURL(...).href.
src/modules/xpDefaults.js Defines new XP defaults for levelUpDm.
src/modules/reputation.js Deep-merges levelUpDm config into resolved XP config (similar to roleRewards merge).
src/modules/levelUpActions.js Derives and appends sendDm actions for crossed levels based on xp.levelUpDm.
src/api/utils/configValidation.js Adds schema validation for xp.levelUpDm and introduces generic uniqueBy array validation.
config.json Adds xp.levelUpDm to the default config contract.

Comment on lines +338 to +349
sendOnEveryLevel: { type: 'boolean' },
defaultMessage: { type: 'string', maxLength: 2000 },
messages: {
type: 'array',
uniqueBy: 'level',
items: {
type: 'object',
required: ['level', 'message'],
properties: {
level: { type: 'number', min: 1, max: 1000 },
message: { type: 'string', minLength: 1, maxLength: 2000 },
},
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

xp.levelUpDm.defaultMessage currently allows empty/whitespace-only strings (only maxLength is enforced). Since sendDm ultimately renders this into payload.content, an empty template can lead to Discord API errors when the action fires. Consider enforcing minLength: 1 and a non-whitespace pattern (e.g. \S) for defaultMessage (and likely messages[].message for consistency, since it currently allows ' ' due to missing pattern).

Copilot uses AI. Check for mistakes.

function renderLevelUpDmPreview(template: string) {
return template.replace(/\{\{(\w+)\}\}/g, (match, key: string) => {
return key in LEVEL_UP_DM_PREVIEW_CONTEXT ? LEVEL_UP_DM_PREVIEW_CONTEXT[key] : match;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

renderLevelUpDmPreview uses key in LEVEL_UP_DM_PREVIEW_CONTEXT, which also matches properties on the prototype chain (e.g. {{toString}}, {{constructor}}) and can produce unexpected preview output. Use an own-property check (e.g. Object.hasOwn(...)) to match the server-side template engine behavior.

Suggested change
return key in LEVEL_UP_DM_PREVIEW_CONTEXT ? LEVEL_UP_DM_PREVIEW_CONTEXT[key] : match;
return Object.hasOwn(LEVEL_UP_DM_PREVIEW_CONTEXT, key)
? LEVEL_UP_DM_PREVIEW_CONTEXT[key]
: match;

Copilot uses AI. Check for mistakes.
Comment on lines +621 to +629
xp: {
...prev.xp,
levelUpDm: {
enabled: value,
sendOnEveryLevel: prev.xp?.levelUpDm?.sendOnEveryLevel ?? false,
defaultMessage:
prev.xp?.levelUpDm?.defaultMessage ??
'🎉 You reached **Level {{level}}** in **{{server}}**! Keep chatting!',
messages: prev.xp?.levelUpDm?.messages ?? [],
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The levelUpDm update blocks repeat the same default message literal and reconstruct the same object shape in multiple handlers. This duplication increases the risk of drift (e.g. updating the default in one place but not others). Consider extracting a shared DEFAULT_LEVEL_UP_DM_MESSAGE constant and/or a small helper that merges/initializes xp.levelUpDm so each handler only specifies the field it is changing.

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

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

Inline comments:
In `@tests/modules/levelUpActions.test.js`:
- Around line 104-172: Add a test exercising a skipped-level DM branch for
resolveActions: simulate leveling from 4 to 6 with levelUpDm enabled,
sendOnEveryLevel true/false as appropriate, and messages containing an override
for level 5 and a default for level 6; assert that resolveActions(4, 6, config)
returns two derived sendDm actions—one for level 5 using the override template
and one for level 6 using the default template—so the resolver emits
per-crossed-level DMs rather than only the final DM or incorrect combinations.

In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 732-749: The onClick handler that calls updateDraftConfig is
creating a new level override with a hardcoded level: 1 which can collide with
existing xp.levelUpDm.messages and cause server validation errors; change the
logic in the add-override code path (the updateDraftConfig call that modifies
xp.levelUpDm.messages) to compute a non-colliding level (e.g., find the max
existing messages[].level and use max+1 or scan for the first unused positive
integer) and append that leveled object, and also optionally guard by
filtering/validating duplicates before set (ensure xp.levelUpDm.messages
contains unique level values) so that the local form never produces duplicate
messages[].level that the backend will reject.
- Around line 52-55: Replace the prototype-inclusive check in
renderLevelUpDmPreview so only own properties from LEVEL_UP_DM_PREVIEW_CONTEXT
are used: in the replacer callback for template.replace, change the condition
that currently uses "key in LEVEL_UP_DM_PREVIEW_CONTEXT" to use
Object.hasOwn(LEVEL_UP_DM_PREVIEW_CONTEXT, key) (or
Object.prototype.hasOwnProperty.call(...) if needed) so placeholders like
{{toString}} are not substituted from the prototype.

In `@web/src/lib/config-utils.ts`:
- Around line 10-26: flattenObjectToLeafPatches currently omits patches for
empty plain objects ({}), causing transitions like undefined -> {} or {} ->
undefined to be lost; modify flattenObjectToLeafPatches so that when
isPlainObject(value) is true but Object.keys(value).length === 0 you push a leaf
patch { path: fullPath, value: {} } instead of recursing (i.e., treat empty
objects as leaves), and apply the same change to any other function/block with
the same plain-object recursion logic to ensure empty-object diffs are
preserved.

In `@web/tests/components/dashboard/config-editor-autosave.test.tsx`:
- Around line 263-306: After adding an override row the test must type into the
override textarea and assert focus/value persist across a rerender: locate the
newly added override textarea after clicking the "Add Override" button (use the
same DOM helpers around levelUpCard/advancedButton and the button with name 'Add
Override' to find the new row), use user.type to enter several characters into
that textarea, trigger a rerender (e.g., toggle advancedButton off then on or
perform an action that causes the component to re-render), then assert that
document.activeElement is the same textarea and its value still contains the
typed characters to verify the focus-retention/key-stability regression is
covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4cda93bf-245a-4f62-b47e-87598b1549dd

📥 Commits

Reviewing files that changed from the base of the PR and between e3514fc and 9266d23.

📒 Files selected for processing (14)
  • config.json
  • src/api/utils/configValidation.js
  • src/modules/levelUpActions.js
  • src/modules/reputation.js
  • src/modules/xpDefaults.js
  • src/utils/loadCommands.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/levelUpActions.test.js
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/src/components/dashboard/config-workspace/config-categories.ts
  • web/src/lib/config-utils.ts
  • web/src/types/config.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/tests/lib/config-utils.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Agent
  • GitHub Check: Greptile Review
  • GitHub Check: Test
  • GitHub Check: E2E Tests (2/2)
  • GitHub Check: E2E Tests (1/2)
🧰 Additional context used
📓 Path-based instructions (13)
**/*.js

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

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

Files:

  • src/utils/loadCommands.js
  • src/modules/reputation.js
  • src/modules/xpDefaults.js
  • src/modules/levelUpActions.js
  • tests/modules/levelUpActions.test.js
  • tests/api/utils/configValidation.test.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:

  • src/utils/loadCommands.js
  • src/modules/reputation.js
  • src/modules/xpDefaults.js
  • web/tests/lib/config-utils.test.ts
  • src/modules/levelUpActions.js
  • web/src/components/dashboard/config-workspace/config-categories.ts
  • tests/modules/levelUpActions.test.js
  • tests/api/utils/configValidation.test.js
  • src/api/utils/configValidation.js
  • web/src/types/config.ts
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/src/lib/config-utils.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/utils/loadCommands.js
  • src/modules/reputation.js
  • src/modules/xpDefaults.js
  • src/modules/levelUpActions.js
  • src/api/utils/configValidation.js
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*

Files:

  • src/utils/loadCommands.js
  • src/modules/reputation.js
  • src/modules/xpDefaults.js
  • web/tests/lib/config-utils.test.ts
  • src/modules/levelUpActions.js
  • web/src/components/dashboard/config-workspace/config-categories.ts
  • tests/modules/levelUpActions.test.js
  • tests/api/utils/configValidation.test.js
  • src/api/utils/configValidation.js
  • web/src/types/config.ts
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/src/lib/config-utils.ts
src/**/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{js,ts}: Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Community features should be gated behind config..enabled. Moderation commands are the exception.

Files:

  • src/utils/loadCommands.js
  • src/modules/reputation.js
  • src/modules/xpDefaults.js
  • src/modules/levelUpActions.js
  • src/api/utils/configValidation.js
**/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use parameterized SQL only; do not use string concatenation for SQL queries

Files:

  • src/utils/loadCommands.js
  • src/modules/reputation.js
  • src/modules/xpDefaults.js
  • web/tests/lib/config-utils.test.ts
  • src/modules/levelUpActions.js
  • web/src/components/dashboard/config-workspace/config-categories.ts
  • tests/modules/levelUpActions.test.js
  • tests/api/utils/configValidation.test.js
  • src/api/utils/configValidation.js
  • web/src/types/config.ts
  • web/src/lib/config-utils.ts
src/modules/**/*.js

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

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

Files:

  • src/modules/reputation.js
  • src/modules/xpDefaults.js
  • src/modules/levelUpActions.js
web/tests/**/*.test.{ts,tsx}

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

web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with the jsdom environment and React Testing Library, matching the web/src/ structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Files:

  • web/tests/lib/config-utils.test.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
web/src/**/*.{ts,tsx}

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

Never use console.* methods in web dashboard code; use appropriate logging mechanisms for React applications

Files:

  • web/src/components/dashboard/config-workspace/config-categories.ts
  • web/src/types/config.ts
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/src/lib/config-utils.ts
tests/**/*.test.js

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

tests/**/*.test.js: Write bot tests using Vitest 4 with 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/levelUpActions.test.js
  • tests/api/utils/configValidation.test.js
**/*.json

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

Use double quotes in JSON files (standard JSON format); this exception applies only to JSON files, not JavaScript/TypeScript

Files:

  • config.json
config.json

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

All community features in config.json should be gated behind a config.<feature>.enabled flag; moderation commands are always available regardless of config settings

Files:

  • config.json
{config.json,src/api/utils/configAllowlist.js}

📄 CodeRabbit inference engine (AGENTS.md)

Config-backed features must be added to config.json and src/api/utils/configAllowlist.js. If a key is missing from SAFE_CONFIG_KEYS, the dashboard cannot save it.

Files:

  • config.json
🧠 Learnings (10)
📚 Learning: 2026-03-26T00:04:14.673Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.673Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use src/logger.js; do not use console.*

Applied to files:

  • src/utils/loadCommands.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to src/commands/**/*.js : Create slash command definitions in `src/commands/`, exporting a slash command builder and an `execute` function

Applied to files:

  • src/utils/loadCommands.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure

Applied to files:

  • web/tests/lib/config-utils.test.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory

Applied to files:

  • web/tests/lib/config-utils.test.ts
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Applied to files:

  • web/tests/lib/config-utils.test.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.673Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.673Z
Learning: Applies to src/**/*.{js,ts} : Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls

Applied to files:

  • src/modules/levelUpActions.js
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)

Applied to files:

  • web/src/components/dashboard/config-workspace/config-categories.ts
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead

Applied to files:

  • tests/api/utils/configValidation.test.js
📚 Learning: 2026-03-10T23:29:51.063Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:29:51.063Z
Learning: Config editor save contract: maintain global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.673Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.673Z
Learning: Applies to {web/src/lib/page-titles.ts,web/src/**/+page.ts,web/src/**/+page.svelte} : New dashboard routes need title wiring in web/src/lib/page-titles.ts: use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation.

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx

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

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


[failure] 831-831: 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=AZ0sfIv2bExGPQYBhAcY&open=AZ0sfIv2bExGPQYBhAcY&pullRequest=399

web/src/lib/config-utils.ts

[warning] 79-79: This assertion is unnecessary since it does not change the type of the expression.

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


[warning] 79-79: This assertion is unnecessary since it does not change the type of the expression.

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

🔇 Additional comments (8)
src/utils/loadCommands.js (1)

3-3: Cross-platform dynamic import fix looks correct.

Switching to pathToFileURL(...).href for import() is a solid Windows-safe ESM approach.

Also applies to: 33-33, 36-36

src/modules/reputation.js (1)

55-61: levelUpDm deep-merge behavior is implemented cleanly.

Default-preserving merge plus messages fallback is consistent with the intended config contract.

config.json (1)

291-296: Default xp.levelUpDm config block is well-structured.

The section includes proper feature gating and sensible defaults for rollout.

web/tests/lib/config-utils.test.ts (1)

1-33: Great regression coverage for nested-object patch flattening.

This test precisely locks in the dotted leaf patch behavior for newly introduced config subtrees.

src/modules/xpDefaults.js (1)

11-16: XP_DEFAULTS extension is clean and consistent.

Adding levelUpDm here keeps the module’s single-source-of-truth pattern intact.

tests/api/utils/configValidation.test.js (1)

414-417: Validation tests for xp.levelUpDm are comprehensive for core constraints.

Good coverage on acceptance path plus duplicate-level, blank-message, and length-rejection paths.

Also applies to: 425-457

web/src/components/dashboard/config-workspace/config-categories.ts (1)

218-226: Search metadata addition for Level-Up DMs is solid.

The new item is well-tagged and mapped to the existing XP Level Actions feature surface.

src/modules/levelUpActions.js (1)

50-64: DM action derivation and injection into the level-crossing loop are implemented correctly.

This cleanly reuses the existing action pipeline while preserving per-level override and default fallback behavior.

Also applies to: 93-96

Comment thread tests/modules/levelUpActions.test.js
Comment thread web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx Outdated
Comment thread web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx Outdated
Comment thread web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx Outdated
Comment thread web/src/lib/config-utils.ts
Comment thread web/tests/components/dashboard/config-editor-autosave.test.tsx
@github-project-automation github-project-automation Bot moved this from Backlog to In Review in Volvox.Bot Mar 26, 2026
@BillChirico BillChirico added scope: backend Bot/runtime/backend logic scope: dashboard Web dashboard scope: engagement Community engagement size:XL type: feature labels Mar 27, 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/modules/actions/sendDm.js (1)

88-99: ⚠️ Potential issue | 🟠 Major

Don't let persisted actions choose their own DM rate-limit bucket.

xp.levelActions and xp.defaultActions still accept arbitrary action properties, so a configured sendDm can now set rateLimitScope and sidestep the intended throttle by picking a fresh bucket. Keep this scope in trusted pipeline metadata only, or ignore it for user-authored actions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/actions/sendDm.js` around lines 88 - 99, The code currently
allows persisted/user actions to set rateLimitScope via action.rateLimitScope;
change sendDm to ignore action.rateLimitScope for untrusted actions and derive
the scope only from trusted pipeline metadata (e.g., a trusted parameter on
templateContext or an explicit trusted arg) with fallback to 'default'; update
the line that sets rateLimitScope and ensure checkDmRateLimit(guild.id, userId,
rateLimitScope) and recordDmSend(guild.id, userId, rateLimitScope) use this
trusted scope (leave safeSend, buildPayload, member.user unchanged), and do not
accept action.rateLimitScope coming from xp.levelActions or xp.defaultActions.
♻️ Duplicate comments (1)
web/tests/components/dashboard/config-editor-autosave.test.tsx (1)

304-324: ⚠️ Potential issue | 🟡 Minor

Force a rerender before asserting focus retention.

This still checks the textarea immediately after typing, so the unstable-key bug can pass even if the row remounts on the next render. Trigger one rerender after Line 320 and then reassert that the same textarea keeps focus and value.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/tests/components/dashboard/config-editor-autosave.test.tsx` around lines
304 - 324, After typing into firstOverrideTextarea, force a rerender (e.g.,
await waitFor(() => {} ) or await act(() => Promise.resolve())) and then
re-query the textarea (use the same selection logic that produced
firstOverrideTextarea) and reassert that document.activeElement is that textarea
and it still has value 'Override text'; update the test around
firstOverrideTextarea, the await user.type call, and the subsequent assertions
to include this forced rerender + re-check to ensure focus retention across a
remount.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modules/levelUpActions.js`:
- Around line 50-74: getLevelUpDmAction currently returns a separate sendDm
action per level which, combined with handleSendDm's single rateLimitScope
('levelUpDm'), causes skipped-level DMs to be dropped; change the behavior to
collapse skipped-level notifications into a single DM by making
getLevelUpDmAction produce one action for a multi-level jump (e.g., accept the
final level or an array/range of levels and choose the highest-level override or
a single default message when sendOnEveryLevel is true) so only one sendDm is
created per pipeline, or alternatively move the limiter application out of
per-action rateLimitScope into the pipeline-level send step (handleSendDm) so
the limiter is applied once; update getLevelUpDmAction and the caller
consistently (or update handleSendDm) so rateLimitScope usage no longer causes
intermediate sends to be lost.

In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 747-765: The Add Override handler creates a new override with
message: '' which backend now rejects; in the onClick updater
(updateDraftConfig) where buildLevelUpDmConfig is called, seed the new messages
entry with a non-empty template instead of an empty string (use
getNextLevelUpDmOverrideLevel(existingMessages) to interpolate a placeholder
like "Congrats on reaching level {level}!" or similar) so the autosave won't
produce a server-rejected draft; update the object created in that block (the
message property of the new array element) to a valid template string.

In `@web/src/lib/config-utils.ts`:
- Around line 85-92: This branch currently emits leaf-null patches
(patches.push(..., value: null)) which leaves nested keys set to null; instead
emit deletion-style patches that match the backend prune/delete path — replace
value: null with value: undefined (or the project's canonical delete marker if
one exists) and when origVal is an object push a single patch for the parent
subtree (patches.push({ path: fullPath, value: undefined })) rather than
flattening to leaf-null entries; update the branch around
modHasKey/modVal/origVal and usages of flattenObjectToLeafPatches to produce the
deletion marker so setNestedValue on the backend can remove the subtree.

---

Outside diff comments:
In `@src/modules/actions/sendDm.js`:
- Around line 88-99: The code currently allows persisted/user actions to set
rateLimitScope via action.rateLimitScope; change sendDm to ignore
action.rateLimitScope for untrusted actions and derive the scope only from
trusted pipeline metadata (e.g., a trusted parameter on templateContext or an
explicit trusted arg) with fallback to 'default'; update the line that sets
rateLimitScope and ensure checkDmRateLimit(guild.id, userId, rateLimitScope) and
recordDmSend(guild.id, userId, rateLimitScope) use this trusted scope (leave
safeSend, buildPayload, member.user unchanged), and do not accept
action.rateLimitScope coming from xp.levelActions or xp.defaultActions.

---

Duplicate comments:
In `@web/tests/components/dashboard/config-editor-autosave.test.tsx`:
- Around line 304-324: After typing into firstOverrideTextarea, force a rerender
(e.g., await waitFor(() => {} ) or await act(() => Promise.resolve())) and then
re-query the textarea (use the same selection logic that produced
firstOverrideTextarea) and reassert that document.activeElement is that textarea
and it still has value 'Override text'; update the test around
firstOverrideTextarea, the await user.type call, and the subsequent assertions
to include this forced rerender + re-check to ensure focus retention across a
remount.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bb59eef2-c445-434c-98da-5664fb939fb9

📥 Commits

Reviewing files that changed from the base of the PR and between 9266d23 and 73e845c.

📒 Files selected for processing (10)
  • src/api/utils/configValidation.js
  • src/modules/actions/sendDm.js
  • src/modules/levelUpActions.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/levelUpActions.test.js
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/src/lib/config-utils.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/tests/lib/config-utils.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Greptile Review
  • GitHub Check: E2E Tests (2/2)
  • GitHub Check: Test
  • GitHub Check: E2E Tests (1/2)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.js

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

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

Files:

  • tests/modules/actions/sendDm.test.js
  • src/modules/levelUpActions.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/levelUpActions.test.js
  • src/modules/actions/sendDm.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/sendDm.test.js
  • web/tests/lib/config-utils.test.ts
  • src/modules/levelUpActions.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/levelUpActions.test.js
  • src/modules/actions/sendDm.js
  • src/api/utils/configValidation.js
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/src/lib/config-utils.ts
tests/**/*.test.js

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

tests/**/*.test.js: Write bot tests using Vitest 4 with 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/sendDm.test.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/levelUpActions.test.js
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*

Files:

  • tests/modules/actions/sendDm.test.js
  • web/tests/lib/config-utils.test.ts
  • src/modules/levelUpActions.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/levelUpActions.test.js
  • src/modules/actions/sendDm.js
  • src/api/utils/configValidation.js
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/src/lib/config-utils.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use parameterized SQL only; do not use string concatenation for SQL queries

Files:

  • tests/modules/actions/sendDm.test.js
  • web/tests/lib/config-utils.test.ts
  • src/modules/levelUpActions.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/levelUpActions.test.js
  • src/modules/actions/sendDm.js
  • src/api/utils/configValidation.js
  • web/src/lib/config-utils.ts
web/tests/**/*.test.{ts,tsx}

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

web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with the jsdom environment and React Testing Library, matching the web/src/ structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Files:

  • web/tests/lib/config-utils.test.ts
  • web/tests/components/dashboard/config-editor-autosave.test.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/sendDm.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/sendDm.js
src/**/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{js,ts}: Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Community features should be gated behind config..enabled. Moderation commands are the exception.

Files:

  • src/modules/levelUpActions.js
  • src/modules/actions/sendDm.js
  • src/api/utils/configValidation.js
web/src/**/*.{ts,tsx}

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

Never use console.* methods in web dashboard code; use appropriate logging mechanisms for React applications

Files:

  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/src/lib/config-utils.ts
🧠 Learnings (8)
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure

Applied to files:

  • web/tests/lib/config-utils.test.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory

Applied to files:

  • web/tests/lib/config-utils.test.ts
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Applied to files:

  • web/tests/lib/config-utils.test.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead

Applied to files:

  • web/tests/lib/config-utils.test.ts
📚 Learning: 2026-03-26T00:04:14.673Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.673Z
Learning: Applies to src/**/*.{js,ts} : Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls

Applied to files:

  • src/modules/levelUpActions.js
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)

Applied to files:

  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.673Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.673Z
Learning: Applies to src/**/*.{js,ts} : Community features should be gated behind config.<feature>.enabled. Moderation commands are the exception.

Applied to files:

  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
📚 Learning: 2026-03-10T23:29:51.063Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:29:51.063Z
Learning: Config editor save contract: maintain global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
🪛 GitHub Check: SonarCloud Code Analysis
src/api/utils/configValidation.js

[warning] 339-339: String.raw should be used to avoid escaping \.

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


[warning] 348-348: String.raw should be used to avoid escaping \.

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

web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx

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

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


[failure] 842-842: 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=AZ0sfIv2bExGPQYBhAcY&open=AZ0sfIv2bExGPQYBhAcY&pullRequest=399


[failure] 803-803: 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=AZ08t8sO2pYty83ebGum&open=AZ08t8sO2pYty83ebGum&pullRequest=399

web/src/lib/config-utils.ts

[warning] 84-84: This assertion is unnecessary since it does not change the type of the expression.

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


[warning] 84-84: This assertion is unnecessary since it does not change the type of the expression.

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

🔇 Additional comments (4)
web/tests/components/dashboard/config-editor-autosave.test.tsx (3)

94-130: Good fixture setup for both seeded and missing xp.levelUpDm states.

This keeps the new onboarding tests focused and gives the autosave regression a realistic “missing subtree” baseline.


231-261: Nice search regression coverage.

This exercises the discoverability path for the new Level-Up Actions card without coupling the test to unrelated onboarding sections.


327-389: Good coverage for dotted leaf patches on newly added nested config.

The assertion against path === 'xp' makes the nested diff regression much harder to reintroduce.

web/src/lib/config-utils.ts (1)

10-17: Nice fix for empty-object diffs.

Treating {} as a leaf patch closes the dropped undefined -> {} and {} -> undefined cases.

Comment thread src/modules/levelUpActions.js
Comment thread web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx Outdated
@claude
Copy link
Copy Markdown

claude Bot commented Apr 3, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@BillChirico
Copy link
Copy Markdown
Collaborator

@greptile review

Copilot AI review requested due to automatic review settings April 3, 2026 04:37
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 29 out of 29 changed files in this pull request and generated 6 comments.

Comment on lines 32 to +38
* Check whether a DM is allowed under the rate limit.
*
* @param {string} guildId
* @param {string} userId
* @returns {boolean}
*/
export function checkDmRateLimit(guildId, userId) {
const key = `${guildId}:${userId}`;
export function checkDmRateLimit(guildId, userId, scope = 'default') {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

checkDmRateLimit/recordDmSend now accept a scope parameter, but the JSDoc only documents guildId and userId. Please update the JSDoc to include scope (and clarify whether rate limiting is intended to be per-scope or global per user).

Copilot uses AI. Check for mistakes.
}, [levelUpDmDefaultMessage]);

useEffect(() => {
setLevelUpDmOverrideDrafts(Object.fromEntries(levelUpDmOverrideDraftEntries));
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This effect fully resets levelUpDmOverrideDrafts from draftConfig whenever levelUpDmMessages changes. Because override messages are only persisted to draftConfig onBlur, any change that reorders/updates levelUpDmMessages (e.g. changing the level number, adding/removing an override) can wipe in-progress textarea edits. Consider merging new entries into the existing drafts state instead of overwriting it wholesale, or only initializing drafts for indices that don’t already exist.

Suggested change
setLevelUpDmOverrideDrafts(Object.fromEntries(levelUpDmOverrideDraftEntries));
setLevelUpDmOverrideDrafts((previousDrafts) => {
const nextDrafts: Record<number, string> = {};
for (const [originalIndex, message] of levelUpDmOverrideDraftEntries) {
nextDrafts[originalIndex] =
previousDrafts[originalIndex] !== undefined
? previousDrafts[originalIndex]
: message;
}
return nextDrafts;
});

Copilot uses AI. Check for mistakes.
Comment on lines 476 to 480

return (
<div
key={field.id ?? `field-${i}`}
key={field.id ?? `field-${field.name}-${field.value}-${field.inline}`}
className={cn(field.inline ? 'col-span-1' : 'col-span-3')}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The fallback React key for rendered fields is derived from mutable content (field.name, field.value, field.inline). If field.id is missing (e.g. initial hydration before ensureFieldIds runs), typing will change the key and can cause React to remount nodes (losing selection/focus). Prefer a stable fallback (e.g. index) until IDs are guaranteed, or ensure IDs exist before first render.

Copilot uses AI. Check for mistakes.
Comment thread web/src/components/ui/embed-builder.tsx Outdated
{value.fields.map((field, i) => (
<div
key={field.id ?? `field-editor-${i}`}
key={field.id ?? `field-editor-${field.name}-${field.value}-${field.inline}`}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Same unstable-key issue in the editor list: key={field.id ?? field-editor-${field.name}-${field.value}-${field.inline}} can remount a field editor while the user types if field.id is absent, causing focus loss. Use a stable fallback key until IDs are hydrated (or hydrate IDs before rendering the list).

Suggested change
key={field.id ?? `field-editor-${field.name}-${field.value}-${field.inline}`}
key={field.id ?? `field-editor-${i}`}

Copilot uses AI. Check for mistakes.
Comment on lines 24 to +29
*/
const dmLimits = new Map();

function getDmLimitKey(guildId, userId, scope = 'default') {
return `${guildId}:${userId}:${scope}`;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The rate-limiter doc comment still describes the Map key as ${guildId}:${userId}, but the implementation now includes a scope segment via getDmLimitKey(...). Please update the comment/JSDoc to reflect the new key format so future debugging matches actual behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +91
if (isPlainObject(origVal) && isPlainObject(modVal)) {
if (Object.keys(modVal).length === 0 && !hasPlainObjectChild(origVal)) {
patches.push({ path: fullPath, value: {} });
continue;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

When modVal is an explicit empty object ({}) but origVal contains nested objects, this code recurses instead of emitting a single { path: fullPath, value: {} }. That recursion will generate child patches like ...enabled: null for removed primitives, which is likely invalid for the PATCH validator (non-nullable booleans) and can break “reset section to empty/defaults” flows. Consider treating any explicit empty-object replacement as a parent-level {} patch regardless of origVal contents.

Copilot uses AI. Check for mistakes.
Comment thread src/api/routes/members.js Dismissed
Comment thread src/api/routes/members.js Dismissed
Comment thread src/api/routes/tickets.js Dismissed
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

♻️ Duplicate comments (2)
tests/modules/actions/sendDm.test.js (1)

169-173: ⚠️ Potential issue | 🟡 Minor

Use runtime-accurate levelUpDm:<level> scope in this test.

Line 172 should assert against a production-format scope (e.g., 'levelUpDm:5') instead of 'levelUpDm', so this test validates the same key shape used by level-up actions.

Diff suggestion
   it('should track rate limits independently by scope', () => {
     recordDmSend('g1', 'u1', 'default');
     expect(checkDmRateLimit('g1', 'u1', 'default')).toBe(false);
-    expect(checkDmRateLimit('g1', 'u1', 'levelUpDm')).toBe(true);
+    expect(checkDmRateLimit('g1', 'u1', 'levelUpDm:5')).toBe(true);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/modules/actions/sendDm.test.js` around lines 169 - 173, The test uses a
non-production scope string; update the assertion to use a runtime-accurate
scope like 'levelUpDm:5' so it matches the real key shape used by level-up
actions: modify the second expect call that currently checks
checkDmRateLimit('g1','u1','levelUpDm') to instead check
checkDmRateLimit('g1','u1','levelUpDm:5') while leaving the
recordDmSend('g1','u1','default') and the first assertion unchanged to ensure
scopes are tracked independently (functions involved: recordDmSend,
checkDmRateLimit).
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx (1)

730-780: ⚠️ Potential issue | 🟠 Major

Use the shared markdown editor here instead of raw textareas.

These blocks still only show a static token list and a plain-text preview, so admins cannot insert variables from a picker and **markdown** is displayed literally instead of as the DM will render. That leaves the level-up DM UI short of the stated feature requirements and duplicates functionality already implemented in web/src/components/ui/discord-markdown-editor.tsx. Wire that editor in for the default template and each override, passing the variable names and preview context you already build.

Also applies to: 908-960

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`
around lines 730 - 780, Replace the raw textarea inputs used for level-up DM
templates with the shared markdown editor component (the one exported from
web/src/components/ui/discord-markdown-editor.tsx) for the default template
(currently using levelUpDmDefaultDraft, isValidLevelUpDmTemplate,
updateDraftConfig, buildLevelUpDmConfig) and for each override block (the other
levelUpDm* drafts between the other mentioned ranges); pass
LEVEL_UP_DM_TEMPLATE_VARS as the variable/picker list and provide the same
preview context by using renderLevelUpDmPreview for live rendering, preserve
disabled={saving}, rows/maxLength behavior, and keep the
onBlur/updateDraftConfig flow so validation via isValidLevelUpDmTemplate and
buildLevelUpDmConfig still runs when editing finishes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 156-179: levelUpDmMessages is recreated every render which causes
levelUpDmOverrideDraftEntries (and the effect that resets
levelUpDmOverrideDrafts) to receive a new reference and wipe in-progress edits;
wrap the construction of levelUpDmMessages in useMemo so it only recomputes when
the underlying messages change (e.g. depend on levelUpDm?.messages), keeping the
mapping, originalIndex injection and sort logic intact; this stabilizes
levelUpDmMessages and prevents setLevelUpDmOverrideDrafts from running on
unrelated renders.

In `@web/src/components/ui/embed-builder.tsx`:
- Around line 473-480: The current key for fields in the config.fields.map uses
a fallback composed of mutable content (field.name/field.value/field.inline)
which can change and cause remounts/collisions when field.id is missing; update
the key expression in the map callback to use field.id as the primary key and a
deterministic non-content fallback based on the iteration index (e.g.
key={field.id ?? `field-${index}`}), and apply the same change to the other
fields map occurrence that currently builds fallback keys from name/value/inline
so keys remain stable across edits and hydration.
- Around line 16-17: The Image components in EmbedBuilder (rendering
thumbnailUrl, imageUrl, footerIconUrl) may receive partial/invalid user-typed
URLs and will trigger Next.js remotePatterns validation errors; update the
render logic to validate the URL before passing it to <Image> (e.g., add an
isValidRemoteImage(url) helper that parses the URL, ensures a valid
protocol/hostname and matches allowed hostnames like "cdn.discordapp.com"), and
if invalid render a safe fallback (placeholder image or null) or wrap the Image
render in an ErrorBoundary component to prevent the editor from crashing; locate
validation around the variables thumbnailUrl, imageUrl, footerIconUrl and the
JSX blocks that render them to apply this change.

---

Duplicate comments:
In `@tests/modules/actions/sendDm.test.js`:
- Around line 169-173: The test uses a non-production scope string; update the
assertion to use a runtime-accurate scope like 'levelUpDm:5' so it matches the
real key shape used by level-up actions: modify the second expect call that
currently checks checkDmRateLimit('g1','u1','levelUpDm') to instead check
checkDmRateLimit('g1','u1','levelUpDm:5') while leaving the
recordDmSend('g1','u1','default') and the first assertion unchanged to ensure
scopes are tracked independently (functions involved: recordDmSend,
checkDmRateLimit).

In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 730-780: Replace the raw textarea inputs used for level-up DM
templates with the shared markdown editor component (the one exported from
web/src/components/ui/discord-markdown-editor.tsx) for the default template
(currently using levelUpDmDefaultDraft, isValidLevelUpDmTemplate,
updateDraftConfig, buildLevelUpDmConfig) and for each override block (the other
levelUpDm* drafts between the other mentioned ranges); pass
LEVEL_UP_DM_TEMPLATE_VARS as the variable/picker list and provide the same
preview context by using renderLevelUpDmPreview for live rendering, preserve
disabled={saving}, rows/maxLength behavior, and keep the
onBlur/updateDraftConfig flow so validation via isValidLevelUpDmTemplate and
buildLevelUpDmConfig still runs when editing finishes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c2e09381-3d16-4f52-b08c-3039cd2cc4db

📥 Commits

Reviewing files that changed from the base of the PR and between 976cbc6 and e21c9ac.

📒 Files selected for processing (9)
  • tests/commands.test.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/cli-process.test.js
  • web/package.json
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/src/components/ui/discord-markdown-editor.tsx
  • web/src/components/ui/embed-builder.tsx
  • web/src/lib/discord-markdown.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Agent
  • GitHub Check: Greptile Review
  • GitHub Check: E2E Tests (1/2)
  • GitHub Check: E2E Tests (2/2)
  • GitHub Check: Test
🧰 Additional context used
📓 Path-based instructions (8)
**/*.js

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

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

Files:

  • tests/commands.test.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/cli-process.test.js
**/*.{js,ts,tsx}

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

**/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes
Always include semicolons at the end of statements
Use 2-space indentation (spaces, not tabs)
Always include trailing commas in multi-line arrays, objects, and function parameters
Maintain a maximum line width of 100 characters

Files:

  • tests/commands.test.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/cli-process.test.js
  • web/src/lib/discord-markdown.ts
  • web/src/components/ui/embed-builder.tsx
  • web/src/components/ui/discord-markdown-editor.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
tests/**/*.test.js

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

tests/**/*.test.js: Write bot tests using Vitest 4 with 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/commands.test.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/cli-process.test.js
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*

Files:

  • tests/commands.test.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/cli-process.test.js
  • web/src/lib/discord-markdown.ts
  • web/src/components/ui/embed-builder.tsx
  • web/src/components/ui/discord-markdown-editor.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
**/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use parameterized SQL only; do not use string concatenation for SQL queries

Files:

  • tests/commands.test.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/cli-process.test.js
  • web/src/lib/discord-markdown.ts
**/*.json

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

Use double quotes in JSON files (standard JSON format); this exception applies only to JSON files, not JavaScript/TypeScript

Files:

  • web/package.json
web/src/**/*.{ts,tsx}

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

Never use console.* methods in web dashboard code; use appropriate logging mechanisms for React applications

Files:

  • web/src/lib/discord-markdown.ts
  • web/src/components/ui/embed-builder.tsx
  • web/src/components/ui/discord-markdown-editor.tsx
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
web/tests/**/*.test.{ts,tsx}

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

web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with the jsdom environment and React Testing Library, matching the web/src/ structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
🧠 Learnings (11)
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory

Applied to files:

  • tests/commands.test.js
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Run the narrowest checks that actually prove the change. Use repo-level commands when the blast radius is real: pnpm mono:lint, pnpm mono:test, pnpm mono:typecheck, pnpm mono:build, pnpm mono:test:coverage.

Applied to files:

  • web/package.json
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to {web/src/lib/page-titles.ts,web/src/**/+page.ts,web/src/**/+page.svelte} : New dashboard routes need title wiring in web/src/lib/page-titles.ts: use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation.

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-10T23:29:51.063Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:29:51.063Z
Learning: Config editor save contract: maintain global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/app/dashboard/**/*.tsx : For dashboard routes, add a matcher entry to `dashboardTitleMatchers` in `web/src/lib/page-titles.ts`: use exact equality for leaf routes (`pathname === '/dashboard/my-route'`) and subtree checks (`pathname.startsWith('/dashboard/my-route/')`); export `metadata` using `createPageMetadata(title)` for SSR entry points

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to src/**/*.{js,ts} : Community features should be gated behind config.<feature>.enabled. Moderation commands are the exception.

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to config.json : All community features in `config.json` should be gated behind a `config.<feature>.enabled` flag; moderation commands are always available regardless of config settings

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/ui/discord-markdown-editor.tsx

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

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


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

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


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

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

web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx

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

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


[failure] 896-896: 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=AZ0sfIv2bExGPQYBhAcY&open=AZ0sfIv2bExGPQYBhAcY&pullRequest=399


[failure] 857-857: 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=AZ08t8sO2pYty83ebGum&open=AZ08t8sO2pYty83ebGum&pullRequest=399

🔇 Additional comments (3)
tests/commands.test.js (1)

25-25: Explicit hook timeout is a good stability guard.

Setting beforeAll(..., 30_000) here is a solid change for slower module initialization paths and keeps test behavior deterministic across environments.

tests/modules/cli-process.test.js (1)

653-653: Good synchronization fix before advancing fake timers.

Using await Promise.resolve(); at Line 653 is a solid way to flush microtasks before
advanceTimersByTimeAsync, which helps keep this timeout test deterministic.

web/src/components/ui/embed-builder.tsx (1)

257-275: Offset-based keys in renderVariablePreview are a solid improvement.

This avoids index-only key churn and keeps token/text nodes stable across edits.

Comment thread web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx Outdated
Comment thread web/src/components/ui/embed-builder.tsx Outdated
Comment thread web/src/components/ui/embed-builder.tsx 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: 2

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

Inline comments:
In `@src/api/routes/members.js`:
- Around line 329-331: The code constructs memberList via
Array.from(discordPage.values()) then re-creates that same array to compute
lastMember; reuse memberList instead to avoid duplicate conversion. Replace the
second Array.from call with retrieving the last element from memberList (e.g.,
memberList[memberList.length - 1]) and leave paginationCursor = lastMember ?
lastMember.id : null; reference: memberList, discordPage, lastMember,
paginationCursor.

In `@web/src/components/dashboard/config-sections/EngagementSection.tsx`:
- Around line 42-45: The current key for each badge uses mutable value-based
fields (`badge.days` and `badge.label`) which causes React to remount rows on
edit and allows duplicate keys; update the rendering in EngagementSection where
the badge row is created (the element using key={`${badge.days ??
0}-${badge.label ?? 'badge'}`}) to use a stable identifier instead—either use
the array index as the key (acceptable because badges are not reorderable) or
generate and store a unique id on badge creation (e.g., add id to the badge
object and use that id as the key) so that editing `days`/`label` does not
change the key and inputs retain focus.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9c87abe1-35b7-47f8-a3b6-0db522262111

📥 Commits

Reviewing files that changed from the base of the PR and between e21c9ac and fd33ae5.

📒 Files selected for processing (5)
  • src/api/routes/guilds.js
  • src/api/routes/members.js
  • src/api/routes/tickets.js
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/src/components/dashboard/config-sections/EngagementSection.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{js,ts,tsx}

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

**/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes
Always include semicolons at the end of statements
Use 2-space indentation (spaces, not tabs)
Always include trailing commas in multi-line arrays, objects, and function parameters
Maintain a maximum line width of 100 characters

Files:

  • web/src/components/dashboard/config-sections/EngagementSection.tsx
  • src/api/routes/guilds.js
  • src/api/routes/tickets.js
  • src/api/routes/members.js
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
web/src/**/*.{ts,tsx}

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

Never use console.* methods in web dashboard code; use appropriate logging mechanisms for React applications

Files:

  • web/src/components/dashboard/config-sections/EngagementSection.tsx
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*

Files:

  • web/src/components/dashboard/config-sections/EngagementSection.tsx
  • src/api/routes/guilds.js
  • src/api/routes/tickets.js
  • src/api/routes/members.js
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
**/*.js

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

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

Files:

  • src/api/routes/guilds.js
  • src/api/routes/tickets.js
  • src/api/routes/members.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/api/routes/guilds.js
  • src/api/routes/tickets.js
  • src/api/routes/members.js
src/api/routes/**/*.js

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

Create API route handlers in src/api/routes/ and mount them in src/api/server.js; add auth middleware if the endpoint requires authentication

Files:

  • src/api/routes/guilds.js
  • src/api/routes/tickets.js
  • src/api/routes/members.js
src/**/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{js,ts}: Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Community features should be gated behind config..enabled. Moderation commands are the exception.

Files:

  • src/api/routes/guilds.js
  • src/api/routes/tickets.js
  • src/api/routes/members.js
**/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use parameterized SQL only; do not use string concatenation for SQL queries

Files:

  • src/api/routes/guilds.js
  • src/api/routes/tickets.js
  • src/api/routes/members.js
🧠 Learnings (5)
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'

Applied to files:

  • web/src/components/dashboard/config-sections/EngagementSection.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to src/api/routes/*.js : Add adaptDeleteGuildIdParam in API route handlers to enforce guild moderation checks on DELETE requests without losing record id

Applied to files:

  • src/api/routes/guilds.js
  • src/api/routes/members.js
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to src/api/ws/logStream.js : Implement backward-compatible WebSocket ticket parsing to accept both legacy 3-part and new 4-part tickets for maintaining existing log stream behavior

Applied to files:

  • src/api/routes/tickets.js
📚 Learning: 2026-03-10T23:29:51.063Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:29:51.063Z
Learning: Applies to src/api/routes/*.js : Apply requireGuildModerator authorization check to DELETE endpoints that modify guild-scoped resources

Applied to files:

  • src/api/routes/members.js
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to src/**/*.{js,ts} : Community features should be gated behind config.<feature>.enabled. Moderation commands are the exception.

Applied to files:

  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
🪛 GitHub Check: CodeQL
src/api/routes/tickets.js

[failure] 340-340: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

src/api/routes/members.js

[failure] 292-292: Missing rate limiting
This route handler performs authorization, but is not rate-limited.


[failure] 294-473: Missing rate limiting
This route handler performs a database access, but is not rate-limited.
This route handler performs a database access, but is not rate-limited.
This route handler performs a database access, but is not rate-limited.

🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx

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

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


[failure] 902-902: 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=AZ0sfIv2bExGPQYBhAcY&open=AZ0sfIv2bExGPQYBhAcY&pullRequest=399


[failure] 863-863: 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=AZ08t8sO2pYty83ebGum&open=AZ08t8sO2pYty83ebGum&pullRequest=399

🔇 Additional comments (8)
src/api/routes/tickets.js (1)

337-394: Rate limiting and parameterized filtering look correct here.

This route now clearly applies ticketRateLimit and keeps SQL inputs parameterized while supporting optional filters. No issues in this changed segment.

src/api/routes/guilds.js (1)

496-496: LGTM—Formatting improvement.

The indentation adjustment aligns the code properly within the callback block and follows the 2-space indentation guideline.

src/api/routes/members.js (1)

289-474: LGTM — Clean reformatting with proper middleware chain.

The route handler reformatting improves readability while maintaining all functionality. The middleware chain correctly applies rate limiting (membersRateLimit), authorization (requireGuildModerator), and validation (validateGuild) before the handler executes.

Note: The CodeQL warnings flagged in static analysis are false positives — rate limiting is properly applied at line 291.

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

73-75: Consider replaceAll for explicit intent (nitpick).

Static analysis suggests using String#replaceAll() instead of String#replace() with a global regex. Both behave identically here, but replaceAll makes the global replacement intent more explicit.


856-888: Extract level-change handler to reduce nesting (flagged by static analysis).

SonarCloud flags this callback for exceeding 4 levels of nesting. Consider extracting the level-change logic into a named helper function for improved readability and testability.


895-911: Extract removal handler to reduce nesting (flagged by static analysis).

Similar to the level-input handler, this callback also exceeds the 4-level nesting threshold. Extracting this would maintain consistency with any level-input refactor.


156-167: Memoization correctly stabilizes override row identity.

The useMemo for levelUpDmMessages with [levelUpDm?.messages] dependency ensures the array reference only changes when the underlying messages change, preventing unnecessary re-renders and draft state resets. Combined with the stable key xp-level-dm-${entry.originalIndex} at line 841, this properly addresses the focus-loss issue for override textareas.


78-91: Level-cap and template validation properly aligned with backend schema.

getNextLevelUpDmOverrideLevel correctly caps at 1000 and returns undefined when exhausted, which disables the "Add Override" button (line 827). isValidLevelUpDmTemplate uses trim().length > 0, matching the backend's pattern: '\\S' constraint. The onBlur handlers at lines 742-745 and 924-928 prevent blank templates from being persisted, avoiding autosave of schema-invalid states.

Also applies to: 93-95

Comment thread src/api/routes/members.js
Comment thread web/src/components/dashboard/config-sections/EngagementSection.tsx Outdated
….tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: MohsinCoding <91380732+MohsinCoding@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 3, 2026 05:19
@BillChirico
Copy link
Copy Markdown
Collaborator

@greptile review

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 31 out of 32 changed files in this pull request and generated 3 comments.

Comment thread web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx Outdated
Comment thread web/src/components/ui/embed-builder.tsx
Comment thread web/src/components/ui/embed-builder.tsx
Copilot AI review requested due to automatic review settings April 5, 2026 00:51
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 33 out of 34 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

web/src/components/ui/embed-builder.tsx:504

  • The fallback React key for preview fields is derived from mutable field contents (name, value, inline). If field.id is missing (e.g. legacy configs), multiple empty fields will produce duplicate keys and edits can cause remounts. Prefer a stable unique fallback (e.g. index for preview-only rendering) or ensure IDs are always present before rendering.
          {config.fields.length > 0 && (
            <div className="grid grid-cols-3 gap-2" data-testid="embed-preview-fields">
              {config.fields.map((field) => {
                const renderedName = renderVariablePreview(field.name);
                const renderedValue = renderDiscordMarkdown(field.value);

                return (
                  <div
                    key={field.id ?? `field-${field.name}-${field.value}-${field.inline}`}
                    className={cn(field.inline ? 'col-span-1' : 'col-span-3')}
                  >
                    <div className="text-xs font-semibold text-white">
                      {renderedName.length > 0 ? renderedName : '\u200b'}
                    </div>
                    <div className="text-xs">
                      {renderedValue.length > 0 ? renderedValue : '\u200b'}
                    </div>
                  </div>
                );
              })}

Comment thread web/src/components/ui/embed-builder.tsx 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: 3

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

Inline comments:
In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 171-185: The effect is replacing the entire
levelUpDmOverrideDrafts map on every change, which clobbers in-progress edits
and leaves the first render empty; change initialization of
levelUpDmOverrideDrafts to use a lazy initializer based on
Object.fromEntries(levelUpDmOverrideDraftEntries) so existing entries are
present on first paint, and update the effect that currently calls
setLevelUpDmOverrideDrafts(Object.fromEntries(levelUpDmOverrideDraftEntries)) to
instead merge diffs: call setLevelUpDmOverrideDrafts(prev => { create a new map
starting from prev, add/update keys from levelUpDmOverrideDraftEntries, and
remove keys that no longer exist in levelUpDmOverrideDraftEntries }) so edits in
other rows are preserved while syncing persisted changes; reference state/setter
names levelUpDmOverrideDrafts and setLevelUpDmOverrideDrafts and the dependency
levelUpDmOverrideDraftEntries.
- Around line 29-40: The dashboard duplicates template tokens and preview
rendering (LEVEL_UP_DM_TEMPLATE_VARS, LEVEL_UP_DM_PREVIEW_CONTEXT, and
renderLevelUpDmPreview) instead of reusing the runtime template builder; replace
the dashboard's manual context/token logic by importing and calling
buildTemplateContext from src/utils/templateEngine.js to construct the preview
context and use the shared rendering behavior so variables (e.g., xpToNext) stay
in sync with runtime, or alternatively add a short code comment documenting why
the dashboard needs separate logic if you intentionally keep it.

In `@web/tests/components/dashboard/community-settings-section.test.tsx`:
- Around line 1-21: The current test only exercises the helper
buildLevelUpDmConfig; add a new React Testing Library-based test that mounts the
CommunitySettingsSection component to cover the editor UI behaviors: render the
DM editor, assert blur-only saves (edit a field and blur, then verify prop/state
or saved output), test adding and removing override messages via the UI controls
(click "add override", populate level/message, then remove and assert list
updates), verify duplicate-level rejection by attempting to add an override with
an existing level and asserting the validation error appears, and check preview
rendering updates when editing defaultMessage or an override; keep the original
buildLevelUpDmConfig unit test small in a util test file and use Vitest with the
jsdom environment and RTL queries (render, fireEvent/userEvent, screen) to drive
interactions against CommunitySettingsSection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f721a716-a23f-490a-80f3-11d1f6f9d244

📥 Commits

Reviewing files that changed from the base of the PR and between fd33ae5 and ea2f84c.

📒 Files selected for processing (4)
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/src/components/ui/embed-builder.tsx
  • web/tests/components/dashboard/community-settings-section.test.tsx
  • web/tests/components/ui/embed-builder.test.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: CI
  • GitHub Check: Agent
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx}

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

**/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes
Always include semicolons at the end of statements
Use 2-space indentation (spaces, not tabs)
Always include trailing commas in multi-line arrays, objects, and function parameters
Maintain a maximum line width of 100 characters

Files:

  • web/tests/components/ui/embed-builder.test.tsx
  • web/tests/components/dashboard/community-settings-section.test.tsx
  • web/src/components/ui/embed-builder.tsx
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
web/tests/**/*.test.{ts,tsx}

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

web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with the jsdom environment and React Testing Library, matching the web/src/ structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Files:

  • web/tests/components/ui/embed-builder.test.tsx
  • web/tests/components/dashboard/community-settings-section.test.tsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*

Files:

  • web/tests/components/ui/embed-builder.test.tsx
  • web/tests/components/dashboard/community-settings-section.test.tsx
  • web/src/components/ui/embed-builder.tsx
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
web/src/**/*.{ts,tsx}

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

Never use console.* methods in web dashboard code; use appropriate logging mechanisms for React applications

Files:

  • web/src/components/ui/embed-builder.tsx
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
🧠 Learnings (6)
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure

Applied to files:

  • web/tests/components/dashboard/community-settings-section.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Applied to files:

  • web/tests/components/dashboard/community-settings-section.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead

Applied to files:

  • web/tests/components/dashboard/community-settings-section.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory

Applied to files:

  • web/tests/components/dashboard/community-settings-section.test.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)

Applied to files:

  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to src/**/*.{js,ts} : Community features should be gated behind config.<feature>.enabled. Moderation commands are the exception.

Applied to files:

  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/ui/embed-builder.tsx

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

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

web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx

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

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


[failure] 902-902: 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=AZ0sfIv2bExGPQYBhAcY&open=AZ0sfIv2bExGPQYBhAcY&pullRequest=399


[failure] 863-863: 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=AZ08t8sO2pYty83ebGum&open=AZ08t8sO2pYty83ebGum&pullRequest=399

🔇 Additional comments (4)
web/src/components/ui/embed-builder.tsx (3)

487-494: Fallback keys are still content-derived and unstable when field.id is missing.

Line 493 and Line 851 still use ${field.name}-${field.value}-${field.inline} as fallback key material. Those values change while typing, so React can remount rows and cause focus/cursor jumps for id-less legacy payloads.

💡 Proposed patch
-              {config.fields.map((field) => {
+              {config.fields.map((field, i) => {
@@
-                    key={field.id ?? `field-${field.name}-${field.value}-${field.inline}`}
+                    key={field.id ?? `field-preview-${i}`}
@@
-          {value.fields.map((field, i) => (
+          {value.fields.map((field, i) => (
             <div
-              key={field.id ?? `field-editor-${field.name}-${field.value}-${field.inline}`}
+              key={field.id ?? `field-editor-${i}`}
               className="space-y-2 rounded-md border border-border bg-muted/30 p-3"
             >

Also applies to: 849-852


256-274: Stable segment-based keys in variable preview look correct.

Using segmentStart for keys avoids index drift and keeps rendered token identity stable during edits.


353-367: PreviewImage extraction is a good simplification.

Centralizing preview image rendering in one local component improves maintainability and keeps sizing explicit.

Also applies to: 468-474, 511-517, 528-534

web/tests/components/ui/embed-builder.test.tsx (1)

500-522: Good regression coverage for custom preview images.

This test cleanly validates that all three custom image slots render with the expected src values.

Comment thread web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx Outdated
Comment thread web/tests/components/dashboard/community-settings-section.test.tsx
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 5, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
9.5% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

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

♻️ Duplicate comments (5)
web/tests/components/dashboard/config-editor-autosave.test.tsx (1)

162-186: 🧹 Nitpick | 🔵 Trivial

Consider documenting or reducing high test timeouts.

Several tests use 15000-30000ms timeouts. If these are needed for CI, a brief comment explaining the requirement would help future maintainers. If tests consistently complete faster, consider reducing to improve feedback speed.

💡 Suggested comment
   it('renders category navigation and AI features', async () => {
+    // 30s timeout: CI environments may be slower with dynamic imports and fetch mocks
     const fetchMock = vi.fn().mockResolvedValue({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/tests/components/dashboard/config-editor-autosave.test.tsx` around lines
162 - 186, This test uses an explicit 30000ms timeout at the end of the "renders
category navigation and AI features" test; either reduce the timeout to a lower
value that still passes locally/CI (e.g., 5000-10000ms) or add a brief inline
comment above the test explaining why the extended timeout is required for CI
stability; reference the test block that renders ConfigLayoutShell and
AiAutomationCategory so reviewers can locate and update the timeout or add the
justification comment.
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx (4)

917-933: 🧹 Nitpick | 🔵 Trivial

Static analysis flags deep nesting in removal handler.

Similar to the level-change handler, the removal callback at line 924 exceeds the 4-level nesting threshold. Extracting this would maintain consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`
around lines 917 - 933, The removal onClick handler is too deeply nested—extract
the callback into a named function to reduce nesting; create a helper like
handleRemoveLevelUpMessage that calls updateDraftConfig and uses
buildLevelUpDmConfig to set xp.levelUpDm.messages to the filtered array
excluding entry.originalIndex, then pass handleRemoveLevelUpMessage to the
Button's onClick and keep disabled={saving}; reference updateDraftConfig,
buildLevelUpDmConfig, xp.levelUpDm, and entry.originalIndex when implementing.

755-797: 🧹 Nitpick | 🔵 Trivial

Variable picker from issue requirements not implemented.

Issue #361 explicitly requests a "variable picker" UI that inserts {{variable}} tokens into the focused textarea. The current implementation only displays a comma-separated reference list (LEVEL_UP_DM_TEMPLATE_VARS.join(', ')), requiring admins to type tokens manually.

Consider adding insertable chips or a dropdown that writes the selected token into the textarea at cursor position, similar to the VariablePalette component used in embed-builder.tsx.

Do you want me to sketch a shared token-insertion helper or open an issue to track this enhancement?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`
around lines 755 - 797, The UI currently only shows LEVEL_UP_DM_TEMPLATE_VARS as
a comma-separated list instead of providing a variable picker that inserts
tokens into the textarea; replace or augment that list with a
VariablePalette-like component (as used in embed-builder.tsx) that renders
clickable chips/dropdown for each variable and, when clicked, inserts the
selected `{{variable}}` at the current cursor position of levelUpDmDefaultDraft
(the textarea controlled by levelUpDmDefaultDraft state), preserving
cursor/focus, and keeping validation via isValidLevelUpDmTemplate before calling
updateDraftConfig and buildLevelUpDmConfig on blur/save; consider factoring
insertion logic into a shared helper so other places can reuse it.

72-76: 🧹 Nitpick | 🔵 Trivial

Consider using replaceAll for consistency with static analysis.

Static analysis suggests replaceAll over replace with a global regex. While functionally equivalent, replaceAll is more explicit.

♻️ Optional fix
 function renderLevelUpDmPreview(template: string, context = LEVEL_UP_DM_PREVIEW_CONTEXT) {
-  return template.replace(/\{\{(\w+)\}\}/g, (match, key: string) => {
+  return template.replaceAll(/\{\{(\w+)\}\}/g, (match, key: string) => {
     return Object.hasOwn(context, key) ? context[key] : match;
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`
around lines 72 - 76, In renderLevelUpDmPreview, replace the use of
template.replace(/\{\{(\w+)\}\}/g, ...) with
template.replaceAll(/\{\{(\w+)\}\}/g, ...) to satisfy the static analysis rule
and make the global-replacement intent explicit; keep the same replacer function
and retain LEVEL_UP_DM_PREVIEW_CONTEXT usage for fallback lookups (no logic
changes required).

876-910: 🧹 Nitpick | 🔵 Trivial

Static analysis flags deep nesting in level-change handler.

The inline onChange callback exceeds 4 levels of nesting. Consider extracting to a named handler for improved readability and testability.

♻️ Suggested extraction
// Define near other helpers or at component scope
const handleLevelChange = (
  targetIndex: number,
  value: number | undefined,
) => {
  if (value === undefined) return;
  const normalizedValue = Math.floor(value);
  updateDraftConfig((prev) => {
    const messages = [...(prev.xp?.levelUpDm?.messages ?? [])];
    const hasDuplicateLevel = messages.some(
      (candidate, index) => index !== targetIndex && candidate?.level === normalizedValue,
    );
    if (hasDuplicateLevel) return prev;
    messages[targetIndex] = { ...messages[targetIndex], level: normalizedValue };
    return {
      ...prev,
      xp: {
        ...prev.xp,
        levelUpDm: buildLevelUpDmConfig(prev.xp?.levelUpDm, { messages }),
      },
    };
  });
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`
around lines 876 - 910, Extract the inline onChange logic into a named handler
(e.g., handleLevelChange) to reduce nesting and improve testability: create a
function that accepts targetIndex (entry.originalIndex) and the raw input string
or parsed number, call parseNumberInput inside the input onChange to get a
number (or forward the raw string and parse inside the handler), return early if
undefined, normalize with Math.floor, then call updateDraftConfig exactly as
before (copying messages from prev.xp?.levelUpDm?.messages, checking duplicates
with messages.some, early-returning prev on duplicate, updating
messages[targetIndex], and building the new xp.levelUpDm via
buildLevelUpDmConfig). Replace the inline onChange with a call to
handleLevelChange(targetIndex, /* value or event.target.value */) and keep
disabled={saving} and className unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/ui/embed-builder.tsx`:
- Around line 353-367: The prop object for PreviewImage should be marked
read-only to satisfy TypeScript immutability rules; update the parameter type
for the PreviewImage function (the inline props type) to use readonly properties
(e.g., make each prop readonly or wrap the object in Readonly<...>) so src, alt,
width, height, and className are immutable in the function signature.

---

Duplicate comments:
In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`:
- Around line 917-933: The removal onClick handler is too deeply nested—extract
the callback into a named function to reduce nesting; create a helper like
handleRemoveLevelUpMessage that calls updateDraftConfig and uses
buildLevelUpDmConfig to set xp.levelUpDm.messages to the filtered array
excluding entry.originalIndex, then pass handleRemoveLevelUpMessage to the
Button's onClick and keep disabled={saving}; reference updateDraftConfig,
buildLevelUpDmConfig, xp.levelUpDm, and entry.originalIndex when implementing.
- Around line 755-797: The UI currently only shows LEVEL_UP_DM_TEMPLATE_VARS as
a comma-separated list instead of providing a variable picker that inserts
tokens into the textarea; replace or augment that list with a
VariablePalette-like component (as used in embed-builder.tsx) that renders
clickable chips/dropdown for each variable and, when clicked, inserts the
selected `{{variable}}` at the current cursor position of levelUpDmDefaultDraft
(the textarea controlled by levelUpDmDefaultDraft state), preserving
cursor/focus, and keeping validation via isValidLevelUpDmTemplate before calling
updateDraftConfig and buildLevelUpDmConfig on blur/save; consider factoring
insertion logic into a shared helper so other places can reuse it.
- Around line 72-76: In renderLevelUpDmPreview, replace the use of
template.replace(/\{\{(\w+)\}\}/g, ...) with
template.replaceAll(/\{\{(\w+)\}\}/g, ...) to satisfy the static analysis rule
and make the global-replacement intent explicit; keep the same replacer function
and retain LEVEL_UP_DM_PREVIEW_CONTEXT usage for fallback lookups (no logic
changes required).
- Around line 876-910: Extract the inline onChange logic into a named handler
(e.g., handleLevelChange) to reduce nesting and improve testability: create a
function that accepts targetIndex (entry.originalIndex) and the raw input string
or parsed number, call parseNumberInput inside the input onChange to get a
number (or forward the raw string and parse inside the handler), return early if
undefined, normalize with Math.floor, then call updateDraftConfig exactly as
before (copying messages from prev.xp?.levelUpDm?.messages, checking duplicates
with messages.some, early-returning prev on duplicate, updating
messages[targetIndex], and building the new xp.levelUpDm via
buildLevelUpDmConfig). Replace the inline onChange with a call to
handleLevelChange(targetIndex, /* value or event.target.value */) and keep
disabled={saving} and className unchanged.

In `@web/tests/components/dashboard/config-editor-autosave.test.tsx`:
- Around line 162-186: This test uses an explicit 30000ms timeout at the end of
the "renders category navigation and AI features" test; either reduce the
timeout to a lower value that still passes locally/CI (e.g., 5000-10000ms) or
add a brief inline comment above the test explaining why the extended timeout is
required for CI stability; reference the test block that renders
ConfigLayoutShell and AiAutomationCategory so reviewers can locate and update
the timeout or add the justification comment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f0805ce9-7978-495c-8f62-4066bdb966f6

📥 Commits

Reviewing files that changed from the base of the PR and between ea2f84c and 802f041.

📒 Files selected for processing (3)
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/src/components/ui/embed-builder.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Greptile Review
  • GitHub Check: E2E Tests (2/2)
  • GitHub Check: Test
  • GitHub Check: E2E Tests (1/2)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx}

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

**/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes
Always include semicolons at the end of statements
Use 2-space indentation (spaces, not tabs)
Always include trailing commas in multi-line arrays, objects, and function parameters
Maintain a maximum line width of 100 characters

Files:

  • web/src/components/ui/embed-builder.tsx
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
web/src/**/*.{ts,tsx}

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

Never use console.* methods in web dashboard code; use appropriate logging mechanisms for React applications

Files:

  • web/src/components/ui/embed-builder.tsx
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*

Files:

  • web/src/components/ui/embed-builder.tsx
  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
web/tests/**/*.test.{ts,tsx}

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

web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with the jsdom environment and React Testing Library, matching the web/src/ structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
🧠 Learnings (10)
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)

Applied to files:

  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to src/**/*.{js,ts} : Community features should be gated behind config.<feature>.enabled. Moderation commands are the exception.

Applied to files:

  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'

Applied to files:

  • web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Write web dashboard tests using Vitest 4 with the `jsdom` environment and React Testing Library, matching the `web/src/` structure

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-10T23:29:51.063Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:29:51.063Z
Learning: Config editor save contract: maintain global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to {web/src/lib/page-titles.ts,web/src/**/+page.ts,web/src/**/+page.svelte} : New dashboard routes need title wiring in web/src/lib/page-titles.ts: use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation.

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/app/dashboard/**/*.tsx : For dashboard routes, add a matcher entry to `dashboardTitleMatchers` in `web/src/lib/page-titles.ts`: use exact equality for leaf routes (`pathname === '/dashboard/my-route'`) and subtree checks (`pathname.startsWith('/dashboard/my-route/')`); export `metadata` using `createPageMetadata(title)` for SSR entry points

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/tests/**/*.test.{ts,tsx} : Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to tests/**/*.test.js : Write bot tests using Vitest 4 with the `node` environment, matching the `src/` structure in the `tests/` directory

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to config.json : All community features in `config.json` should be gated behind a `config.<feature>.enabled` flag; moderation commands are always available regardless of config settings

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/ui/embed-builder.tsx

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

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

web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx

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

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


[failure] 924-924: 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=AZ0sfIv2bExGPQYBhAcY&open=AZ0sfIv2bExGPQYBhAcY&pullRequest=399


[failure] 884-884: 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=AZ08t8sO2pYty83ebGum&open=AZ08t8sO2pYty83ebGum&pullRequest=399

🔇 Additional comments (12)
web/src/components/ui/embed-builder.tsx (2)

256-274: LGTM! Stable key generation using cumulative offset.

Using a running offset counter to generate unique keys (var-${segmentStart}-${varName} and text-${segmentStart}) provides stable identity across variable tokenization shifts, preventing unnecessary remounts when template content changes.


487-493: LGTM! Stable key fallback for field preview.

Using field.id ?? \field-${index}`provides a deterministic non-content fallback while keepingfield.id` as the primary key, which addresses the previous focus-loss concern for older configs without ids.

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

3-3: LGTM! Required React hooks imported.

The import of useEffect, useMemo, and useState properly supports the new local draft state management and memoization for Level-Up DMs.


58-70: LGTM! Dynamic preview context for per-level overrides.

buildLevelUpDmPreviewContext correctly derives level, previousLevel, and nextLevel from the override's actual level, ensuring preview accuracy for each row. The Number.isFinite guard and floor/max clamping handle edge cases appropriately.


78-91: LGTM! Bounded level generation with upper limit.

getNextLevelUpDmOverrideLevel correctly caps at 1000 (matching the backend schema constraint at src/api/utils/configValidation.js:347) and returns undefined when exhausted, enabling the UI to disable "Add Override" appropriately.


97-108: LGTM! Centralized buildLevelUpDmConfig helper.

This helper consolidates defaults and merges updates, reducing duplication across handlers and ensuring consistent fallback behavior.


156-167: LGTM! Properly memoized levelUpDmMessages.

Wrapping the mapping and sorting in useMemo with [levelUpDm?.messages] as the dependency stabilizes the reference and prevents the downstream effect from resetting drafts on unrelated renders.


183-204: LGTM! Merge-based draft synchronization preserves in-progress edits.

The effect correctly merges new entries and prunes removed keys rather than replacing the whole map, preventing clobbering of edits in other rows when one row persists.

web/tests/components/dashboard/config-editor-autosave.test.tsx (4)

1-10: LGTM! Updated mock setup with shared mockPush.

Extracting mockPush as a module-level vi.fn() and clearing it in beforeEach enables proper router interaction testing across the suite.


86-147: LGTM! Comprehensive config fixtures for xp.levelUpDm testing.

minimalConfig now includes the full xp.levelUpDm structure, and minimalConfigWithoutLevelUpDm enables testing the newly-added object PATCH behavior. These fixtures align with the TypeScript types in web/src/types/config.ts.


280-368: LGTM! Focus-retention regression test properly exercises key stability.

The test now:

  1. Adds override rows and types into textareas
  2. Asserts textarea identity persists (refreshedFirstOverrideTextarea === firstOverrideTextarea)
  3. Verifies value retention after rerender
  4. Tests decimal level input normalization and preview update

This addresses the prior concern about not exercising the key-stability bug fix.


370-434: LGTM! Dotted patch assertion validates nested config diffing fix.

The test correctly verifies that:

  1. No PATCH is sent before "Confirm Save"
  2. After confirm, patches use dotted paths like xp.levelUpDm.enabled
  3. Top-level path === 'xp' never appears

This aligns with computePatches behavior in web/src/lib/config-utils.ts.

Comment thread web/src/components/ui/embed-builder.tsx
@BillChirico BillChirico merged commit d130899 into main Apr 7, 2026
23 of 24 checks passed
@BillChirico BillChirico deleted the mohsin/xp-levelup-dms branch April 7, 2026 02:01
@github-project-automation github-project-automation Bot moved this from In Review to Done in Volvox.Bot Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: backend Bot/runtime/backend logic scope: dashboard Web dashboard scope: engagement Community engagement size:XL

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

feat(xp): configurable DM messages on level-up with per-level templates

5 participants