Skip to content

feat(dashboard): add visual Discord embed builder component#423

Merged
BillChirico merged 7 commits intomainfrom
feat/discord-embed-builder
Apr 3, 2026
Merged

feat(dashboard): add visual Discord embed builder component#423
BillChirico merged 7 commits intomainfrom
feat/discord-embed-builder

Conversation

@BillChirico
Copy link
Copy Markdown
Collaborator

@BillChirico BillChirico commented Apr 2, 2026

Summary

Implements the Visual Discord Embed Builder component for Issue #371.

New Files

  • web/src/components/ui/embed-builder.tsx — Full-featured embed builder component
  • web/tests/components/ui/embed-builder.test.tsx — embed builder unit tests

Features

  1. Color picker — Discord preset colors + hex input
  2. Title field with template variable support (max 256 chars)
  3. Description field with Discord markdown support + variables (max 4096 chars)
  4. Thumbnail selector — user avatar, server icon, custom URL, or none
  5. Fields — add/remove/reorder, inline toggle, name (256) + value (1024) with variables
  6. Footer — text (2048) + optional icon URL
  7. Image — optional full-width image URL
  8. Timestamp toggle
  9. Live preview — Discord-style embed rendering in real-time with accent color bar
  10. Format selector — text only / embed only / text + embed

Component API

<EmbedBuilder
  value={embedConfig}
  onChange={setEmbedConfig}
  variables={["username", "mention", "level", ...]}
/>

Character Limits

  • Title: 256, Description: 4096, Field name: 256, Field value: 1024, Footer: 2048, Total: 6000
  • Color-coded character count indicators (green → yellow → red)

Additional scope in this PR

  • Integrates the embed builder into the dashboard config editing flow
  • Adds/updates dashboard autosave coverage around config editing
  • Hardens the NextAuth app-route fallback so auth endpoints degrade safely when env configuration is missing
  • Cleans up dead ReputationSection code surfaced during review

Technical Details

  • Uses existing Radix UI + Tailwind + Lucide patterns
  • Variable tokens {{var}} rendered as styled badges
  • Lightweight Discord markdown preview (bold, italic, code)
  • Accessible: aria-labels on all interactive switches
  • Zero new dependencies

Tests

Web and root test suites pass after the final review fixes, including:

  • embed builder component coverage
  • auth route fallback coverage
  • dashboard config/autosave coverage

Closes #371

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

changeset-bot bot commented Apr 2, 2026

⚠️ No Changeset found

Latest commit: 95d2c36

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

@railway-app
Copy link
Copy Markdown

railway-app bot commented Apr 2, 2026

🚅 Deployed to the volvox-bot-pr-423 environment in volvox-bot

Service Status Web Updated (UTC)
bot 🕒 Building (View Logs) Web Apr 3, 2026 at 12:20 am
web 🕒 Building (View Logs) Apr 3, 2026 at 12:20 am

@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-423 April 2, 2026 16:48 Destroyed
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 2, 2026

Greptile Summary

This PR delivers the visual Discord embed builder component (EmbedBuilder), wires up a functional save/discard flow in config-editor.tsx, adds a graceful NextAuth fallback for missing env config, and removes the dead ReputationSection.

  • P1 — save error unmounts editor: config-editor.tsx shares one error state between load and save. A failed PATCH call triggers the load-error early return, replacing the full editor UI with a bare error string and discarding the user's draft with no retry path. A separate saveError state rendered inline is needed.
  • P2 — hardcoded 'en-US' locale: formatPreviewTimestamp in embed-builder.tsx violates the project's no-hardcoded-locale rule; pass undefined instead.

Confidence Score: 4/5

Safe to merge after the save-error UX bug in config-editor is addressed; embed builder and auth fallback are solid.

One P1 defect: a failed save replaces the entire config editor UI with a bare error message, trapping the user's unsaved draft. Needs a targeted fix before merge. All other findings are P2 or lower.

web/src/components/dashboard/config-editor.tsx — shared error state causes editor unmount on save failure.

Important Files Changed

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

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 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 / EmbedPreview components 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.

@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-423 April 2, 2026 17:07 Destroyed
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c92e6041-edcd-4cfd-a6bd-1973ba176c7c

📥 Commits

Reviewing files that changed from the base of the PR and between 1a3133c and 95d2c36.

📒 Files selected for processing (9)
  • tests/modules/ai.test.js
  • web/src/app/api/auth/[...nextauth]/route.ts
  • web/src/components/dashboard/config-editor.tsx
  • web/src/components/dashboard/config-sections/ReputationSection.tsx
  • web/src/components/dashboard/config-sections/index.ts
  • web/src/components/ui/embed-builder.tsx
  • web/tests/api/auth-route.test.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/tests/components/ui/embed-builder.test.tsx

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Embed builder with live preview for creating and editing Discord embeds, featuring character counting, variable insertion, and formatting controls.
  • Improvements

    • Configuration editor refactored with improved state management and reliability for saving changes.

Walkthrough

Refactored ConfigEditor from context-based architecture to a self-contained component with local state and direct API calls. Removed the ReputationSection component entirely. Added a new EmbedBuilder component for Discord rich embeds with live preview. Refactored NextAuth route handlers to separate functions with error fallbacks and handler caching. Updated tests accordingly.

Changes

Cohort / File(s) Summary
ConfigEditor Refactoring
web/src/components/dashboard/config-editor.tsx
Transitioned from context-driven state management to local state (loading, saving, error, hasChanges). Added getSelectedGuildId() localStorage helper. Implemented direct API calls: fetch config on mount via GET /api/guilds/{guildId}/config, save via PATCH /api/guilds/{guildId}/config. Introduced saveChanges() and discardChanges() handlers with structuredClone for state management. Simplified error UI and removed diff modal flow. Wired SystemPromptEditor to immutable draft config updates.
ConfigEditor Tests
web/tests/components/dashboard/config-editor-autosave.test.tsx
Removed next/navigation mocking setup. Added integration tests validating fetch calls for save (PATCH with JSON body) and discard workflows, including editor field interactions and button state assertions.
ReputationSection Removal
web/src/components/dashboard/config-sections/ReputationSection.tsx, web/src/components/dashboard/config-sections/index.ts
Deleted ReputationSection component (156 lines) and its props interface. Removed re-export from config-sections barrel file. Eliminated XP range, level threshold, and cooldown input validation logic.
EmbedBuilder Component
web/src/components/ui/embed-builder.tsx
New 947-line component for building Discord rich embeds with visual editor and live preview. Supports color picker, title/description with character counters, thumbnail selector, dynamic field management (add/remove/reorder with inline toggle), footer/image/timestamp editing, and template variable insertion. Includes field ID hydration via generateId(), two-column layout with real-time Discord-style preview, and markdown rendering support.
EmbedBuilder Tests
web/tests/components/ui/embed-builder.test.tsx
Comprehensive 482-line test suite covering editor UI rendering, field management (add/remove/reorder), color normalization, format/thumbnail selection, variable insertion with boundary checks, field ID hydration idempotency, and preview rendering (title, markdown description, fields, footer timestamp, accent bar). Includes unit tests for getTotalCharCount, defaultEmbedConfig, and CHAR_LIMITS constants.
NextAuth Route Handler Refactoring
web/src/app/api/auth/[...nextauth]/route.ts
Replaced single module-level handler with separate async GET and POST functions. Introduced getHandler() and cachedHandler for lazy handler initialization and reuse. Added fallbackAuthResponse(request, error) for path-specific error responses: /session and /providers return {} with HTTP 200, /csrf returns { csrfToken: '' } with HTTP 200, other paths return HTTP 503 with { error: 'AuthUnavailable' }. Wrapped handler invocation in try/catch with cache invalidation on failure.
NextAuth Route Tests
web/tests/api/auth-route.test.ts
New 205-line test suite validating fallback responses when getAuthOptions throws (HTTP 200 with empty object for /session//providers, HTTP 200 with empty csrf token for /csrf, HTTP 503 for others). Verifies handler delegation, caching behavior across requests, and cache invalidation on handler rejection. Tests both Error and non-Error throw scenarios with warning emission.
Conversation History Tests
tests/modules/ai.test.js
Loosened test assertions to focus on role and content fields instead of strict full history entry shape matching. Updated DB query to select only role, content (removed created_at). Simplified concurrent hydration and cache-miss test expectations using objectContaining.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.58% 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 accurately describes the primary change: addition of a visual Discord embed builder component for the dashboard.
Description check ✅ Passed The PR description clearly relates to the changeset, providing a comprehensive summary of the new embed builder component, features, component API, character limits, and additional scope.
Linked Issues check ✅ Passed The PR successfully implements all core requirements from issue #371: editable embed fields (color, title, description, thumbnail, fields, footer, image, timestamp), field management (add/remove/reorder, inline toggle), template variable support, live preview, character limit enforcement, format selector, reusable component API, and comprehensive tests.
Out of Scope Changes check ✅ Passed While the PR includes additional scope (NextAuth fallback hardening, dashboard autosave coverage, ReputationSection removal, import reordering, formatting), these changes are either clearly related to integrating the embed builder into the dashboard or represent minor cleanup/maintenance without contradicting the stated objectives.

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

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #371

✨ 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 feat/discord-embed-builder
  • 🛠️ 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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

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

📥 Commits

Reviewing files that changed from the base of the PR and between f3675bb and 5bca27b.

📒 Files selected for processing (4)
  • web/src/components/dashboard/config-editor.tsx
  • web/src/components/dashboard/config-sections/ReputationSection.tsx
  • web/src/components/ui/embed-builder.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). (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.tsx
  • web/src/components/dashboard/config-editor.tsx
  • web/tests/components/ui/embed-builder.test.tsx
  • web/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.tsx
  • web/src/components/dashboard/config-editor.tsx
  • web/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.tsx
  • web/src/components/dashboard/config-editor.tsx
  • web/tests/components/ui/embed-builder.test.tsx
  • web/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 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
🧠 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.

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


[warning] 110-110: The empty object is useless.

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

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

[warning] 495-495: Use

Details, , , or instead of the "group" role to ensure accessibility across all devices.

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


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

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


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

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


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

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


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

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


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

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


[warning] 607-607: Use

, , , or instead of the "group" role to ensure accessibility across all devices.

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


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

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


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

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


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

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

🔇 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 where draftConfig or its ai property is null/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 map and filter callbacks. 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 levelThresholds to the xp config 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_LIMITS match Discord's documented limits, defaultEmbedConfig() provides sensible defaults, and getTotalCharCount() correctly aggregates character usage.

Also applies to: 59-93, 143-165

@github-project-automation github-project-automation bot moved this from Backlog to In Review in Volvox.Bot Apr 2, 2026
Copilot AI review requested due to automatic review settings April 2, 2026 17:35
@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-423 April 2, 2026 17:35 Destroyed
coderabbitai[bot]
coderabbitai bot previously approved these changes Apr 2, 2026
coderabbitai[bot]
coderabbitai bot previously approved these changes Apr 2, 2026
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 19 out of 19 changed files in this pull request and generated 4 comments.

@BillChirico
Copy link
Copy Markdown
Collaborator Author

@coderabbitai autofix stacked pr

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2026

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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

@BillChirico
Copy link
Copy Markdown
Collaborator Author

@coderabbitai autofix stacked pr

@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-423 April 2, 2026 23:53 Destroyed
@BillChirico
Copy link
Copy Markdown
Collaborator Author

@claude review

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@claude
Copy link
Copy Markdown

claude bot commented Apr 2, 2026

Claude finished @BillChirico's task in 5m 27s —— View job


Code Review — PR #423

  • Read CLAUDE.md / AGENTS.md for project rules
  • Review changed files for violations
  • Post inline comments (via comment — gh api requires additional approval)

Issues Found

4 issues requiring changes before merge:


1. web/src/components/ui/embed-builder.tsx:338 — Hardcoded 'en-US' locale

formatPreviewTimestamp uses new Intl.DateTimeFormat('en-US', ...). This forces all users into 12-hour US English format regardless of their browser locale. Use undefined to let the runtime locale apply. This was flagged by Greptile in a previous review and never addressed.

Fix:

function formatPreviewTimestamp(date: Date): string {
  return new Intl.DateTimeFormat(undefined, {
    hour: 'numeric',
    minute: '2-digit',
  }).format(date);
}

Fix this →


2. src/modules/actions/buildPayload.js:25-37 — Missing embed features silently dropped

The embed builder UI exposes fields, footerIconUrl, imageUrl, and showTimestamp — none of which are handled in buildPayload. Users who configure these will see a broken Discord embed: the data is saved to the config but never included in the message payload.

From AGENTS.md: "If a feature is configurable, ship the whole path: runtime logic, API/dashboard wiring, and tests."

Missing wiring:

  • embed.addFields(embedConfig.fields ?? []) for field array support
  • embed.setImage(renderTemplate(embedConfig.imageUrl, templateContext)) for full-width image
  • embed.setTimestamp() when embedConfig.showTimestamp is true
  • iconURL in setFooter when embedConfig.footerIconUrl is set
  • Resolve thumbnailType special tokens (user_avatartemplateContext.userAvatarUrl, server_icontemplateContext.serverIconUrl) before calling setThumbnail

Fix this →


3. web/src/components/dashboard/config-editor.tsx:56 — Shared object reference for savedConfig

setDraftConfig(data) and setSavedConfig(data) both store the same object reference. While React state updates are immutable, storing a shared reference as the restore baseline is fragile. Use structuredClone to ensure savedConfig is always an independent copy.

Fix:

setDraftConfig(data);
setSavedConfig(structuredClone(data));
setHasChanges(false);

Fix this →


4. web/src/components/dashboard/config-editor.tsx:99 — Save hardcoded to ai.systemPrompt only

saveChanges sends { path: 'ai.systemPrompt', value: draftConfig.ai?.systemPrompt } regardless of what was actually changed. Any other edits (embed config, welcome message settings, etc.) will cause hasChanges to become true, the user clicks Save, the PATCH succeeds, but the edits are silently discarded and lost on next page load.

Either scope hasChanges tracking to only ai.systemPrompt (and document the intentional limitation), or implement per-section PATCH batching.

Fix this →


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: 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 | 🟠 Major

Align this mapper with the new embed-builder contract.

web/src/components/ui/embed-builder.tsx now models thumbnails, footer text/icon, image URL, fields, and timestamps on separate keys, but this function still serializes only thumbnail and footer. 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 | 🟡 Minor

Overlong single-line assertions/calls violate width guideline

Lines 92, 115, and 128 exceed the 100-character max width. Please re-wrap these calls.

♻️ 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,
+    );
As per coding guidelines, "Maintain a maximum line width of 100 characters".

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

Line width regression in makeContext signature

Line 12 is over the 100-character limit; this appears to reintroduce a previously fixed formatting issue.

♻️ Suggested fix
-function makeContext({ displayName = 'TestUser', hasPermission = true, isOwner = false } = {}) {
+function makeContext({
+  displayName = 'TestUser',
+  hasPermission = true,
+  isOwner = false,
+} = {}) {
As per coding guidelines, "Maintain a maximum line width of 100 characters".
🤖 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 | 🟠 Major

Catch 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 only getHandler() 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 | 🟠 Major

Use one guildId source of truth for both load and save.

The initial fetch captures the selected guild once, but saveChanges() re-reads localStorage on 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 | 🟡 Minor

Thumbnail-only embeds still render the empty preview.

hasContent ignores thumbnailType/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 | 🟡 Minor

Recompute 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 | 🟠 Major

Reject configs above Discord's 6000-character embed ceiling.

Per-field maxLength is enforced, but update() and updateField() still emit configs whose combined size exceeds CHAR_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

📥 Commits

Reviewing files that changed from the base of the PR and between 01430b9 and 1a3133c.

📒 Files selected for processing (21)
  • .github/workflows/ci.yml
  • src/modules/actions/buildPayload.js
  • src/modules/actions/xpBonus.js
  • src/modules/levelUpActions.js
  • src/modules/triage.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/actions/addReaction.test.js
  • tests/modules/actions/announce.test.js
  • tests/modules/actions/nickPrefix.test.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/actions/webhook.test.js
  • tests/modules/actions/xpBonus.test.js
  • tests/modules/ai.test.js
  • web/src/app/api/auth/[...nextauth]/route.ts
  • web/src/components/dashboard/config-editor.tsx
  • web/src/components/dashboard/config-sections/ReputationSection.tsx
  • web/src/components/dashboard/config-sections/index.ts
  • web/src/components/ui/embed-builder.tsx
  • web/tests/api/auth-route.test.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/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, never require()/module.exports

Files:

  • tests/modules/actions/xpBonus.test.js
  • src/modules/actions/xpBonus.js
  • tests/modules/actions/addReaction.test.js
  • tests/modules/actions/nickPrefix.test.js
  • src/modules/triage.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/actions/announce.test.js
  • src/modules/levelUpActions.js
  • tests/modules/actions/webhook.test.js
  • src/modules/actions/buildPayload.js
  • tests/api/utils/configValidation.test.js
  • tests/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.js
  • src/modules/actions/xpBonus.js
  • tests/modules/actions/addReaction.test.js
  • tests/modules/actions/nickPrefix.test.js
  • src/modules/triage.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/actions/announce.test.js
  • src/modules/levelUpActions.js
  • tests/modules/actions/webhook.test.js
  • src/modules/actions/buildPayload.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/ai.test.js
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/src/app/api/auth/[...nextauth]/route.ts
  • web/src/components/dashboard/config-editor.tsx
  • web/tests/api/auth-route.test.ts
  • web/src/components/ui/embed-builder.tsx
  • web/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 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/xpBonus.test.js
  • tests/modules/actions/addReaction.test.js
  • tests/modules/actions/nickPrefix.test.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/actions/announce.test.js
  • tests/modules/actions/webhook.test.js
  • tests/api/utils/configValidation.test.js
  • tests/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.js
  • src/modules/actions/xpBonus.js
  • tests/modules/actions/addReaction.test.js
  • tests/modules/actions/nickPrefix.test.js
  • src/modules/triage.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/actions/announce.test.js
  • src/modules/levelUpActions.js
  • tests/modules/actions/webhook.test.js
  • src/modules/actions/buildPayload.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/ai.test.js
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/src/app/api/auth/[...nextauth]/route.ts
  • web/src/components/dashboard/config-editor.tsx
  • web/tests/api/auth-route.test.ts
  • web/src/components/ui/embed-builder.tsx
  • web/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.js
  • src/modules/actions/xpBonus.js
  • tests/modules/actions/addReaction.test.js
  • tests/modules/actions/nickPrefix.test.js
  • src/modules/triage.js
  • tests/modules/actions/sendDm.test.js
  • tests/modules/actions/announce.test.js
  • src/modules/levelUpActions.js
  • tests/modules/actions/webhook.test.js
  • src/modules/actions/buildPayload.js
  • tests/api/utils/configValidation.test.js
  • tests/modules/ai.test.js
  • web/src/app/api/auth/[...nextauth]/route.ts
  • web/tests/api/auth-route.test.ts
src/**/*.js

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

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

Files:

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

Files:

  • src/modules/actions/xpBonus.js
  • src/modules/triage.js
  • src/modules/levelUpActions.js
  • src/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.js
  • src/modules/triage.js
  • src/modules/levelUpActions.js
  • src/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 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
  • web/tests/api/auth-route.test.ts
  • web/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.ts
  • web/src/components/dashboard/config-editor.tsx
  • web/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.js
  • tests/modules/actions/webhook.test.js
  • web/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.js
  • src/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.js
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/tests/api/auth-route.test.ts
  • 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 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.js
  • tests/api/utils/configValidation.test.js
  • 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/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
  • web/tests/api/auth-route.test.ts
  • web/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.tsx
  • web/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.tsx
  • 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 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.ts
  • web/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.

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


[warning] 110-110: The empty object is useless.

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


[warning] 165-165: The empty object is useless.

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

web/tests/api/auth-route.test.ts

[warning] 78-78: Expected an error object to be thrown.

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

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

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

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


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

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


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

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


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

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


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

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


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

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


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

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

🔇 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 for xp.defaultActions.


425-427: Formatting-only change is safe and preserves test behavior.
Assertion remains correct for the valid xp.roleRewards object shape.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get all review comments on PR `#423`
gh pr view 423 --repo VolvoxLLC/volvox-bot --json reviews,comments 2>/dev/null | jq '.'

# Get review comments (inline)
gh api repos/VolvoxLLC/volvox-bot/pulls/423/comments 2>/dev/null | jq '[.[] | {id: .id, path: .path, line: .line, body: .body, user: .login // .user.login}]'

Length of output: 122132


🏁 Script executed:

#!/bin/bash
# Get inline review comments on PR `#423`
gh api repos/VolvoxLLC/volvox-bot/pulls/423/comments 2>/dev/null | jq '[.[] | {id: .id, path: .path, line: .line, body: (.body | .[0:300]), user: .user.login, resolved: .position}]'

Length of output: 14242


🏁 Script executed:

#!/bin/bash
# Get latest review state - check for completed review on newest commit
gh api repos/VolvoxLLC/volvox-bot/pulls/423/reviews --jq '[.[] | select(.user.login == "coderabbitai") | {id: .id, state: .state, commit: .commit_id, submitted_at: .submitted_at}]'

echo "---LATEST COMMIT---"
gh api repos/VolvoxLLC/volvox-bot/pulls/423 --jq '.head.sha'

echo "---UNRESOLVED CODERABBIT INLINE COMMENTS---"
# Get inline comments where position is not null (still in diff) from coderabbitai
gh api repos/VolvoxLLC/volvox-bot/pulls/423/comments --jq '[.[] | select(.user.login == "coderabbitai[bot]") | {id: .id, path: .path, line: .line, position: .position, original_position: .original_position, body: (.body | .[0:400])}]'

Length of output: 5201


🏁 Script executed:

#!/bin/bash
# Read the relevant sections of embed-builder.tsx
echo "=== embed-builder.tsx: lines 90-145 (module counter + useEffect area) ==="
sed -n '90,145p' web/src/components/ui/embed-builder.tsx

echo ""
echo "=== embed-builder.tsx: lines 185-270 (renderDiscordMarkdown) ==="
sed -n '185,270p' web/src/components/ui/embed-builder.tsx

echo ""
echo "=== embed-builder.tsx: lines 230,250 (nested ternary) ==="
sed -n '228,260p' web/src/components/ui/embed-builder.tsx

echo ""
echo "=== embed-builder.tsx: lines 415,560 (useEffect infinite loop) ==="
sed -n '415,560p' web/src/components/ui/embed-builder.tsx

echo ""
echo "=== embed-builder.tsx: lines 490,515 role=group ==="
sed -n '488,520p' web/src/components/ui/embed-builder.tsx

echo ""
echo "=== embed-builder.test.tsx: lines 35-50 (weak assertion) ==="
sed -n '35,55p' web/tests/components/ui/embed-builder.test.tsx

echo ""
echo "=== embed-builder.test.tsx: lines 405-430 (locale timestamp) ==="
sed -n '405,432p' web/tests/components/ui/embed-builder.test.tsx

echo ""
echo "=== config-editor.tsx: lines 130-175 (save/discard) ==="
sed -n '130,175p' web/src/components/dashboard/config-editor.tsx

Length of output: 14432


🏁 Script executed:

#!/bin/bash
echo "=== embed-builder.tsx: CharCount component (nested ternary) ==="
grep -n "CharCount\|getRatioColor\|text-destructive\|text-yellow" web/src/components/ui/embed-builder.tsx | head -30

echo ""
echo "=== embed-builder.tsx: Format selector (role=group) ==="
grep -n 'role="group"\|fieldset\|aria-label="Format"\|aria-label="Thumbnail"' web/src/components/ui/embed-builder.tsx

echo ""
echo "=== embed-builder.tsx: CharCount full component ==="
sed -n '$(grep -n "function CharCount" web/src/components/ui/embed-builder.tsx | head -1 | cut -d: -f1)p' web/src/components/ui/embed-builder.tsx 2>/dev/null || true
grep -n "function CharCount" web/src/components/ui/embed-builder.tsx
LINE=$(grep -n "function CharCount" web/src/components/ui/embed-builder.tsx | head -1 | cut -d: -f1)
sed -n "${LINE},$((LINE+20))p" web/src/components/ui/embed-builder.tsx

echo ""
echo "=== embed-builder.test.tsx: full title test assertion ==="
sed -n '40,70p' web/tests/components/ui/embed-builder.test.tsx

echo ""
echo "=== embed-builder.tsx: format selector section ==="
grep -n "Format\|format" web/src/components/ui/embed-builder.tsx | grep -i "selector\|group\|label\|aria" | head -20
# Find format buttons section
LINE2=$(grep -n "FORMAT_OPTIONS\|formatOptions\|text.*embed.*both\|format.*selector\|Format selector" web/src/components/ui/embed-builder.tsx | head -1 | cut -d: -f1)
echo "Format options around line $LINE2:"
sed -n "$((LINE2-2)),$((LINE2+25))p" web/src/components/ui/embed-builder.tsx

Length of output: 3900


@BillChirico Good news — the latest commits have already addressed all 9 of the open CodeRabbit inline comments:

Comment Status
Save Changes no onClick ✅ Fixed — onClick={() => void saveChanges()} is present
DiscardChangesButton doesn't revert draft ✅ Fixed — wired to discardChanges()
Module-level mutable counter ✅ Fixed — createFieldId now delegates to generateId()
useEffect infinite loop ✅ Fixed — lastHydratedFieldsKeyRef prevents re-emit
Nested ternary in CharCount ✅ Fixed — extracted to getCharCountClassName()
role="group" accessibility ✅ Fixed — replaced with <fieldset>
FormatType uses 'text_embed' ✅ Fixed — now uses 'both'
Weak title assertion ✅ Fixed — toBe('Hello')
Locale-dependent timestamp test ✅ Fixed — uses formatPreviewTimestamp(new Date(...))

The one remaining blocking issue (SonarCloud failure) is tokenizeMarkdownSegments still reporting cognitive complexity 23 (limit: 15). I'll create a stacked PR to fix that plus two minor SonarCloud warnings.

Autofix stacked PR: reduce tokenizeMarkdownSegments complexity + minor SonarCloud warnings

  • Refactor tokenizeMarkdownSegments using a token-rule table + helper to bring complexity ≤ 15
  • Replace String#replace with String#replaceAll on the global regex
  • Fix array-index key in renderVariablePreview

🏁 Script executed:

grep -n "tokenizeMarkdownSegments\|tokenizeVariableSegments\|TOKEN_RULES\|matchToken" \
  web/src/components/ui/embed-builder.tsx
LINE=$(grep -n "^function tokenizeMarkdownSegments" web/src/components/ui/embed-builder.tsx | head -1 | cut -d: -f1)
echo "--- tokenizeMarkdownSegments starts at line $LINE ---"
sed -n "${LINE},$((LINE+60))p" web/src/components/ui/embed-builder.tsx
# Also grab the line right above it so we can insert the rule table before it
PREV=$((LINE-1))
sed -n "$((PREV-2)),$((PREV+1))p" web/src/components/ui/embed-builder.tsx

Length of output: 2177


🏁 Script executed:

# Confirm exact lines for both issues
grep -n "\.replace\(" web/src/components/ui/embed-builder.tsx
grep -n 'key={`\${varName}-\${index}`' web/src/components/ui/embed-builder.tsx
grep -n 'key={`text-\${index}`' web/src/components/ui/embed-builder.tsx

Length of output: 123


🏁 Script executed:

# Get the component function signatures with their prop destructuring to see which lack Readonly<>
grep -n "^function\|^export function" web/src/components/ui/embed-builder.tsx
LINE=$(grep -n "^function CharCount" web/src/components/ui/embed-builder.tsx | cut -d: -f1)
sed -n "${LINE},$((LINE+3))p" web/src/components/ui/embed-builder.tsx
LINE2=$(grep -n "^function VariablePalette" web/src/components/ui/embed-builder.tsx | cut -d: -f1)
sed -n "${LINE2},$((LINE2+8))p" web/src/components/ui/embed-builder.tsx
LINE3=$(grep -n "^function EmbedPreview" web/src/components/ui/embed-builder.tsx | cut -d: -f1)
sed -n "${LINE3},$((LINE3+3))p" web/src/components/ui/embed-builder.tsx

Length of output: 1654


@BillChirico Here is the autofix stacked PR plan. All 9 CodeRabbit inline comments are already addressed by the latest commits — the one remaining blocking SonarCloud failure is tokenizeMarkdownSegments (cognitive complexity 23 > 15 limit), plus a few non-blocking Sonar warnings.

Autofix: fix SonarCloud failure + warnings in embed-builder.tsx

  • Extract TOKEN_RULES + matchToken helper to reduce tokenizeMarkdownSegments complexity to ≤ 15
  • Fix String#replaceString#replaceAll on the global-flag regex
  • Fix array-index keys in renderVariablePreview (use character offset instead)
  • Add Readonly<> wrappers on inline prop shapes for CharCount, VariablePalette, EmbedPreview

Failed to handle agent chat message. Please try again.

)

- 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
Copilot AI review requested due to automatic review settings April 3, 2026 00:20
@BillChirico BillChirico force-pushed the feat/discord-embed-builder branch from 1a3133c to 95d2c36 Compare April 3, 2026 00:20
@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-423 April 3, 2026 00:20 Destroyed
@BillChirico BillChirico merged commit bc3fd95 into main Apr 3, 2026
10 of 13 checks passed
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@BillChirico BillChirico deleted the feat/discord-embed-builder branch April 3, 2026 00:20
@github-project-automation github-project-automation bot moved this from In Review to Done in Volvox.Bot Apr 3, 2026
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 3, 2026

Comment on lines +49 to +52
const data: unknown = await res.json();
if (!isGuildConfig(data)) {
throw new Error('Invalid config response');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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'),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

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

Comment on lines +16 to +18
import * as React from 'react';
import { generateId } from '@/components/dashboard/config-editor-utils';
import { Button } from '@/components/ui/button';
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 UI-level component imports generateId from the dashboard config-editor utils, creating an unexpected dependency from components/uicomponents/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.

Copilot uses AI. Check for mistakes.
Comment on lines +939 to +947
export {
CharCount,
EmbedBuilder,
EmbedPreview,
formatPreviewTimestamp,
renderDiscordMarkdown,
renderVariablePreview,
VariablePalette,
};
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 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.

Copilot uses AI. Check for mistakes.
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'),
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 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.

Suggested change
expect.stringContaining('SELECT role, content FROM conversations'),
expect.stringContaining('SELECT role, content, created_at FROM conversations'),

Copilot uses AI. Check for mistakes.
MohsinCoding added a commit that referenced this pull request Apr 3, 2026
* 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
BillChirico added a commit that referenced this pull request Apr 3, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

feat(dashboard): visual Discord embed builder component

2 participants