feat: shadcn/ui overhaul with dark mode and role/channel selectors#170
feat: shadcn/ui overhaul with dark mode and role/channel selectors#170BillChirico merged 27 commits intomainfrom
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis 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
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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-themesprovider + a header theme toggle, and removes forceddarkclass 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
RoleSelectorandChannelSelectorcombobox 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
Labelhere isn't associated with theInput(nohtmlFor/id), which reduces accessibility and click-to-focus behavior. Consider generating a stable id (e.g.React.useId()) and wiringLabel htmlForto theInput 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.
There was a problem hiding this comment.
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 | 🔵 TrivialConsider consolidating Radix UI dependencies.
The package includes both individual
@radix-ui/*scoped packages (lines 16-20) and the umbrellaradix-uipackage (line 29). The codebase currently imports from both (e.g.,Dialogfromradix-uibutSlotfrom@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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
web/package.jsonweb/src/app/layout.tsxweb/src/components/dashboard/config-sections/AiSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/NumberField.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/layout/header.tsxweb/src/components/providers.tsxweb/src/components/theme-provider.tsxweb/src/components/theme-toggle.tsxweb/src/components/ui/channel-selector.tsxweb/src/components/ui/checkbox.tsxweb/src/components/ui/command.tsxweb/src/components/ui/dialog.tsxweb/src/components/ui/form.tsxweb/src/components/ui/popover.tsxweb/src/components/ui/role-selector.tsxweb/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.tsxweb/src/components/ui/checkbox.tsxweb/src/components/dashboard/config-sections/NumberField.tsxweb/src/components/theme-provider.tsxweb/src/components/ui/switch.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/ui/role-selector.tsxweb/src/components/theme-toggle.tsxweb/src/components/ui/popover.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/ui/dialog.tsxweb/src/components/ui/command.tsxweb/src/components/dashboard/config-sections/AiSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/app/layout.tsxweb/src/components/ui/form.tsxweb/src/components/ui/channel-selector.tsxweb/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.tsxweb/src/components/ui/checkbox.tsxweb/src/components/dashboard/config-sections/NumberField.tsxweb/src/components/theme-provider.tsxweb/src/components/ui/switch.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/ui/role-selector.tsxweb/src/components/theme-toggle.tsxweb/src/components/ui/popover.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/ui/dialog.tsxweb/src/components/ui/command.tsxweb/src/components/dashboard/config-sections/AiSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/ui/form.tsxweb/src/components/ui/channel-selector.tsxweb/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.tsxweb/src/components/ui/switch.tsxweb/src/components/ui/role-selector.tsxweb/src/components/theme-toggle.tsxweb/src/components/ui/popover.tsxweb/src/components/ui/command.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/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
darkclass is correct since theming is now dynamically managed by theThemeProvider. ThesuppressHydrationWarningis appropriately retained as it's required bynext-themesto 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-themesthrough a thin wrapper allows for future customization if needed.web/src/components/layout/header.tsx (1)
19-19: LGTM!The
ThemeToggleis 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-sizeattribute and the thumb positioning withcalc(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/Inputwiring is clean,onCheckedChangeusage 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
Switchand labeledTextareaintegration is clean, with properid/htmlForlinkage and unchanged behavior.web/src/components/dashboard/config-sections/TriageSection.tsx (1)
35-162: Looks good: triage controls are consistently migrated.
Switchhandlers, 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
showCloseButtonprop provides good flexibility.
94-119: LGTM!The
flex-col-reverse/sm:flex-rowpattern 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-slotattributes for styling hooks, andcn()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 constfor 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
useCallbackwith correct dependencies, anduseMemofor 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.
|
| 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
Last reviewed commit: a8742e0
|
@claude review |
|
I'll analyze this and get back to you. |
…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
There was a problem hiding this comment.
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
📒 Files selected for processing (10)
web/src/components/dashboard/config-sections/AiSection.tsxweb/src/components/dashboard/config-sections/NumberField.tsxweb/src/components/theme-toggle.tsxweb/src/components/ui/channel-selector.tsxweb/src/components/ui/checkbox.tsxweb/src/components/ui/command.tsxweb/src/components/ui/dialog.tsxweb/src/components/ui/form.tsxweb/src/components/ui/popover.tsxweb/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.tsxweb/src/components/dashboard/config-sections/NumberField.tsxweb/src/components/ui/role-selector.tsxweb/src/components/ui/popover.tsxweb/src/components/ui/form.tsxweb/src/components/ui/command.tsxweb/src/components/theme-toggle.tsxweb/src/components/ui/channel-selector.tsxweb/src/components/ui/dialog.tsxweb/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.tsxweb/src/components/dashboard/config-sections/NumberField.tsxweb/src/components/ui/role-selector.tsxweb/src/components/ui/popover.tsxweb/src/components/ui/form.tsxweb/src/components/ui/command.tsxweb/src/components/theme-toggle.tsxweb/src/components/ui/channel-selector.tsxweb/src/components/ui/dialog.tsxweb/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.tsxweb/src/components/ui/popover.tsxweb/src/components/ui/form.tsxweb/src/components/ui/command.tsxweb/src/components/theme-toggle.tsxweb/src/components/ui/dialog.tsxweb/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.tsxweb/src/components/ui/form.tsxweb/src/components/ui/command.tsxweb/src/components/ui/dialog.tsxweb/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.tsxweb/src/components/ui/form.tsxweb/src/components/ui/command.tsxweb/src/components/ui/dialog.tsxweb/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.tsxweb/src/components/ui/form.tsxweb/src/components/ui/command.tsxweb/src/components/ui/dialog.tsxweb/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.
LabelandSwitchare cleanly imported and match the updated component usage.
32-43: Accessibility linkage for the AI toggle is correctly implemented.The
Switch/Labelpairing is now properly bound throughidandhtmlFor, 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
LabelandInputviahtmlFor/idusinguseId, 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.
Checkboxis 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
rolesbefore 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
useFormFieldare a solid safety improvement.web/src/components/ui/command.tsx (1)
43-55: Dialog accessibility structure is fixed and looks correct.Keeping
DialogTitle/DialogDescriptioninsideDialogContentis 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
web/src/components/ui/channel-selector.tsxweb/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.tsxweb/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.tsxweb/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
unknownSelectedIdsfallback 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
DiscordRoleinterface is properly exported for external consumers, andRoleSelectorPropscorrectly defines all required and optional props with appropriate TypeScript types.
35-38: LGTM!The helper correctly handles Discord's color integer format. Returning
nullfor0(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
unknownSelectedIdsmemo 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"andaria-expandedon 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
unknownSelectedIdsrendering ensures stale/missing role IDs can be removed by the user.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Additional Comments (1)
|
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.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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

Summary
Complete shadcn/ui design system implementation for the dashboard:
Changes
Theme & Dark Mode
Dashboard Migration
New Components
Files Changed