Skip to content

feat: shadcn/ui overhaul with dark mode and role/channel selectors#170

Merged
BillChirico merged 27 commits intomainfrom
feat/shadcn-ui-overhaul
Mar 1, 2026
Merged

feat: shadcn/ui overhaul with dark mode and role/channel selectors#170
BillChirico merged 27 commits intomainfrom
feat/shadcn-ui-overhaul

Conversation

@BillChirico
Copy link
Collaborator

@BillChirico BillChirico commented Mar 1, 2026

Summary

Complete shadcn/ui design system implementation for the dashboard:

Changes

Theme & Dark Mode

  • Theme provider with next-themes (defaults to system preference)
  • Dark mode toggle component in header
  • CSS variables for light/dark theming

Dashboard Migration

  • Migrated config sections to shadcn components (Switch, Input, Label, Form)
  • Replaced custom ToggleSwitch with shadcn Switch
  • Added form validation with shadcn Form

New Components

  • RoleSelector: Multi-select combobox for Discord roles with color indicators
  • ChannelSelector: Multi-select combobox for Discord channels with type icons
  • Both support auto-fill from Discord API

Files Changed

  • 20+ files across web/src/components/
  • New UI components: theme-provider, theme-toggle, switch, form, checkbox, popover, command
  • New selector components: role-selector, channel-selector

Copilot AI review requested due to automatic review settings March 1, 2026 05:08
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces two new UI components for Discord channel and role selection, updates the toast notification theming system to sync with the app's theme preference, refines dialog and form component structure, and adds testing infrastructure for browser API compatibility.

Changes

Cohort / File(s) Summary
Provider and Theme Updates
web/src/components/providers.tsx
Introduces ThemedToaster component that dynamically reads the current theme via useTheme and forwards it to Toaster with richColors, ensuring the notification theme aligns with the app's resolved light/dark theme preference.
New Selection Components
web/src/components/ui/channel-selector.tsx, web/src/components/ui/role-selector.tsx
Adds two new multi-select components: ChannelSelector for Discord channels with filtering by type (text, voice, announcement, thread, forum) and async loading with abort support; RoleSelector for Discord roles with color indicators and dynamic fetching from the API.
UI Component Refinements
web/src/components/ui/dialog.tsx, web/src/components/ui/form.tsx
Removes data-slot attributes from Dialog and DialogPortal wrappers; updates form.tsx to import Slot directly from @radix-ui/react-slot and adjusts FormControl signature to use Slot instead of Slot.Root.
Testing Infrastructure
web/tests/setup.ts
Adds window.matchMedia polyfill using vitest mocking to support jsdom compatibility with next-themes, enabling proper theme detection in test environments.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main changes: shadcn/ui implementation with dark mode support and new role/channel selector components.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the theme system, dashboard migration, new components, and files affected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/shadcn-ui-overhaul

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.

Pull request overview

Implements a shadcn/ui-based design system update for the web dashboard, adding theme (dark/light/system) support and introducing role/channel multi-select selectors backed by Discord API endpoints.

Changes:

  • Adds next-themes provider + a header theme toggle, and removes forced dark class from the root layout.
  • Introduces new shadcn-style UI primitives (switch, form, checkbox, popover, command, dialog) and migrates several config sections to use them.
  • Adds RoleSelector and ChannelSelector combobox components with multi-select + badge removal UI.

Reviewed changes

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

Show a summary per file
File Description
web/src/components/ui/switch.tsx New Switch primitive wrapper.
web/src/components/ui/popover.tsx New Popover wrapper used by selectors/combobox.
web/src/components/ui/dialog.tsx Refactors Dialog component implementation and adds close-button options.
web/src/components/ui/command.tsx Adds cmdk-based Command components (used for searchable combobox UIs).
web/src/components/ui/checkbox.tsx New Checkbox primitive wrapper.
web/src/components/ui/form.tsx Adds react-hook-form helpers for shadcn-style form composition.
web/src/components/ui/role-selector.tsx New Discord role multi-select combobox with badges + API fetch.
web/src/components/ui/channel-selector.tsx New Discord channel multi-select combobox with badges + API fetch + filtering.
web/src/components/theme-provider.tsx Wraps next-themes ThemeProvider.
web/src/components/theme-toggle.tsx Adds header theme toggle dropdown.
web/src/components/providers.tsx Wraps app in ThemeProvider (alongside SessionProvider + Toaster).
web/src/components/layout/header.tsx Adds ThemeToggle to header.
web/src/components/dashboard/config-sections/WelcomeSection.tsx Migrates Welcome section to shadcn Switch/Label/Textarea.
web/src/components/dashboard/config-sections/TriageSection.tsx Migrates inputs/toggles to shadcn Input/Label/Switch.
web/src/components/dashboard/config-sections/NumberField.tsx Migrates number inputs to shadcn Input/Label.
web/src/components/dashboard/config-sections/ModerationSection.tsx Migrates moderation toggles/inputs to shadcn components.
web/src/components/dashboard/config-sections/AiSection.tsx Migrates AI toggle to shadcn Switch/Label.
web/src/app/layout.tsx Removes forced dark mode class from <html>.
web/package.json Adds cmdk + next-themes dependencies.
pnpm-lock.yaml Locks new dependencies.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

web/src/components/dashboard/config-sections/NumberField.tsx:24

  • The Label here isn't associated with the Input (no htmlFor/id), which reduces accessibility and click-to-focus behavior. Consider generating a stable id (e.g. React.useId()) and wiring Label htmlFor to the Input id.
    <div className="space-y-2">
      <Label>{label}</Label>
      <Input
        type="number"
        step={step}
        min={min}
        value={value}
        onChange={(e) => {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

Caution

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

⚠️ Outside diff range comments (1)
web/package.json (1)

16-29: 🧹 Nitpick | 🔵 Trivial

Consider consolidating Radix UI dependencies.

The package includes both individual @radix-ui/* scoped packages (lines 16-20) and the umbrella radix-ui package (line 29). The codebase currently imports from both (e.g., Dialog from radix-ui but Slot from @radix-ui/react-slot), since the umbrella package re-exports all primitives. Standardizing on a single approach—either using only the umbrella package or only the scoped packages—would reduce dependency duplication and simplify maintenance.

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

In `@web/package.json` around lines 16 - 29, The project mixes umbrella "radix-ui"
and scoped packages like "@radix-ui/react-slot" (e.g., importing Dialog from
"radix-ui" but Slot from "@radix-ui/react-slot"); pick one approach and
consolidate dependencies in package.json by removing the duplicates (either keep
"radix-ui" and remove all "@radix-ui/*" entries, or remove "radix-ui" and keep
the individual "@radix-ui/react-*" packages), then update all import sites
(e.g., change imports of Slot, Dialog, etc.) to the chosen source and run a
fresh install to ensure lockfile consistency.
🤖 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/AiSection.tsx`:
- Around line 33-41: The hidden Label with htmlFor="ai-toggle" isn't associated
with the Switch because the Switch lacks an id; open the AiSection.tsx
component, locate the Switch element (the one using
checked={draftConfig.ai?.enabled ?? false} and
onCheckedChange={onEnabledChange}) and add id="ai-toggle" to the Switch props so
the Label and control are correctly bound (keep the existing aria-label and
sr-only Label as-is).

In `@web/src/components/dashboard/config-sections/NumberField.tsx`:
- Around line 3-4: The Label and Input in NumberField.tsx are not
programmatically connected; update the NumberField component (where Label and
Input are rendered) to supply a matching id/htmlFor pair: give the Input an id
prop and the Label an htmlFor prop with the same value (use React's useId or a
passed-in id prop to generate a stable unique id), and ensure any existing id
prop passed to NumberField is forwarded to the Input and used by the Label;
apply the same change to the other occurrences in the component (lines ~15-20)
so all Label/Input pairs use the shared id/htmlFor.

In `@web/src/components/providers.tsx`:
- Around line 22-30: The Toaster is hardcoded with theme="system" which can
conflict with the resolved theme from ThemeProvider; update the Toaster inside
the ThemeProvider (symbol: Toaster) to either remove the theme prop so it
inherits styling from the theme class applied by ThemeProvider (symbol:
ThemeProvider) or use next-themes' useTheme hook to read the resolved theme and
pass that value to Toaster (e.g., get theme from useTheme and supply it to
Toaster) so toast UI follows the user's selected theme rather than always
following system preference.

In `@web/src/components/theme-toggle.tsx`:
- Line 21: The destructured theme from useTheme() in theme-toggle.tsx is unused;
either remove theme from the destructuring to silence the lint warning or use it
to mark the active selection in the dropdown (e.g., compare theme to
"light"/"dark"/"system" inside the click handlers or inside the DropdownMenuItem
render and render a checkmark/active style). Update the line with const {
setTheme, theme } = useTheme() accordingly (either to const { setTheme } =
useTheme() or keep theme and conditionally render an active indicator on the
Light/Dark/System items), and ensure any UI element or aria attribute reflects
the current theme state.

In `@web/src/components/ui/channel-selector.tsx`:
- Around line 178-181: Replace the direct window.location.href assignment in the
response.status === 401 branch with Next.js navigation by importing and using
useRouter from 'next/navigation'; inside the ChannelSelector component (or the
component that contains the effect where this check runs) call const router =
useRouter() and use router.push('/login') instead of window.location.href to
perform the redirect, and ensure the useRouter import and hook are added at the
top of the component so the redirect remains client-safe and idiomatic.
- Around line 345-352: The remove button in ChannelSelector (the button that
calls removeChannel(channel.id) and renders the X icon) needs an accessible
label so screen readers understand its action; add an aria-label (or
aria-labelledby) to the button such as aria-label={`Remove channel
${channel.name}`} or a generic aria-label="Remove channel" (including
channel.name is preferred for clarity), or include visually hidden text inside
the button tied to the label; keep the existing disabled handling and maintain
the X icon rendering.

In `@web/src/components/ui/checkbox.tsx`:
- Around line 1-32: Update the file to follow project style: replace double
quotes with single quotes for all imports, JSX attributes and strings, and add
missing semicolons where required (e.g., after the closing brace of the Checkbox
function and the export statement). Ensure imports for React, CheckIcon,
CheckboxPrimitive and cn remain unchanged except for quoting, and keep the
component name Checkbox and its JSX structure intact while adding semicolons at
statement endings to satisfy the linter/Biome rules.

In `@web/src/components/ui/command.tsx`:
- Around line 1-14: Update this file to follow project style by switching all
double quotes to single quotes and adding missing semicolons so it matches
dialog.tsx; specifically change the "use client" directive and all import
strings for React, "cmdk" (CommandPrimitive), "lucide-react" (SearchIcon),
"@/lib/utils" (cn) and the dialog component imports (Dialog, DialogContent,
DialogDescription, DialogHeader, DialogTitle) to use single quotes and ensure
each statement ends with a semicolon; apply the same quoting and semicolon rules
throughout any JSX/exports in this module so linting/biome rules pass.
- Around line 45-60: Move the DialogHeader (containing DialogTitle and
DialogDescription) so it is rendered inside DialogContent rather than as a
sibling to ensure Radix DialogPrimitive.Content can set
aria-labelledby/aria-describedby correctly; update the JSX in the component that
returns Dialog... to nest DialogHeader within the DialogContent element
(preserving className, showCloseButton, and the Command children and props) so
DialogTitle and DialogDescription become descendants of DialogContent.

In `@web/src/components/ui/dialog.tsx`:
- Around line 1-8: Update the file to follow project styling: replace all double
quotes with single quotes and add missing semicolons across the module (imports,
export lines, variable declarations, JSX string props, and any other string
literals) — for example update import lines for React, XIcon, DialogPrimitive,
cn, and Button and ensure exported components or constants in this file end with
semicolons; run the project's formatter/linter (Biome/Prettier) after changes to
ensure consistency and fix any remaining semicolon/quote issues.
- Around line 22-26: Remove the meaningless data-slot attribute from
DialogPortal (it’s being applied to DialogPrimitive.Portal which doesn’t render
a DOM node) by deleting data-slot="dialog-portal" and just spreading {...props}
in DialogPortal; if you actually need a data attribute for testing/selection,
apply data-slot="..." to the real DOM-rendering component (e.g. the top-level
element in DialogContent or the consumer component) instead of
DialogPrimitive.Portal.

In `@web/src/components/ui/form.tsx`:
- Around line 28-30: The context guards never trigger because FormFieldContext
and the other context are initialized with a cast empty object ({} as ...), so
change their default to null (e.g., React.createContext<FormFieldContextValue |
null>(null)) and update useFormField (and the other useX hooks at lines ~45-57
and ~72-74) to check for null and throw a clear error when the hook is used
outside its Provider; reference FormFieldContext and useFormField (and the
corresponding second context/use-hook names in the file) to locate and fix the
initialization and guard checks.
- Around line 1-167: The file uses double quotes and omits semicolons contrary
to project formatting rules; update all string literals and import/export quotes
to single quotes and ensure every statement ends with a semicolon (including
imports, constant declarations like Form = FormProvider, context creations,
function declarations/returns and JSX attribute values) across symbols such as
FormField, useFormField, FormItem, FormLabel, FormControl, FormDescription, and
FormMessage; preserve existing logic and JSX structure but run a quick pass to
replace "..." with '...' and add missing semicolons after declarations and
expressions to comply with the `**/*.{js,jsx,mjs,ts,tsx}` linting rule.

In `@web/src/components/ui/popover.tsx`:
- Around line 1-89: This file violates repo style: replace double quotes with
single quotes and ensure semicolons are present; update all import and string
literals (e.g., the module import lines and data-slot attributes) and add
missing trailing semicolons for every statement and function/export in this file
(notably in Popover, PopoverTrigger, PopoverContent, PopoverAnchor,
PopoverHeader, PopoverTitle, PopoverDescription and the export block) so the
file uses single quotes and ends statements with semicolons consistent with the
project's /*.{js,jsx,mjs,ts,tsx} style rules.
- Around line 58-64: PopoverTitle is typed with React.ComponentProps<"h2"> but
renders a div; change the rendered element to an h2 so props and semantics
match. Update the JSX in the PopoverTitle function to return an <h2> (preserving
data-slot="popover-title", className={cn("font-medium", className)} and
spreading {...props}) instead of a div, ensuring any imports/usage of
PopoverTitle remain unchanged.

In `@web/src/components/ui/role-selector.tsx`:
- Around line 242-249: The icon-only remove button lacks an accessible name;
update the button rendered in the RoleSelector component (the button with
onClick={() => removeRole(role.id)} and the <X /> icon) to provide an explicit
accessible label—e.g., add an aria-label={`Remove ${role.name || 'role'}`} or
include visually-hidden text describing the action—and ensure the label uses the
role's name or a generic "remove role" fallback so screen readers can convey the
target.
- Around line 67-69: When starting to load a new guild's roles (the place where
setLoading(true) and setError(null) are called), clear stale roles first by
resetting the roles state (call the roles setter, e.g., setRoles([]) or
equivalent) before performing the fetch; also ensure on fetch failure you leave
roles cleared (or explicitly reset them in the error path) so old role IDs
aren't selectable. Update the handler that invokes setLoading and setError (the
role-loading function) to reset roles at start and on error.

---

Outside diff comments:
In `@web/package.json`:
- Around line 16-29: The project mixes umbrella "radix-ui" and scoped packages
like "@radix-ui/react-slot" (e.g., importing Dialog from "radix-ui" but Slot
from "@radix-ui/react-slot"); pick one approach and consolidate dependencies in
package.json by removing the duplicates (either keep "radix-ui" and remove all
"@radix-ui/*" entries, or remove "radix-ui" and keep the individual
"@radix-ui/react-*" packages), then update all import sites (e.g., change
imports of Slot, Dialog, etc.) to the chosen source and run a fresh install to
ensure lockfile consistency.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c4856c and a2f4719.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • web/package.json
  • web/src/app/layout.tsx
  • web/src/components/dashboard/config-sections/AiSection.tsx
  • web/src/components/dashboard/config-sections/ModerationSection.tsx
  • web/src/components/dashboard/config-sections/NumberField.tsx
  • web/src/components/dashboard/config-sections/TriageSection.tsx
  • web/src/components/dashboard/config-sections/WelcomeSection.tsx
  • web/src/components/layout/header.tsx
  • web/src/components/providers.tsx
  • web/src/components/theme-provider.tsx
  • web/src/components/theme-toggle.tsx
  • web/src/components/ui/channel-selector.tsx
  • web/src/components/ui/checkbox.tsx
  • web/src/components/ui/command.tsx
  • web/src/components/ui/dialog.tsx
  • web/src/components/ui/form.tsx
  • web/src/components/ui/popover.tsx
  • web/src/components/ui/role-selector.tsx
  • web/src/components/ui/switch.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). (2)
  • GitHub Check: Greptile Review
  • GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,mjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,mjs,ts,tsx}: Always use semicolons
Use single quotes (enforced by Biome)
Use 2-space indentation (enforced by Biome)

Files:

  • web/src/components/layout/header.tsx
  • web/src/components/ui/checkbox.tsx
  • web/src/components/dashboard/config-sections/NumberField.tsx
  • web/src/components/theme-provider.tsx
  • web/src/components/ui/switch.tsx
  • web/src/components/dashboard/config-sections/TriageSection.tsx
  • web/src/components/ui/role-selector.tsx
  • web/src/components/theme-toggle.tsx
  • web/src/components/ui/popover.tsx
  • web/src/components/dashboard/config-sections/ModerationSection.tsx
  • web/src/components/ui/dialog.tsx
  • web/src/components/ui/command.tsx
  • web/src/components/dashboard/config-sections/AiSection.tsx
  • web/src/components/dashboard/config-sections/WelcomeSection.tsx
  • web/src/app/layout.tsx
  • web/src/components/ui/form.tsx
  • web/src/components/ui/channel-selector.tsx
  • web/src/components/providers.tsx
web/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Web/React components in web/src/components/ must follow TypeScript conventions and export React components with proper prop types

Files:

  • web/src/components/layout/header.tsx
  • web/src/components/ui/checkbox.tsx
  • web/src/components/dashboard/config-sections/NumberField.tsx
  • web/src/components/theme-provider.tsx
  • web/src/components/ui/switch.tsx
  • web/src/components/dashboard/config-sections/TriageSection.tsx
  • web/src/components/ui/role-selector.tsx
  • web/src/components/theme-toggle.tsx
  • web/src/components/ui/popover.tsx
  • web/src/components/dashboard/config-sections/ModerationSection.tsx
  • web/src/components/ui/dialog.tsx
  • web/src/components/ui/command.tsx
  • web/src/components/dashboard/config-sections/AiSection.tsx
  • web/src/components/dashboard/config-sections/WelcomeSection.tsx
  • web/src/components/ui/form.tsx
  • web/src/components/ui/channel-selector.tsx
  • web/src/components/providers.tsx
🧠 Learnings (4)
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to web/src/components/**/*.{ts,tsx} : Web/React components in `web/src/components/` must follow TypeScript conventions and export React components with proper prop types

Applied to files:

  • web/src/components/ui/checkbox.tsx
  • web/src/components/ui/switch.tsx
  • web/src/components/ui/role-selector.tsx
  • web/src/components/theme-toggle.tsx
  • web/src/components/ui/popover.tsx
  • web/src/components/ui/command.tsx
  • web/src/components/dashboard/config-sections/WelcomeSection.tsx
  • web/src/components/ui/form.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to src/modules/{triage,cli-process}.{js,jsx,mjs} : Split triage evaluation: two-step flow where Haiku classifies (cheap, ~80% are 'ignore'), then Sonnet responds only when needed; CLIProcess wraps the `claude` CLI binary with token-based recycling (default 20k accumulated tokens)

Applied to files:

  • web/src/components/dashboard/config-sections/TriageSection.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to src/modules/triage.{js,jsx,mjs} : Triage budget limits: `classifyBudget` caps Haiku classifier spend; `respondBudget` caps Sonnet responder spend per call; if exceeded, CLI returns error result (`is_error: true`); monitor `total_cost_usd` in logs

Applied to files:

  • web/src/components/dashboard/config-sections/TriageSection.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to src/commands/*.{js,jsx,mjs} : Slash command files must export `data` (SlashCommandBuilder) and `execute(interaction)` function; optionally export `adminOnly = true` for mod-only commands

Applied to files:

  • web/src/components/ui/command.tsx
🧬 Code graph analysis (9)
web/src/components/layout/header.tsx (1)
web/src/components/theme-toggle.tsx (1)
  • ThemeToggle (20-60)
web/src/components/ui/checkbox.tsx (1)
web/src/lib/utils.ts (1)
  • cn (4-6)
web/src/components/ui/switch.tsx (1)
web/src/lib/utils.ts (1)
  • cn (4-6)
web/src/components/ui/role-selector.tsx (5)
web/src/lib/utils.ts (1)
  • cn (4-6)
web/src/components/ui/popover.tsx (3)
  • Popover (82-82)
  • PopoverTrigger (83-83)
  • PopoverContent (84-84)
web/src/components/ui/button.tsx (1)
  • Button (50-50)
web/src/components/ui/command.tsx (6)
  • Command (175-175)
  • CommandInput (177-177)
  • CommandList (178-178)
  • CommandEmpty (179-179)
  • CommandGroup (180-180)
  • CommandItem (181-181)
web/src/components/ui/badge.tsx (1)
  • Badge (46-46)
web/src/components/theme-toggle.tsx (2)
web/src/components/ui/button.tsx (1)
  • Button (50-50)
web/src/components/ui/dropdown-menu.tsx (4)
  • DropdownMenu (75-75)
  • DropdownMenuTrigger (76-76)
  • DropdownMenuContent (77-77)
  • DropdownMenuItem (78-78)
web/src/components/ui/popover.tsx (1)
web/src/lib/utils.ts (1)
  • cn (4-6)
web/src/components/ui/command.tsx (2)
web/src/lib/utils.ts (1)
  • cn (4-6)
web/src/components/ui/dialog.tsx (5)
  • Dialog (148-148)
  • DialogHeader (153-153)
  • DialogTitle (156-156)
  • DialogDescription (151-151)
  • DialogContent (150-150)
web/src/components/dashboard/config-sections/WelcomeSection.tsx (4)
web/src/components/ui/switch.tsx (1)
  • Switch (35-35)
web/src/components/ui/card.tsx (2)
  • CardHeader (55-55)
  • CardContent (55-55)
web/src/components/ui/label.tsx (1)
  • Label (21-21)
web/src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
web/src/components/providers.tsx (1)
web/src/components/theme-provider.tsx (1)
  • ThemeProvider (12-14)
🔇 Additional comments (14)
web/src/app/layout.tsx (1)

14-21: LGTM!

The removal of the hardcoded dark class is correct since theming is now dynamically managed by the ThemeProvider. The suppressHydrationWarning is appropriately retained as it's required by next-themes to prevent hydration mismatches when the theme class is applied client-side.

web/src/components/theme-provider.tsx (1)

1-14: LGTM!

Clean wrapper component with proper TypeScript typing and helpful documentation. The pattern of re-exporting next-themes through a thin wrapper allows for future customization if needed.

web/src/components/layout/header.tsx (1)

19-19: LGTM!

The ThemeToggle is cleanly integrated into the header's right-aligned controls. The placement before authentication controls is logical and the import path is correct.

Also applies to: 50-50

web/src/components/ui/switch.tsx (1)

8-35: LGTM!

Well-structured Switch component with proper TypeScript typing using intersection types. The size variants via data-size attribute and the thumb positioning with calc(100%-2px) for the checked state are implemented correctly. Dark mode variants are appropriately included.

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

38-97: Looks good: control migration is consistent and accessible.

Switch/Label/Input wiring is clean, onCheckedChange usage is correct, and labeled control IDs are properly connected.

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

30-50: LGTM on the Welcome section migration.

The new Switch and labeled Textarea integration is clean, with proper id/htmlFor linkage and unchanged behavior.

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

35-162: Looks good: triage controls are consistently migrated.

Switch handlers, input bindings, and labeled IDs are all aligned with the updated component pattern.

web/src/components/ui/dialog.tsx (2)

50-82: LGTM on DialogContent composition.

The component correctly composes Portal, Overlay, and Content with proper accessibility (sr-only close button label). The showCloseButton prop provides good flexibility.


94-119: LGTM!

The flex-col-reverse / sm:flex-row pattern with children rendered before the close button achieves the standard dialog footer layout where the primary action appears on the right.

web/src/components/ui/command.tsx (1)

63-172: LGTM!

The Command subcomponents are well-structured with consistent patterns: proper TypeScript typing, data-slot attributes for styling hooks, and cn() for className composition.

web/src/components/ui/channel-selector.tsx (4)

1-38: LGTM on imports and interface definition.

Proper use of single quotes, semicolons, and well-defined TypeScript interface for DiscordChannel.


40-144: LGTM on channel type utilities.

Good use of as const for type safety, comprehensive switch statements with sensible defaults, and clear separation of concerns between icon rendering, labeling, and filtering.


231-254: LGTM on selection logic.

Proper use of useCallback with correct dependencies, and useMemo for derived state. The toggle and remove logic handles max selections correctly.


368-369: LGTM on exports.

Good public API surface—exports the component, type constants, utility functions, and the filter type for consumers.

@greptile-apps
Copy link

greptile-apps bot commented Mar 1, 2026

Greptile Summary

This PR completes the shadcn/ui implementation with dark mode support and fixes issues from previous review feedback.

Key Changes:

  • Fixed race condition in role-selector.tsx and channel-selector.tsx by adding stale request guards (lines 96-103 and 215-222 respectively)
  • Added matchMedia polyfill in test setup with proper existence check and configurable flag for next-themes compatibility
  • Introduced ThemedToaster component to sync Sonner toast notifications with the active theme
  • Updated form component to use @radix-ui/react-slot individual package and simplified from Slot.Root to Slot
  • Applied Biome auto-fixes: type-only imports for React, import organization, and formatting improvements

Technical Quality:

  • Proper error handling with try/catch blocks for all API calls
  • Correct use of AbortController for request cancellation with cleanup in useEffect
  • Type-safe API response validation using type guards
  • Good accessibility with ARIA attributes (aria-expanded, aria-describedby, aria-invalid, aria-label)
  • Security: uses encodeURIComponent for URL parameters

No Issues Found:
All changes follow project conventions (ESM, conventional commits, error handling). The stale request guard fix properly addresses the race condition identified in the previous review thread.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • All changes are well-implemented fixes and improvements. The critical race condition from the previous review has been properly addressed with stale request guards. Code follows best practices for error handling, type safety, and accessibility. No console logging, proper use of types, and correct implementation of new features.
  • No files require special attention

Important Files Changed

Filename Overview
web/src/components/ui/role-selector.tsx Added stale request guards to prevent state updates from aborted/stale API calls - fixes race condition from previous review
web/src/components/ui/channel-selector.tsx Added stale request guards matching role-selector pattern, minor formatting improvements
web/tests/setup.ts Added matchMedia polyfill with existence check and configurable flag for next-themes testing compatibility
web/src/components/providers.tsx Added ThemedToaster component to sync Sonner toast theme with next-themes resolved theme
web/src/components/ui/form.tsx Updated Slot import to individual package, changed from Slot.Root to Slot, reorganized imports with type-only imports
web/package.json Added next-themes and react-hook-form dependencies for theme support and form validation

Sequence Diagram

sequenceDiagram
    participant User
    participant RoleSelector
    participant ChannelSelector
    participant ThemeToggle
    participant API
    participant ThemeProvider

    User->>ThemeToggle: Click theme toggle
    ThemeToggle->>ThemeProvider: setTheme('dark'/'light'/'system')
    ThemeProvider->>ThemedToaster: Update toast theme
    
    User->>RoleSelector: Open role selector
    RoleSelector->>RoleSelector: Create AbortController
    RoleSelector->>API: GET /api/guilds/{guildId}/roles
    
    alt Request completes
        API-->>RoleSelector: Return roles array
        RoleSelector->>RoleSelector: Validate with type guards
        RoleSelector->>RoleSelector: Check if controller is current
        RoleSelector->>RoleSelector: setRoles() only if not stale
    else Request aborted (user closes/reopens)
        RoleSelector->>RoleSelector: Abort previous request
        RoleSelector->>API: New GET request with new controller
        API-->>RoleSelector: AbortError on old request
        RoleSelector->>RoleSelector: Skip state update (stale guard)
    end
    
    User->>ChannelSelector: Open channel selector
    ChannelSelector->>ChannelSelector: Create AbortController
    ChannelSelector->>API: GET /api/guilds/{guildId}/channels
    
    alt Request completes
        API-->>ChannelSelector: Return channels array
        ChannelSelector->>ChannelSelector: Validate & sort (categories last)
        ChannelSelector->>ChannelSelector: Check if controller is current
        ChannelSelector->>ChannelSelector: setChannels() only if not stale
    else 401 Unauthorized
        API-->>ChannelSelector: 401 status
        ChannelSelector->>User: Redirect to /login
    end
Loading

Last reviewed commit: a8742e0

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

20 files reviewed, 9 comments

Edit Code Review Agent Settings | Greptile

@BillChirico
Copy link
Collaborator Author

@claude review

@claude
Copy link

claude bot commented Mar 1, 2026

Claude Code is working…

I'll analyze this and get back to you.

View job run

Pip Build added 2 commits March 1, 2026 00:33
…move unused theme var

- FormFieldContext/FormItemContext: initialize to null, move null checks
  before context is accessed so useFormField() throws early if used outside
  its providers
- PopoverTitle: render <h2> instead of <div> for semantic correctness
- role-selector: clear stale roles (setRoles([])) before fetching new guild's
  role list to prevent stale IDs from being applied
- theme-toggle: remove unused 'theme' destructure from useTheme()
…variant in command.tsx

- DialogHeader (with sr-only title/description) must be a descendant of
  DialogContent for Radix Dialog accessibility (title/description bound to
  the dialog role via aria-labelledby/aria-describedby)
- **:data-[slot=command-input-wrapper]:h-12 was an invalid Tailwind variant;
  corrected to *:data-[slot=command-input-wrapper]:h-12
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

🤖 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/theme-toggle.tsx`:
- Around line 47-50: Reduce duplicated JSX in the theme menu by creating a typed
array of options (e.g., const themeOptions: {label: string, value: ThemeType}[]
= [...]) and map over it to render DropdownMenuItem entries; replace the three
hard-coded DropdownMenuItem lines inside DropdownMenuContent with
themeOptions.map(o => <DropdownMenuItem onClick={() =>
setTheme(o.value)}>{o.label}</DropdownMenuItem>), ensuring types align with the
setTheme parameter and keeping DropdownMenuContent and DropdownMenuItem
unchanged.

In `@web/src/components/ui/channel-selector.tsx`:
- Around line 174-176: Before starting a new guild channel fetch, clear any
stale channel state so old options can't be selected: call the channel-clearing
setters (e.g. setChannels([]) and, if present, setSelectedChannel(null) or
setChannelOptions([])) immediately before setLoading(true) and setError(null) in
the fetch flow inside channel-selector.tsx; this ensures the UI shows no old
channel options while the new request is in flight or if it fails.
- Around line 255-258: selectedChannels currently only maps IDs present in
channels, causing missing selected IDs to vanish; update the React.useMemo that
computes selectedChannels to also include fallback entries for any ids in
selected that don't exist in channels (e.g., create objects for missing ids with
the same id and a placeholder label like "(deleted)" and ensure they carry the
same removable metadata/handlers). Locate the selectedChannels computation in
this file (the React.useMemo referencing channels and selected) and merge
channels.filter(...) with selected.filter(id => !found).map(...) so the UI can
render and remove missing selections consistently.

In `@web/src/components/ui/role-selector.tsx`:
- Around line 132-135: selectedRoles only includes fetched roles so any id in
selected that is missing becomes invisible in the chip list and cannot be
removed; change the selectedRoles computation (used in the RoleSelector
component) to map over selected ids and for each id return the matching role
from roles or a placeholder object { id, name: 'Unknown role' } (or similar)
when not found, so the UI shows removable chips for stale IDs while preserving
existing role objects for found ids.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2f4719 and e6141b2.

📒 Files selected for processing (10)
  • web/src/components/dashboard/config-sections/AiSection.tsx
  • web/src/components/dashboard/config-sections/NumberField.tsx
  • web/src/components/theme-toggle.tsx
  • web/src/components/ui/channel-selector.tsx
  • web/src/components/ui/checkbox.tsx
  • web/src/components/ui/command.tsx
  • web/src/components/ui/dialog.tsx
  • web/src/components/ui/form.tsx
  • web/src/components/ui/popover.tsx
  • web/src/components/ui/role-selector.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,mjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,mjs,ts,tsx}: Always use semicolons
Use single quotes (enforced by Biome)
Use 2-space indentation (enforced by Biome)

Files:

  • web/src/components/dashboard/config-sections/AiSection.tsx
  • web/src/components/dashboard/config-sections/NumberField.tsx
  • web/src/components/ui/role-selector.tsx
  • web/src/components/ui/popover.tsx
  • web/src/components/ui/form.tsx
  • web/src/components/ui/command.tsx
  • web/src/components/theme-toggle.tsx
  • web/src/components/ui/channel-selector.tsx
  • web/src/components/ui/dialog.tsx
  • web/src/components/ui/checkbox.tsx
web/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Web/React components in web/src/components/ must follow TypeScript conventions and export React components with proper prop types

Files:

  • web/src/components/dashboard/config-sections/AiSection.tsx
  • web/src/components/dashboard/config-sections/NumberField.tsx
  • web/src/components/ui/role-selector.tsx
  • web/src/components/ui/popover.tsx
  • web/src/components/ui/form.tsx
  • web/src/components/ui/command.tsx
  • web/src/components/theme-toggle.tsx
  • web/src/components/ui/channel-selector.tsx
  • web/src/components/ui/dialog.tsx
  • web/src/components/ui/checkbox.tsx
🧠 Learnings (7)
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to web/src/components/**/*.{ts,tsx} : Web/React components in `web/src/components/` must follow TypeScript conventions and export React components with proper prop types

Applied to files:

  • web/src/components/ui/role-selector.tsx
  • web/src/components/ui/popover.tsx
  • web/src/components/ui/form.tsx
  • web/src/components/ui/command.tsx
  • web/src/components/theme-toggle.tsx
  • web/src/components/ui/dialog.tsx
  • web/src/components/ui/checkbox.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to src/commands/*{ban,kick,timeout,warn,mute,tempban}*.{js,jsx,mjs} : Always call `checkHierarchy(moderator, target)` before executing moderation actions to prevent moderating users with equal or higher roles

Applied to files:

  • web/src/components/ui/role-selector.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to **/*.{js,jsx,mjs,ts,tsx} : Use single quotes (enforced by Biome)

Applied to files:

  • web/src/components/ui/popover.tsx
  • web/src/components/ui/form.tsx
  • web/src/components/ui/command.tsx
  • web/src/components/ui/dialog.tsx
  • web/src/components/ui/checkbox.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to **/*.{js,jsx,mjs,ts,tsx} : Use 2-space indentation (enforced by Biome)

Applied to files:

  • web/src/components/ui/popover.tsx
  • web/src/components/ui/form.tsx
  • web/src/components/ui/command.tsx
  • web/src/components/ui/dialog.tsx
  • web/src/components/ui/checkbox.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to **/*.{js,jsx,mjs,ts,tsx} : Always use semicolons

Applied to files:

  • web/src/components/ui/popover.tsx
  • web/src/components/ui/form.tsx
  • web/src/components/ui/command.tsx
  • web/src/components/ui/dialog.tsx
  • web/src/components/ui/checkbox.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to src/commands/*.{js,jsx,mjs} : Slash command files must export `data` (SlashCommandBuilder) and `execute(interaction)` function; optionally export `adminOnly = true` for mod-only commands

Applied to files:

  • web/src/components/ui/command.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to web/src/app/api/**/*.{ts,tsx} : Next.js API routes in `web/src/app/api/` must proxy requests to the bot API with parameter allowlisting for security

Applied to files:

  • web/src/components/ui/channel-selector.tsx
🧬 Code graph analysis (9)
web/src/components/dashboard/config-sections/AiSection.tsx (2)
web/src/components/ui/switch.tsx (1)
  • Switch (35-35)
web/src/components/ui/label.tsx (1)
  • Label (21-21)
web/src/components/dashboard/config-sections/NumberField.tsx (2)
web/src/components/ui/label.tsx (1)
  • Label (21-21)
web/src/components/ui/input.tsx (1)
  • Input (21-21)
web/src/components/ui/popover.tsx (1)
web/src/lib/utils.ts (1)
  • cn (4-6)
web/src/components/ui/form.tsx (2)
web/src/lib/utils.ts (1)
  • cn (4-6)
web/src/components/ui/label.tsx (1)
  • Label (21-21)
web/src/components/ui/command.tsx (2)
web/src/lib/utils.ts (1)
  • cn (4-6)
web/src/components/ui/dialog.tsx (5)
  • Dialog (134-134)
  • DialogContent (136-136)
  • DialogHeader (139-139)
  • DialogTitle (142-142)
  • DialogDescription (137-137)
web/src/components/theme-toggle.tsx (2)
web/src/components/ui/button.tsx (1)
  • Button (50-50)
web/src/components/ui/dropdown-menu.tsx (4)
  • DropdownMenu (75-75)
  • DropdownMenuTrigger (76-76)
  • DropdownMenuContent (77-77)
  • DropdownMenuItem (78-78)
web/src/components/ui/channel-selector.tsx (2)
src/api/utils/webhook.js (1)
  • controller (29-29)
web/src/lib/utils.ts (1)
  • cn (4-6)
web/src/components/ui/dialog.tsx (2)
web/src/lib/utils.ts (1)
  • cn (4-6)
web/src/components/ui/button.tsx (1)
  • Button (50-50)
web/src/components/ui/checkbox.tsx (1)
web/src/lib/utils.ts (1)
  • cn (4-6)
🔇 Additional comments (12)
web/src/components/dashboard/config-sections/AiSection.tsx (2)

5-6: Imports are correctly aligned with the migrated UI controls.

Label and Switch are cleanly imported and match the updated component usage.


32-43: Accessibility linkage for the AI toggle is correctly implemented.

The Switch/Label pairing is now properly bound through id and htmlFor, and the control keeps an explicit accessible name.

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

18-23: Accessibility wiring is correctly implemented.

Line 18 and Lines 21-23 correctly pair Label and Input via htmlFor/id using useId, which resolves the prior labeling gap cleanly.

web/src/components/theme-toggle.tsx (2)

1-13: Conventions and exported component structure look good.

Imports and formatting follow project rules, and this file cleanly exports a TSX component with an appropriate signature.

As per coding guidelines: "**/*.{js,jsx,mjs,ts,tsx}: Always use semicolons / Use single quotes / Use 2-space indentation" and "web/src/components/**/*.{ts,tsx}: ... export React components with proper prop types".


24-36: Hydration mismatch prevention is correctly handled.

The mounted gate is a solid approach to avoid SSR/client theme rendering mismatch in this toggle UI.

web/src/components/ui/checkbox.tsx (1)

9-29: Clean typed wrapper and prop forwarding.

Checkbox is correctly typed and composes classes/props in a maintainable way.

web/src/components/ui/popover.tsx (1)

16-74: Popover composition and semantics look solid.

The typed wrappers and slot structure are consistent and accessible.

web/src/components/ui/role-selector.tsx (1)

63-66: Good guard against cross-guild stale role options.

Resetting roles before fetch prevents old guild roles from being selectable during reload.

web/src/components/ui/form.tsx (1)

28-68: Context initialization and guard logic are correctly implemented.

The nullable contexts plus explicit runtime errors in useFormField are a solid safety improvement.

web/src/components/ui/command.tsx (1)

43-55: Dialog accessibility structure is fixed and looks correct.

Keeping DialogTitle/DialogDescription inside DialogContent is the right composition.

web/src/components/ui/channel-selector.tsx (1)

344-350: Accessible name on the remove-channel button is in place.

Nice fix for screen-reader clarity on icon-only controls.

web/src/components/ui/dialog.tsx (1)

42-74: Dialog content composition is cohesive and reusable.

DialogContent + optional close behavior are implemented cleanly with consistent slots and props.

Copilot AI review requested due to automatic review settings March 1, 2026 05:49
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: 1

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

Inline comments:
In `@web/src/components/ui/channel-selector.tsx`:
- Around line 207-223: The fetchedChannels result and error handlers must be
guarded against stale in-flight requests: when you create the local controller
(controller) and later setChannels(sortedChannels) or setError(...), first
verify that abortControllerRef.current === controller so you don't apply results
from an outdated fetch; move or gate the sorting/setting logic so sortedChannels
is only applied when the controller matches, and likewise only call setError if
the controller is still current; keep the existing abort/AbortError handling and
the final check for setLoading unchanged.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6141b2 and 2eaed47.

📒 Files selected for processing (2)
  • web/src/components/ui/channel-selector.tsx
  • web/src/components/ui/role-selector.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). (2)
  • GitHub Check: Greptile Review
  • GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,mjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,mjs,ts,tsx}: Always use semicolons
Use single quotes (enforced by Biome)
Use 2-space indentation (enforced by Biome)

Files:

  • web/src/components/ui/role-selector.tsx
  • web/src/components/ui/channel-selector.tsx
web/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Web/React components in web/src/components/ must follow TypeScript conventions and export React components with proper prop types

Files:

  • web/src/components/ui/role-selector.tsx
  • web/src/components/ui/channel-selector.tsx
🧠 Learnings (5)
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to web/src/components/**/*.{ts,tsx} : Web/React components in `web/src/components/` must follow TypeScript conventions and export React components with proper prop types

Applied to files:

  • web/src/components/ui/role-selector.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to src/commands/*{ban,kick,timeout,warn,mute,tempban}*.{js,jsx,mjs} : Always call `checkHierarchy(moderator, target)` before executing moderation actions to prevent moderating users with equal or higher roles

Applied to files:

  • web/src/components/ui/role-selector.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to web/src/app/api/**/*.{ts,tsx} : Next.js API routes in `web/src/app/api/` must proxy requests to the bot API with parameter allowlisting for security

Applied to files:

  • web/src/components/ui/channel-selector.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to src/modules/triage.{js,jsx,mjs} : Channel buffer eviction: triage tracks at most 100 channels; channels inactive for 30 minutes are evicted; if a channel is evicted mid-conversation, the buffer is lost and evaluation restarts from scratch

Applied to files:

  • web/src/components/ui/channel-selector.tsx
📚 Learning: 2026-03-01T00:56:12.261Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T00:56:12.261Z
Learning: Applies to web/src/stores/**/*.{ts,tsx} : Zustand stores should use the `create` factory and handle fetch-on-demand pattern with per-guild caching

Applied to files:

  • web/src/components/ui/channel-selector.tsx
🧬 Code graph analysis (1)
web/src/components/ui/channel-selector.tsx (9)
src/logger.js (1)
  • error (244-246)
src/api/utils/webhook.js (1)
  • controller (29-29)
src/modules/config.js (1)
  • err (94-94)
web/src/lib/utils.ts (1)
  • cn (4-6)
web/src/components/ui/popover.tsx (3)
  • Popover (67-67)
  • PopoverTrigger (68-68)
  • PopoverContent (69-69)
web/src/components/ui/button.tsx (1)
  • Button (50-50)
src/modules/reviewHandler.js (1)
  • disabled (132-132)
web/src/components/ui/command.tsx (6)
  • Command (152-152)
  • CommandInput (154-154)
  • CommandList (155-155)
  • CommandEmpty (156-156)
  • CommandGroup (157-157)
  • CommandItem (158-158)
web/src/components/ui/badge.tsx (1)
  • Badge (46-46)
🔇 Additional comments (8)
web/src/components/ui/channel-selector.tsx (1)

261-376: Nice recovery path for missing selected IDs.

The unknownSelectedIds fallback chips keep orphaned IDs visible and removable, which prevents stuck selections when API results are incomplete or stale.

web/src/components/ui/role-selector.tsx (7)

1-33: Well-structured imports and type definitions.

The DiscordRole interface is properly exported for external consumers, and RoleSelectorProps correctly defines all required and optional props with appropriate TypeScript types.


35-38: LGTM!

The helper correctly handles Discord's color integer format. Returning null for 0 (the default/no-color value) allows call sites to apply the fallback #99aab5.


55-112: Robust fetch lifecycle with proper cleanup.

Good implementation:

  • AbortController prevents race conditions and ensures cleanup on unmount.
  • Stale roles are cleared before fetching (line 64).
  • Response is validated at runtime with type narrowing.
  • The controller identity check at line 101 prevents stale state updates.

114-130: LGTM!

Toggle and remove logic is correct with properly specified dependencies.


132-140: Past review concern addressed.

The unknownSelectedIds memo correctly identifies selected IDs not present in the fetched roles, enabling removable chips for stale/unknown IDs. Note that during the brief loading period, all selected IDs will temporarily appear as "Unknown role" until the fetch completes—this is acceptable behavior that self-recovers.


144-213: Well-implemented accessible combobox.

The Popover/Command integration provides good UX:

  • Clear loading and error states within the dropdown.
  • Proper role="combobox" and aria-expanded on the trigger.
  • Visual disabled state for items when max selection is reached.

215-276: Accessible badge rendering with proper aria-labels.

Both known and unknown role badges have accessible remove buttons with descriptive labels. The unknownSelectedIds rendering ensures stale/missing role IDs can be removed by the user.

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.

Pull request overview

Copilot reviewed 19 out of 20 changed files in this pull request and generated 7 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 1, 2026
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.

Pull request overview

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

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@greptile-apps
Copy link

greptile-apps bot commented Mar 1, 2026

Additional Comments (1)

web/package.json, line 34
next-themes is imported in multiple files (theme-provider.tsx, providers.tsx, theme-toggle.tsx) but missing from dependencies

    "tailwind-merge": "^3.5.0",
    "next-themes": "^0.4.6"

next-themes is imported in theme-provider.tsx, providers.tsx, and
theme-toggle.tsx but was missing from web/package.json dependencies,
causing module resolution failures at build time.

Fixes reviewer comments on missing next-themes dep.
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 1, 2026
form.tsx imports Controller, FormProvider, useFormContext, useFormState
from react-hook-form but the package was missing from web/package.json,
causing CI lockfile validation failures.
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.

Pull request overview

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

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Switch to  since LabelPrimitive is only referenced
in type positions (React.ComponentProps<typeof LabelPrimitive.Root>).
This avoids an unnecessary runtime import/bundle side effect with
isolatedModules.

Addresses copilot review suggestion.
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 1, 2026
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.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…urable flag

Addresses copilot review thread: Object.defineProperty unconditionally
redefining window.matchMedia can throw if a future jsdom version provides
a non-configurable implementation. Add typeof guard and configurable: true
to make the setup more robust.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants