feat(dashboard): add visual Discord embed builder component#423
feat(dashboard): add visual Discord embed builder component#423BillChirico merged 7 commits intomainfrom
Conversation
|
|
🚅 Deployed to the volvox-bot-pr-423 environment in volvox-bot
|
|
| Filename | Overview |
|---|---|
| web/src/components/ui/embed-builder.tsx | New 947-line embed builder component; well-structured with variable palette, live preview, and char counters. One violation: hardcoded 'en-US' locale in formatPreviewTimestamp. |
| web/src/components/dashboard/config-editor.tsx | Refactored from context-provider to direct state; save and discard now wired up, but shared error state for load/save causes editor to unmount on any save failure, and save hardcodes ai.systemPrompt path. |
| web/src/app/api/auth/[...nextauth]/route.ts | Adds graceful fallback responses when auth env vars are missing; uses logger correctly and caches handler to avoid redundant reconstruction. |
| web/src/components/dashboard/config-sections/ReputationSection.tsx | File deleted; export removed from barrel index — straightforward dead-code removal. |
| tests/modules/ai.test.js | Test assertions updated to drop created_at/timestamp checks and tighten SQL query assertion to match a query without created_at; consistent with a production AI module change to remove timestamp tracking. |
| web/tests/components/dashboard/config-editor-autosave.test.tsx | Adds save-via-PATCH and discard-restores-draft integration tests; removes now-irrelevant Next.js navigation mocks. |
| web/tests/api/auth-route.test.ts | New test suite with comprehensive coverage of auth fallback paths, handler caching, and recovery after failure. |
| web/tests/components/ui/embed-builder.test.tsx | 482-line test file covering rendering, editing, variable insertion, char limits, preview, and field reordering. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User edits config] --> B{Click Save Changes}
B --> C[saveChanges called]
C --> D[setError null\nsetSaving true]
D --> E[PATCH /api/guilds/:id/config\npath: ai.systemPrompt]
E --> F{Response ok?}
F -- Yes --> G[Update draftConfig\nsetSavedConfig\nsetHasChanges false]
F -- No --> H[setError err.message]
H --> I[Re-render: if error return early]
I --> J[❌ Editor unmounts\nDraft lost]
G --> K[✅ Editor stays\nChanges saved]
L[User clicks Discard] --> M[discardChanges called]
M --> N[setDraftConfig structuredClone savedConfig\nsetHasChanges false]
N --> O[✅ Editor reverts to last saved state]
Prompt To Fix All With AI
This is a comment left during a code review.
Path: web/src/components/ui/embed-builder.tsx
Line: 344-348
Comment:
**Hardcoded `'en-US'` locale violates project rule**
The `no-hardcoded-locale` rule requires using `undefined` so the user's runtime locale is respected. As written, all users see US-formatted times regardless of their browser locale.
```suggestion
function formatPreviewTimestamp(date: Date): string {
return new Intl.DateTimeFormat(undefined, {
hour: 'numeric',
minute: '2-digit',
}).format(date);
}
```
**Rule Used:** Do not hardcode locale strings like 'en-US' in Int... ([source](https://app.greptile.com/review/custom-context?memory=no-hardcoded-locale))
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: web/src/components/dashboard/config-editor.tsx
Line: 49-52
Comment:
**Save error unmounts the entire editor, trapping the user's draft**
`error` is shared between load errors and save errors. When `saveChanges` catches and calls `setError(...)`, the `if (error) return <div role="alert">{error}</div>` early return fires on the next render, replacing the full editor with a bare error string. The user's unsaved edits disappear with no retry path — they must hard-refresh and re-enter their changes.
The fix is to use a separate piece of state for in-editor save errors and render it inline (e.g. next to the Save button), keeping the load-error early return for the initial failure case only.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: web/src/components/dashboard/config-editor.tsx
Line: 272-278
Comment:
**`saveChanges` hardcodes `path: 'ai.systemPrompt'`**
Only the system-prompt field is sent to the server regardless of what else the user has edited in `draftConfig`. Any future section added to this editor will silently not be persisted on click of "Save Changes". Consider either saving the full diff, or constraining the button label/scope to make the single-field save explicit.
How can I resolve this? If you propose a fix, please make it concise.Reviews (10): Last reviewed commit: "test: cover auth route fallback cache re..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d41f1f105b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds a reusable Visual Discord Embed Builder UI component to the dashboard (Issue #371), with a live Discord-style embed preview and unit tests.
Changes:
- Introduces
EmbedBuilder/EmbedPreviewcomponents plus embed config utilities (defaults, char limits, total char count). - Implements editor controls for color, title, description, thumbnail type, fields (add/remove/reorder/inline), footer, image URL, and timestamp toggle.
- Adds a comprehensive Vitest + React Testing Library test suite for editor behavior, preview rendering, and utilities.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| web/src/components/ui/embed-builder.tsx | New embed builder + preview components, helper renderers, and embed config utilities. |
| web/tests/components/ui/embed-builder.test.tsx | New unit tests covering editor interactions, preview rendering, and utility functions. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (9)
📝 WalkthroughSummary by CodeRabbitRelease Notes
WalkthroughRefactored Changes
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📋 Issue PlannerBuilt with CodeRabbit's Coding Plans for faster development and fewer bugs. View plan used: ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5bca27b121
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 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-editor.tsx`:
- Around line 95-100: The Save Changes button currently has no onClick handler;
add an async handler (e.g., handleSave or saveConfig) in config-editor.tsx that
sends the current draftConfig to the backend (PUT/POST to the appropriate config
API), awaits the response, handles errors (showing a notification or setting an
error state) and updates local state/UI on success; then attach it to the button
via onClick={handleSave}. Reference the existing draftConfig variable and any
local state setters (e.g., setDraftConfig, setIsSaving) so the handler can
optimistically disable the button while saving and refresh the saved config on
success.
- Around line 90-94: The DiscardChangesButton's onReset only calls
setHasChanges(false) and doesn't revert draftConfig to the last saved config;
update the handler to restore the draft by setting draftConfig back to the
stored original/saved config (e.g., maintain an originalConfig or reuse the
loaded config state) and then call setHasChanges(false); locate the
DiscardChangesButton usage and the state variables draftConfig and setHasChanges
to implement this restore so the UI returns to the server-loaded configuration
when users discard changes.
In `@web/src/components/ui/embed-builder.tsx`:
- Line 495: The div at the format/thumbnail button group uses role="group" which
is less semantic; replace the <div className="flex gap-1" role="group"
aria-label="Format"> (and the similar instance around line 607) with a
<fieldset> and include a visually appropriate <legend> (e.g., "Format" or
"Thumbnail") to preserve the accessible label for screen readers, keep the
existing className styling on the fieldset (or wrap an inner element if
necessary to maintain layout), and remove role="group"/aria-label so the legend
provides the accessible name for the grouped buttons.
- Around line 94-104: The module-level mutable counter embedFieldIdCounter used
by createFieldId can produce duplicate IDs in SSR/concurrent React; replace the
counter fallback with a deterministic UUID-based approach (e.g., always use
crypto.randomUUID when available and otherwise generate a RFC4122-like UUID or
include a secure/random component) and remove or stop relying on
embedFieldIdCounter; ensure createFieldId returns only UUID-style IDs so
crypto.randomUUID and the fallback both produce unique values across
environments.
- Around line 189-227: The renderDiscordMarkdown function is too complex;
extract the segment-type detection and element creation into a helper (e.g.,
renderSegment or createSegmentElement) that accepts the segment string plus its
li and si indices and returns the appropriate React.ReactNode (handling {{var}},
**bold**, *italic*, `code`, and plain text) so renderDiscordMarkdown only does
line-splitting, loops, and calls the helper for each segment; preserve the
existing classNames and key generation format (e.g., `var-${li}-${si}`,
`b-${li}-${si}`, `i-${li}-${si}`, `c-${li}-${si}`, `t-${li}-${si}`) and ensure
behavior and output remain unchanged.
- Around line 424-429: The current useEffect (using ensureFieldIds, value, and
onChange) can trigger an infinite render loop because calling onChange with a
new value object causes parent to supply a new reference and re-run the effect;
fix this by making the effect only react to the actual fields and by ensuring
the update runs only when IDs genuinely differ: change the dependency from value
to value.fields (keep onChange), compute fieldsWithIds via
ensureFieldIds(fields), perform a deep-equality check between fieldsWithIds and
value.fields (not just reference), and only call onChange when they differ; to
be extra safe, protect the one-time initialization with a ref (e.g.,
initializedRef) so ensureFieldIds only triggers onChange once per
mount/first-normalization; refer to ensureFieldIds, useEffect, onChange,
value.fields and addField in your edits.
- Around line 236-243: Extract the nested ternary that computes the color class
for the ratio into a small helper (e.g., getRatioColorClass or
computeRatioClass) and use that helper inside the className call instead of the
inline ternary; update the component in embed-builder.tsx where ratio is used
(the cn(...) call) to call the helper, keep the same thresholds (>=1 =>
'text-destructive font-semibold', >=0.9 => 'text-yellow-500', else
'text-muted-foreground'), and place the helper near the component or in a local
utilities file for clarity and testability.
In `@web/tests/components/ui/embed-builder.test.tsx`:
- Around line 383-388: The test is flaky because it compares footer text using
toLocaleTimeString(undefined, ...) which picks the system locale; update the
test to use an explicit, fixed locale (e.g., 'en-GB' or 'en-US') when calling
toLocaleTimeString so the expected string is deterministic, and ensure the
EmbedPreview component (or the code path that formats the timestamp) also uses
the same explicit locale or mock Date.prototype.toLocaleTimeString in the test
to keep behavior consistent; locate the assertion in embed-builder.test.tsx (the
expect(footer).toHaveTextContent(...) line) and the timestamp formatting logic
in the EmbedPreview component and make them use the same fixed locale.
- Around line 38-46: The test for "calls onChange when title is edited" uses a
weak assertion; update the final expectation on the captured onChange payload to
assert the full title value (e.g., expect(lastCall.title).toBe('Hello') or
toContain('Hello')) instead of checking for a single character. Keep the
existing retrieval of the latest mock call
(onChange.mock.calls[onChange.mock.calls.length - 1][0]) and replace
expect(lastCall.title).toContain('o') with a precise equality or substring
assertion so the test verifies the full edited title produced by
renderBuilder()/onChange after typing into titleInput obtained via
screen.getByPlaceholderText('Embed title...').
🪄 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: 093d62a6-a0dd-4bfc-b5b9-9132478a1c09
📒 Files selected for processing (4)
web/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/ReputationSection.tsxweb/src/components/ui/embed-builder.tsxweb/tests/components/ui/embed-builder.test.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Greptile Review
- GitHub Check: Cursor Bugbot
- GitHub Check: E2E Tests (1/2)
- GitHub Check: E2E Tests (2/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/dashboard/config-sections/ReputationSection.tsxweb/src/components/dashboard/config-editor.tsxweb/tests/components/ui/embed-builder.test.tsxweb/src/components/ui/embed-builder.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/ReputationSection.tsxweb/src/components/dashboard/config-editor.tsxweb/src/components/ui/embed-builder.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/ReputationSection.tsxweb/src/components/dashboard/config-editor.tsxweb/tests/components/ui/embed-builder.test.tsxweb/src/components/ui/embed-builder.tsx
web/tests/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with thejsdomenvironment and React Testing Library, matching theweb/src/structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Files:
web/tests/components/ui/embed-builder.test.tsx
🧠 Learnings (6)
📚 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-editor.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to {config.json,src/api/utils/configAllowlist.js} : 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.
Applied to files:
web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'
Applied to files:
web/src/components/dashboard/config-editor.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/ui/embed-builder.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/ui/embed-builder.test.tsx
📚 Learning: 2026-03-11T05:32:46.325Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T05:32:46.325Z
Learning: Applies to web/src/app/**/*.{ts,tsx} : Apply static metadata to server-rendered dashboard entry pages and use title template format for root app metadata
Applied to files:
web/src/components/ui/embed-builder.tsx
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/dashboard/config-editor.tsx
[warning] 112-112: The empty object is useless.
[warning] 110-110: The empty object is useless.
web/src/components/ui/embed-builder.tsx
[warning] 495-495: Use
Details
, , , or instead of the "group" role to ensure accessibility across all devices.[warning] 133-133: Prefer String#replaceAll() over String#replace().
[failure] 189-189: Refactor this function to reduce its Cognitive Complexity from 19 to the 15 allowed.
[warning] 278-278: Mark the props of the component as read-only.
[warning] 231-231: Mark the props of the component as read-only.
[warning] 240-242: Extract this nested ternary operation into an independent statement.
[warning] 607-607: Use
[warning] 252-258: Mark the props of the component as read-only.
[warning] 184-184: Do not use Array index in keys
[warning] 417-417: Mark the props of the component as read-only.
🔇 Additional comments (5)
web/src/components/dashboard/config-editor.tsx (1)
108-117: Empty object spreads are intentional defensive patterns; SonarCloud warnings can be ignored here.The
(prev ?? {})and(prev?.ai ?? {})spreads handle the case wheredraftConfigor itsaiproperty isnull/undefined. While SonarCloud flags these as "useless," they prevent runtime errors when spreading into a new object. This is a valid defensive pattern for immutable state updates.web/src/components/dashboard/config-sections/ReputationSection.tsx (2)
135-136: Explicit type annotations are good but redundant.TypeScript can infer the types from the
mapandfiltercallbacks. However, the explicit annotations don't cause harm and may improve readability for some developers.
34-37: This component is unused dead code and should be removed.ReputationSection is not imported or used anywhere in the codebase—it only exists in its own file and the barrel export. The Reputation/XP settings are handled by CommunitySettingsSection instead, which correctly reads and writes
levelThresholdsto thexpconfig section.The read/write location mismatch and type assertion issues identified in the original review are valid, but they have no runtime impact since this component is never instantiated. Either remove ReputationSection or update it if it's intended for future use.
web/tests/components/ui/embed-builder.test.tsx (1)
1-11: LGTM!Good test file structure with proper imports, helper function for rendering, and comprehensive test organization covering rendering, interactions, field management, and utility functions.
web/src/components/ui/embed-builder.tsx (1)
1-56: LGTM!Well-structured types, constants, and utility functions. The
CHAR_LIMITSmatch Discord's documented limits,defaultEmbedConfig()provides sensible defaults, andgetTotalCharCount()correctly aggregates character usage.Also applies to: 59-93, 143-165
web/src/components/dashboard/config-sections/ReputationSection.tsx
Outdated
Show resolved
Hide resolved
|
@coderabbitai autofix stacked pr |
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. Autofix skipped. No unresolved CodeRabbit review comments with fix instructions found. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e2630f5a78
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@coderabbitai autofix stacked pr |
|
@claude review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Claude finished @BillChirico's task in 5m 27s —— View job Code Review — PR #423
Issues Found4 issues requiring changes before merge: 1.
|
There was a problem hiding this comment.
Actionable comments posted: 5
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/buildPayload.js (1)
25-36:⚠️ Potential issue | 🟠 MajorAlign this mapper with the new embed-builder contract.
web/src/components/ui/embed-builder.tsxnow models thumbnails, footer text/icon, image URL, fields, and timestamps on separate keys, but this function still serializes onlythumbnailandfooter. As written, the previewed embed can diverge from what actually gets sent to Discord because custom thumbnails, footer icons, fields, images, and timestamps are dropped here unless something upstream normalizes the new shape first.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/actions/buildPayload.js` around lines 25 - 36, The embed serialization in buildPayload.js only maps thumbnail and footer but must follow the new embed-builder contract: update the mapping inside the format === 'embed' || format === 'both' block (where embedConfig, EmbedBuilder, renderTemplate, templateContext and payload.embeds are used) to also handle embedConfig.image, embedConfig.footerText and embedConfig.footerIcon (combine into setFooter({ text, iconURL })), embedConfig.fields (map into addFields with rendered name/value/inline), and embedConfig.timestamp (use setTimestamp after rendering/parsing); ensure you still render templates via renderTemplate for any string values so the preview matches the actual Discord payload.
♻️ Duplicate comments (7)
tests/modules/actions/webhook.test.js (1)
92-92:⚠️ Potential issue | 🟡 MinorOverlong single-line assertions/calls violate width guideline
Lines 92, 115, and 128 exceed the 100-character max width. Please re-wrap these calls.
As per coding guidelines, "Maintain a maximum line width of 100 characters".♻️ Suggested fix
- expect(info).toHaveBeenCalledWith('webhook fired', expect.objectContaining({ status: 200 })); + expect(info).toHaveBeenCalledWith( + 'webhook fired', + expect.objectContaining({ status: 200 }), + ); - await handleWebhook({ type: 'webhook', url: 'https://example.com/hook', payload: '{}' }, ctx); + await handleWebhook( + { type: 'webhook', url: 'https://example.com/hook', payload: '{}' }, + ctx, + ); - await handleWebhook({ type: 'webhook', url: 'https://example.com/hook', payload: '{}' }, ctx); + await handleWebhook( + { type: 'webhook', url: 'https://example.com/hook', payload: '{}' }, + ctx, + );Also applies to: 115-115, 128-128
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/modules/actions/webhook.test.js` at line 92, The long single-line assertions in tests/modules/actions/webhook.test.js (the expect(info).toHaveBeenCalledWith('webhook fired', expect.objectContaining({ status: 200 })) call and the two similar expect calls later) exceed the 100-character width; re-format each expect(...).toHaveBeenCalledWith(...) invocation to multiple lines (split the function call, the first argument, and the expect.objectContaining({...}) argument across lines) so each line stays under 100 characters while preserving the same assertions and order.tests/modules/actions/nickPrefix.test.js (1)
12-12:⚠️ Potential issue | 🟡 MinorLine width regression in
makeContextsignatureLine 12 is over the 100-character limit; this appears to reintroduce a previously fixed formatting issue.
As per coding guidelines, "Maintain a maximum line width of 100 characters".♻️ Suggested fix
-function makeContext({ displayName = 'TestUser', hasPermission = true, isOwner = false } = {}) { +function makeContext({ + displayName = 'TestUser', + hasPermission = true, + isOwner = false, +} = {}) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/modules/actions/nickPrefix.test.js` at line 12, The function signature for makeContext exceeds the 100-character line width limit; split the parameter list across multiple lines or move default values onto their own indented lines so the declaration stays under 100 chars (e.g., break the destructured params { displayName = 'TestUser', hasPermission = true, isOwner = false } across lines in the makeContext function signature). Ensure indentation matches surrounding test style and run the formatter to enforce the line-length rule.web/src/app/api/auth/[...nextauth]/route.ts (1)
44-50:⚠️ Potential issue | 🟠 MajorCatch only handler-construction failures.
This still wraps
handler(request, context), so runtime NextAuth failures are converted into the "missing env" fallback, logged as config problems, and treated as cache-invalidation events. Catch onlygetHandler()failures here and let invocation errors surface normally;web/tests/api/auth-route.test.ts, Lines 160-189, will need to move with that change.Suggested patch
async function handleAuth( request: NextRequest, context: { params: Promise<{ nextauth: string[] }> }, ) { + let handler: ReturnType<typeof NextAuth>; try { - const handler = getHandler(); - return await handler(request, context); + handler = getHandler(); } catch (error) { // Reset cache on failure so next request retries handler creation cachedHandler = undefined; return fallbackAuthResponse(request, error); } + + return handler(request, context); }Run this to confirm that the current catch spans both construction and invocation, and that the suite currently encodes the invocation-failure fallback:
#!/bin/bash rg -n -C2 'getHandler\(\)|handler\(request, context\)|cached handler failed' \ web/src/app/api/auth/[...nextauth]/route.ts \ web/tests/api/auth-route.test.ts🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/api/auth/`[...nextauth]/route.ts around lines 44 - 50, The try/catch currently wraps both getHandler() and handler(request, context), causing runtime NextAuth invocation errors to be treated as handler-construction failures; change the code so only getHandler() is inside the try/catch (catch failures from getHandler(), clear cachedHandler and call fallbackAuthResponse(request, error)), then invoke handler(request, context) outside the catch so invocation errors bubble normally; update the related test block (web/tests/api/auth-route.test.ts lines ~160-189) to move the assertion that expects invocation failures to the new behavior after this change.web/src/components/dashboard/config-editor.tsx (1)
26-27:⚠️ Potential issue | 🟠 MajorUse one
guildIdsource of truth for both load and save.The initial fetch captures the selected guild once, but
saveChanges()re-readslocalStorageon every click. If the selected guild changes while this component stays mounted, the UI is still editing guild A while the PATCH goes to guild B.Also applies to: 85-87
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-editor.tsx` around lines 26 - 27, The component reads selected guild once in useEffect via getSelectedGuildId() but saveChanges() re-reads localStorage causing mismatched targets; fix by storing the selected guild id in a single state/variable when the component mounts (e.g., setGuildId state initialized in useEffect using getSelectedGuildId()) and have saveChanges() and any other handlers reference that stored guildId instead of calling getSelectedGuildId() or reading localStorage again (update references in saveChanges and the other spots mentioned around the save logic).web/src/components/ui/embed-builder.tsx (3)
387-393:⚠️ Potential issue | 🟡 MinorThumbnail-only embeds still render the empty preview.
hasContentignoresthumbnailType/thumbnailUrl, so choosing User Avatar, Server Icon, or a populated custom thumbnail still shows “Start editing to see a preview” until some unrelated field is filled.Suggested fix
+ const hasThumbnail = + config.thumbnailType !== 'none' && + (config.thumbnailType !== 'custom' || Boolean(config.thumbnailUrl)); + const hasContent = config.title || config.description || config.fields.length > 0 || config.footerText || config.imageUrl || - config.showTimestamp; + config.showTimestamp || + hasThumbnail;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/ui/embed-builder.tsx` around lines 387 - 393, The preview flag hasContent currently ignores thumbnails causing thumbnail-only embeds to show the empty preview; update the hasContent computation in embed-builder.tsx to also consider thumbnail presence by including checks for config.thumbnailUrl being truthy and/or config.thumbnailType not equal to the "none" value (or specifically checking for values like "user_avatar" or "server_icon"), so that hasContent becomes true when a thumbnail is selected or a custom thumbnailUrl is provided (keep using the existing hasContent variable and config fields to locate the change).
395-395:⚠️ Potential issue | 🟡 MinorRecompute the preview timestamp when the toggle changes.
useMemo(..., [])snapshots mount time once. If the user enables the timestamp later, the footer shows a stale value instead of the current time.Suggested fix
- const previewTimestamp = React.useMemo(() => formatPreviewTimestamp(new Date()), []); + const previewTimestamp = React.useMemo( + () => formatPreviewTimestamp(new Date()), + [config.showTimestamp], + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/ui/embed-builder.tsx` at line 395, The previewTimestamp is memoized only on mount (useMemo(..., [])) so it never updates when the user toggles timestamps; update the hook to recompute whenever the timestamp toggle changes by adding the toggle state/prop (e.g., the variable that controls timestamp visibility such as showTimestamp or timestampEnabled) to the useMemo dependency array used with formatPreviewTimestamp(new Date()), so previewTimestamp is regenerated when the toggle flips.
545-559:⚠️ Potential issue | 🟠 MajorReject configs above Discord's 6000-character embed ceiling.
Per-field
maxLengthis enforced, butupdate()andupdateField()still emit configs whose combined size exceedsCHAR_LIMITS.total. That pushes the failure to save/send time instead of enforcing the limit in the editor.Suggested fix
+ const applyConfig = React.useCallback( + (next: EmbedConfig) => { + if (getTotalCharCount(next) > CHAR_LIMITS.total) return; + onChange(next); + }, + [onChange], + ); + const update = React.useCallback( (patch: Partial<EmbedConfig>) => { - onChange({ ...value, ...patch }); + applyConfig({ ...value, ...patch }); }, - [value, onChange], + [value, applyConfig], ); const updateField = React.useCallback( (index: number, patch: Partial<EmbedField>) => { const fields = [...value.fields]; fields[index] = { ...fields[index], ...patch }; - onChange({ ...value, fields }); + applyConfig({ ...value, fields }); }, - [value, onChange], + [value, applyConfig], );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/ui/embed-builder.tsx` around lines 545 - 559, The editor callbacks update and updateField currently call onChange without checking the total embed size, so build a candidate config first (e.g., const candidate = { ...value, ...patch } in update and for updateField clone fields then const candidate = { ...value, fields }) and compute its combined character count against CHAR_LIMITS.total (or the existing embed-length utility if available); if the candidate exceeds CHAR_LIMITS.total, do not call onChange (or return a validation error) else proceed to call onChange(candidate). Ensure you reference and use the existing update and updateField functions and the value.fields array so the check prevents emitting oversized configs.
🤖 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/actions/xpBonus.js`:
- Around line 68-72: The pool.query(...) call in xpBonus.js exceeds the
100-character line width; refactor the call to wrap the SQL string and the
parameter array across multiple lines so no line exceeds 100 characters.
Specifically, break the single long line into a multi-line call like: call
pool.query with the SQL string on its own line (or split the string into
concatenated lines) and place the parameter array on subsequent indented lines
(referencing the existing pool.query invocation and the amount, guildId, userId
parameters) ensuring commas and brackets remain correct and indentation follows
project style.
In `@tests/modules/ai.test.js`:
- Around line 52-53: The test currently uses loose expect.objectContaining
assertions for history entries (e.g., the assertion checking history[0] in
tests/modules/ai.test.js), which hides mismatched shapes between in-memory and
DB-hydrated history objects; replace the objectContaining assertions with strict
assertions that verify the full expected object shape (use exact equality checks
on history[0] and the entries asserted at lines ~86-90) so the test enforces the
contract (e.g., assert the exact keys and values for user/assistant/system
entries rather than allowing extra/missing properties).
In `@web/src/components/dashboard/config-editor.tsx`:
- Around line 94-101: The save handler currently always PATCHes the fixed path
'ai.systemPrompt' which ignores the global dirty tracking (hasChanges) and
prevents batching or partial-failure handling; update the save logic in
config-editor.tsx (the function that does the fetch call using draftConfig,
config, and hasChanges) to compute the actual set of changed config paths by
diffing draftConfig vs config, then send those changes as either a batched PATCH
payload (or multiple per-section PATCHes) rather than a single hard-coded path;
ensure the code handling the fetch response processes per-path success/failure,
updates local state (config/draftConfig) for successful items, and surfaces
partial-failure errors to the diff-confirmation UI so the global save/discard
semantics remain correct.
In `@web/src/components/ui/embed-builder.tsx`:
- Around line 196-249: tokenizeMarkdownSegments is over-complex (high cognitive
complexity); refactor by extracting the repeated delimited-token scanning into
small helpers and use an ordered matcher table: create a helper like
scanDelimited(line, cursor, open, close) that returns {token, end} or null when
no match, and build an ordered array of token specs [{name:'variable',
open:'{{', close:'}}'}, {name:'bold', open:'**', close:'**'}, {name:'italic',
open:'*', close:'*'}, {name:'code', open:'`', close:'`'}]; in
tokenizeMarkdownSegments iterate those specs in priority order to test
startsWith(spec.open) and call scanDelimited to push the segment and advance
cursor, otherwise compute nextTokenStart by scanning spec.open positions (or
fall back to line.length) and push the plain text slice—this reduces branching
in tokenizeMarkdownSegments while preserving token priority and cursor movement.
In `@web/tests/api/auth-route.test.ts`:
- Around line 45-55: The test case name "returns a null session when auth env is
unavailable for /session" is misleading because the assertions expect an empty
object ({}); update the it(...) description to reflect the actual asserted
payload (for example, "returns an empty session object when auth env is
unavailable for /session") so the test name matches the behavior verified in the
test that uses mockGetAuthOptions, importRouteModule, GET, createRequest, and
createContext.
---
Outside diff comments:
In `@src/modules/actions/buildPayload.js`:
- Around line 25-36: The embed serialization in buildPayload.js only maps
thumbnail and footer but must follow the new embed-builder contract: update the
mapping inside the format === 'embed' || format === 'both' block (where
embedConfig, EmbedBuilder, renderTemplate, templateContext and payload.embeds
are used) to also handle embedConfig.image, embedConfig.footerText and
embedConfig.footerIcon (combine into setFooter({ text, iconURL })),
embedConfig.fields (map into addFields with rendered name/value/inline), and
embedConfig.timestamp (use setTimestamp after rendering/parsing); ensure you
still render templates via renderTemplate for any string values so the preview
matches the actual Discord payload.
---
Duplicate comments:
In `@tests/modules/actions/nickPrefix.test.js`:
- Line 12: The function signature for makeContext exceeds the 100-character line
width limit; split the parameter list across multiple lines or move default
values onto their own indented lines so the declaration stays under 100 chars
(e.g., break the destructured params { displayName = 'TestUser', hasPermission =
true, isOwner = false } across lines in the makeContext function signature).
Ensure indentation matches surrounding test style and run the formatter to
enforce the line-length rule.
In `@tests/modules/actions/webhook.test.js`:
- Line 92: The long single-line assertions in
tests/modules/actions/webhook.test.js (the
expect(info).toHaveBeenCalledWith('webhook fired', expect.objectContaining({
status: 200 })) call and the two similar expect calls later) exceed the
100-character width; re-format each expect(...).toHaveBeenCalledWith(...)
invocation to multiple lines (split the function call, the first argument, and
the expect.objectContaining({...}) argument across lines) so each line stays
under 100 characters while preserving the same assertions and order.
In `@web/src/app/api/auth/`[...nextauth]/route.ts:
- Around line 44-50: The try/catch currently wraps both getHandler() and
handler(request, context), causing runtime NextAuth invocation errors to be
treated as handler-construction failures; change the code so only getHandler()
is inside the try/catch (catch failures from getHandler(), clear cachedHandler
and call fallbackAuthResponse(request, error)), then invoke handler(request,
context) outside the catch so invocation errors bubble normally; update the
related test block (web/tests/api/auth-route.test.ts lines ~160-189) to move the
assertion that expects invocation failures to the new behavior after this
change.
In `@web/src/components/dashboard/config-editor.tsx`:
- Around line 26-27: The component reads selected guild once in useEffect via
getSelectedGuildId() but saveChanges() re-reads localStorage causing mismatched
targets; fix by storing the selected guild id in a single state/variable when
the component mounts (e.g., setGuildId state initialized in useEffect using
getSelectedGuildId()) and have saveChanges() and any other handlers reference
that stored guildId instead of calling getSelectedGuildId() or reading
localStorage again (update references in saveChanges and the other spots
mentioned around the save logic).
In `@web/src/components/ui/embed-builder.tsx`:
- Around line 387-393: The preview flag hasContent currently ignores thumbnails
causing thumbnail-only embeds to show the empty preview; update the hasContent
computation in embed-builder.tsx to also consider thumbnail presence by
including checks for config.thumbnailUrl being truthy and/or
config.thumbnailType not equal to the "none" value (or specifically checking for
values like "user_avatar" or "server_icon"), so that hasContent becomes true
when a thumbnail is selected or a custom thumbnailUrl is provided (keep using
the existing hasContent variable and config fields to locate the change).
- Line 395: The previewTimestamp is memoized only on mount (useMemo(..., [])) so
it never updates when the user toggles timestamps; update the hook to recompute
whenever the timestamp toggle changes by adding the toggle state/prop (e.g., the
variable that controls timestamp visibility such as showTimestamp or
timestampEnabled) to the useMemo dependency array used with
formatPreviewTimestamp(new Date()), so previewTimestamp is regenerated when the
toggle flips.
- Around line 545-559: The editor callbacks update and updateField currently
call onChange without checking the total embed size, so build a candidate config
first (e.g., const candidate = { ...value, ...patch } in update and for
updateField clone fields then const candidate = { ...value, fields }) and
compute its combined character count against CHAR_LIMITS.total (or the existing
embed-length utility if available); if the candidate exceeds CHAR_LIMITS.total,
do not call onChange (or return a validation error) else proceed to call
onChange(candidate). Ensure you reference and use the existing update and
updateField functions and the value.fields array so the check prevents emitting
oversized configs.
🪄 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: 867020c4-2a51-4121-be2f-7be43d67fed3
📒 Files selected for processing (21)
.github/workflows/ci.ymlsrc/modules/actions/buildPayload.jssrc/modules/actions/xpBonus.jssrc/modules/levelUpActions.jssrc/modules/triage.jstests/api/utils/configValidation.test.jstests/modules/actions/addReaction.test.jstests/modules/actions/announce.test.jstests/modules/actions/nickPrefix.test.jstests/modules/actions/sendDm.test.jstests/modules/actions/webhook.test.jstests/modules/actions/xpBonus.test.jstests/modules/ai.test.jsweb/src/app/api/auth/[...nextauth]/route.tsweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/ReputationSection.tsxweb/src/components/dashboard/config-sections/index.tsweb/src/components/ui/embed-builder.tsxweb/tests/api/auth-route.test.tsweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/tests/components/ui/embed-builder.test.tsx
💤 Files with no reviewable changes (2)
- web/src/components/dashboard/config-sections/index.ts
- web/src/components/dashboard/config-sections/ReputationSection.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: Greptile Review
- GitHub Check: Cursor Bugbot
- GitHub Check: E2E Tests (1/2)
- GitHub Check: E2E Tests (2/2)
- GitHub Check: Test
🧰 Additional context used
📓 Path-based instructions (10)
**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESM-only syntax:
import/export, neverrequire()/module.exports
Files:
tests/modules/actions/xpBonus.test.jssrc/modules/actions/xpBonus.jstests/modules/actions/addReaction.test.jstests/modules/actions/nickPrefix.test.jssrc/modules/triage.jstests/modules/actions/sendDm.test.jstests/modules/actions/announce.test.jssrc/modules/levelUpActions.jstests/modules/actions/webhook.test.jssrc/modules/actions/buildPayload.jstests/api/utils/configValidation.test.jstests/modules/ai.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/modules/actions/xpBonus.test.jssrc/modules/actions/xpBonus.jstests/modules/actions/addReaction.test.jstests/modules/actions/nickPrefix.test.jssrc/modules/triage.jstests/modules/actions/sendDm.test.jstests/modules/actions/announce.test.jssrc/modules/levelUpActions.jstests/modules/actions/webhook.test.jssrc/modules/actions/buildPayload.jstests/api/utils/configValidation.test.jstests/modules/ai.test.jsweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/app/api/auth/[...nextauth]/route.tsweb/src/components/dashboard/config-editor.tsxweb/tests/api/auth-route.test.tsweb/src/components/ui/embed-builder.tsxweb/tests/components/ui/embed-builder.test.tsx
tests/**/*.test.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
tests/**/*.test.js: Write bot tests using Vitest 4 with thenodeenvironment, matching thesrc/structure in thetests/directory
Maintain test coverage thresholds: statements 85%, branches 82%, functions 85%, lines 85%; never lower thresholds—add tests to cover new code instead
Files:
tests/modules/actions/xpBonus.test.jstests/modules/actions/addReaction.test.jstests/modules/actions/nickPrefix.test.jstests/modules/actions/sendDm.test.jstests/modules/actions/announce.test.jstests/modules/actions/webhook.test.jstests/api/utils/configValidation.test.jstests/modules/ai.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/xpBonus.test.jssrc/modules/actions/xpBonus.jstests/modules/actions/addReaction.test.jstests/modules/actions/nickPrefix.test.jssrc/modules/triage.jstests/modules/actions/sendDm.test.jstests/modules/actions/announce.test.jssrc/modules/levelUpActions.jstests/modules/actions/webhook.test.jssrc/modules/actions/buildPayload.jstests/api/utils/configValidation.test.jstests/modules/ai.test.jsweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/app/api/auth/[...nextauth]/route.tsweb/src/components/dashboard/config-editor.tsxweb/tests/api/auth-route.test.tsweb/src/components/ui/embed-builder.tsxweb/tests/components/ui/embed-builder.test.tsx
**/*.{js,ts}
📄 CodeRabbit inference engine (AGENTS.md)
Use parameterized SQL only; do not use string concatenation for SQL queries
Files:
tests/modules/actions/xpBonus.test.jssrc/modules/actions/xpBonus.jstests/modules/actions/addReaction.test.jstests/modules/actions/nickPrefix.test.jssrc/modules/triage.jstests/modules/actions/sendDm.test.jstests/modules/actions/announce.test.jssrc/modules/levelUpActions.jstests/modules/actions/webhook.test.jssrc/modules/actions/buildPayload.jstests/api/utils/configValidation.test.jstests/modules/ai.test.jsweb/src/app/api/auth/[...nextauth]/route.tsweb/tests/api/auth-route.test.ts
src/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/**/*.js: Never useconsole.*methods; use the Winston logger instead viaimport logger from '../logger.js'(adjust path as needed), then calllogger.info(),logger.warn(),logger.error(), orlogger.debug()
Always usesafeReply(),safeSend(), orsafeEditReply()instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully
Files:
src/modules/actions/xpBonus.jssrc/modules/triage.jssrc/modules/levelUpActions.jssrc/modules/actions/buildPayload.js
src/modules/**/*.js
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Create feature modules in
src/modules/and add corresponding config sections toconfig.json
Files:
src/modules/actions/xpBonus.jssrc/modules/triage.jssrc/modules/levelUpActions.jssrc/modules/actions/buildPayload.js
src/**/*.{js,ts}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,ts}: Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Community features should be gated behind config..enabled. Moderation commands are the exception.
Files:
src/modules/actions/xpBonus.jssrc/modules/triage.jssrc/modules/levelUpActions.jssrc/modules/actions/buildPayload.js
web/tests/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
web/tests/**/*.test.{ts,tsx}: Write web dashboard tests using Vitest 4 with thejsdomenvironment and React Testing Library, matching theweb/src/structure
Maintain test coverage thresholds of 85% across all metrics (statements, branches, functions, lines) for web dashboard tests
Files:
web/tests/components/dashboard/config-editor-autosave.test.tsxweb/tests/api/auth-route.test.tsweb/tests/components/ui/embed-builder.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/app/api/auth/[...nextauth]/route.tsweb/src/components/dashboard/config-editor.tsxweb/src/components/ui/embed-builder.tsx
🧠 Learnings (19)
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to **/*.{js,ts,tsx} : Maintain a maximum line width of 100 characters
Applied to files:
tests/modules/actions/nickPrefix.test.jstests/modules/actions/webhook.test.jsweb/src/components/ui/embed-builder.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} : Use the safe Discord messaging helpers in src/utils/safeSend.js instead of raw reply/send/edit calls
Applied to files:
tests/modules/actions/announce.test.jssrc/modules/levelUpActions.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/**/*.js : Always use `safeReply()`, `safeSend()`, or `safeEditReply()` instead of raw Discord.js methods for safe Discord messaging that handles errors gracefully
Applied to files:
tests/modules/actions/announce.test.js
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Use `pnpm changeset` to create release notes for user-facing changes
Applied to files:
.github/workflows/ci.yml
📚 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:
tests/modules/actions/webhook.test.jsweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/tests/api/auth-route.test.tsweb/tests/components/ui/embed-builder.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/modules/actions/webhook.test.jstests/api/utils/configValidation.test.jsweb/tests/api/auth-route.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} : 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.tsxweb/tests/api/auth-route.test.tsweb/tests/components/ui/embed-builder.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.tsxweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to web/src/app/api/**/route.ts : Include guildId in signed WebSocket ticket payload when issuing tickets from dashboard endpoints
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 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:
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.tsxweb/tests/api/auth-route.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 src/api/routes/**/*.js : Create API route handlers in `src/api/routes/` and mount them in `src/api/server.js`; add auth middleware if the endpoint requires authentication
Applied to files:
web/src/app/api/auth/[...nextauth]/route.tsweb/tests/api/auth-route.test.ts
📚 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-editor.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'
Applied to files:
web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to {config.json,src/api/utils/configAllowlist.js} : 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.
Applied to files:
web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-11T17:18:17.626Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T17:18:17.626Z
Learning: Applies to src/**/{startup,command-register,reload}*.{js,ts} : Remove process.env.GUILD_ID runtime reads from bot startup and reload command registration
Applied to files:
web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-10T20:36:29.483Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T20:36:29.483Z
Learning: Do not persist GUILD_ID as a shared environment variable for multi-guild deployments; preserve dev-only guild-scoped deploy support via CLI flag instead
Applied to files:
web/src/components/dashboard/config-editor.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/api/auth-route.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/src/**/*.{ts,tsx} : Never use `console.*` methods in web dashboard code; use appropriate logging mechanisms for React applications
Applied to files:
web/src/components/ui/embed-builder.tsx
🪛 GitHub Check: SonarCloud Code Analysis
web/src/components/dashboard/config-editor.tsx
[warning] 163-163: The empty object is useless.
[warning] 110-110: The empty object is useless.
[warning] 165-165: The empty object is useless.
web/tests/api/auth-route.test.ts
[warning] 78-78: Expected an error object to be thrown.
web/src/components/ui/embed-builder.tsx
[warning] 386-386: Mark the props of the component as read-only.
[warning] 518-518: Mark the props of the component as read-only.
[warning] 134-134: Prefer String#replaceAll() over String#replace().
[warning] 360-366: Mark the props of the component as read-only.
[warning] 346-346: Mark the props of the component as read-only.
[failure] 196-196: Refactor this function to reduce its Cognitive Complexity from 23 to the 15 allowed.
[warning] 268-268: Do not use Array index in keys
🔇 Additional comments (4)
src/modules/triage.js (1)
541-549: Looks good — formatting change is safe and style-compliant.This multiline
sendResponses(...)call keeps behavior unchanged and correctly includes the trailing
comma for multiline arguments.As per coding guidelines, "Always include trailing commas in multi-line arrays, objects, and function parameters".
src/modules/levelUpActions.js (1)
16-16: Import reorder at Line 16 looks good.Non-functional cleanup only; behavior remains unchanged and style rules are respected.
tests/api/utils/configValidation.test.js (2)
414-417: Formatting-only change is safe and keeps assertion intent unchanged.
No functional concerns here; this remains a valid positive-path test forxp.defaultActions.
425-427: Formatting-only change is safe and preserves test behavior.
Assertion remains correct for the validxp.roleRewardsobject shape.
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 122132 🏁 Script executed: Length of output: 14242 🏁 Script executed: Length of output: 5201 🏁 Script executed: Length of output: 14432 🏁 Script executed: Length of output: 3900
The one remaining blocking issue (SonarCloud failure) is Autofix stacked PR: reduce
|
) - Color picker with Discord preset colors + hex input - Title, description fields with character limits (256, 4096) - Thumbnail selector: none / user avatar / server icon / custom URL - Field management: add/remove/reorder with inline toggle - Footer text (2048) + icon URL, full-width image URL - Timestamp toggle, format selector (text/embed/text+embed) - Live Discord-style embed preview with accent color bar - Variable template support with {{var}} badge rendering - Lightweight Discord markdown preview (bold, italic, code) - Character count indicators with color-coded warnings - Aria-labels on switches for accessibility - 29 unit tests covering all features
1a3133c to
95d2c36
Compare
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
| const data: unknown = await res.json(); | ||
| if (!isGuildConfig(data)) { | ||
| throw new Error('Invalid config response'); | ||
| } |
There was a problem hiding this comment.
Save error unmounts the entire editor, trapping the user's draft
error is shared between load errors and save errors. When saveChanges catches and calls setError(...), the if (error) return <div role="alert">{error}</div> early return fires on the next render, replacing the full editor with a bare error string. The user's unsaved edits disappear with no retry path — they must hard-refresh and re-enter their changes.
The fix is to use a separate piece of state for in-editor save errors and render it inline (e.g. next to the Save button), keeping the load-error early return for the initial failure case only.
Prompt To Fix With AI
This is a comment left during a code review.
Path: web/src/components/dashboard/config-editor.tsx
Line: 49-52
Comment:
**Save error unmounts the entire editor, trapping the user's draft**
`error` is shared between load errors and save errors. When `saveChanges` catches and calls `setError(...)`, the `if (error) return <div role="alert">{error}</div>` early return fires on the next render, replacing the full editor with a bare error string. The user's unsaved edits disappear with no retry path — they must hard-refresh and re-enter their changes.
The fix is to use a separate piece of state for in-editor save errors and render it inline (e.g. next to the Save button), keeping the load-error early return for the initial failure case only.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| expect(history[1].content).toBe('response'); | ||
| expect(mockQuery).toHaveBeenCalledWith( | ||
| expect.stringContaining('SELECT role, content, created_at FROM conversations'), | ||
| expect.stringContaining('SELECT role, content FROM conversations'), |
There was a problem hiding this comment.
Test asserts wrong SQL query substring after edit
High Severity
The test assertion was changed to expect 'SELECT role, content FROM conversations' but the actual production query in src/modules/ai.js line 224 is 'SELECT role, content, created_at FROM conversations'. Since expect.stringContaining checks for an exact substring, the expected string "SELECT role, content FROM conversations" is NOT a substring of the actual query (which has , created_at between content and FROM). This test will always fail.
| import * as React from 'react'; | ||
| import { generateId } from '@/components/dashboard/config-editor-utils'; | ||
| import { Button } from '@/components/ui/button'; |
There was a problem hiding this comment.
This UI-level component imports generateId from the dashboard config-editor utils, creating an unexpected dependency from components/ui → components/dashboard. Consider moving the ID helper to a shared utility (e.g. web/src/lib/ids.ts) or generating IDs locally so EmbedBuilder remains reusable outside the dashboard config editor.
| export { | ||
| CharCount, | ||
| EmbedBuilder, | ||
| EmbedPreview, | ||
| formatPreviewTimestamp, | ||
| renderDiscordMarkdown, | ||
| renderVariablePreview, | ||
| VariablePalette, | ||
| }; |
There was a problem hiding this comment.
The PR description mentions integrating the Embed Builder into the dashboard config editing flow, but there are no usages of EmbedBuilder/embed-builder anywhere in web/src beyond this component file. Either wire the component into the intended dashboard page/section, or update the PR description to reflect the actual scope.
| expect(history[1].content).toBe('response'); | ||
| expect(mockQuery).toHaveBeenCalledWith( | ||
| expect.stringContaining('SELECT role, content, created_at FROM conversations'), | ||
| expect.stringContaining('SELECT role, content FROM conversations'), |
There was a problem hiding this comment.
This assertion expects the SQL query to omit created_at, but src/modules/ai.js still selects created_at when hydrating history (SELECT role, content, created_at FROM conversations ...). Update the expectation (or the implementation) so the test matches the actual query.
| expect.stringContaining('SELECT role, content FROM conversations'), | |
| expect.stringContaining('SELECT role, content, created_at FROM conversations'), |
* feat(dashboard): add visual Discord embed builder component (Closes #371) - Color picker with Discord preset colors + hex input - Title, description fields with character limits (256, 4096) - Thumbnail selector: none / user avatar / server icon / custom URL - Field management: add/remove/reorder with inline toggle - Footer text (2048) + icon URL, full-width image URL - Timestamp toggle, format selector (text/embed/text+embed) - Live Discord-style embed preview with accent color bar - Variable template support with {{var}} badge rendering - Lightweight Discord markdown preview (bold, italic, code) - Character count indicators with color-coded warnings - Aria-labels on switches for accessibility - 29 unit tests covering all features * fix: address review comments on embed-builder * fix: address embed builder review feedback * fix: remove regex hotspots from embed preview * fix: address final embed builder review feedback * test: cover auth route fallback branches * test: cover auth route fallback cache recovery # Conflicts: # tests/modules/ai.test.js # web/src/components/dashboard/config-editor.tsx # web/tests/components/dashboard/config-editor-autosave.test.tsx
* Add dashboard permission access controls * Harden guild access resolution * Validate web guild access responses * Fix retry header fallback handling * Align dashboard roles with moderator access * fix(api): parallelize guild access checks * refactor(web): share guild directory state * fix(web): correct retry attempt logging * docs: align moderator access comments * fix(api): preserve dashboard xp actor attribution * fix(api): cap guild access batch size * fix(web): require botPresent in guild context * fix(web): validate forwarded discord identities * test: align dashboard and permission coverage * test: increase root vitest timeout budget * Refactor conditional checks for claude-review job Signed-off-by: Bill Chirico <bill@chirico.dev> * Update claude-review workflow to specify project context and enhance inline comment guidelines - Clarified the project context in the review prompt for the Discord bot Volvox.Bot. - Added detailed instructions for posting inline comments, including formatting requirements and examples for AI fix prompts. - Ensured that the review process emphasizes only reporting issues without providing praise or compliments. * feat(dashboard): add WYSIWYG Discord markdown editor component (#422) * feat(dashboard): add Discord markdown parser and WYSIWYG editor component - Add discord-markdown.ts utility with parser, wrapSelection, insertAtCursor, wrapLine - Add DiscordMarkdownEditor component with: - Toolbar (Bold, Italic, Underline, Strikethrough, Code, Code Block, Spoiler, Quote, H1-H3, Lists) - Variable inserter dropdown for template variables - Split view: raw editor + live Discord-style preview - Character counter with configurable limit - Keyboard shortcuts (Ctrl+B, Ctrl+I, Ctrl+U) - Follows existing Radix UI + Tailwind + Lucide patterns * test: add DiscordMarkdownEditor and discord-markdown parser tests - 29 tests covering parser, utility functions, and component behavior - Tests: toolbar rendering, variable insertion, character counter, markdown preview, disabled state, XSS prevention * fix: address markdown editor review feedback * fix: restore dashboard config test compatibility * fix: address discord markdown review feedback * fix: resolve markdown parser edge cases * fix(ci): stabilize markdown editor branch checks * fix(sonar): remove regex hotspot parsing * fix(sonar): remove remaining regex markdown hotspots * fix: address remaining review feedback on markdown editor - Use stable index-based keys for badge rows to prevent remount on edit - Add timestamp to initConversationHistory startup hydration entries - Replace placeholder text in Welcome Messages section with proper UI copy - Fix block quote parser to require space after > (matching Discord behavior) - Render plain newlines as line breaks instead of paragraph blocks - Update ReputationSection JSDoc to remove stale 'announcements' reference - Add fallback to reputation.levelThresholds for backward compatibility * fix: address final review feedback on markdown editor - Fix hydration mismatch in preview rendering (isMounted gate) - Remove * as unordered list marker (Discord only supports -) - Remove unused act import from autosave tests - Add TODO for Welcome Messages section placeholder - Unify ReputationSection to use updateDraftConfig consistently - Remove stale reputation.levelThresholds fallback - Add sweepDmLimits test for stale-entry eviction - Strengthen ai.test.js hydration timestamp assertions with exact values # Conflicts: # tests/modules/ai.test.js # web/src/components/dashboard/config-editor.tsx # web/tests/components/dashboard/config-editor-autosave.test.tsx * feat(dashboard): add visual Discord embed builder component (#423) * feat(dashboard): add visual Discord embed builder component (Closes #371) - Color picker with Discord preset colors + hex input - Title, description fields with character limits (256, 4096) - Thumbnail selector: none / user avatar / server icon / custom URL - Field management: add/remove/reorder with inline toggle - Footer text (2048) + icon URL, full-width image URL - Timestamp toggle, format selector (text/embed/text+embed) - Live Discord-style embed preview with accent color bar - Variable template support with {{var}} badge rendering - Lightweight Discord markdown preview (bold, italic, code) - Character count indicators with color-coded warnings - Aria-labels on switches for accessibility - 29 unit tests covering all features * fix: address review comments on embed-builder * fix: address embed builder review feedback * fix: remove regex hotspots from embed preview * fix: address final embed builder review feedback * test: cover auth route fallback branches * test: cover auth route fallback cache recovery # Conflicts: # tests/modules/ai.test.js # web/src/components/dashboard/config-editor.tsx # web/tests/components/dashboard/config-editor-autosave.test.tsx * Fix guild access batching and XP auth fallback * Enforce editor and embed character limits --------- Signed-off-by: Bill Chirico <bill@chirico.dev> Signed-off-by: MohsinCoding <91380732+MohsinCoding@users.noreply.github.com> Co-authored-by: Bill Chirico <bill@chirico.dev>





Summary
Implements the Visual Discord Embed Builder component for Issue #371.
New Files
web/src/components/ui/embed-builder.tsx— Full-featured embed builder componentweb/tests/components/ui/embed-builder.test.tsx— embed builder unit testsFeatures
Component API
Character Limits
Additional scope in this PR
ReputationSectioncode surfaced during reviewTechnical Details
{{var}}rendered as styled badgesTests
Web and root test suites pass after the final review fixes, including:
Closes #371