Skip to content

Refactor dashboard config-editor into maintainable section architecture#244

Merged
BillChirico merged 7 commits intomainfrom
feat/issue-243-config-editor-refactor
Mar 5, 2026
Merged

Refactor dashboard config-editor into maintainable section architecture#244
BillChirico merged 7 commits intomainfrom
feat/issue-243-config-editor-refactor

Conversation

@BillChirico
Copy link
Collaborator

Closes #243

Summary

Refactor web/src/components/dashboard/config-editor.tsx into maintainable section architecture while preserving behavior and API payload compatibility.

Changes

  • New utilities: config-normalization.ts, config-updates.ts with full test coverage
  • Extracted sections: All sections now in dedicated components under config-sections/
  • Simplified config-editor: Reduced to orchestration/composition layer
  • Updated tests: Fixed autosave tests for new architecture

Validation

  • ✅ Root lint passed
  • ✅ Root tests passed (3631 passed)
  • ✅ Web lint passed
  • ✅ Web typecheck passed
  • ✅ Web build passed

Closes #243

Copilot AI review requested due to automatic review settings March 5, 2026 01:19
@github-project-automation github-project-automation bot moved this to Backlog in Volvox.Bot Mar 5, 2026
@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-244 March 5, 2026 01:20 Destroyed
@railway-app
Copy link

railway-app bot commented Mar 5, 2026

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

Service Status Web Updated (UTC)
web ✅ Success (View Logs) Web Mar 5, 2026 at 3:02 am
bot 🚨 Crashed (View Logs) Web Mar 5, 2026 at 3:02 am
docs ✅ Success (View Logs) Web Mar 5, 2026 at 3:02 am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added many configurable dashboard sections: AI Auto‑Mod, AI blocked channels, Challenges, Community Features, Engagement badges, GitHub feed, Memory, Permissions, Reputation, Starboard, Tickets, Triage, and Welcome enhancements; consolidated exports.
  • Improvements

    • Reorganized config editor into modular, section-based UI with consistent styling, standardized inputs, patch-based/batched saves, undo/redo updates, and improved normalization/update helpers.
  • Tests

    • Added unit tests for normalization and update helpers and updated editor integration tests.
  • Chore

    • Updated Next.js ambient type reference path.

Walkthrough

Refactors the dashboard config editor into modular per-section components, adds config-normalization and immutable update utilities, switches the editor to a patch-based save flow, updates a Next.js type reference, and adds unit tests for the new utilities and update helpers.

Changes

Cohort / File(s) Summary
Type System
web/next-env.d.ts
Updated Next.js routes type reference import from ./.next/dev/types/routes.d.ts to ./.next/types/routes.d.ts.
Config Editor Core
web/src/components/dashboard/config-editor.tsx
Refactored large monolithic editor into an orchestration layer using per-section updaters, patch computation (computePatches), grouped/batched saves, undo/redo and revised type usage (GuildConfig imported from @/lib/config-utils).
Config Utils
web/src/lib/config-normalization.ts, web/src/lib/config-updates.ts
Added normalization helpers (parse/format lists, parseNumberInput, percent/decimal conversions, normalizeOptionalString) and immutable update helpers (updateSectionEnabled, updateSectionField, updateNestedField, updateArrayItem, removeArrayItem, appendArrayItem).
Section Components (new / refactored)
web/src/components/dashboard/config-sections/...
web/src/components/dashboard/config-sections/AiAutoModSection.tsx, .../AiSection.tsx, .../ChallengesSection.tsx, .../CommunityFeaturesSection.tsx, .../EngagementSection.tsx, .../GitHubSection.tsx, .../MemorySection.tsx, .../ModerationSection.tsx, .../PermissionsSection.tsx, .../ReputationSection.tsx, .../StarboardSection.tsx, .../TicketsSection.tsx, .../TriageSection.tsx, .../WelcomeSection.tsx
Extracted and/or rewrote 14 section components; standardized props (draftConfig, saving, callbacks) and UI controls (ToggleSwitch, native inputs), added new props where required (e.g., guildId, protectRoleIdsRaw, dmStepsRaw).
Section Barrel
web/src/components/dashboard/config-sections/index.ts
Added barrel file re-exporting all section components.
Tests
web/tests/components/dashboard/config-editor-autosave.test.tsx, web/tests/lib/config-normalization.test.ts, web/tests/lib/config-updates.test.ts
Adjusted editor tests away from autosave specifics; added comprehensive unit tests covering normalization utilities and immutable config update helpers (parsing, clamping, nested/array ops).

Possibly related PRs

  • PR #83: Refactors the dashboard ConfigEditor into modular per-section components and introduces normalization/updater utilities (direct overlap with extracted components and lib utilities).
  • PR #239: Changes Next.js routes type import paths (conflicts/overlap with the web/next-env.d.ts change).
  • PR #217: Modifies config editor save/diff flow to use patch-based computation and batched updates (strong overlap with computePatches and save flow changes).
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main objective: refactoring config-editor into a section-based architecture for improved maintainability.
Description check ✅ Passed The description clearly relates to the changeset, detailing the refactor scope, new utilities, extracted sections, test updates, and validation results.
Linked Issues check ✅ Passed The PR meets issue #243 acceptance criteria: config-editor reduced to orchestration layer, sections extracted with no regressions, consistent field helpers added, dead code removed, and full CI validation passed.
Out of Scope Changes check ✅ Passed All changes directly support the refactor objective. The Next.js type path update is a minor, related configuration fix. No unrelated features or scope creep detected.
Docstring Coverage ✅ Passed Docstring coverage is 96.88% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/issue-243-config-editor-refactor
  • 🛠️ 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
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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps
Copy link

greptile-apps bot commented Mar 5, 2026

Greptile Summary

This PR refactors the monolithic config-editor.tsx (~2500 lines) into a section-based architecture with 14 dedicated components, two new utility libraries (config-normalization.ts, config-updates.ts), and a shared createSectionUpdater factory in the orchestration layer. The structural goals are well-executed, but a broad pattern of UX regressions — replacing ChannelSelector/RoleSelector dropdowns with plain text inputs across the majority of new section components — remains unresolved.

  • Architecture improvements: createSectionUpdater cleanly reduces boilerplate; config-normalization.ts and config-updates.ts are well-tested with explicit return types and no any usage.
  • UX regressions (broad): In addition to the WelcomeSection and AiAutoModSection regressions flagged previously, the same issue affects StarboardSection (channelId, ignoredChannels), GitHubSection (feed channelId), ChallengesSection (channelId), ReputationSection (announceChannelId), TriageSection (moderationLogChannel), TicketsSection (supportRole, transcriptChannel), and ModerationSection (protectRoles roleIds) — users must now type raw Discord snowflake IDs manually in these fields.
  • Ctrl+S data loss (new occurrences): PermissionsSection.tsx's botOwners field and ReputationSection.tsx's levelThresholds field were changed from onChange-commit to onBlur-only commit, introducing the same data-loss bug already flagged for ModerationSection and WelcomeSection.
  • AiAutoModSection always renders: The component no longer returns null when draftConfig.aiAutoMod is absent; interacting with defaults will create an aiAutoMod PATCH payload for guilds that never had the section configured.
  • inputClasses duplication: The same 3-line Tailwind string is copy-pasted into ~10 component files; it should be extracted to a shared module to align with the maintainability goals of this refactor.

Confidence Score: 2/5

  • Not safe to merge — broad UX regressions (plain text inputs replacing channel/role dropdowns) and new Ctrl+S data-loss regressions across multiple sections need resolution first.
  • The core architectural refactor is sound and the utility libraries are clean, but the PR explicitly claims to "preserve behavior" while introducing a wide pattern of UX regressions across most new section components. Two new Ctrl+S data-loss scenarios (PermissionsSection.botOwners, ReputationSection.levelThresholds) echo the same root cause already flagged in ModerationSection and WelcomeSection but not addressed here. The overall surface of unresolved regressions is large enough to block a merge.
  • All new section files that replaced ChannelSelector/RoleSelector with plain text inputs: StarboardSection.tsx, GitHubSection.tsx, ChallengesSection.tsx, ReputationSection.tsx, TriageSection.tsx, TicketsSection.tsx, and ModerationSection.tsx. Also PermissionsSection.tsx for the botOwners onBlur-only commit.

Important Files Changed

Filename Overview
web/src/components/dashboard/config-editor.tsx Orchestration layer significantly reduced (~1600 lines removed); now delegates to section components via createSectionUpdater. The guildId effect dependency fix and new protectRoleIdsRaw state are correct. Core save/revert/undo logic is unchanged.
web/src/components/dashboard/config-sections/AiAutoModSection.tsx ChannelSelector (flag review channel) replaced with plain text input (UX regression already flagged). Section now always renders when aiAutoMod is absent instead of returning null, which may cause unintended PATCH payloads for guilds without aiAutoMod configured. inputClasses duplicated.
web/src/components/dashboard/config-sections/WelcomeSection.tsx ChannelSelector/RoleSelector for rulesChannel, introChannel, verifiedRole replaced with plain text inputs (UX regression, previously flagged). DM steps onBlur-only commit also previously flagged. Role menu and DM sequence logic otherwise well-extracted.
web/src/components/dashboard/config-sections/ModerationSection.tsx Rate limiting and link filtering sub-sections correctly extracted. protectRoles RoleSelector regressed to text input. blockedDomainsRaw onBlur-only pattern (Ctrl+S data loss) already flagged in previous threads. protectRoleIdsRaw correctly calls onProtectRolesChange on every onChange, so no data loss there.
web/src/components/dashboard/config-sections/PermissionsSection.tsx New file. Admin/Moderator role selectors retained (RoleSelector). botOwners field regressed to onBlur-only commit — data loss if Ctrl+S pressed while input is focused. Previously this field committed on every onChange.
web/src/components/dashboard/config-sections/ReputationSection.tsx New file. announceChannelId regressed from ChannelSelector to text input. levelThresholds uses onBlur-only commit pattern causing Ctrl+S data loss. XP range logic (keeping min ≤ max) correctly preserved.
web/src/components/dashboard/config-sections/StarboardSection.tsx New file. Both channelId and ignoredChannels regressed from ChannelSelector to plain text inputs. Core starboard logic (threshold, emoji, selfStar) correctly extracted.
web/src/components/dashboard/config-sections/TicketsSection.tsx New file. supportRole regressed from RoleSelector to text input; transcriptChannel regressed from ChannelSelector to text input. Ticket mode select, autoCloseHours, and maxOpenPerUser correctly implemented.
web/src/components/dashboard/config-sections/TriageSection.tsx moderationLogChannel regressed from ChannelSelector to plain text input. All other triage fields (models, budgets, intervals, toggles) correctly extracted from the monolith.
web/src/lib/config-updates.ts New utility file with updateSectionEnabled, updateSectionField, updateNestedField, updateArrayItem, removeArrayItem, appendArrayItem. The traversal/rebuild logic in updateArrayItem/removeArrayItem/appendArrayItem correctly tracks levels for paths of any depth (previously flagged path reconstruction bug has been fixed). All current call sites use ≤2-segment paths.
web/src/lib/config-normalization.ts New utility file extracting parseNumberInput, parseCommaSeparatedList, parseNewlineSeparatedList, percentToDecimal, decimalToPercent, normalizeOptionalString. Clean, well-tested, no issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    CE[ConfigEditor] --> CSU[createSectionUpdater]
    CSU --> AU[aiUpdater]
    CSU --> WU[welcomeUpdater]
    CSU --> MU[moderationUpdater]
    CSU --> TU[triageUpdater]
    CSU --> SU[starboardUpdater]
    CSU --> PU[permissionsUpdater]
    CSU --> MEU[memoryUpdater]
    CSU --> RU[reputationUpdater]
    CSU --> CU[challengesUpdater]

    CE --> AiSection
    CE --> WelcomeSection
    CE --> ModerationSection
    CE --> AiAutoModSection
    CE --> TriageSection
    CE --> StarboardSection
    CE --> PermissionsSection
    CE --> MemorySection
    CE --> CommunityFeaturesSection
    CE --> EngagementSection
    CE --> ReputationSection
    CE --> ChallengesSection
    CE --> GitHubSection
    CE --> TicketsSection

    AU --> CUL[config-updates.ts\nupdateSectionEnabled\nupdateSectionField\nupdateNestedField]
    CN[config-normalization.ts\nparseNumberInput\npercentToDecimal\ndecimalToPercent\nparseCommaSeparatedList] --> AiAutoModSection
    CN --> ModerationSection
    CN --> TriageSection
    CN --> StarboardSection
    CN --> ReputationSection
    CN --> GitHubSection
    CN --> MemorySection
    CN --> TicketsSection
Loading

Last reviewed commit: ff80c9f

Copy link
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: 16

🤖 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 626-743: Several sections (AiAutoModSection,
CommunityFeaturesSection, EngagementSection, GitHubSection, TicketsSection) use
inline update callbacks instead of the shared updater helpers; create
corresponding updaters (e.g., aiAutoModUpdater, communityFeaturesUpdater,
engagementUpdater, githubUpdater, ticketsUpdater) that encapsulate the
updateDraftConfig logic and expose methods like setField, setEnabled/setToggle,
setActivityBadges as needed, then replace the inline
onFieldChange/onToggleChange/onEnabledChange props with calls to those updater
methods (similar to triageUpdater.setField, memoryUpdater.setEnabled,
reputationUpdater.setField) so save/revert semantics are centralized and
consistent.
- Around line 394-397: The effect that clears the undo snapshot currently runs
only once; update the useEffect that calls setPrevSavedConfig(null) so it
depends on the guild identifier (e.g., guildId, selectedGuildId, or guild?.id
used in this component) instead of an empty array, ensuring prevSavedConfig is
reset whenever the user switches guilds; keep the setPrevSavedConfig(null) body
but add the correct guild dependency to the dependency array so the undo button
UI no longer shows a stale snapshot after guild changes.

In `@web/src/components/dashboard/config-sections/AiAutoModSection.tsx`:
- Around line 24-29: The component AiAutoModSection currently returns null when
draftConfig.aiAutoMod is missing; instead ensure the section always renders so
users can enable AI Auto-Moderation on partial/default configs by treating
draftConfig.aiAutoMod as optional and providing sensible defaults: compute
thresholds and actions from draftConfig.aiAutoMod if present otherwise use
empty/default objects, and remove the early return that hides the section;
update the component to read thresholds = (draftConfig.aiAutoMod?.thresholds as
Record<string, number>) ?? {} and actions = (draftConfig.aiAutoMod?.actions as
Record<string, string>) ?? {}, and ensure the UI uses onFieldChange to
create/initialize draftConfig.aiAutoMod when the user toggles/enables the
feature.

In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx`:
- Line 44: In ChallengesSection.tsx update the grid container's classes to be
mobile-first: find the div with className "grid grid-cols-2 gap-4" and change it
so the default is one column and switches to two at medium screens (e.g.,
replace with "grid grid-cols-1 md:grid-cols-2 gap-4") to avoid crowding inputs
on narrow screens.

In `@web/src/components/dashboard/config-sections/EngagementSection.tsx`:
- Around line 43-44: The key for each badge uses mutable fields (badge.days and
badge.label) which can collide and change on edit; update the badge list
rendering in EngagementSection to use a stable unique identifier instead: ensure
each badge object has an immutable id (e.g., badge.id) assigned when the badge
is created (or persisted), use that id as the React key in the map instead of
the computed string, and update any add/create handlers (where badges are
created) to generate and attach that id so existing change handlers (for
days/label inputs) do not mutate the key; if a stable id cannot be added
immediately, fall back to a deterministic index-only fallback but prefer the
persistent badge.id for reconciliation.

In `@web/src/components/dashboard/config-sections/ModerationSection.tsx`:
- Around line 238-246: The Blocked Domains input currently normalizes the array
on every keystroke (using draftConfig.moderation?.linkFilter?.blockedDomains
joined) which strips in-progress typing; change it to use a local raw string
state (e.g., blockedDomainsRaw) fed to the input value, update that raw state in
the input's onChange, and only parse (split by ',', trim, filter(Boolean)) and
call onLinkFilterChange('blockedDomains', parsedArray) onBlur — follow the same
protectRoleIdsRaw pattern used elsewhere in ModerationSection.tsx so typing and
delimiters are preserved until blur.

In `@web/src/components/dashboard/config-sections/PermissionsSection.tsx`:
- Around line 82-90: The botOwners input currently parses and normalizes on
every keystroke (see PermissionsSection.tsx using
draftConfig.permissions?.botOwners and onFieldChange inside the input's
onChange), which prevents natural typing of delimiters; fix by introducing a
local string state (e.g., rawBotOwners) initialized from
draftConfig.permissions?.botOwners.join(', ') and use that as the input value,
update rawBotOwners onChange without splitting/filtering, and only
parse/split/trim/filter and call onFieldChange('botOwners', parsedArray) from
the input's onBlur (also update rawBotOwners when draftConfig changes) so
normalization happens on blur instead of every keystroke.

In `@web/src/components/dashboard/config-sections/ReputationSection.tsx`:
- Around line 119-128: The input currently parses and sorts levelThresholds on
every keystroke (value={levelThresholds.join(', ')} and the onChange handler),
which drops partial comma states; change this to use a local raw string state in
the ReputationSection component (e.g., rawLevelThresholds) initialized from
levelThresholds.join(', '), update rawLevelThresholds on every onChange to
preserve typing, and only parse, validate, sort, and call
onFieldChange('levelThresholds', sorted) on onBlur (or Enter) to commit; ensure
the displayed value is bound to rawLevelThresholds while keeping the existing
parsing/sorting logic reused when committing.

In `@web/src/components/dashboard/config-sections/StarboardSection.tsx`:
- Line 54: The grid in StarboardSection uses a fixed "grid-cols-2" which
squashes controls on small screens; change the layout to be responsive (e.g.,
use a single column on small screens and two columns on medium/up) by updating
the container's className in the StarboardSection component (the div with
className "grid grid-cols-2 gap-4") to use responsive Tailwind classes such as
"grid grid-cols-1 md:grid-cols-2 gap-4" (or equivalent sm/md breakpoints per
design) so controls stack on mobile and become two columns on larger viewports.
- Around line 86-90: The active-button check treats an unset emoji as inactive;
update the className conditional in StarboardSection (where
draftConfig.starboard?.emoji is checked) to treat undefined/null as the default
'*' (e.g. use the nullish coalescing result (draftConfig.starboard?.emoji ??
'*') or equivalent) so the "Any" button renders with the active styles when
emoji is unset; adjust the ternary that decides 'bg-primary
text-primary-foreground' vs 'bg-muted ...' to use that normalized value.

In `@web/src/components/dashboard/config-sections/TriageSection.tsx`:
- Around line 215-217: Normalize the moderation log channel input in
TriageSection by trimming whitespace before calling onFieldChange; change the
onChange handler that currently calls onFieldChange('moderationLogChannel',
e.target.value) to compute const v = e.target.value.trim() and then call
onFieldChange('moderationLogChannel', v === '' ? '' : v) so whitespace-only
input clears the field consistent with section-wide channel clear semantics.

In `@web/src/components/dashboard/config-sections/WelcomeSection.tsx`:
- Around line 223-233: The onBlur handler is reading dmStepsRaw (which may be
stale) instead of the textarea's latest value; change the onBlur to accept the
blur event and parse the current textarea value (e.g., use e.currentTarget.value
or cast e.target) to build parsed = value.split(...).map(...).filter(Boolean),
then call onDmSequenceChange('steps', parsed) and
onDmStepsRawChange(parsed.join('\n')); update references to the textarea
handlers (value={dmStepsRaw}, onChange, onBlur) so onBlur uses the event value
rather than the dmStepsRaw variable.

In `@web/src/lib/config-updates.ts`:
- Around line 113-116: The cloned-array mutation doesn't validate index bounds:
before assigning to arr[index] (and before any splice that replaces elements)
verify index is an integer and 0 <= index < arr.length; if invalid, throw or
return a controlled error instead of allowing negative or out-of-range indices
to create sparse arrays or mutate wrong elements. Update the block that creates
arr from target[lastKey] (symbols: lastKey, arr, target, index, item) to check
Number.isInteger(index) and bounds, and apply the replacement only when the
check passes (or normalize the index only for allowed negative semantics after
explicit validation).

In `@web/tests/components/dashboard/config-editor-autosave.test.tsx`:
- Around line 178-252: This file duplicates unit tests for utility functions;
remove the overlapping assertions for parseNumberInput, percentToDecimal,
decimalToPercent, updateSectionEnabled, updateSectionField, and
updateNestedField from
web/tests/components/dashboard/config-editor-autosave.test.tsx and replace them
with integration-focused assertions that exercise the autosave/config-editor
behavior (e.g., simulate editing fields and verify autosave triggers and final
config) so the utilities remain tested only in
web/tests/lib/config-normalization.test.ts and
web/tests/lib/config-updates.test.ts.

In `@web/tests/lib/config-updates.test.ts`:
- Around line 52-56: The test "does not mutate original config" uses a shallow
copy so nested objects (baseConfig.ai) remain shared; replace the shallow clone
with a deep clone of baseConfig (e.g., use structuredClone,
JSON.parse(JSON.stringify(...)), or lodash's cloneDeep) before calling
updateSectionEnabled so original.ai is truly independent, then assert that
baseConfig.ai?.enabled equals the copied original.ai?.enabled to verify
immutability of updateSectionEnabled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: adab6ccb-abd5-43ac-8df8-094ce8e26b80

📥 Commits

Reviewing files that changed from the base of the PR and between ee55d19 and c843e3b.

📒 Files selected for processing (22)
  • web/next-env.d.ts
  • web/src/components/dashboard/config-editor.tsx
  • web/src/components/dashboard/config-sections/AiAutoModSection.tsx
  • web/src/components/dashboard/config-sections/AiSection.tsx
  • web/src/components/dashboard/config-sections/ChallengesSection.tsx
  • web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx
  • web/src/components/dashboard/config-sections/EngagementSection.tsx
  • web/src/components/dashboard/config-sections/GitHubSection.tsx
  • web/src/components/dashboard/config-sections/MemorySection.tsx
  • web/src/components/dashboard/config-sections/ModerationSection.tsx
  • web/src/components/dashboard/config-sections/PermissionsSection.tsx
  • web/src/components/dashboard/config-sections/ReputationSection.tsx
  • web/src/components/dashboard/config-sections/StarboardSection.tsx
  • web/src/components/dashboard/config-sections/TicketsSection.tsx
  • web/src/components/dashboard/config-sections/TriageSection.tsx
  • web/src/components/dashboard/config-sections/WelcomeSection.tsx
  • web/src/components/dashboard/config-sections/index.ts
  • web/src/lib/config-normalization.ts
  • web/src/lib/config-updates.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/tests/lib/config-normalization.test.ts
  • web/tests/lib/config-updates.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (2)
{src,web}/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Winston logger from src/logger.js, NEVER use console.*

Files:

  • web/src/components/dashboard/config-sections/GitHubSection.tsx
  • web/next-env.d.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/src/components/dashboard/config-sections/StarboardSection.tsx
  • web/src/components/dashboard/config-sections/AiSection.tsx
  • web/src/components/dashboard/config-sections/PermissionsSection.tsx
  • web/src/lib/config-normalization.ts
  • web/src/components/dashboard/config-sections/MemorySection.tsx
  • web/src/components/dashboard/config-sections/EngagementSection.tsx
  • web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx
  • web/src/components/dashboard/config-sections/ReputationSection.tsx
  • web/src/components/dashboard/config-sections/TicketsSection.tsx
  • web/src/components/dashboard/config-sections/TriageSection.tsx
  • web/tests/lib/config-normalization.test.ts
  • web/src/components/dashboard/config-sections/ModerationSection.tsx
  • web/src/lib/config-updates.ts
  • web/src/components/dashboard/config-sections/ChallengesSection.tsx
  • web/src/components/dashboard/config-sections/AiAutoModSection.tsx
  • web/tests/lib/config-updates.test.ts
  • web/src/components/dashboard/config-sections/WelcomeSection.tsx
  • web/src/components/dashboard/config-sections/index.ts
  • web/src/components/dashboard/config-editor.tsx
web/**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Use Next.js 16 App Router for web dashboard, with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design

Files:

  • web/src/components/dashboard/config-sections/GitHubSection.tsx
  • web/next-env.d.ts
  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/src/components/dashboard/config-sections/StarboardSection.tsx
  • web/src/components/dashboard/config-sections/AiSection.tsx
  • web/src/components/dashboard/config-sections/PermissionsSection.tsx
  • web/src/lib/config-normalization.ts
  • web/src/components/dashboard/config-sections/MemorySection.tsx
  • web/src/components/dashboard/config-sections/EngagementSection.tsx
  • web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx
  • web/src/components/dashboard/config-sections/ReputationSection.tsx
  • web/src/components/dashboard/config-sections/TicketsSection.tsx
  • web/src/components/dashboard/config-sections/TriageSection.tsx
  • web/tests/lib/config-normalization.test.ts
  • web/src/components/dashboard/config-sections/ModerationSection.tsx
  • web/src/lib/config-updates.ts
  • web/src/components/dashboard/config-sections/ChallengesSection.tsx
  • web/src/components/dashboard/config-sections/AiAutoModSection.tsx
  • web/tests/lib/config-updates.test.ts
  • web/src/components/dashboard/config-sections/WelcomeSection.tsx
  • web/src/components/dashboard/config-sections/index.ts
  • web/src/components/dashboard/config-editor.tsx
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to web/**/*.{ts,tsx,jsx,js} : Use Next.js 16 App Router for web dashboard, with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Create modules in `src/modules/` for new features, add config section to `config.json`, update `SAFE_CONFIG_KEYS`, create slash command if needed, add database migration if needed, write tests, and update dashboard UI if configurable
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to web/**/*.{ts,tsx,jsx,js} : Use Next.js 16 App Router for web dashboard, with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design

Applied to files:

  • web/next-env.d.ts
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to src/api/routes/**/*.{js,ts} : API endpoints must include tests in `tests/api/` directory

Applied to files:

  • web/next-env.d.ts
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to tests/**/*.{js,ts} : Maintain 80% test coverage threshold — Never lower the coverage requirement

Applied to files:

  • web/tests/components/dashboard/config-editor-autosave.test.tsx
  • web/tests/lib/config-normalization.test.ts
  • web/tests/lib/config-updates.test.ts
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to src/modules/**/*.{js,ts} : Config section additions MUST be added to `SAFE_CONFIG_KEYS` in `src/api/utils/configAllowlist.js` to enable API saves

Applied to files:

  • web/src/components/dashboard/config-sections/PermissionsSection.tsx
  • web/src/lib/config-normalization.ts
  • web/src/components/dashboard/config-sections/MemorySection.tsx
  • web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx
  • web/src/components/dashboard/config-sections/ModerationSection.tsx
  • web/src/lib/config-updates.ts
  • web/tests/lib/config-updates.test.ts
  • web/src/components/dashboard/config-sections/index.ts
  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to src/modules/**/*.{js,ts} : Gate all community features behind `config.<feature>.enabled` configuration checks

Applied to files:

  • web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx
  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Create modules in `src/modules/` for new features, add config section to `config.json`, update `SAFE_CONFIG_KEYS`, create slash command if needed, add database migration if needed, write tests, and update dashboard UI if configurable

Applied to files:

  • web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx
  • web/src/components/dashboard/config-sections/WelcomeSection.tsx
  • web/src/components/dashboard/config-sections/index.ts
  • web/src/components/dashboard/config-editor.tsx
🔇 Additional comments (12)
web/next-env.d.ts (1)

3-3: Typed-route reference update looks correct.

Line 3 is a valid modernization of the generated route type import path and is safe to keep.

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

42-76: Clean section extraction and toggle flow.

This is a solid modular section with safe defaults for missing feature config.

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

43-85: AI section wiring looks good.

Enabled state, system prompt updates, and blocked-channel updates are all cleanly separated.

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

14-93: Good utility extraction and pure behavior.

These helpers are clean, side-effect free, and easy to test.

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

23-69: Clean extraction with consistent field wiring.

The section wiring for saving, onFieldChange, and numeric normalization looks solid.

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

25-137: Section is well-structured and callback usage is consistent.

Good extraction and clear per-field constraints (min/max + parseNumberInput) across the ticket settings.

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

12-188: Great utility test coverage for normalization edge cases.

This suite is broad and directly reinforces behavior contracts for shared parsing/coercion helpers.

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

24-74: Solid section extraction with safe numeric update flow.

The component is cohesive, and the parseNumberInput gate before onFieldChange keeps invalid number transitions out of state.

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

95-177: Good breadth on array helper behavior.

The cases cover create/update/remove/append paths and preserve-item behavior, which gives strong regression protection for list operations.

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

104-128: Good regression guard for unintended autosave-on-mount.

The explicit PATCH-call assertion after initial load is a strong integration-level safety check.

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

438-710: Nice orchestration split with reusable section updaters.

The updater factory + section composition significantly improves readability versus inline monolithic update logic.

web/src/components/dashboard/config-sections/index.ts (1)

1-14: Clean barrel export for section modules.

This improves import ergonomics in config-editor.tsx and keeps section wiring maintainable.

@github-project-automation github-project-automation bot moved this from Backlog to In Review in Volvox.Bot Mar 5, 2026
* fix(sections): responsive grid layouts

* fix(config-editor): clear undo snapshot on guild change

* fix(config-updates): correct buildPath for deep nesting

* fix(sections): use onBlur for comma-separated inputs

---------

Co-authored-by: Bill <[email protected]>
@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-244 March 5, 2026 02:46 Destroyed
Copilot AI review requested due to automatic review settings March 5, 2026 02:51
@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-244 March 5, 2026 02:51 Destroyed
Copy link
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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coveralls
Copy link

coveralls commented Mar 5, 2026

Coverage Status

coverage: 87.878% (-0.006%) from 87.884%
when pulling ff80c9f on feat/issue-243-config-editor-refactor
into ee55d19 on main.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
web/src/components/dashboard/config-sections/StarboardSection.tsx (1)

86-90: ⚠️ Potential issue | 🟡 Minor

Treat unset emoji as default '*' when deciding “Any” button active state.

Line 87 currently marks “Any” inactive when emoji is unset, even though Line 76 renders '*' as the effective default.

💡 Proposed fix
-                  draftConfig.starboard?.emoji === '*'
+                  (draftConfig.starboard?.emoji ?? '*') === '*'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboard/config-sections/StarboardSection.tsx` around
lines 86 - 90, The "Any" button active check in StarboardSection uses
draftConfig.starboard?.emoji === '*' but the UI treats unset emoji as '*'
elsewhere; change the condition to treat undefined/null as '*' (e.g., use
nullish coalescing or equivalent) so the active state uses
(draftConfig.starboard?.emoji ?? '*') === '*' when computing the className for
the "Any" button in the StarboardSection component.
web/src/lib/config-updates.ts (1)

120-122: ⚠️ Potential issue | 🟠 Major

Validate array index bounds before mutating cloned arrays.

Line 121 and Line 166 currently accept invalid indices. That can create sparse arrays (arr[index] = item) or remove unintended elements (splice with negative index semantics).

💡 Proposed fix
  const lastKey = path[path.length - 1];
  const arr = [...((cursor[lastKey] as T[]) || [])];
+ if (!Number.isInteger(index) || index < 0 || index >= arr.length) return config;
  arr[index] = item;
  const lastKey = path[path.length - 1];
  const arr = [...((cursor[lastKey] as unknown[]) || [])];
+ if (!Number.isInteger(index) || index < 0 || index >= arr.length) return config;
  arr.splice(index, 1);

Also applies to: 165-167

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

In `@web/src/lib/config-updates.ts` around lines 120 - 122, The clone-and-mutate
logic that creates arr from cursor[lastKey] (variables: cursor, lastKey, arr)
must validate the provided index (variable: index) before performing arr[index]
= item or using splice (also referenced in the other block around lines
165-167); ensure index is an integer and within 0 <= index < arr.length for
replacement, allow index === arr.length only if you intend to append (handle
that explicitly), and for splice ensure you normalize/clip negative or
out-of-range values or bail/throw rather than letting JavaScript create sparse
arrays—add an index bounds check and early return or explicit append/insert
behavior in the functions that perform these mutations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx`:
- Line 69: The timezone field label currently uses an unconditional "col-span-2"
(className "space-y-2 col-span-2") which breaks the mobile one-column grid;
change it to a breakpointed span such as "md:col-span-2" (or another appropriate
breakpoint like "sm:col-span-2") so the label occupies full width on small
screens and two columns only on larger screens; update the className on the
label element in ChallengesSection (the label wrapping the timezone input)
accordingly and verify the grid defined earlier (mobile one-column) behaves
correctly.

In `@web/src/components/dashboard/config-sections/ModerationSection.tsx`:
- Line 151: In ModerationSection, the fixed "grid grid-cols-2" and the other
forced multi-column grid are not responsive and will crowd inputs on small
screens; update both grid containers referenced in the component (the divs
currently using "grid grid-cols-2 gap-4" and the other using the 3-column
variant) to use responsive Tailwind classes such as "grid-cols-1 sm:grid-cols-2"
for the two-column block and "grid-cols-1 sm:grid-cols-2 md:grid-cols-3" for the
three-column block so numeric inputs stack on mobile and expand into columns on
larger breakpoints.

In `@web/src/components/dashboard/config-sections/ReputationSection.tsx`:
- Line 55: The XP settings grid in ReputationSection.tsx currently uses a fixed
two-column layout ("grid grid-cols-2 gap-4") which breaks mobile responsiveness;
change the grid to be mobile-first by using one column by default and enabling
two columns at the small breakpoint (e.g., replace "grid-cols-2" with
"grid-cols-1 sm:grid-cols-2") on the div in the ReputationSection component so
controls stack on narrow screens and expand to two columns on larger viewports.

In `@web/src/components/dashboard/config-sections/StarboardSection.tsx`:
- Around line 115-124: The input currently normalizes
draftConfig.starboard?.ignoredChannels on every keystroke (value=(...).join(',
') + onChange parsing), which collapses intermediate states; fix by introducing
a local string buffer (e.g., ignoredChannelsBuffer state in StarboardSection)
bound to the input value, update that buffer onChange, and only parse+call
onFieldChange('ignoredChannels', parsedArray) onBlur; also initialize/sync the
buffer from draftConfig.starboard?.ignoredChannels via useEffect so the buffer
reflects prop changes.

---

Duplicate comments:
In `@web/src/components/dashboard/config-sections/StarboardSection.tsx`:
- Around line 86-90: The "Any" button active check in StarboardSection uses
draftConfig.starboard?.emoji === '*' but the UI treats unset emoji as '*'
elsewhere; change the condition to treat undefined/null as '*' (e.g., use
nullish coalescing or equivalent) so the active state uses
(draftConfig.starboard?.emoji ?? '*') === '*' when computing the className for
the "Any" button in the StarboardSection component.

In `@web/src/lib/config-updates.ts`:
- Around line 120-122: The clone-and-mutate logic that creates arr from
cursor[lastKey] (variables: cursor, lastKey, arr) must validate the provided
index (variable: index) before performing arr[index] = item or using splice
(also referenced in the other block around lines 165-167); ensure index is an
integer and within 0 <= index < arr.length for replacement, allow index ===
arr.length only if you intend to append (handle that explicitly), and for splice
ensure you normalize/clip negative or out-of-range values or bail/throw rather
than letting JavaScript create sparse arrays—add an index bounds check and early
return or explicit append/insert behavior in the functions that perform these
mutations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4ba5af0e-6b25-4d29-abf9-93a64e7578ac

📥 Commits

Reviewing files that changed from the base of the PR and between c843e3b and e3a6b77.

📒 Files selected for processing (7)
  • web/src/components/dashboard/config-editor.tsx
  • web/src/components/dashboard/config-sections/ChallengesSection.tsx
  • web/src/components/dashboard/config-sections/ModerationSection.tsx
  • web/src/components/dashboard/config-sections/PermissionsSection.tsx
  • web/src/components/dashboard/config-sections/ReputationSection.tsx
  • web/src/components/dashboard/config-sections/StarboardSection.tsx
  • web/src/lib/config-updates.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (2)
{src,web}/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Winston logger from src/logger.js, NEVER use console.*

Files:

  • web/src/components/dashboard/config-sections/StarboardSection.tsx
  • web/src/components/dashboard/config-sections/PermissionsSection.tsx
  • web/src/components/dashboard/config-sections/ChallengesSection.tsx
  • web/src/components/dashboard/config-sections/ReputationSection.tsx
  • web/src/components/dashboard/config-sections/ModerationSection.tsx
  • web/src/components/dashboard/config-editor.tsx
  • web/src/lib/config-updates.ts
web/**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Use Next.js 16 App Router for web dashboard, with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design

Files:

  • web/src/components/dashboard/config-sections/StarboardSection.tsx
  • web/src/components/dashboard/config-sections/PermissionsSection.tsx
  • web/src/components/dashboard/config-sections/ChallengesSection.tsx
  • web/src/components/dashboard/config-sections/ReputationSection.tsx
  • web/src/components/dashboard/config-sections/ModerationSection.tsx
  • web/src/components/dashboard/config-editor.tsx
  • web/src/lib/config-updates.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Create modules in `src/modules/` for new features, add config section to `config.json`, update `SAFE_CONFIG_KEYS`, create slash command if needed, add database migration if needed, write tests, and update dashboard UI if configurable
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to web/**/*.{ts,tsx,jsx,js} : Use Next.js 16 App Router for web dashboard, with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to web/**/*.{ts,tsx,jsx,js} : Use Next.js 16 App Router for web dashboard, with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design

Applied to files:

  • web/src/components/dashboard/config-sections/StarboardSection.tsx
  • web/src/components/dashboard/config-sections/ChallengesSection.tsx
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to src/modules/**/*.{js,ts} : Config section additions MUST be added to `SAFE_CONFIG_KEYS` in `src/api/utils/configAllowlist.js` to enable API saves

Applied to files:

  • web/src/components/dashboard/config-sections/PermissionsSection.tsx
  • web/src/components/dashboard/config-sections/ChallengesSection.tsx
  • web/src/components/dashboard/config-sections/ModerationSection.tsx
  • web/src/components/dashboard/config-editor.tsx
  • web/src/lib/config-updates.ts
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Create modules in `src/modules/` for new features, add config section to `config.json`, update `SAFE_CONFIG_KEYS`, create slash command if needed, add database migration if needed, write tests, and update dashboard UI if configurable

Applied to files:

  • web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to src/modules/**/*.{js,ts} : Gate all community features behind `config.<feature>.enabled` configuration checks

Applied to files:

  • web/src/components/dashboard/config-editor.tsx

- AiAutoModSection: render even when aiAutoMod is missing
- EngagementSection: use stable keys for badge rows
- StarboardSection: fix default Any button active state
- TriageSection: normalize moderationLogChannel before patch
- WelcomeSection: read dmSteps from event target in onBlur
- config-updates: validate array index bounds before mutations
@railway-app railway-app bot temporarily deployed to volvox-bot / volvox-bot-pr-244 March 5, 2026 03:01 Destroyed
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (2)
web/src/components/dashboard/config-sections/StarboardSection.tsx (1)

115-124: ⚠️ Potential issue | 🟠 Major

Avoid normalizing ignored channel IDs on every keystroke.

At Line 115 and Lines 116-124, parsing/splitting in onChange causes input rewrites (e.g., trailing commas/spaces), making multi-ID typing unreliable. Keep a raw local string buffer and only normalize on blur.

💡 Proposed fix
+'use client';
+import { useEffect, useState } from 'react';
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
@@
 export function StarboardSection({ draftConfig, saving, onFieldChange }: StarboardSectionProps) {
+  const ignoredChannelsDisplay = (draftConfig.starboard?.ignoredChannels ?? []).join(', ');
+  const [ignoredChannelsRaw, setIgnoredChannelsRaw] = useState(ignoredChannelsDisplay);
+
+  useEffect(() => {
+    setIgnoredChannelsRaw(ignoredChannelsDisplay);
+  }, [ignoredChannelsDisplay]);
+
   return (
@@
           <input
             id="ignored-channels"
             type="text"
-            value={(draftConfig.starboard?.ignoredChannels ?? []).join(', ')}
-            onChange={(e) =>
-              onFieldChange(
-                'ignoredChannels',
-                e.target.value
-                  .split(',')
-                  .map((s) => s.trim())
-                  .filter(Boolean),
-              )
-            }
+            value={ignoredChannelsRaw}
+            onChange={(e) => setIgnoredChannelsRaw(e.target.value)}
+            onBlur={() =>
+              onFieldChange(
+                'ignoredChannels',
+                ignoredChannelsRaw
+                  .split(',')
+                  .map((s) => s.trim())
+                  .filter(Boolean),
+              )
+            }
             disabled={saving}
             className={inputClasses}
             placeholder="Comma-separated channel IDs"
           />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboard/config-sections/StarboardSection.tsx` around
lines 115 - 124, The current onChange in StarboardSection is normalizing
draftConfig.starboard?.ignoredChannels on every keystroke which rewrites the
input; instead, add a local state string (e.g., ignoredChannelsText) initialized
from draftConfig.starboard?.ignoredChannels.join(', ') in a useEffect, bind the
text input's value to that local state and update it raw in the input's
onChange, and move the split/trim/filter normalization into the input's onBlur
handler to call onFieldChange('ignoredChannels', parsedArray); ensure the local
state is kept in sync if draftConfig.starboard changes.
web/src/components/dashboard/config-sections/TriageSection.tsx (1)

216-216: ⚠️ Potential issue | 🟠 Major

Keep moderationLogChannel as a string when clearing input.

Line 216 writes null for empty input. web/src/types/config.ts defines moderationLogChannel as string, so this can introduce payload/schema drift.

💡 Proposed fix
-            onChange={(e) => onFieldChange('moderationLogChannel', e.target.value.trim() || null)}
+            onChange={(e) => onFieldChange('moderationLogChannel', e.target.value.trim())}
#!/bin/bash
set -euo pipefail

# Verify declared type for moderationLogChannel.
rg -nP 'moderationLogChannel\s*:' web/src/types/config.ts -C2

# Verify all usages and null handling patterns.
rg -nP 'moderationLogChannel' -g '**/*.{ts,tsx}' web/src -C2
rg -nP "onFieldChange\\('moderationLogChannel'" web/src/components/dashboard/config-sections/TriageSection.tsx -C2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboard/config-sections/TriageSection.tsx` at line 216,
The onChange handler sets moderationLogChannel to null for empty input which
mismatches the declared string type; update the handler in TriageSection (the
onChange using onFieldChange('moderationLogChannel', ...)) to pass an empty
string instead of null (e.g., use e.target.value.trim() || ''), ensuring all
places that consume moderationLogChannel expect a string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/dashboard/config-sections/AiAutoModSection.tsx`:
- Around line 34-35: The issue is that thresholds and actions fall back to {}
and then the update spread `{ ...thresholds, [cat]: v }` loses sibling keys; fix
by ensuring thresholds and actions are initialized by merging
cfg.thresholds/cfg.actions into a complete default object containing all
expected category keys (e.g., create DEFAULT_THRESHOLDS and DEFAULT_ACTIONS or a
single DEFAULTS map), e.g. let thresholds = { ...DEFAULT_THRESHOLDS,
...(cfg.thresholds as Record<string, number>)} and similarly for actions, then
keep the existing update site that does `{ ...thresholds, [cat]: v }` inside the
AiAutoModSection so updates preserve unspecified sibling categories.

In `@web/src/components/dashboard/config-sections/EngagementSection.tsx`:
- Around line 43-79: The form controls in EngagementSection (the number Input
bound to badge.days, the text Input bound to badge.label, and the remove Button
rendering "✕") lack accessible names; update the Input and Button elements to
include clear ARIA names (eg. aria-label or aria-labelledby) that incorporate
the badge context (use the index i or badge.label to produce unique, descriptive
labels like "Badge X days", "Badge X label", and "Remove badge X") so screen
readers can identify each control, without changing onActivityBadgesChange,
badges, badge.days, badge.label, or the removal logic.
- Around line 8-12: The prop type for onActivityBadgesChange is too loose;
update EngagementSectionProps so onActivityBadgesChange accepts the stricter
badge shape required by the config schema (use Array<ActivityBadge> or Array<{
days: number; label: string }> with both fields non-optional) and adjust any
related local types/uses in EngagementSection to match (ensure places that call
onActivityBadgesChange construct badges with both days and label as required).
Reference: EngagementSectionProps and the onActivityBadgesChange prop and the
ActivityBadge schema/type.

In `@web/src/components/dashboard/config-sections/WelcomeSection.tsx`:
- Around line 26-38: The generateId function is duplicated; extract the
implementation into a shared utility (e.g., export function generateId(...) in a
common utils module) and replace local implementations by importing that single
exported generateId; update both the WelcomeSection's generateId and the
config-loading code that currently repeats the logic to import and use the
centralized generateId, and ensure the utility exports the same string return
type and any needed runtime fallback behavior.
- Around line 145-165: The role option rendering and delete handler assume
opt.id exists and is unique; change to use the map index instead: render with a
stable index-based key (e.g., key={`role-opt-${i}`} or key={i}) and update the
delete/update logic to operate by index (use opts.splice(i,1) or opts.filter((_,
idx) => idx !== i)) instead of comparing ids; update the onClick delete and any
other places that rely on id-based removal so they use the index i, leaving id
generation/validation separate when options are created or saved.

In `@web/src/lib/config-updates.ts`:
- Around line 120-121: Guard the array cloning so it only spreads when the
terminal value is actually an array: replace the naive spread of cursor[lastKey]
into arr (the line creating const arr = [...((cursor[lastKey] as T[]) || [])];)
with a safe check that uses Array.isArray(cursor[lastKey]) and falls back to an
empty array when it's not an array; apply the same pattern to the other spots
flagged (the similar clones at the blocks used by updateArrayItem,
removeArrayItem, and appendArrayItem) so cursor, lastKey, and arr are always
handled safely when terminal values are objects/strings/undefined.

---

Duplicate comments:
In `@web/src/components/dashboard/config-sections/StarboardSection.tsx`:
- Around line 115-124: The current onChange in StarboardSection is normalizing
draftConfig.starboard?.ignoredChannels on every keystroke which rewrites the
input; instead, add a local state string (e.g., ignoredChannelsText) initialized
from draftConfig.starboard?.ignoredChannels.join(', ') in a useEffect, bind the
text input's value to that local state and update it raw in the input's
onChange, and move the split/trim/filter normalization into the input's onBlur
handler to call onFieldChange('ignoredChannels', parsedArray); ensure the local
state is kept in sync if draftConfig.starboard changes.

In `@web/src/components/dashboard/config-sections/TriageSection.tsx`:
- Line 216: The onChange handler sets moderationLogChannel to null for empty
input which mismatches the declared string type; update the handler in
TriageSection (the onChange using onFieldChange('moderationLogChannel', ...)) to
pass an empty string instead of null (e.g., use e.target.value.trim() || ''),
ensuring all places that consume moderationLogChannel expect a string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1448554a-cbc9-4759-8393-08448895e976

📥 Commits

Reviewing files that changed from the base of the PR and between e3a6b77 and ff80c9f.

📒 Files selected for processing (6)
  • web/src/components/dashboard/config-sections/AiAutoModSection.tsx
  • web/src/components/dashboard/config-sections/EngagementSection.tsx
  • web/src/components/dashboard/config-sections/StarboardSection.tsx
  • web/src/components/dashboard/config-sections/TriageSection.tsx
  • web/src/components/dashboard/config-sections/WelcomeSection.tsx
  • web/src/lib/config-updates.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (2)
{src,web}/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Winston logger from src/logger.js, NEVER use console.*

Files:

  • web/src/components/dashboard/config-sections/StarboardSection.tsx
  • web/src/components/dashboard/config-sections/AiAutoModSection.tsx
  • web/src/components/dashboard/config-sections/TriageSection.tsx
  • web/src/lib/config-updates.ts
  • web/src/components/dashboard/config-sections/EngagementSection.tsx
  • web/src/components/dashboard/config-sections/WelcomeSection.tsx
web/**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Use Next.js 16 App Router for web dashboard, with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design

Files:

  • web/src/components/dashboard/config-sections/StarboardSection.tsx
  • web/src/components/dashboard/config-sections/AiAutoModSection.tsx
  • web/src/components/dashboard/config-sections/TriageSection.tsx
  • web/src/lib/config-updates.ts
  • web/src/components/dashboard/config-sections/EngagementSection.tsx
  • web/src/components/dashboard/config-sections/WelcomeSection.tsx
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Create modules in `src/modules/` for new features, add config section to `config.json`, update `SAFE_CONFIG_KEYS`, create slash command if needed, add database migration if needed, write tests, and update dashboard UI if configurable
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to web/**/*.{ts,tsx,jsx,js} : Use Next.js 16 App Router for web dashboard, with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to src/modules/**/*.{js,ts} : Config section additions MUST be added to `SAFE_CONFIG_KEYS` in `src/api/utils/configAllowlist.js` to enable API saves

Applied to files:

  • web/src/components/dashboard/config-sections/StarboardSection.tsx
  • web/src/components/dashboard/config-sections/AiAutoModSection.tsx
  • web/src/lib/config-updates.ts
📚 Learning: 2026-03-02T21:23:59.512Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-02T21:23:59.512Z
Learning: Applies to web/**/*.{ts,tsx,jsx,js} : Use Next.js 16 App Router for web dashboard, with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design

Applied to files:

  • web/src/components/dashboard/config-sections/StarboardSection.tsx
🔇 Additional comments (7)
web/src/components/dashboard/config-sections/StarboardSection.tsx (2)

54-54: Responsive grid fix looks good.

grid-cols-1 md:grid-cols-2 keeps this section usable on small screens while preserving desktop density.


86-90: Default “Any” active-state logic is correctly normalized.

Using (draftConfig.starboard?.emoji ?? '*') correctly treats unset emoji as the default mode.

web/src/components/dashboard/config-sections/AiAutoModSection.tsx (3)

77-106: LGTM!

The threshold conversion logic correctly uses decimalToPercent for display and percentToDecimal for storage. The fieldset with legend provides proper semantic grouping, and labels are correctly associated with inputs.


107-134: LGTM!

The actions fieldset follows a consistent pattern with proper label associations and comprehensive moderation action options.


47-52: The onFieldChange field paths are correctly scoped to aiAutoMod. The parent component in config-editor.tsx wraps the callback to explicitly nest all field updates under the aiAutoMod object: aiAutoMod: { ...prev.aiAutoMod, [field]: value }. When the child component calls onFieldChange('enabled', v), the parent correctly routes it to aiAutoMod.enabled.

			> Likely an incorrect or invalid review comment.
web/src/components/dashboard/config-sections/EngagementSection.tsx (1)

41-42: Good improvement on badge row key source.

Using a non-user-editable source for the row key prevents the prior mutable-key collision behavior.

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

223-233: Good fix: blur now parses from the event value.

Using e.currentTarget.value here avoids stale-state reads and keeps raw text + parsed steps synchronized.

BillChirico pushed a commit that referenced this pull request Mar 5, 2026
- config-editor: centralize updater methods for AiAutoMod, CommunityFeatures, Engagement, GitHub feed, and Tickets; expose setField/setEnabled/setToggle/setActivityBadges
- AiAutoModSection: remove strict dependency on aiAutoMod object; use sensible defaults and optional chaining fallbacks for toggles/inputs
- EngagementSection: use stable index key for badge rows (prevents key collisions)
- ReputationSection: commit thresholds on blur using event value; keep raw text buffer
- StarboardSection: fix Any mode active state with (emoji ?? '*') === '*'
- TriageSection: normalize moderationLogChannel on blur (trim/clear)
- WelcomeSection: parse DM steps from event; update sequence on every change to avoid Ctrl+S data loss
- ModerationSection: update blockedDomains on each change; keep blur normalization
- config-updates: add index bounds checks for updateArrayItem/removeArrayItem; return unchanged config when OOB
- tests: prune duplicate normalization utility tests from config-editor-autosave; fix immutability test using structuredClone(baseConfig)
BillChirico pushed a commit that referenced this pull request Mar 5, 2026
UX Regressions (CRITICAL):
- Restore ChannelSelector in StarboardSection for starboard channel

Mobile Responsive:
- ChallengesSection: Use responsive col-span (col-span-1 md:col-span-2)
- ModerationSection: Use grid-cols-1 md:grid-cols-2 for rate limit grid
- ModerationSection: Use grid-cols-1 md:grid-cols-3 for mute settings grid
- ReputationSection: Use grid-cols-1 md:grid-cols-2 for XP settings

Input Normalization:
- StarboardSection: Add raw buffer + blur pattern for ignored channels
- TriageSection: Trim whitespace for moderation log channel (already done)

Code Quality:
- EngagementSection: Refactor activityBadges updater with updateBadge helper
- EngagementSection: Add accessible names to all form controls
- EngagementSection: Fix badge row key collision (use days-label composite key)
- AiAutoModSection: Handle partial config sibling categories with defaults
- config-updates.ts: Guard array cloning against non-array values
- config-updates.ts: Validate array index bounds in update/remove/append

All tests pass (3628 passed, 2 skipped).
@BillChirico BillChirico merged commit a17c193 into main Mar 5, 2026
22 of 24 checks passed
@BillChirico BillChirico deleted the feat/issue-243-config-editor-refactor branch March 5, 2026 06:13
@github-project-automation github-project-automation bot moved this from In Review to Done in Volvox.Bot Mar 5, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

🧹 Preview Environment Cleaned Up

The Railway preview environment for this PR has been removed.

Environment: pr-244

BillChirico added a commit that referenced this pull request Mar 7, 2026
…re (#244)

* refactor(config): add normalization utilities for config editor

* refactor(config): add config update utilities with tests

* refactor(config): extract section components from config-editor

* refactor(config): simplify config-editor.tsx to orchestration layer

* fix: address PR #244 review comments (#245)

* fix(sections): responsive grid layouts

* fix(config-editor): clear undo snapshot on guild change

* fix(config-updates): correct buildPath for deep nesting

* fix(sections): use onBlur for comma-separated inputs

---------

Co-authored-by: Bill <[email protected]>

* fix: address remaining PR #244 review comments

- AiAutoModSection: render even when aiAutoMod is missing
- EngagementSection: use stable keys for badge rows
- StarboardSection: fix default Any button active state
- TriageSection: normalize moderationLogChannel before patch
- WelcomeSection: read dmSteps from event target in onBlur
- config-updates: validate array index bounds before mutations

---------

Co-authored-by: Bill <[email protected]>
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.

Refactor dashboard config-editor.tsx into maintainable section architecture

3 participants