feat(landing): overhaul landing page with Skeuomorphic 2.0 aesthetic …#390
feat(landing): overhaul landing page with Skeuomorphic 2.0 aesthetic …#390
Conversation
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughRemoved multiple Docker Compose services and migrated numerous dashboard pages/components to centralized stores and an AnalyticsProvider; introduced a material dropdown, StableResponsiveContainer, FloatingSaveIsland, and SettingsTabs; standardized welcome templates to double-brace syntax and updated renderer/tests; large visual/token/style revamp across the web UI. Changes
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Pull request overview
Updates the web UI with a new “Skeuomorphic 2.0” aesthetic across landing and dashboard surfaces, while also introducing new client-side state stores and refactoring dashboard settings/navigation.
Changes:
- Overhauls landing + dashboard UI components (navigation, cards, controls, and interaction styling).
- Adds multiple new Zustand stores for tickets, temp roles, conversations, audit log, and bot health.
- Refactors dashboard settings UX (tabs, config layout + floating save island) and adjusts platform config (CSP, docker-compose, tsconfig).
Reviewed changes
Copilot reviewed 67 out of 67 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| web/tsconfig.json | Adjusts TS compiler settings (notably deprecations + module path config). |
| web/tests/components/landing/footer.test.tsx | Updates landing footer expectations for new copy/brand rendering. |
| web/src/stores/tickets-store.ts | Adds Zustand store for ticket list/stats fetching and paging/filter state. |
| web/src/stores/temp-roles-store.ts | Adds Zustand store for temp roles list and pagination. |
| web/src/stores/members-store.ts | Adds a store-level refresh() helper to re-fetch members with current sort/search. |
| web/src/stores/health-store.ts | Adds Zustand store for bot health fetching + payload validation. |
| web/src/stores/conversations-store.ts | Adds Zustand store for conversation list fetching/filtering. |
| web/src/stores/audit-log-store.ts | Adds Zustand store for audit log fetching + filter state. |
| web/src/contexts/analytics-context.tsx | Introduces analytics context with refresh + CSV/PDF export helpers. |
| web/src/components/ui/theme-toggle.tsx | Restyles theme toggle and switches to radio-group dropdown UX. |
| web/src/components/ui/switch.tsx | Restyles switch component for new aesthetic. |
| web/src/components/ui/role-selector.tsx | Visual redesign of role selector input/popover/list/badges. |
| web/src/components/ui/neo-minimal-footer.tsx | Updates footer brand rendering (icon image + typography). |
| web/src/components/ui/dropdown-menu.tsx | Restyles dropdown menu primitives to match new aesthetic. |
| web/src/components/ui/command.tsx | Restyles command palette primitives and input affordances. |
| web/src/components/ui/channel-selector.tsx | Visual redesign of channel selector input/popover/list/badges. |
| web/src/components/ui/button.tsx | Tweaks outline variant styles for better readability/contrast. |
| web/src/components/layout/sidebar.tsx | Refactors sidebar into grouped navigation + new server selector placement. |
| web/src/components/layout/server-selector.tsx | Restyles server selector and splits “manageable” vs “community” sections. |
| web/src/components/layout/dashboard-shell.tsx | Restructures dashboard layout and injects SettingsTabs + AnalyticsProvider. |
| web/src/components/landing/Pricing.tsx | Minor markup/class adjustments for pricing toggle. |
| web/src/components/landing/Footer.tsx | Overhauls landing footer CTA visuals/copy + premium footer wrapper. |
| web/src/components/landing/FeatureGrid.tsx | Redesigns feature grid with motion/tilt effects and new copy/structure. |
| web/src/components/dashboard/system-prompt-editor.tsx | Redesigns system prompt editor input + counter/progress visuals. |
| web/src/components/dashboard/settings-tabs.tsx | Adds settings tab strip for settings sub-routes. |
| web/src/components/dashboard/restart-history.tsx | Redesigns restart history presentation and skeleton handling. |
| web/src/components/dashboard/moderation-stats.tsx | Redesigns moderation stats cards and breakdown sections. |
| web/src/components/dashboard/member-table.tsx | Updates table container styling and “Load More” control. |
| web/src/components/dashboard/log-viewer.tsx | Redesigns log viewer UI and toolbar behavior/labels. |
| web/src/components/dashboard/log-filters.tsx | Restyles log filters (inputs + button variants/copy). |
| web/src/components/dashboard/health-section.tsx | Refactors health section to use new Zustand store and new visuals. |
| web/src/components/dashboard/health-cards.tsx | Replaces card set with a new StatCard component + progress bars. |
| web/src/components/dashboard/floating-save-island.tsx | Adds a floating “save/discard/undo” island for config edits. |
| web/src/components/dashboard/conversation-replay.tsx | Redesigns conversation replay UI and flagging dialog styles. |
| web/src/components/dashboard/config-workspace/settings-feature-card.tsx | Restyles feature card and adds animated advanced section expansion. |
| web/src/components/dashboard/config-workspace/config-search.tsx | Redesigns config search input + results panel UI. |
| web/src/components/dashboard/config-sections/ChannelModeSection.tsx | Restyles per-channel mode UI including search and mode selector. |
| web/src/components/dashboard/config-layout-shell.tsx | Refactors config layout to remove sidebar grid and add route-guard + floating island. |
| web/src/components/dashboard/config-editor-utils.ts | Updates shared input classes to match new config editor styling. |
| web/src/components/dashboard/config-categories/support-integrations.tsx | Replaces category content with new tabbed layout + ticket/GitHub feed settings UI. |
| web/src/components/dashboard/config-categories/config-landing.tsx | Restyles settings landing category cards. |
| web/src/components/dashboard/config-categories/community-tools.tsx | Replaces category content with new tabbed layout + bot presence settings UI. |
| web/src/app/page.tsx | Restyles landing header/nav + brand icon rendering. |
| web/src/app/dashboard/settings/page.tsx | Changes settings root page to auto-redirect to a default category route. |
| web/src/app/dashboard/moderation/moderation-client.tsx | Removes PageHeader refresh UI and restyles panels/inputs. |
| web/src/app/dashboard/members/members-client.tsx | Removes PageHeader refresh UI and restyles stats + search bar. |
| web/src/app/dashboard/logs/page.tsx | Redesigns logs page into “island” panel layout and adds status indicator. |
| web/src/app/dashboard/conversations/[conversationId]/page.tsx | Restyles conversation detail header/back button + skeleton/error state UI. |
| web/next.config.mjs | Adjusts CSP header to allow unsafe-eval in non-production. |
| docker-compose.yml | Removes bot/web/docs services from compose config, leaving infra volumes/services only. |
|
| Filename | Overview |
|---|---|
| src/modules/welcome.js | Template variable syntax changed from {var} to {{var}} via new renderTemplate utility — breaking change for any existing welcome configs stored with single-brace syntax |
| web/src/components/layout/header.tsx | Massively expanded with per-page refresh handlers for every dashboard store; now unconditionally calls useAnalytics() making it crash outside AnalyticsProvider, which caused the corresponding test to be skipped |
| web/tests/components/layout/header.test.tsx | Entire test suite silently skipped with describe.skip instead of updating setup to include AnalyticsProvider |
| web/src/contexts/analytics-context.tsx | New context extracting analytics state, data fetching, CSV/PDF export from the dashboard component — clean separation of concerns with proper abort handling |
| web/src/lib/discord.server.ts | Adds in-flight request deduplication for concurrent user guild fetches using a module-level Map keyed by SHA-256 token hash; abort handling via waitForPromiseOrAbort is correct |
| web/src/components/landing/Hero.tsx | Replaces typewriter/conversation demo with floating 3D widgets using per-frame mouse tracking and spring physics — visually significant redesign with no logic issues |
| web/src/components/landing/FeatureGrid.tsx | New 3D card tilt effect via per-card mouse tracking; bar chart keys use height value (fragile if heights repeat, but currently unique) |
| web/src/components/layout/dashboard-shell.tsx | GuildDirectoryProvider replaced with AnalyticsProvider; test suite skipped as a result |
| migrations/002_add-audit-logs-user-tag.cjs | Adds user_tag VARCHAR(100) column to audit_logs with IF NOT EXISTS guards; up and down migrations both present and correct |
| src/api/routes/members.js | Reformatting only — multi-arg router.get() call style changed to indented callback style; no logic changes |
Sequence Diagram
sequenceDiagram
participant Client as Browser / Client Component
participant Header as Header (useAnalytics)
participant Provider as AnalyticsProvider
participant API as /api/guilds/:id/analytics
participant Discord as discord.server.ts
Client->>Provider: Mount AnalyticsProvider (DashboardShell)
Provider->>API: fetch analytics on mount / refresh
API-->>Provider: DashboardAnalytics payload
Provider->>Provider: validate with isDashboardAnalyticsPayload()
Provider-->>Header: analytics, refresh, exportCsv, exportPdf via context
Note over Header: useAnalytics() throws if outside Provider
Header->>Provider: setRangePreset / setCustomRange / refresh()
Provider->>API: re-fetch with updated queryString
Note over Discord: fetchUserGuilds in-flight deduplication
Client->>Discord: fetchUserGuilds(accessToken)
Discord->>Discord: hash token, check inFlightUserGuildRequests map
alt no in-flight request
Discord->>Discord: fetchAllUserGuildPages(accessToken)
Discord-->>Client: DiscordGuild[]
Discord->>Discord: cleanup map entry via .finally()
else in-flight request exists
Discord->>Discord: waitForPromiseOrAbort(existingRequest, signal)
Discord-->>Client: DiscordGuild[] shared result
end
Comments Outside Diff (1)
-
src/modules/welcome.js, line 83 (link)Breaking change: existing welcome configs using
{var}syntax will silently stop substituting variablesThe template engine was changed from inline regex replacements to the shared
renderTemplateutility that uses{{var}}syntax. Any guild that has a welcome message stored in the database using the old single-brace format (e.g."Welcome {user} to {server}!") will now see the raw placeholder text in Discord instead of the resolved value — with no error, no warning, and no indication that anything went wrong.The test added in this PR (
'leaves single-brace placeholders as plain text') confirms this is the intended behavior, but there is no migration path for existing stored configs.Before merging, consider either:
- Running a one-time DB migration to rewrite stored welcome templates from
{var}to{{var}}, or - Making
renderTemplateaccept both syntaxes during a transition window.
- Running a one-time DB migration to rewrite stored welcome templates from
Prompt To Fix All With AI
This is a comment left during a code review.
Path: web/tests/components/layout/header.test.tsx
Line: 38
Comment:
**Eleven test suites silently skipped rather than fixed**
`Header`, `DashboardShell`, `LandingPage`, `LoginPage`, `AnalyticsDashboard`, `ConfigEditor integration`, `FeatureGrid`, `Hero`, `Sidebar`, `ThemeToggle`, and one `test.skip` block are all skipped in this PR. The root cause is that `Header` now unconditionally calls `useAnalytics()`, which throws when rendered outside `AnalyticsProvider`, breaking any test that mounts `Header` (or a component that contains it) without the full provider tree.
Skipping rather than fixing means the entire dashboard shell, the header, the sidebar, the landing page, and the login page are no longer covered by automated tests. That's a substantial regression in coverage for safety-critical UI paths.
The correct fix is to wrap affected renders in test helpers with `AnalyticsProvider` (mocked or real), not to skip the suites.
The same issue is present in all of the following files:
- `web/tests/components/layout/dashboard-shell.test.tsx`
- `web/tests/app/landing.test.tsx`
- `web/tests/app/login.test.tsx`
- `web/tests/components/dashboard/analytics-dashboard.test.tsx`
- `web/tests/components/dashboard/config-editor-autosave.test.tsx`
- `web/tests/components/landing/feature-grid.test.tsx`
- `web/tests/components/landing/hero.test.tsx`
- `web/tests/components/layout/sidebar.test.tsx`
- `web/tests/components/layout/theme-toggle.test.tsx`
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/modules/welcome.js
Line: 83
Comment:
**Breaking change: existing welcome configs using `{var}` syntax will silently stop substituting variables**
The template engine was changed from inline regex replacements to the shared `renderTemplate` utility that uses `{{var}}` syntax. Any guild that has a welcome message stored in the database using the old single-brace format (e.g. `"Welcome {user} to {server}!"`) will now see the raw placeholder text in Discord instead of the resolved value — with no error, no warning, and no indication that anything went wrong.
The test added in this PR (`'leaves single-brace placeholders as plain text'`) confirms this is the intended behavior, but there is no migration path for existing stored configs.
Before merging, consider either:
1. Running a one-time DB migration to rewrite stored welcome templates from `{var}` to `{{var}}`, or
2. Making `renderTemplate` accept both syntaxes during a transition window.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: web/src/components/landing/FeatureGrid.tsx
Line: 78-82
Comment:
**Bar height used as React key — fragile if values repeat**
The bar chart maps over an array of height values and uses each height as its React key. If any two entries share the same value, React will see duplicate keys and produce incorrect reconciliation. An index-based key is safer here since this array is static and never reordered.
How can I resolve this? If you propose a fix, please make it concise.Reviews (7): Last reviewed commit: "refactor: reduce dashboard duplication h..." | Re-trigger Greptile
web/src/components/dashboard/config-categories/support-integrations.tsx
Outdated
Show resolved
Hide resolved
|
@claude review |
|
I'll analyze this and get back to you. |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
1 similar comment
✅ Actions performedReview triggered.
|
|
@claude review |
|
Claude finished @BillChirico's task in 3m 3s —— View job PR #390 Code Review
Blocker
The High-Confidence Issues
iconColor.replace('text-', 'bg-')Tailwind does not scan runtime-constructed class names during the build. The progress bar fill color will be absent in production. Use an explicit mapping object keyed on
onValueChange={(v) => v !== 'custom' && setRangePreset(v as any)}No justification comment present. Use
The component uses
Icons use
The back button on the conversation detail page reads "Return to logs" — this is the wrong destination label for a conversations page. Should be "Back to conversations".
return ticketsResult === 'unauthorized' ? 'unauthorized' : 'ok';Returns
The unsaved-changes navigation guard intercepts all
Wrapping the entire dashboard in AGENTS.md ViolationVisual verification not performed AGENTS.md states: "Any visual dashboard or landing page change must be verified with Chrome DevTools MCP before you call it done. Take a screenshot after the change. Check both themes, light and dark… Check responsive behavior…" This PR makes extensive visual changes across landing and dashboard surfaces. There is no evidence of screenshot verification in either theme or at any viewport. This is a required gate per project rules. |
There was a problem hiding this comment.
Actionable comments posted: 46
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (12)
package.json (1)
3-3:⚠️ Potential issue | 🟠 MajorUpdate all outdated dependencies to their latest versions.
The coding guidelines explicitly require: "Use the latest version of ALL package.json dependencies. These should always be upgraded to the latest version before committing."
The following packages are outdated and need to be updated:
Dependencies:
@anthropic-ai/sdk: 0.80.0 → 0.82.0@sentry/node: 10.46.0 → 10.47.0discord.js: 14.25.1 → 14.26.0dotenv: 17.3.1 → 17.4.0mem0ai: 2.4.2 → 2.4.5DevDependencies:
@biomejs/biome: 2.4.9 → 2.4.10@vitest/coverage-v8: 4.1.1 → 4.1.2vitest: 4.1.1 → 4.1.2pnpm (10.33.0), turbo (2.9.3), and the remaining dependencies are already at their latest versions.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` at line 3, Update package.json to bump the listed dependencies to their latest versions: set `@anthropic-ai/sdk` to 0.82.0, `@sentry/node` to 10.47.0, discord.js to 14.26.0, dotenv to 17.4.0, mem0ai to 2.4.5, and update devDependencies `@biomejs/biome` to 2.4.10, `@vitest/coverage-v8` to 4.1.2, and vitest to 4.1.2; ensure the "packageManager" entry remains pnpm@10.33.0, run pnpm install to refresh the lockfile, and commit the updated package.json and lockfile changes.web/src/app/dashboard/temp-roles/page.tsx (1)
69-105:⚠️ Potential issue | 🟡 MinorMissing
fetchinhandleRevokedependency array.The
handleRevokecallback usesfetchfrom the store (line 92) but doesn't include it in the dependency array. This could lead to stale closure issues if thefetchfunction reference changes.🔧 Proposed fix
}, - [guildId, page, router], + [guildId, page, router, fetch], );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/temp-roles/page.tsx` around lines 69 - 105, The handleRevoke callback captures the store fetch function but doesn't list it in the dependency array, risking a stale closure; update the dependency array of handleRevoke to include fetch (the same fetch used in the body where you call fetch(guildId, page)), so that handleRevoke is recreated when fetch changes and continues to reference the latest implementation while keeping existing dependencies [guildId, page, router] and leaving setRevoking/setConfirmRevoke as they are (or add them if you prefer explicitness).web/src/components/dashboard/moderation-stats.tsx (1)
4-4: 🧹 Nitpick | 🔵 TrivialPotentially unused imports.
Card,CardContent,CardHeader, andCardTitleare imported but appear to no longer be used in this file. TheStatCardcomponent and the section wrappers have been refactored to use rawdivandsectionelements instead.#!/bin/bash # Verify if Card components are still used in this file echo "=== Checking usage of Card components ===" rg -n "<Card|CardContent|CardHeader|CardTitle" web/src/components/dashboard/moderation-stats.tsx🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/moderation-stats.tsx` at line 4, Remove the unused UI imports (Card, CardContent, CardHeader, CardTitle) from the top of the file since the component now uses StatCard and raw div/section wrappers; locate the import line that includes those symbols and delete them (or replace the entire import with only needed exports if any other exports from that module are used), then run the repository lint/type checks to ensure no references to those identifiers remain.web/src/components/dashboard/performance-dashboard.tsx (1)
167-198:⚠️ Potential issue | 🟠 MajorDon't let background polls cancel interactive loads.
fetchData(true)abortsabortRef.currentthe same way a foreground refresh does. If the 30s poll fires while a user-triggered load is in flight, it cancels that foreground request, and the aborted request'sfinallycan clearloadingand emitperformance-loading-endout of order. Keep foreground/background requests separate, or skip the background poll while a foreground fetch is active.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/performance-dashboard.tsx` around lines 167 - 198, The background poll (fetchData with bg=true) reuses abortRef and can cancel an in-flight foreground fetch causing loading state/events to be cleared out of order; change fetchData (the useCallback) to keep foreground and background requests separate by either: a) using a separate AbortController store for background polls (e.g., bgAbortRef) so bg calls don't abort abortRef used by interactive calls, or b) early-return when bg is true and abortRef.current is non-null (skip background poll while a foreground fetch is active); ensure you only call setLoading(true)/setError(null)/window.dispatchEvent('performance-loading-start') for foreground (bg=false) and only clear loading/events in that same branch so finally doesn't clear a foreground spinner due to a background abort.web/src/app/dashboard/members/members-client.tsx (1)
149-205:⚠️ Potential issue | 🟠 MajorKeep a retry affordance in this toolbar.
The page still surfaces transient fetch failures, but there is no side-effect-free way to rerun the same request from this UI anymore. After an error, users have to change search/sort state or reload the page. Reintroduce an explicit refresh/retry control here.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/members/members-client.tsx` around lines 149 - 205, The toolbar lacks an explicit retry/refresh control so users can't re-run the member fetch after a transient failure; add a retry button in the compact toolbar (near the Search/Input and clear button) that calls the same fetch/refetch function used to load members (e.g., a prop or hook method like refetch, refetchMembers, fetchMembers, or react-query's refetch) without mutating search/sort state; ensure the button is visible when total === 0 or when an error is present (or always visible) and wired to the existing handlers (use existing setSearch/handleClearSearch context) so it simply re-invokes the network request side-effect-free.web/src/app/dashboard/conversations/[conversationId]/page.tsx (1)
33-37:⚠️ Potential issue | 🟡 MinorMissing UI feedback when
guildIdis not provided.When
!guildId,fetchDetailreturns early after settingloading = false, but no error message or guidance is shown to the user. The page will render with no loading skeleton, no error, and no data—leaving users with an empty state.Proposed fix - add early return with guidance
const fetchDetail = useCallback(async () => { if (!guildId || !conversationId) { setLoading(false); + if (!guildId) setError('No guild selected. Please select a server first.'); return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/conversations/`[conversationId]/page.tsx around lines 33 - 37, fetchDetail currently returns early when !guildId after calling setLoading(false) but doesn't set any user-visible state; update fetchDetail (and related state) so it sets a clear UI error/empty-state flag/message before returning. Specifically, add a call like setError(...) or setMissingGuild(true) inside the !guildId branch (alongside setLoading(false)), and ensure the page render checks that state (e.g., error, missingGuild, or missingContext) to show a friendly guidance banner or empty-state UI explaining the missing guildId and next steps; reference the fetchDetail callback, setLoading, guildId, and conversationId to locate and wire the new state.web/src/app/dashboard/logs/page.tsx (1)
1-20:⚠️ Potential issue | 🟠 MajorRefactor page to export SSR metadata like other dashboard routes.
The matcher entry for
/dashboard/logsexists indashboardTitleMatchers, but the page doesn't exportmetadatabecause it's marked'use client'. In Next.js, metadata can only be exported from Server Components. Restructure this page to use a server component wrapper (export metadata and error boundary) that delegates to a client component for interactive content, matching the pattern in other dashboard pages likemembersandtickets.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/logs/page.tsx` around lines 1 - 20, This page is a client component so it cannot export SSR metadata; refactor by extracting the interactive UI into a client-only component (e.g., rename the current LogsPage to LogsClient or LogsViewer that keeps 'use client' and uses useGuildSelection, useLogStream, sendFilter, clearLogs, LogViewer, LogFilters, HealthSection, cn) and then create a server component wrapper as the default export that exports the metadata and wraps ErrorBoundary (matching other dashboard routes), rendering the client component inside it; ensure the server wrapper file exports the same metadata shape and uses ErrorBoundary for server-side errors while delegating interactive behavior to the client component.web/src/app/login/page.tsx (1)
11-11: 🧹 Nitpick | 🔵 TrivialUnused imports from Card component.
Card,CardContent,CardDescription,CardHeader, andCardTitleare imported but not used after the UI refactor to custom styled elements.🧹 Remove unused imports
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/login/page.tsx` at line 11, The import line brings in Card, CardContent, CardDescription, CardHeader, and CardTitle from '@/components/ui/card' but these components are no longer used in page.tsx after the UI refactor; remove those unused symbols from the import (or delete the entire import statement if nothing else is imported from that module) so there are no unused imports referenced (look for the import that currently lists Card, CardContent, CardDescription, CardHeader, CardTitle).web/src/components/ui/role-selector.tsx (1)
69-76:⚠️ Potential issue | 🟠 MajorReset the eager-fetch cache when
guildIdchanges.
hasFetchedRefsurvives guild switches, so preselected role IDs in the next guild no longer trigger the eager fetch path. The selector then renders every configured role asUnknownuntil the popover is manually opened.Also applies to: 138-138
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/ui/role-selector.tsx` around lines 69 - 76, hasFetchedRef (the eager-fetch cache) is not reset when guildId changes, so preselected role IDs from a new guild won't trigger the eager fetch and render as "Unknown role"; update the effect that depends on guildId/open/selected to clear the cache when the guildId changes by setting hasFetchedRef.current = false (or reinitialize it) before checking needsEagerFetch so that a new guild with preselected IDs will perform the eager fetch path; reference hasFetchedRef, guildId, selected, open and the React.useEffect block around those symbols to locate and modify the logic.web/src/app/dashboard/members/[userId]/page.tsx (1)
236-255:⚠️ Potential issue | 🟠 MajorRefresh the full reputation payload after XP edits.
This only patches
xpandlevellocally. If the adjustment crosses a level boundary,next_level_xpstays stale, so the level badge/progress bars render the wrong fill until the page is reloaded. Please either refetch the member payload here or update the rest ofreputationfrom the mutation response as well.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/members/`[userId]/page.tsx around lines 236 - 255, The local state update only patches xp and level in setData, leaving fields like next_level_xp stale; change the handler after res.json() to either refetch the full member payload (call the existing fetch/mutate/refetch function used elsewhere for the member) or replace the entire reputation object from the mutation response (use result.reputation if present) when calling setData so all reputation fields (xp, level, next_level_xp, etc.) are updated consistently; locate the update in the page handler that uses setData(...) and ensure you use the full payload from result or trigger the member refetch instead of only setting xp/level.web/src/components/layout/server-selector.tsx (1)
26-27:⚠️ Potential issue | 🟡 MinorFix the pluralization helper for irregular nouns.
formatServerCount(memberOnly.length, 'view-only community')renders"view-only communitys"for counts above one. Please pass explicit singular/plural labels here instead of always appending's'.Also applies to: 65-72
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/layout/server-selector.tsx` around lines 26 - 27, The pluralization helper formatServerCount currently appends 's' blindly causing incorrect forms for irregular nouns (e.g., "view-only communitys"); change its signature to accept explicit singular and plural labels (e.g., formatServerCount(count: number, singular: string, plural?: string)) and use plural when count !== 1 (fallback plural = singular + 's' if not provided), then update all call sites (including where memberOnly.length is passed) to provide both singular and plural labels (or rely on fallback) so irregular nouns render correctly.web/src/components/dashboard/config-sections/ChannelModeSection.tsx (1)
40-50: 🧹 Nitpick | 🔵 TrivialRemove unused
isDefaultprop.The
isDefaultprop is declared and received but never used withinModeSelector. Per the summary, the checkmark indicator logic that used it was removed.🧹 Proposed fix
function ModeSelector({ mode, onChange, disabled, - isDefault, }: { mode: ChannelMode; onChange: (mode: ChannelMode) => void; disabled: boolean; - isDefault: boolean; }) {Also update call sites at lines 308-313 and 423-428 to remove the
isDefaultprop.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/ChannelModeSection.tsx` around lines 40 - 50, ModeSelector currently declares and accepts an unused prop isDefault; remove isDefault from the ModeSelector props type and parameter list so the component signature only takes mode, onChange, and disabled, and then update every call site that passes isDefault (where ModeSelector is instantiated) to stop supplying that prop so props match the new signature (search for ModeSelector usages and remove the isDefault prop).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/claude-review.yml:
- Line 52: Replace the floating tag "anthropics/claude-code-action@v1" with an
immutable commit SHA: look up the current commit SHA for the v1 release in the
anthropics/claude-code-action repo (via Releases or the v1 tag), then change the
usage string to "anthropics/claude-code-action@<full-commit-sha>" so the
workflow references a specific immutable commit instead of `@v1`; update the
single line containing uses: anthropics/claude-code-action@v1 to use the full
commit hash.
In `@docker-compose.yml`:
- Around line 42-45: Remove the orphaned Docker volume declaration named
"botdata" from the volumes section in the docker-compose configuration; locate
the volumes block that defines pgdata, redisdata, and botdata and delete the
botdata entry so only active volumes (e.g., pgdata and redisdata) remain, then
save the updated docker-compose.yml.
In `@web/next.config.mjs`:
- Line 30: The CSP template literal that currently appends " 'unsafe-eval'" when
process.env.NODE_ENV !== "production" is too broad; change the condition so "
'unsafe-eval'" is only appended when process.env.NODE_ENV === "development"
(locate the template literal containing `script-src 'self'
'unsafe-inline'${process.env.NODE_ENV !== "production" ? " 'unsafe-eval'" :
""}`) to ensure test/misconfigured environments don't get unsafe-eval enabled.
In `@web/src/app/dashboard/audit-log/page.tsx`:
- Around line 114-123: The onGuildChange handler resets the global store but
leaves local user filter state stale; update onGuildChange to also reset local
filters by calling setUserSearch('') and setDebouncedUserSearch('') (or
appropriate initial value) alongside useAuditLogStore.getState().reset() and
setExpandedRows(new Set()) so subsequent fetches don't use the previous guild's
userId; ensure you update both userSearch and debouncedUserSearch in the
onGuildChange callback (also apply same change where similar logic exists around
lines 135-136).
In `@web/src/app/dashboard/conversations/`[conversationId]/page.tsx:
- Around line 71-80: The back button currently calls router.back() which can
navigate to an unexpected external or unrelated page if the user landed via a
direct URL; replace the implicit navigation by using an explicit route push
(e.g., call router.push('/dashboard/conversations') or a named route) in the
button's onClick handler so the Return to logs button always navigates to the
intended conversations/logs list; update the onClick on the button component
that currently invokes router.back() to call router.push with the correct path
and ensure any needed import/usage of the router instance (router) remains
valid.
In `@web/src/app/dashboard/conversations/conversations-client.tsx`:
- Around line 82-92: The guild channels fetch in the useEffect (triggered by
guildId) lacks stale-response protection so a slow response can overwrite
channels for a newer guild; fix it by creating an AbortController (or a local
requestId) inside the effect, pass controller.signal to window.fetch for
cancellable requests, and on cleanup call controller.abort() (or mark the
requestId as stale) before setting state; update the async block around the
fetch/await and the setChannels call (referencing guildId, setChannels, and the
fetch to /api/guilds/${encodeURIComponent(guildId)}/channels) so you only call
setChannels when the response belongs to the current effect and is not aborted.
- Around line 65-70: onGuildChange currently clears search but leaves the
pending debounced query active, so reset the debounced value when the guild
changes: in onGuildChange (and the analogous handler around the 94-99 region,
e.g., onChannelChange), add logic to clear debouncedSearch (e.g., call
setDebouncedSearch('') or invoke the debounce cancel method) in addition to
setSearch(''), setChannelFilter(''), setPage(1); if your debounce hook exposes a
cancel/flush API call that to prevent the stale delayed fetch from running.
Ensure you update both onGuildChange and the other handler referenced so no
pending debounced query from the previous context triggers a fetch.
- Around line 255-265: The TableRow rendered inside conversations.map (the
interactive row using onClick and router.push with guildId) lacks keyboard and
semantic accessibility; update the TableRow component to include tabIndex={0},
role="link", an aria-label (e.g., deriving from convo.title or convo.id) and add
an onKeyDown handler that triggers the same navigation when Enter or Space is
pressed (mirroring the member-table.tsx pattern) while preserving the existing
onClick and guildId checks.
In `@web/src/app/dashboard/moderation/moderation-client.tsx`:
- Line 129: The section element in moderation-client.tsx uses the class
"overflow-hidden" (see the <section> with className containing "overflow-hidden
rounded-[24px] ..."), which can clip child dropdowns/popovers/tooltips (table
controls); remove or replace "overflow-hidden" with "overflow-visible" on these
panel containers (or move overflow-hidden to an inner clipped element while
keeping the outer panel overflow-visible) so child interactive overlays are not
clipped, and preserve existing styles like rounded-[24px], border and
backdrop-blur on the outer container.
- Around line 169-170: The input currently uses a fixed width class w-[300px]
which can overflow on small screens; update the JSX input element (the
moderation input with placeholder "Discord ID (e.g. 123456...)") to use a fluid
width with a maximum cap by replacing w-[300px] with responsive classes such as
w-full max-w-[300px] (keeping the existing h-10, rounded-xl, border, bg and
padding classes) so it fills available space on narrow viewports but does not
exceed 300px on larger screens.
In `@web/src/app/dashboard/settings/page.tsx`:
- Around line 1-7: Add a missing metadata export and dashboard title matcher:
import and export const metadata = createPageMetadata('Settings') from the
shared helper alongside the existing default SettingsPage (ensure
createPageMetadata is imported), so the SSR entry point participates in the
dashboard title conventions; then add a matcher entry for '/dashboard/settings'
in the dashboardTitleMatchers array (use exact equality for the leaf route
pathname === '/dashboard/settings' and a subtree check
pathname.startsWith('/dashboard/settings/')) in the page-titles module so the
route and its subtree are covered.
In `@web/src/app/dashboard/tickets/`[ticketId]/page.tsx:
- Around line 117-124: Replace the button's direct onClick calling router.back()
with a deterministic handler: implement a small function (e.g., handleBack) that
attempts router.back() first and falls back to router.push(...) to the tickets
list when there is no in-app history; ensure you derive/preserve guildId (from
route params or the ticket data available in this page) and use it in the
fallback path (e.g., push to the tickets list for that guild). Update the
button's onClick to call handleBack instead of calling router.back() directly.
In `@web/src/app/dashboard/tickets/tickets-client.tsx`:
- Line 134: Remove the dead onUnauthorized callback declaration: delete the line
that defines const onUnauthorized = useCallback(() => router.replace('/login'),
[router]) since unauthorized handling is already performed inline in the
fetchTickets effect; search for any other references to onUnauthorized in this
module (none expected) and remove them to avoid unused-symbol warnings.
In `@web/src/components/dashboard/config-categories/ai-automation.tsx`:
- Around line 328-343: The ChannelSelector instances (e.g., the one using id
"ai-automod-flag-channel" with props selected derived from
draftConfig.aiAutoMod.flagChannelId and onChange calling updateAiAutoModField)
must be guarded so they are not rendered or queried until a valid guildId
exists; update the automod and triage pane ChannelSelector usages to check
guildId (or disable them when !guildId) before rendering to prevent passing an
invalid guildId and avoid pointless fetches or failures—apply the same guard
logic used in the AI Chat pane to all ChannelSelector occurrences mentioned
(including the other instances around lines 532-547).
- Around line 47-56: The activeTab state is only initialized once from
availableTabs[0] and stays null if visibleFeatureIds is empty at mount; update
the useEffect logic to also setActiveTab to (availableTabs[0]?.id as
ConfigFeatureId) when activeTab is null and availableTabs has entries, and keep
the existing guard that if activeTab is set but no longer in visibleFeatureIds
then reset it to the first available tab; update the effect to depend on
availableTabs and visibleFeatureIds and call setActiveTab accordingly (use
symbols availableTabs, activeTab, setActiveTab, visibleFeatureIds).
In `@web/src/components/dashboard/config-categories/community-tools.tsx`:
- Around line 12-25: TABS contains synthetic ids and the effect that sets
availableTabs/activeTab (using availableTabs, ConfigFeatureId, activeTab,
visibleFeatureIds) can leave activeTab null when visibleFeatureIds only contains
real feature keys or is populated after mount; update the logic to build
availableTabs by mapping visibleFeatureIds to the corresponding TABS entries
(match real feature keys to TABS.id), and in the effect ensure activeTab is
initialized to the first available tab when activeTab is null (or when
availableTabs becomes non-empty) so the UI renders; adjust the code paths that
compute availableTabs and the effect that sets activeTab to handle
late-populated visibleFeatureIds and to fall back to the first matching TABS
entry.
In `@web/src/components/dashboard/config-categories/config-landing.tsx`:
- Around line 59-61: The heading in the ConfigLanding component uses a hardcoded
utility class "text-white/90" which breaks light-theme accessibility; replace
that hardcoded color class on the <h1> (and the other occurrences around the
same component at the other header block mentioned) with the semantic token
class (e.g., "text-foreground" or your project's equivalent) so the text color
responds to light/dark themes; update the className on the <h1> in ConfigLanding
(and the similar heading instances) to use the semantic token instead of
"text-white/90".
- Line 12: Remove the unused Badge import: the import statement that brings in
Badge (import { Badge } from '@/components/ui/badge') should be deleted from
config-landing.tsx since the component is no longer referenced; search for any
remaining usages of Badge in the file (e.g., inside the ConfigLanding component)
and remove or replace them if found, then run the linter/TypeScript check to
confirm no unused-import errors remain.
In `@web/src/components/dashboard/config-categories/moderation-safety.tsx`:
- Around line 61-70: The effect that maintains activeTab only handles the case
where activeTab is non-null and becomes invalid; change the useEffect for
activeTab so it also initializes activeTab when tabs become visible by checking
if availableTabs has an entry and (activeTab === null ||
!visibleFeatureIds.has(activeTab)), then call setActiveTab((availableTabs[0]?.id
as ConfigFeatureId) ?? null); reference the variables availableTabs, activeTab,
setActiveTab, visibleFeatureIds and TABS when making this change and keep the
dependency array accurate.
In `@web/src/components/dashboard/config-categories/onboarding-growth.tsx`:
- Around line 21-52: The TABS array contains a synthetic id ('tldr-afk') that
won't match real feature ids, and the current logic using
visibleFeatureIds.has(t.id as ConfigFeatureId) plus activeTab only self-healing
when non-null can leave availableTabs empty or activeTab null after visibility
updates; update filtering to map synthetic tabs to their underlying feature ids
(e.g., treat 'tldr-afk' as matching 'tldr' OR 'afk') when computing
availableTabs and ensure activeTab is initialized/fallbacked whenever
availableTabs changes (in the components that reference TABS, availableTabs,
visibleFeatureIds and activeTab) so the UI picks the first available tab if the
previous activeTab is no longer visible.
In `@web/src/components/dashboard/config-categories/support-integrations.tsx`:
- Around line 34-47: The component can remain stuck with activeTab === null when
visibleFeatureIds populates after mount; update the effect that currently checks
activeTab visibility to also seed activeTab when it's missing: inside the
useEffect that depends on visibleFeatureIds, activeTab, and availableTabs
(symbols: useEffect, activeTab, setActiveTab, availableTabs, visibleFeatureIds),
add a branch that if activeTab is falsy and availableTabs.length > 0 then call
setActiveTab((availableTabs[0]?.id as ConfigFeatureId) ?? null); keep the
existing check that replaces a hidden activeTab with the first available tab.
In `@web/src/components/dashboard/config-editor-utils.ts`:
- Around line 7-8: The single long CSS class string assigned to inputClasses
exceeds the 100-char line width; split it into multiple shorter strings (e.g.,
an array of class fragments joined with ' ' or concatenated template literals)
and reassign inputClasses to the joined result so the value is identical; update
the declaration of inputClasses to use the new multiline format while preserving
all class tokens and spacing (refer to the inputClasses constant to locate the
change).
In `@web/src/components/dashboard/config-sections/ChannelModeSection.tsx`:
- Around line 278-295: The CardHeader/CardTitle/CardDescription JSX inside the
ChannelModeSection component is being rendered without their parent Card
wrapper; either wrap the existing header block in the Card component (i.e., put
a <Card> around the CardHeader...CardDescription group) or replace
CardHeader/CardTitle/CardDescription with plain semantic elements (div/h2/p) to
avoid misleading use of the Card primitives; locate the header block in the
ChannelModeSection function and apply the chosen change so the header elements
are either nested in Card or converted to simple elements.
- Around line 7-8: Remove the now-unused component imports: drop Card,
CardContent, and Input from the import lines in ChannelModeSection.tsx; keep
only the components still used (e.g., CardDescription, CardHeader, CardTitle) so
the import statement references only the symbols actually used in the file.
In `@web/src/components/dashboard/config-workspace/config-search.tsx`:
- Line 5: Remove the unused imported Button symbol: delete the import of Button
from the top of config-search.tsx (the import statement that reads "import {
Button } from '@/components/ui/button';") so the file only imports actually used
symbols; ensure any references to a Button component are replaced by the
existing native <button> usage or the correct component names if missed.
In `@web/src/components/dashboard/config-workspace/settings-feature-card.tsx`:
- Line 2: The import list includes an unused symbol ChevronUp; remove ChevronUp
from the import statement that currently imports ChevronDown, ChevronUp, Zap
(e.g., update the import so only ChevronDown and Zap are imported) to eliminate
the unused import warning and rely on CSS rotation for the open-state arrow.
In `@web/src/components/dashboard/health-cards.tsx`:
- Around line 76-89: The runtime generation of Tailwind classes via
iconColor.replace('text-', 'bg-') prevents Purge from including those bg-*
classes; update the HealthCards component (health-cards.tsx) to stop deriving
classes at runtime by accepting an explicit progressClass prop (e.g.,
progressClass) or by using an inline background style on the progress fill div
instead of iconColor.replace(...). Locate the JSX that builds the inner progress
div (references: progress, iconColor) and replace the dynamic class generation
with either the provided progressClass value or a style={{ backgroundColor: ...
}} computed from a mapped set of approved class names/colors so Tailwind can
include the required styles.
In `@web/src/components/dashboard/health-section.tsx`:
- Around line 38-43: Replace use of window.setInterval and window.clearInterval
with globalThis equivalents for cross-environment compatibility: in the
auto-refresh setup where autoRefreshTimerRef.current = window.setInterval(() =>
{ void refresh(guildId); }, AUTO_REFRESH_MS); and in the cleanup where
window.clearInterval(autoRefreshTimerRef.current) is called, change both
references to globalThis.setInterval and globalThis.clearInterval while keeping
the same callback (refresh) and AUTO_REFRESH_MS constant.
In `@web/src/components/dashboard/restart-history.tsx`:
- Around line 103-105: The JSX list in restart-history (the <tr
key={restart.timestamp} ...> in the render/map that produces restart rows) uses
restart.timestamp as the sole React key which can collide; change the key to a
stable composite or fallback by including the map index or another unique field
(e.g., use `${restart.timestamp}-${index}` or `${restart.id ??
restart.timestamp}-${index}`) by accepting the index in the map callback and
using it in the key so each <tr> has a unique key even when timestamps repeat.
In `@web/src/components/dashboard/settings-tabs.tsx`:
- Around line 34-55: The mobile-only icon links in the settings-tabs component
render the label hidden and the Icon set aria-hidden, leaving Link elements
without an accessible name; update the Link (rendered in the map for each tab)
to include an accessible name by adding aria-label={tab.label} (or include a
visually-hidden sr-only span inside the Link) while keeping Icon aria-hidden so
screen readers get the tab label; modify the Link component around
tab.id/tab.label to provide this accessible name.
In `@web/src/components/landing/FeatureGrid.tsx`:
- Around line 3-13: Remove the unused imports to clean up the component: delete
useEffect and useInView from the framer-motion import list and remove Bot and
Star from the lucide-react import list (references: symbols useEffect,
useInView, Bot, Star, and the surrounding imports in FeatureGrid / the component
header). Ensure remaining imports (motion, useMotionValue, useReducedMotion,
useSpring, useTransform and the used icons Activity, BarChart3, MessageSquare,
Shield, Sparkles, Zap) remain unchanged.
- Around line 201-207: The FeatureGrid component defines and assigns
containerRef (const containerRef = useRef(null)) to the container div but never
reads it; remove the unused ref by deleting the containerRef declaration and
removing ref={containerRef} from the div, and also remove the now-unused useRef
import if present so there are no unused variables or imports left (leave
useReducedMotion and other logic intact).
In `@web/src/components/landing/Hero.tsx`:
- Around line 48-56: The effect's mouse handler and event registration use the
global window object; replace occurrences of window with globalThis to be
environment-agnostic: in the useEffect callback inside handleMouseMove change
window.innerWidth and window.innerHeight to globalThis.innerWidth /
globalThis.innerHeight, and when adding/removing the listener swap
window.addEventListener/removeEventListener to
globalThis.addEventListener/removeEventListener (keep the same function names
x.set, y.set and the handleMouseMove reference).
In `@web/src/components/landing/Pricing.tsx`:
- Around line 89-90: The className string in the JSX for Pricing (the element
using isAnnual) is over 100 chars; break the template literal into multiple
concatenated or multiline template parts to keep each line ≤100 chars and
maintain the same conditional logic. Locate the JSX expression that sets
className={`relative h-7 w-14 rounded-full border transition-colors ${isAnnual ?
'border-accent/25 bg-accent/10' : 'border-secondary/20 bg-secondary/10' }`} and
split the static classes and the conditional classes across lines (for example
put the static classes on one line and the ${isAnnual ? ... : ...} on the next)
so the isAnnual conditional and string literals remain unchanged but no line
exceeds 100 characters.
- Around line 99-100: The className string in Pricing.tsx on the JSX element
uses a long inline template literal that exceeds 100 characters; refactor by
moving the conditional portion onto a new line or computing it into a small
variable (e.g., const annualClass = isAnnual ? 'text-foreground' :
'text-muted-foreground') and then use className={`text-sm font-medium
transition-colors ${annualClass}`} (or split the template literal across
multiple lines) to ensure no line exceeds 100 characters; update the JSX that
currently references isAnnual and className accordingly.
- Around line 78-79: The className template string on the Pricing component
(className={`text-sm font-medium transition-colors ${isAnnual ?
'text-muted-foreground' : 'text-foreground'}`} ) is over 100 chars; break it
into multiple concatenated parts or compute the classname in a short
helper/const (e.g., const textClass = isAnnual ? 'text-muted-foreground' :
'text-foreground') and then use className={`text-sm font-medium
transition-colors ${textClass}`} so each line stays under 100 characters while
preserving the same logic.
In `@web/src/components/layout/header.tsx`:
- Line 277: The inline handler uses an unnecessary "as any" cast when calling
setRangePreset; remove the cast so the value is passed with its proper type:
change the onValueChange handler (the arrow function using v) to call
setRangePreset(v) directly when v !== 'custom' and, if needed, adjust
setRangePreset's parameter type or the component's value type to match the
control's value union so no assertion is required.
- Around line 570-578: Remove the non-null assertions on session.user.image and
session.user.name in the Avatar rendering (AvatarImage and AvatarFallback) and
instead use safe checks and fallbacks: read session?.user?.image and
session?.user?.name with optional chaining, supply a default image URL or
undefined for AvatarImage, compute initials defensively (e.g. take (name || "")
and substring/uppercase only after ensuring it's a string) and provide a
sensible alt text fallback (e.g. "User" or empty string) so rendering won't
throw when user properties are missing.
- Around line 112-131: The AbortController created in handleModerationRefresh is
never used to cancel in-flight requests; modify handleModerationRefresh to
create a controller, store it in a ref (e.g., moderationAbortRef) and before
starting new requests abort any existing controller, pass its signal to
fetchStats/fetchCases/fetchUserHistory, and add a useEffect cleanup that aborts
the current controller on unmount; alternatively, if aborting is not needed
remove the controller/signal and their references and only call
fetchStats/fetchCases/fetchUserHistory. Ensure you update references to
AbortController/signal and keep function name handleModerationRefresh and the
fetchStats/fetchCases/fetchUserHistory calls consistent.
- Around line 180-194: The header component is using global window CustomEvents
(refresh-performance, performance-loading-start, performance-loading-end) via
handlePerformanceRefresh and the useEffect that manages performanceLoading;
replace this pattern with the app's existing state solution (e.g., the dashboard
store or a React context) by adding a boolean/loading action in the store (e.g.,
setPerformanceLoading / refreshPerformance) and have Header call the store
action in handlePerformanceRefresh and subscribe to the store's loading value
instead of adding/removing window event listeners in the useEffect; update any
other components that currently dispatch or listen to those window events to use
the same store actions/selectors so all communication flows through the
store/context.
- Around line 157-177: The refresh handlers handleTempRolesRefresh and
handleHealthRefresh currently just call refreshTempRoles(guildId) /
refreshHealth(guildId) without checking for 'unauthorized' like other handlers;
update these handlers (and similarly handleConversationsRefresh and
handleAuditLogRefresh if you want full consistency) to await the refresh call
and if it returns the string 'unauthorized' then navigate the user to the login
page (use the same router/navigation method used by
handleModerationRefresh/handleMembersRefresh) so expired sessions redirect to
/login instead of leaving stale UI.
In `@web/src/components/layout/sidebar.tsx`:
- Line 167: The LifeBuoy icon is set to spin indefinitely via the className
"animate-[spin_4s_linear_infinite]"; remove or restrict that animation to avoid
UX/perf issues by replacing the persistent class with a hover-only or
group-hover class (e.g., change the className on the LifeBuoy element to use
"hover:animate-[spin_4s_linear]" or "group-hover:animate-[spin_4s_linear]" or
simply remove the animation class entirely); update the surrounding element to
include "group" if you use group-hover so the icon only spins on user hover and
not continuously.
- Line 44: Remove the "Bot Config" navigation entry from the sidebar items: find
the nav array/object that includes the item with name: 'Bot Config', href:
'/dashboard/config', icon: Cog and delete that object so the sidebar no longer
shows "Bot Config" while keeping the existing "Settings" entry at the bottom
intact.
- Line 24: Remove the unused import "Separator" from the top of sidebar.tsx;
locate the import line import { Separator } from '@/components/ui/separator' and
delete it (or remove "Separator" from the import list) so the file no longer
references an unused symbol and the linter stops flagging it.
In `@web/src/components/ui/channel-selector.tsx`:
- Around line 430-434: The cloneElement call uses a broad any cast
(React.ReactElement<any>) which bypasses type safety; update the badge rendering
in channel-selector (the icon handling around the icon variable and
React.cloneElement) to avoid any: either change getChannelIcon to return a
component type (e.g., React.ComponentType<React.SVGProps<SVGSVGElement> | {
className?: string }>) and render it as <IconComponent className="h-3 w-3" />
or, if keeping JSX, narrow the type to React.ReactElement< { className?: string
} > before cloning so you can safely pass className; replace the any cast and
ensure TypeScript knows the icon accepts className.
In `@web/src/components/ui/material-dropdown-menu.tsx`:
- Around line 205-213: The close handler in handleOpenChange uses a 300ms
setTimeout to reset setActivePage('main') and setHistory(['main']) but doesn't
cancel the timer if the menu reopens or the component unmounts; change this by
storing the timer id in a ref (e.g., closeTimerRef),
clearTimeout(closeTimerRef.current) before scheduling a new timeout and also
clear it when open becomes true (reopen) and in a useEffect cleanup on unmount;
update handleOpenChange, the timeout scheduling logic, and add the cleanup to
ensure stale callbacks can't reset activePage or history mid-session.
---
Outside diff comments:
In `@package.json`:
- Line 3: Update package.json to bump the listed dependencies to their latest
versions: set `@anthropic-ai/sdk` to 0.82.0, `@sentry/node` to 10.47.0, discord.js
to 14.26.0, dotenv to 17.4.0, mem0ai to 2.4.5, and update devDependencies
`@biomejs/biome` to 2.4.10, `@vitest/coverage-v8` to 4.1.2, and vitest to 4.1.2;
ensure the "packageManager" entry remains pnpm@10.33.0, run pnpm install to
refresh the lockfile, and commit the updated package.json and lockfile changes.
In `@web/src/app/dashboard/conversations/`[conversationId]/page.tsx:
- Around line 33-37: fetchDetail currently returns early when !guildId after
calling setLoading(false) but doesn't set any user-visible state; update
fetchDetail (and related state) so it sets a clear UI error/empty-state
flag/message before returning. Specifically, add a call like setError(...) or
setMissingGuild(true) inside the !guildId branch (alongside setLoading(false)),
and ensure the page render checks that state (e.g., error, missingGuild, or
missingContext) to show a friendly guidance banner or empty-state UI explaining
the missing guildId and next steps; reference the fetchDetail callback,
setLoading, guildId, and conversationId to locate and wire the new state.
In `@web/src/app/dashboard/logs/page.tsx`:
- Around line 1-20: This page is a client component so it cannot export SSR
metadata; refactor by extracting the interactive UI into a client-only component
(e.g., rename the current LogsPage to LogsClient or LogsViewer that keeps 'use
client' and uses useGuildSelection, useLogStream, sendFilter, clearLogs,
LogViewer, LogFilters, HealthSection, cn) and then create a server component
wrapper as the default export that exports the metadata and wraps ErrorBoundary
(matching other dashboard routes), rendering the client component inside it;
ensure the server wrapper file exports the same metadata shape and uses
ErrorBoundary for server-side errors while delegating interactive behavior to
the client component.
In `@web/src/app/dashboard/members/`[userId]/page.tsx:
- Around line 236-255: The local state update only patches xp and level in
setData, leaving fields like next_level_xp stale; change the handler after
res.json() to either refetch the full member payload (call the existing
fetch/mutate/refetch function used elsewhere for the member) or replace the
entire reputation object from the mutation response (use result.reputation if
present) when calling setData so all reputation fields (xp, level,
next_level_xp, etc.) are updated consistently; locate the update in the page
handler that uses setData(...) and ensure you use the full payload from result
or trigger the member refetch instead of only setting xp/level.
In `@web/src/app/dashboard/members/members-client.tsx`:
- Around line 149-205: The toolbar lacks an explicit retry/refresh control so
users can't re-run the member fetch after a transient failure; add a retry
button in the compact toolbar (near the Search/Input and clear button) that
calls the same fetch/refetch function used to load members (e.g., a prop or hook
method like refetch, refetchMembers, fetchMembers, or react-query's refetch)
without mutating search/sort state; ensure the button is visible when total ===
0 or when an error is present (or always visible) and wired to the existing
handlers (use existing setSearch/handleClearSearch context) so it simply
re-invokes the network request side-effect-free.
In `@web/src/app/dashboard/temp-roles/page.tsx`:
- Around line 69-105: The handleRevoke callback captures the store fetch
function but doesn't list it in the dependency array, risking a stale closure;
update the dependency array of handleRevoke to include fetch (the same fetch
used in the body where you call fetch(guildId, page)), so that handleRevoke is
recreated when fetch changes and continues to reference the latest
implementation while keeping existing dependencies [guildId, page, router] and
leaving setRevoking/setConfirmRevoke as they are (or add them if you prefer
explicitness).
In `@web/src/app/login/page.tsx`:
- Line 11: The import line brings in Card, CardContent, CardDescription,
CardHeader, and CardTitle from '@/components/ui/card' but these components are
no longer used in page.tsx after the UI refactor; remove those unused symbols
from the import (or delete the entire import statement if nothing else is
imported from that module) so there are no unused imports referenced (look for
the import that currently lists Card, CardContent, CardDescription, CardHeader,
CardTitle).
In `@web/src/components/dashboard/config-sections/ChannelModeSection.tsx`:
- Around line 40-50: ModeSelector currently declares and accepts an unused prop
isDefault; remove isDefault from the ModeSelector props type and parameter list
so the component signature only takes mode, onChange, and disabled, and then
update every call site that passes isDefault (where ModeSelector is
instantiated) to stop supplying that prop so props match the new signature
(search for ModeSelector usages and remove the isDefault prop).
In `@web/src/components/dashboard/moderation-stats.tsx`:
- Line 4: Remove the unused UI imports (Card, CardContent, CardHeader,
CardTitle) from the top of the file since the component now uses StatCard and
raw div/section wrappers; locate the import line that includes those symbols and
delete them (or replace the entire import with only needed exports if any other
exports from that module are used), then run the repository lint/type checks to
ensure no references to those identifiers remain.
In `@web/src/components/dashboard/performance-dashboard.tsx`:
- Around line 167-198: The background poll (fetchData with bg=true) reuses
abortRef and can cancel an in-flight foreground fetch causing loading
state/events to be cleared out of order; change fetchData (the useCallback) to
keep foreground and background requests separate by either: a) using a separate
AbortController store for background polls (e.g., bgAbortRef) so bg calls don't
abort abortRef used by interactive calls, or b) early-return when bg is true and
abortRef.current is non-null (skip background poll while a foreground fetch is
active); ensure you only call
setLoading(true)/setError(null)/window.dispatchEvent('performance-loading-start')
for foreground (bg=false) and only clear loading/events in that same branch so
finally doesn't clear a foreground spinner due to a background abort.
In `@web/src/components/layout/server-selector.tsx`:
- Around line 26-27: The pluralization helper formatServerCount currently
appends 's' blindly causing incorrect forms for irregular nouns (e.g.,
"view-only communitys"); change its signature to accept explicit singular and
plural labels (e.g., formatServerCount(count: number, singular: string, plural?:
string)) and use plural when count !== 1 (fallback plural = singular + 's' if
not provided), then update all call sites (including where memberOnly.length is
passed) to provide both singular and plural labels (or rely on fallback) so
irregular nouns render correctly.
In `@web/src/components/ui/role-selector.tsx`:
- Around line 69-76: hasFetchedRef (the eager-fetch cache) is not reset when
guildId changes, so preselected role IDs from a new guild won't trigger the
eager fetch and render as "Unknown role"; update the effect that depends on
guildId/open/selected to clear the cache when the guildId changes by setting
hasFetchedRef.current = false (or reinitialize it) before checking
needsEagerFetch so that a new guild with preselected IDs will perform the eager
fetch path; reference hasFetchedRef, guildId, selected, open and the
React.useEffect block around those symbols to locate and modify the logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f53a33c2-4280-4a93-8ce9-a3e4abbc5510
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (72)
.github/workflows/claude-review.yml.gitignoredocker-compose.ymlmigrations/015_add-audit-logs-user-tag.cjspackage.jsonweb/next.config.mjsweb/src/app/dashboard/audit-log/page.tsxweb/src/app/dashboard/conversations/[conversationId]/page.tsxweb/src/app/dashboard/conversations/conversations-client.tsxweb/src/app/dashboard/logs/page.tsxweb/src/app/dashboard/members/[userId]/page.tsxweb/src/app/dashboard/members/members-client.tsxweb/src/app/dashboard/moderation/moderation-client.tsxweb/src/app/dashboard/settings/page.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/tickets/[ticketId]/page.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/app/globals.cssweb/src/app/login/page.tsxweb/src/app/page.tsxweb/src/components/dashboard/analytics-dashboard.tsxweb/src/components/dashboard/config-categories/ai-automation.tsxweb/src/components/dashboard/config-categories/community-tools.tsxweb/src/components/dashboard/config-categories/config-landing.tsxweb/src/components/dashboard/config-categories/moderation-safety.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/components/dashboard/config-categories/support-integrations.tsxweb/src/components/dashboard/config-editor-utils.tsweb/src/components/dashboard/config-layout-shell.tsxweb/src/components/dashboard/config-sections/ChannelModeSection.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/config-workspace/config-search.tsxweb/src/components/dashboard/config-workspace/settings-feature-card.tsxweb/src/components/dashboard/conversation-replay.tsxweb/src/components/dashboard/floating-save-island.tsxweb/src/components/dashboard/health-cards.tsxweb/src/components/dashboard/health-section.tsxweb/src/components/dashboard/log-filters.tsxweb/src/components/dashboard/log-viewer.tsxweb/src/components/dashboard/member-table.tsxweb/src/components/dashboard/moderation-stats.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/restart-history.tsxweb/src/components/dashboard/settings-tabs.tsxweb/src/components/dashboard/system-prompt-editor.tsxweb/src/components/landing/FeatureGrid.tsxweb/src/components/landing/Footer.tsxweb/src/components/landing/Hero.tsxweb/src/components/landing/Pricing.tsxweb/src/components/layout/dashboard-shell.tsxweb/src/components/layout/header.tsxweb/src/components/layout/server-selector.tsxweb/src/components/layout/sidebar.tsxweb/src/components/ui/button.tsxweb/src/components/ui/channel-selector.tsxweb/src/components/ui/command.tsxweb/src/components/ui/dropdown-menu.tsxweb/src/components/ui/material-dropdown-menu.tsxweb/src/components/ui/neo-minimal-footer.tsxweb/src/components/ui/role-selector.tsxweb/src/components/ui/switch.tsxweb/src/components/ui/theme-toggle.tsxweb/src/contexts/analytics-context.tsxweb/src/stores/audit-log-store.tsweb/src/stores/conversations-store.tsweb/src/stores/health-store.tsweb/src/stores/members-store.tsweb/src/stores/temp-roles-store.tsweb/src/stores/tickets-store.tsweb/src/types/config.tsweb/tests/components/landing/footer.test.tsxweb/tsconfig.json
💤 Files with no reviewable changes (1)
- web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
web/src/components/dashboard/config-sections/ChannelModeSection.tsx
Outdated
Show resolved
Hide resolved
|
A couple more:
Main Page Issues:
|
|
@coderabbitai review |
|
@claude review |
✅ Actions performedReview triggered.
|
PR #390 Code Review
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 71 out of 124 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (3)
web/src/components/dashboard/health-cards.tsx:1
- Two issues here: (1)
heapPctnow uses a hard-codedMEMORY_MAX_MB = 4096rather than the real heap limit/total fromhealth.memory.heapTotal, which makes the percentage inaccurate (and can exceed 100% depending on runtime). (2)iconColor.replace('text-', 'bg-')generates Tailwind class names dynamically at runtime; Tailwind won't reliably include these generatedbg-*classes in the build without safelisting, leading to missing progress bar colors in production. Prefer an explicit mapping (e.g.,{ 'text-primary': 'bg-primary', ... }) or passprogressBarClassNamedirectly.
'use client';
web/src/components/dashboard/config-sections/ChannelModeSection.tsx:107
_ModeDotappears to be leftover after the UI refactor (the new UI uses a different indicator). If it is no longer referenced in this file, it should be removed to avoid dead code and potential lint failures.
function _ModeDot({ mode }: { mode: ChannelMode }) {
const color =
mode === 'off' ? 'bg-destructive' : mode === 'vibe' ? 'bg-emerald-500' : 'bg-primary';
return <span className={cn('inline-block h-1.5 w-1.5 rounded-full shrink-0', color)} />;
}
docker-compose.yml:1
- This PR is titled/positioned as a landing-page overhaul, but it also removes the
bot,web, anddocsservices fromdocker-compose.yml. That’s a significant local-dev workflow change and seems unrelated to the stated PR scope. If intentional, it should be called out explicitly in the PR description and likely moved to a separate PR; otherwise, please restore the removed compose services.
services:
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@claude review |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
✅ Actions performedReview triggered.
|
|
@greptile review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
web/src/components/dashboard/performance-dashboard.tsx (1)
204-208: 🧹 Nitpick | 🔵 TrivialThreshold edit state may become stale after server-side updates.
The condition
Object.keys(thresholdEdit).length === 0prevents re-syncingthresholdEditifdata.thresholdschanges after the initial load. If thresholds are updated elsewhere (e.g., another admin), a background refresh will updatedatabut leavethresholdEditshowing the old values.Consider tracking whether the user has made local edits to determine when to re-sync:
💡 Possible improvement
+ const [hasLocalEdits, setHasLocalEdits] = useState(false); + useEffect(() => { - if (data && Object.keys(thresholdEdit).length === 0) { + if (data && !hasLocalEdits) { setThresholdEdit({ ...data.thresholds }); } - }, [data, thresholdEdit]); + }, [data, hasLocalEdits]);Then set
setHasLocalEdits(true)in theonChangehandler and reset it after a successful save.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/performance-dashboard.tsx` around lines 204 - 208, The useEffect that sets thresholdEdit only when Object.keys(thresholdEdit).length === 0 causes thresholdEdit to diverge when data.thresholds updates; change the logic to re-sync thresholds unless the user has made local edits by introducing a hasLocalEdits boolean state (e.g., [hasLocalEdits, setHasLocalEdits]) and update it to true from your threshold onChange handlers, then modify the useEffect to setThresholdEdit({ ...data.thresholds }) whenever data changes and hasLocalEdits is false, and reset setHasLocalEdits(false) after a successful save so background updates can re-sync correctly.web/src/components/dashboard/member-table.tsx (1)
156-185:⚠️ Potential issue | 🟡 MinorKeep the column count consistent across header, loading, and loaded states.
The new chevron cell makes loaded rows nine columns wide, but the header and
TableSkeletonstill render eight cells. That leaves the table misaligned once data loads and breaks header associations for assistive tech.
↔️ Proposed fix<TableHeader> <TableRow> <TableHead className="w-10" /> {/* Username & Display Name are not API-sortable, shown as plain headers */} <TableHead>Username</TableHead> <TableHead>Display Name</TableHead> <SortableHead column="messages" label="Messages" currentColumn={sortColumn} currentOrder={sortOrder} onSort={onSort} className="hidden md:table-cell" /> <SortableHead column="xp" label="XP / Level" currentColumn={sortColumn} currentOrder={sortOrder} onSort={onSort} /> <SortableHead column="warnings" label="Warnings" currentColumn={sortColumn} currentOrder={sortOrder} onSort={onSort} /> <TableHead>Last Active</TableHead> <SortableHead column="joined" label="Joined" currentColumn={sortColumn} currentOrder={sortOrder} onSort={onSort} className="hidden md:table-cell" /> + <TableHead className="w-8" aria-hidden="true" /> </TableRow> </TableHeader> @@ <TableRow key={key}> <TableCell> <Skeleton className="h-8 w-8 rounded-full" /> </TableCell> @@ <TableCell className="hidden md:table-cell"> <Skeleton className="h-4 w-20" /> </TableCell> + <TableCell className="w-8"> + <Skeleton className="h-4 w-4" /> + </TableCell> </TableRow>Also applies to: 240-245, 369-372
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/member-table.tsx` around lines 156 - 185, TableSkeleton (and the table header) currently render eight TableCell columns while the loaded rows include a new chevron/expand column (nine columns), causing misalignment and accessibility issues; update TableSkeleton, the header component that renders column headings, and any other loading placeholders (see references around TableSkeleton, TableRow, TableCell and the chevron/expand cell) to include the extra empty/skeleton TableCell to match the loaded row count so header, loading, and loaded states all render nine columns consistently.
♻️ Duplicate comments (8)
web/src/app/dashboard/tickets/tickets-client.tsx (1)
119-125:⚠️ Potential issue | 🟠 MajorReset
pagewhen either filter changes.Line 119’s debounce path and Line 244’s status handler still preserve the current
page. Narrowing results from a later page can request an out-of-range page, render a false empty state, and hide pagination oncetotalPagesdrops to1. This is the same regression that was already raised earlier and is still present.📄 Proposed fix
useEffect(() => { clearTimeout(searchTimerRef.current); searchTimerRef.current = setTimeout(() => { setDebouncedSearch(search); + setPage(1); }, 300); return () => clearTimeout(searchTimerRef.current); - }, [search, setDebouncedSearch]); + }, [search, setDebouncedSearch, setPage]);<Select value={statusFilter} onValueChange={(val) => { setStatusFilter(val === 'all' ? '' : val); + setPage(1); }} >Also applies to: 242-246
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/tickets/tickets-client.tsx` around lines 119 - 125, When filters change (search or status) reset pagination so results don't request an out-of-range page: update the debounce effect that uses searchTimerRef and setDebouncedSearch to also call setPage(1) when it applies the debounced search, and likewise update the status handler (the function that sets status/filter around setStatus or similar at the other location) to call setPage(1) whenever the status/filter is changed; ensure you only reset page when actually changing a filter value to avoid unnecessary reloads.web/src/app/dashboard/members/members-client.tsx (1)
239-245:⚠️ Potential issue | 🟠 MajorAdd a retry action to the error state.
After a transient
fetchMembers()failure, this screen only renders the alert. There is still no way to re-run the same request without changing filters or leaving the route.🔁 Proposed fix
{error && ( <div role="alert" className="rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive" > <strong>Error:</strong> {error} + <button + type="button" + className="ml-3 underline underline-offset-4 disabled:opacity-50" + disabled={loading} + onClick={() => { + if (!guildId) return; + void runFetch({ + guildId, + search: debouncedSearch, + sortColumn, + sortOrder, + after: null, + append: false, + }); + }} + > + Retry + </button> </div> )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/members/members-client.tsx` around lines 239 - 245, The error alert currently renders when the fetchMembers() call fails but offers no retry path; update the error UI inside the MembersClient component (the block that renders when error is truthy) to include a retry action/button that calls fetchMembers() (or a small wrapper like handleRetry that re-invokes fetchMembers and resets relevant state such as error/loading). Ensure the button is accessible (e.g., labeled "Retry") and placed alongside the error message so users can re-run the same request without changing filters or leaving the route.web/src/app/dashboard/conversations/conversations-client.tsx (4)
82-92:⚠️ Potential issue | 🟠 MajorGuard the channel fetch against stale guild responses.
This effect still has no abort or request-staleness protection. If the user switches guilds quickly, a slower response from the previous guild can overwrite
channelsfor the current one.🛡️ Proposed fix
useEffect(() => { if (!guildId) return; + const controller = new AbortController(); + setChannels([]); void (async () => { try { - const res = await window.fetch(`/api/guilds/${encodeURIComponent(guildId)}/channels`); - if (res.ok) setChannels(((await res.json()) as Channel[]).filter((c) => c.type === 0)); + const res = await window.fetch( + `/api/guilds/${encodeURIComponent(guildId)}/channels`, + { signal: controller.signal }, + ); + if (!res.ok || controller.signal.aborted) return; + const body = (await res.json()) as Channel[]; + setChannels(body.filter((c) => c.type === 0)); } catch { - /* non-critical */ + if (!controller.signal.aborted) { + setChannels([]); + } } })(); + return () => controller.abort(); }, [guildId]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/conversations/conversations-client.tsx` around lines 82 - 92, The fetch in the useEffect for loading channels can be overwritten by slower responses from previous guilds; add request-staleness protection by using an AbortController: create a controller inside the effect, pass controller.signal to window.fetch(`/api/guilds/${encodeURIComponent(guildId)}/channels`), and in the cleanup return () => controller.abort() so prior requests are aborted when guildId changes; before calling setChannels ensure the fetch completed successfully (res.ok) and handle the abort case in the catch (ignore DOMException with name 'AbortError') so aborted requests don’t overwrite channels; keep references to guildId and setChannels as before.
225-230:⚠️ Potential issue | 🟡 MinorUse static Tailwind width classes here.
w-${w}is still generated dynamically, so Tailwind cannot see those utilities during class extraction. In production the skeleton widths can collapse.🎨 Proposed fix
- {Array.from({ length: 8 }).map((_, i) => ( + {Array.from({ length: 8 }).map((_, i) => ( <TableRow key={`sk-${i}`} className="border-border/10"> - {[28, 32, 8, 16, 48, 20].map((w, j) => ( - <TableCell key={j}> - <Skeleton className={`h-4 w-${w}`} /> + {['w-28', 'w-32', 'w-8', 'w-16', 'w-48', 'w-20'].map((widthClass) => ( + <TableCell key={widthClass}> + <Skeleton className={`h-4 ${widthClass}`} /> </TableCell> ))} </TableRow> ))}Does Tailwind CSS generate utilities for dynamically constructed class names like `w-${w}` inside template literals, or do classes need to appear as complete strings in source files?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/conversations/conversations-client.tsx` around lines 225 - 230, The dynamic Tailwind class `w-${w}` prevents utility generation; replace it with static class strings by mapping the widths array to explicit Tailwind classes and using those in the Skeleton component (e.g., create const widthClasses = ['w-28','w-32','w-8','w-16','w-48','w-20'] and then use className={`h-4 ${widthClasses[j]}`} inside the map). Update the code around the TableRow/TableCell/Skeleton map so it references widthClasses[j] instead of `w-${w}` to ensure Tailwind includes the utilities.
65-70:⚠️ Potential issue | 🟠 MajorReset the debounced query on guild change.
The first fetch after a guild switch still uses the previous guild's
debouncedSearch, so the new guild can load with a stale filter until the 300 ms timer catches up.🧹 Proposed fix
const onGuildChange = useCallback(() => { useConversationsStore.getState().reset(); + clearTimeout(searchTimerRef.current); setSearch(''); + setDebouncedSearch(''); setChannelFilter(''); setPage(1); }, []);Also applies to: 94-99
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/conversations/conversations-client.tsx` around lines 65 - 70, onGuildChange currently resets the conversations store and UI filters but leaves the debounced search value/timer intact, causing the first fetch after a guild switch to use the previous guild's debouncedSearch; update onGuildChange to also clear the debounced search (e.g. call the debounced-search reset/set function such as setDebouncedSearch('') or resetDebounced() from the debouncing hook) so the immediate fetch uses the new empty search, and apply the same change to the other handler block that mirrors this logic (the block around lines 94-99) to ensure both paths clear debouncedSearch.
255-265:⚠️ Potential issue | 🟠 MajorMake the conversation row keyboard-accessible.
This row is still mouse-only. Keyboard users cannot activate it because it has no focusability, no Enter/Space handler, and no semantic role.
⌨️ Proposed fix
{conversations.map((convo) => ( <TableRow key={convo.id} className="cursor-pointer border-border/10 transition-colors hover:bg-muted/30" + tabIndex={0} + role="link" + aria-label={`Open conversation in ${convo.channelName}`} onClick={() => { if (guildId) router.push( `/dashboard/conversations/${convo.id}?guildId=${encodeURIComponent(guildId)}`, ); }} + onKeyDown={(e) => { + if (e.key !== 'Enter' && e.key !== ' ') return; + e.preventDefault(); + if (guildId) { + router.push( + `/dashboard/conversations/${convo.id}?guildId=${encodeURIComponent(guildId)}`, + ); + } + }} >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/conversations/conversations-client.tsx` around lines 255 - 265, The TableRow rendered in conversations.map (keyed by convo.id) is mouse-only; make it keyboard-accessible by adding tabIndex={0}, role="button", and an onKeyDown handler that calls the same navigation logic (router.push to `/dashboard/conversations/${convo.id}?guildId=${encodeURIComponent(guildId)}`) when Enter or Space is pressed, matching the existing onClick behavior and preserving the guildId check; ensure focus styles remain and do not duplicate navigation if both click and key event occur.web/src/app/dashboard/audit-log/page.tsx (1)
114-121:⚠️ Potential issue | 🟠 MajorReset the local user filter when the guild changes.
useAuditLogStore.getState().reset()clears the store filter, butuserSearchanddebouncedUserSearchstill keep the previous guild's value. The next fetch therefore continues sending that staleuserId.🧹 Proposed fix
const onGuildChange = useCallback(() => { useAuditLogStore.getState().reset(); + setUserSearch(''); + setDebouncedUserSearch(''); setExpandedRows(new Set()); }, []);Also applies to: 133-151
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/audit-log/page.tsx` around lines 114 - 121, onGuildChange resets the store but leaves local userSearch and debouncedUserSearch stale; update onGuildChange (the callback that calls useAuditLogStore.getState().reset and setExpandedRows) to also clear the local filter state by calling setUserSearch('') and setDebouncedUserSearch('') (or the appropriate empty/default value) and clear any pending debounce timer via clearTimeout(searchTimerRef.current) and setting searchTimerRef.current = undefined so subsequent fetches don't send the previous guild's userId.web/src/app/dashboard/members/[userId]/page.tsx (1)
113-160:⚠️ Potential issue | 🟡 MinorExtract the XP percent calculation instead of duplicating it inline.
The header badge now has a second copy of the progress formula, and it has already drifted from
XpProgress: this version clamps the upper bound only, so inconsistent data can produce a negative width.♻️ Proposed refactor
+function getXpProgressPct( + xp: number, + currentLevelXp: number | null | undefined, + nextLevelXp: number | null, +): number { + if (nextLevelXp && currentLevelXp != null && nextLevelXp > currentLevelXp) { + return Math.min( + Math.max(((xp - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100, 0), + 100, + ); + } + if (nextLevelXp) { + return Math.min(Math.max((xp / nextLevelXp) * 100, 0), 100); + } + return 100; +} + function XpProgress({ level, xp, currentLevelXp, nextLevelXp, @@ - let pct: number; - if (nextLevelXp && currentLevelXp != null && nextLevelXp > currentLevelXp) { - // Correct formula: progress within the current level - pct = Math.min( - Math.max(((xp - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100, 0), - 100, - ); - } else if (nextLevelXp) { - // Fallback when currentLevelXp is unavailable - pct = Math.min(Math.max((xp / nextLevelXp) * 100, 0), 100); - } else { - pct = 100; - } + const pct = getXpProgressPct(xp, currentLevelXp, nextLevelXp); @@ - let badgePct: number; - if (nlx && clx != null && nlx > clx) { - badgePct = Math.min(((xp - clx) / (nlx - clx)) * 100, 100); - } else if (nlx) { - badgePct = Math.min((xp / nlx) * 100, 100); - } else { - badgePct = 100; - } + const badgePct = getXpProgressPct(xp, clx, nlx); return ( <div className="h-1 w-12 overflow-hidden rounded-full bg-white/5 mt-1"> <div className="h-full rounded-full bg-gradient-to-r from-primary to-primary/60"Also applies to: 455-475
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/app/dashboard/members/`[userId]/page.tsx around lines 113 - 160, There are two different XP percentage calculations (in XpProgress's pct and a duplicate in the header badge) that have drifted; extract the logic into a single utility like computeXpPercent(xp, currentLevelXp, nextLevelXp) and replace both usages with it. Implement computeXpPercent to: use the in-level formula when nextLevelXp and currentLevelXp are present (pct = (xp - currentLevelXp)/(nextLevelXp - currentLevelXp) * 100), fall back to xp/nextLevelXp * 100 when currentLevelXp is null/undefined but nextLevelXp exists, and always clamp the result to the 0–100 range; then update XpProgress (pct) and the header badge to call this function so they share the same clamped logic.
🤖 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/app/dashboard/temp-roles/page.tsx`:
- Around line 1-5: The file page.tsx is a client component (has 'use client') so
it cannot export server-side metadata; move the interactive UI and hooks into a
new client child component (e.g., TempRolesClient or TempRolesPageClient) and
keep page.tsx as a thin server wrapper that imports that client component and
exports createPageMetadata(...) (or metadata/generateMetadata) as required;
specifically remove 'use client' from the route entry, move
useRouter/useCallback/useEffect/useState and any lucide-react UI into the new
client component, and ensure page.tsx simply renders <TempRolesClient /> while
exporting the server metadata functions.
In `@web/src/components/dashboard/analytics-dashboard.tsx`:
- Around line 1105-1124: The tooltip cells are not keyboard-accessible because
TooltipTrigger wraps a plain div; update the trigger to be focusable and provide
an accessible name: replace the inner non-focusable div used with TooltipTrigger
(the element created in the JSX that uses cn, hexToRgba, and the
level/chart/day/hour/value vars) with a focusable element (either a button or
the same div with tabIndex={0} and role="button") and add an aria-label that
clearly describes the cell (e.g., "{day} {hour}:00 — {value} message(s)"). Keep
the existing className and inline style logic so visual appearance and hover
behavior remain unchanged, and ensure TooltipContent remains unchanged.
- Around line 229-240: SAMPLE_ACTIVITY is being used by LiveActivityFeed to
display as a live stream in AnalyticsDashboard; either replace SAMPLE_ACTIVITY
with real telemetry fetched for LiveActivityFeed or clearly label/flag the card
as preview/demo content. Update the component that renders LiveActivityFeed (and
any parent text in AnalyticsDashboard) to fetch real activity via the existing
telemetry API or add a visible "Preview data" badge/message and an opt-in
toggle, and remove any copy that implies the feed is live when SAMPLE_ACTIVITY
is shown.
- Line 675: The extracted DashboardCard wrappers inside the parent with class
"xl:grid-cols-12" are missing explicit xl column spans, causing them to collapse
to one column on extra-large screens; add the appropriate xl:col-span-* classes
(e.g., xl:col-span-4 for three equal-width cards) to the DashboardCard instances
that render Message Volume, Top Channels, and Command Telemetry (and the other
two occurrences noted) so they occupy the intended columns within the
xl:grid-cols-12 container.
- Around line 319-324: The topChannels assignment uses the nullish coalescing
operator (analytics?.topChannels ?? analytics?.channelActivity ?? []) which
treats an empty array as valid and prevents falling back to channelActivity;
change the logic in the topChannels calculation (the const topChannels) to
prefer analytics.topChannels only when it exists and has length > 0, otherwise
fall back to analytics.channelActivity and then to an empty array so the
chart/filter show channelActivity when topChannels is an empty array.
In `@web/src/components/dashboard/case-table.tsx`:
- Around line 259-262: The TableRow click-only toggle is not keyboard
accessible; update the TableRow (or move the toggle into a real <button> in a
cell) so it supports keyboard users: add tabIndex={0}, role="button", and
aria-expanded set from the row's expanded state (use whatever expands state you
already track for row c), and add an onKeyDown handler that detects Enter or
Space and calls toggleExpand(c) (mirroring the onClick). Ensure the same toggle
logic lives in either the onKeyDown or the inner button so screen readers and
keyboard users can open/close rows.
In `@web/src/components/dashboard/health-cards.tsx`:
- Around line 140-146: The iconColor ternary in health-cards.tsx incorrectly
treats missing ping as a warning; update the logic in the iconColor expression
to first check for a defined numeric ping (e.g., health?.discord?.ping !==
undefined && health?.discord?.ping !== null or
Number.isFinite(health?.discord?.ping)) and only apply the green/amber/red
thresholds when a numeric ping exists; otherwise return a neutral color like
'text-muted-foreground/40' so the icon matches the displayed '—' no-data state.
---
Outside diff comments:
In `@web/src/components/dashboard/member-table.tsx`:
- Around line 156-185: TableSkeleton (and the table header) currently render
eight TableCell columns while the loaded rows include a new chevron/expand
column (nine columns), causing misalignment and accessibility issues; update
TableSkeleton, the header component that renders column headings, and any other
loading placeholders (see references around TableSkeleton, TableRow, TableCell
and the chevron/expand cell) to include the extra empty/skeleton TableCell to
match the loaded row count so header, loading, and loaded states all render nine
columns consistently.
In `@web/src/components/dashboard/performance-dashboard.tsx`:
- Around line 204-208: The useEffect that sets thresholdEdit only when
Object.keys(thresholdEdit).length === 0 causes thresholdEdit to diverge when
data.thresholds updates; change the logic to re-sync thresholds unless the user
has made local edits by introducing a hasLocalEdits boolean state (e.g.,
[hasLocalEdits, setHasLocalEdits]) and update it to true from your threshold
onChange handlers, then modify the useEffect to setThresholdEdit({
...data.thresholds }) whenever data changes and hasLocalEdits is false, and
reset setHasLocalEdits(false) after a successful save so background updates can
re-sync correctly.
---
Duplicate comments:
In `@web/src/app/dashboard/audit-log/page.tsx`:
- Around line 114-121: onGuildChange resets the store but leaves local
userSearch and debouncedUserSearch stale; update onGuildChange (the callback
that calls useAuditLogStore.getState().reset and setExpandedRows) to also clear
the local filter state by calling setUserSearch('') and
setDebouncedUserSearch('') (or the appropriate empty/default value) and clear
any pending debounce timer via clearTimeout(searchTimerRef.current) and setting
searchTimerRef.current = undefined so subsequent fetches don't send the previous
guild's userId.
In `@web/src/app/dashboard/conversations/conversations-client.tsx`:
- Around line 82-92: The fetch in the useEffect for loading channels can be
overwritten by slower responses from previous guilds; add request-staleness
protection by using an AbortController: create a controller inside the effect,
pass controller.signal to
window.fetch(`/api/guilds/${encodeURIComponent(guildId)}/channels`), and in the
cleanup return () => controller.abort() so prior requests are aborted when
guildId changes; before calling setChannels ensure the fetch completed
successfully (res.ok) and handle the abort case in the catch (ignore
DOMException with name 'AbortError') so aborted requests don’t overwrite
channels; keep references to guildId and setChannels as before.
- Around line 225-230: The dynamic Tailwind class `w-${w}` prevents utility
generation; replace it with static class strings by mapping the widths array to
explicit Tailwind classes and using those in the Skeleton component (e.g.,
create const widthClasses = ['w-28','w-32','w-8','w-16','w-48','w-20'] and then
use className={`h-4 ${widthClasses[j]}`} inside the map). Update the code around
the TableRow/TableCell/Skeleton map so it references widthClasses[j] instead of
`w-${w}` to ensure Tailwind includes the utilities.
- Around line 65-70: onGuildChange currently resets the conversations store and
UI filters but leaves the debounced search value/timer intact, causing the first
fetch after a guild switch to use the previous guild's debouncedSearch; update
onGuildChange to also clear the debounced search (e.g. call the debounced-search
reset/set function such as setDebouncedSearch('') or resetDebounced() from the
debouncing hook) so the immediate fetch uses the new empty search, and apply the
same change to the other handler block that mirrors this logic (the block around
lines 94-99) to ensure both paths clear debouncedSearch.
- Around line 255-265: The TableRow rendered in conversations.map (keyed by
convo.id) is mouse-only; make it keyboard-accessible by adding tabIndex={0},
role="button", and an onKeyDown handler that calls the same navigation logic
(router.push to
`/dashboard/conversations/${convo.id}?guildId=${encodeURIComponent(guildId)}`)
when Enter or Space is pressed, matching the existing onClick behavior and
preserving the guildId check; ensure focus styles remain and do not duplicate
navigation if both click and key event occur.
In `@web/src/app/dashboard/members/`[userId]/page.tsx:
- Around line 113-160: There are two different XP percentage calculations (in
XpProgress's pct and a duplicate in the header badge) that have drifted; extract
the logic into a single utility like computeXpPercent(xp, currentLevelXp,
nextLevelXp) and replace both usages with it. Implement computeXpPercent to: use
the in-level formula when nextLevelXp and currentLevelXp are present (pct = (xp
- currentLevelXp)/(nextLevelXp - currentLevelXp) * 100), fall back to
xp/nextLevelXp * 100 when currentLevelXp is null/undefined but nextLevelXp
exists, and always clamp the result to the 0–100 range; then update XpProgress
(pct) and the header badge to call this function so they share the same clamped
logic.
In `@web/src/app/dashboard/members/members-client.tsx`:
- Around line 239-245: The error alert currently renders when the fetchMembers()
call fails but offers no retry path; update the error UI inside the
MembersClient component (the block that renders when error is truthy) to include
a retry action/button that calls fetchMembers() (or a small wrapper like
handleRetry that re-invokes fetchMembers and resets relevant state such as
error/loading). Ensure the button is accessible (e.g., labeled "Retry") and
placed alongside the error message so users can re-run the same request without
changing filters or leaving the route.
In `@web/src/app/dashboard/tickets/tickets-client.tsx`:
- Around line 119-125: When filters change (search or status) reset pagination
so results don't request an out-of-range page: update the debounce effect that
uses searchTimerRef and setDebouncedSearch to also call setPage(1) when it
applies the debounced search, and likewise update the status handler (the
function that sets status/filter around setStatus or similar at the other
location) to call setPage(1) whenever the status/filter is changed; ensure you
only reset page when actually changing a filter value to avoid unnecessary
reloads.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: e52ec246-a5e3-4293-bdc6-11aa58399d19
📒 Files selected for processing (18)
web/src/app/dashboard/audit-log/page.tsxweb/src/app/dashboard/conversations/conversations-client.tsxweb/src/app/dashboard/members/[userId]/page.tsxweb/src/app/dashboard/members/members-client.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/components/dashboard/analytics-dashboard-sections.tsxweb/src/components/dashboard/analytics-dashboard.tsxweb/src/components/dashboard/case-table.tsxweb/src/components/dashboard/dashboard-card.tsxweb/src/components/dashboard/health-cards.tsxweb/src/components/dashboard/member-table.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/layout/header.tsxweb/src/components/ui/neo-minimal-footer.tsxweb/tests/components/landing/comparison-table.test.tsxweb/tests/components/landing/footer.test.tsxweb/tests/components/landing/pricing.test.tsx
💤 Files with no reviewable changes (1)
- web/src/components/dashboard/analytics-dashboard-sections.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes
Always include semicolons at the end of statements
Use 2-space indentation (spaces, not tabs)
Always include trailing commas in multi-line arrays, objects, and function parameters
Maintain a maximum line width of 100 characters
Files:
web/src/components/dashboard/case-table.tsxweb/src/components/dashboard/dashboard-card.tsxweb/src/app/dashboard/members/members-client.tsxweb/src/components/dashboard/member-table.tsxweb/src/app/dashboard/audit-log/page.tsxweb/src/app/dashboard/conversations/conversations-client.tsxweb/src/components/dashboard/health-cards.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/analytics-dashboard.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/members/[userId]/page.tsx
web/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Never use
console.*methods in web dashboard code; use appropriate logging mechanisms for React applications
Files:
web/src/components/dashboard/case-table.tsxweb/src/components/dashboard/dashboard-card.tsxweb/src/app/dashboard/members/members-client.tsxweb/src/components/dashboard/member-table.tsxweb/src/app/dashboard/audit-log/page.tsxweb/src/app/dashboard/conversations/conversations-client.tsxweb/src/components/dashboard/health-cards.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/analytics-dashboard.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/members/[userId]/page.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*
Files:
web/src/components/dashboard/case-table.tsxweb/src/components/dashboard/dashboard-card.tsxweb/src/app/dashboard/members/members-client.tsxweb/src/components/dashboard/member-table.tsxweb/src/app/dashboard/audit-log/page.tsxweb/src/app/dashboard/conversations/conversations-client.tsxweb/src/components/dashboard/health-cards.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/analytics-dashboard.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/members/[userId]/page.tsx
web/src/app/dashboard/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
For dashboard routes, add a matcher entry to
dashboardTitleMatchersinweb/src/lib/page-titles.ts: use exact equality for leaf routes (pathname === '/dashboard/my-route') and subtree checks (pathname.startsWith('/dashboard/my-route/')); exportmetadatausingcreatePageMetadata(title)for SSR entry points
Files:
web/src/app/dashboard/members/members-client.tsxweb/src/app/dashboard/audit-log/page.tsxweb/src/app/dashboard/conversations/conversations-client.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/members/[userId]/page.tsx
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/app/dashboard/**/*.tsx : For dashboard routes, add a matcher entry to `dashboardTitleMatchers` in `web/src/lib/page-titles.ts`: use exact equality for leaf routes (`pathname === '/dashboard/my-route'`) and subtree checks (`pathname.startsWith('/dashboard/my-route/')`); export `metadata` using `createPageMetadata(title)` for SSR entry points
Applied to files:
web/src/components/dashboard/case-table.tsxweb/src/components/dashboard/dashboard-card.tsxweb/src/components/dashboard/member-table.tsxweb/src/app/dashboard/audit-log/page.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/analytics-dashboard.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/members/[userId]/page.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)
Applied to files:
web/src/components/dashboard/case-table.tsxweb/src/components/dashboard/dashboard-card.tsxweb/src/components/dashboard/health-cards.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/members/[userId]/page.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'
Applied to files:
web/src/components/dashboard/dashboard-card.tsxweb/src/app/dashboard/members/members-client.tsxweb/src/app/dashboard/audit-log/page.tsxweb/src/components/dashboard/health-cards.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/analytics-dashboard.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/members/[userId]/page.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to web/src/pages/dashboard/**/*.{ts,tsx} : Use shared title helpers from web/src/lib/page-titles.ts for setting browser titles in dashboard pages
Applied to files:
web/src/components/dashboard/dashboard-card.tsxweb/src/app/dashboard/members/members-client.tsxweb/src/components/dashboard/member-table.tsxweb/src/app/dashboard/audit-log/page.tsxweb/src/components/dashboard/health-cards.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/analytics-dashboard.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/members/[userId]/page.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to {web/src/lib/page-titles.ts,web/src/**/+page.ts,web/src/**/+page.svelte} : New dashboard routes need title wiring in web/src/lib/page-titles.ts: use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation.
Applied to files:
web/src/components/dashboard/dashboard-card.tsxweb/src/app/dashboard/members/members-client.tsxweb/src/app/dashboard/audit-log/page.tsxweb/src/app/dashboard/conversations/conversations-client.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/analytics-dashboard.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/members/[userId]/page.tsx
📚 Learning: 2026-03-11T05:32:46.325Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T05:32:46.325Z
Learning: Applies to web/src/app/**/*.{ts,tsx} : Apply static metadata to server-rendered dashboard entry pages and use title template format for root app metadata
Applied to files:
web/src/components/dashboard/dashboard-card.tsxweb/src/components/dashboard/member-table.tsxweb/src/app/dashboard/audit-log/page.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/analytics-dashboard.tsxweb/src/app/dashboard/temp-roles/page.tsxweb/src/app/dashboard/members/[userId]/page.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/**/*.{ts,tsx} : Never use `console.*` methods in web dashboard code; use appropriate logging mechanisms for React applications
Applied to files:
web/src/components/dashboard/dashboard-card.tsxweb/src/app/dashboard/audit-log/page.tsxweb/src/components/dashboard/health-cards.tsxweb/src/app/dashboard/tickets/tickets-client.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/analytics-dashboard.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to src/api/ws/*.js : Bind audit-log WebSocket auth tickets to guild context by including nonce.expiry.guildId.hmac in ticket validation
Applied to files:
web/src/app/dashboard/audit-log/page.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to web/src/app/api/**/route.ts : Include guildId in signed WebSocket ticket payload when issuing tickets from dashboard endpoints
Applied to files:
web/src/app/dashboard/conversations/conversations-client.tsxweb/src/app/dashboard/tickets/tickets-client.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to src/api/ws/**/*.js : Enforce tenant scoping on WebSocket handlers: reject mismatched guildId in filters and require entry.guild_id === ws.guildId for broadcast matching
Applied to files:
web/src/app/dashboard/conversations/conversations-client.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to src/api/ws/logStream.js : Implement backward-compatible WebSocket ticket parsing to accept both legacy 3-part and new 4-part tickets for maintaining existing log stream behavior
Applied to files:
web/src/app/dashboard/tickets/tickets-client.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to src/api/routes/*.js : Add adaptDeleteGuildIdParam in API route handlers to enforce guild moderation checks on DELETE requests without losing record id
Applied to files:
web/src/app/dashboard/members/[userId]/page.tsx
🔇 Additional comments (17)
web/src/components/dashboard/performance-dashboard.tsx (12)
163-163: Previous feedback not addressed: preferglobalThisoverwindow.Static analysis and prior review flagged usage of
windowat lines 163, 184, 192-195, and 200-201. While functionally equivalent in'use client'components,globalThisis the modern standard.
240-245: Previous feedback not addressed: merge heap and RSS samples by timestamp, not array index.The current implementation pairs
memoryHeapMb[i]withmemoryRssMb[i], which will misalign data if either array has missing samples. This issue was previously flagged with a suggested fix using a timestamp-keyedMapto merge the series correctly.
483-484: Previous feedback not addressed: inconsistent capitalization.The heading "latency Distribution" should be "Latency Distribution" to match other chart headings (e.g., "Memory Usage", "CPU Utilization").
595-618: Previous feedback not addressed: threshold input validation.The input
onChangehandler still usesNumber(e.target.value), which converts empty strings to0and invalid input toNaN. The save button remains enabled regardless of validation state, allowing invalid or zero thresholds to be submitted.The prior review provided a detailed fix using
valueAsNumber, validation logic, and conditional button disabling.
1-10: LGTM!Imports are clean, properly using single quotes and ESM syntax. The switch to
StableResponsiveContaineraligns with the refactoring goals.
12-59: LGTM!Type definitions are comprehensive and well-structured for the performance monitoring domain.
61-74: LGTM!Helper functions are concise and handle formatting correctly.
87-141: LGTM!The
StatCardcomponent has been cleanly refactored with the new glassmorphic styling. The conditional alert state handling and loading skeleton are well-implemented.
253-260: LGTM!The histogram bucketing logic correctly groups response times into 500ms intervals and sorts them numerically.
347-416: LGTM!The Memory Usage chart implementation is clean with proper empty state handling and consistent glassmorphic styling.
544-563: LGTM!The duplicate React key issue has been properly addressed by including the array index in the key. Row hover highlighting aligns with PR feedback.
210-238: LGTM!The
saveThresholdsfunction has proper error handling and user feedback via toast notifications. The validation concern is addressed in the input handling comment.web/src/components/dashboard/health-cards.tsx (5)
36-43: Mark the props of the component as read-only.This was flagged in a previous review. The props object should use TypeScript's
readonlymodifier to prevent accidental mutation and signal intent.
84-90: Don't derive the progress-fill Tailwind class at runtime.This was flagged in a previous review.
iconColor.replace('text-', 'bg-')generatesbg-*classes that Tailwind cannot statically discover. Classes likebg-primary/60,bg-emerald-500/40, andbg-muted-foreground/40will be purged from the production bundle.Pass an explicit
progressClassprop or use an inline style instead.
1-26: LGTM!Imports and utility function are well-structured with proper formatting conventions.
98-109: LGTM!The memory and CPU percentage calculations are well-guarded with null-safe access and proper clamping (
Math.min(Math.max(rawPct, 0), 100)). The 4GB max for memory percentage aligns with the PR feedback to fix the misleadingly high percentage issue.
111-195: LGTM!The StatCard grid layout and individual card configurations are well-structured. The
loading && !healthpattern correctly shows skeletons during initial load while preserving existing data during refreshes.
|
Too many files changed for review. ( |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 71 out of 124 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
web/next.config.mjs:1
allowedDevOriginstypically expects full origins (scheme + host [+ port]), not just a hostname. With['127.0.0.1'], Next dev/HMR may still be rejected depending on how Next matches origins. Consider using explicit origins likehttp://127.0.0.1:6969(and/orhttp://localhost:6969) to ensure dev requests are allowed. Also, this file mixes quote styles inside the template string; if Biome is enforcing single quotes, this may fail lint.
import path from "node:path";
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 @.github/workflows/claude-review.yml:
- Around line 166-167: The --allowedTools entry currently grants overly broad
GitHub API access via Bash(gh api:*); update the claude_args allowedTools string
so it only includes the exact API calls needed for PR reviews instead of the
wildcard. Replace Bash(gh api:*) with specific endpoints such as Bash(gh
api:repos/*/pulls/*/reviews) (or the exact repo/pull pattern your workflow will
call), and remove any other unnecessary wildcards so only the minimal GitHub API
calls required for creating comments/reviews are permitted; ensure the
allowedTools still contains the existing
mcp__github_inline_comment__create_inline_comment and the gh pr
comment/review/view/diff entries.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9baaedea-8651-49bf-b080-3aa72ba31d88
📒 Files selected for processing (1)
.github/workflows/claude-review.yml
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)
🔇 Additional comments (1)
.github/workflows/claude-review.yml (1)
59-59: Prompt wording update is safe.Line 59 is a non-functional copy edit and does not alter workflow behavior.
|
❌ The last analysis has failed. |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (8)
web/src/components/dashboard/config-categories/moderation-safety.tsx (1)
61-70:⚠️ Potential issue | 🟠 MajorSeed
activeTabwhen moderation tabs become visible.If
visibleFeatureIdsis empty during the initial render,activeTabstaysnullforever because this effect only repairs hidden non-null tabs. Lines 203-204 then keep the category blank after permissions load.Also applies to: 202-204
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-categories/moderation-safety.tsx` around lines 61 - 70, The current effect only fixes activeTab when it is non-null but hidden; to handle the case where visibleFeatureIds is empty on mount and later becomes populated, update the effect watching visibleFeatureIds/availableTabs to set activeTab to availableTabs[0]?.id when either activeTab is null or activeTab is not in visibleFeatureIds. Concretely, in the useEffect that references availableTabs, activeTab, visibleFeatureIds, change the condition to something like: if ((!activeTab || !visibleFeatureIds.has(activeTab)) && availableTabs[0]) { setActiveTab(availableTabs[0].id as ConfigFeatureId) } so the first visible tab is seeded once tabs become available.web/src/components/dashboard/config-categories/onboarding-growth.tsx (3)
419-429:⚠️ Potential issue | 🟡 MinorPersist DM sequence edits before unmount, not only on blur.
dmStepsRawonly gets copied intodraftConfiginsideonBlur. If this textarea unmounts before blur completes, the typed steps are lost even though the user already edited them.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-categories/onboarding-growth.tsx` around lines 419 - 429, The textarea only persists dmStepsRaw to draftConfig onBlur, so edits are lost if the component unmounts before blur; add a synchronization on unmount (and optionally on dmStepsRaw changes) that parses dmStepsRaw (split, trim, filter(Boolean)) and calls updateWelcomeDmSequence('steps', parsed) and normalizes dmStepsRaw via setDmStepsRaw(parsed.join('\n')) so typed steps are persisted; implement this in a useEffect cleanup or an effect that runs on component unmount and references dmStepsRaw, setDmStepsRaw, and updateWelcomeDmSequence to locate the logic.
22-53:⚠️ Potential issue | 🟠 Major
tldr-afkstill needs explicit mapping back to the real feature ids.
availableTabsonly checks for the synthetic'tldr-afk'id, so this tab disappears whenevervisibleFeatureIdscontains only'tldr'or'afk'. Because the effect also never seeds a missing tab, Lines 205-207 can blank the whole category after visibility loads.Also applies to: 109-126, 205-207
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-categories/onboarding-growth.tsx` around lines 22 - 53, TABS defines a synthetic id 'tldr-afk' but availableTabs only tests for that synthetic id, causing the tab to vanish if visibleFeatureIds contains only 'tldr' or 'afk'; update the logic that computes availableTabs and the effect that seeds tabs (referencing availableTabs and the effect using visibleFeatureIds) to treat 'tldr-afk' as mapped to the real feature ids ('tldr' OR 'afk') — i.e., consider the tab available when visibleFeatureIds includes either 'tldr' or 'afk', and when seeding/setting visible tabs ensure you add the 'tldr-afk' tab if either real feature is present so the category doesn't blank out.
535-617:⚠️ Potential issue | 🟠 MajorUse stable ids for activity badges instead of the array index.
Inserting or deleting a badge will cause React to reuse the wrong row because Line 559 keys by
index, and new badges on Lines 540-543 still do not get an id.generateIdis already imported here, so this flow is very close to being fixable.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-categories/onboarding-growth.tsx` around lines 535 - 617, The activity badges use array index as React keys and new badges are created without ids; change the add flow in the + Add Tier handler (where you create the new badge in draftConfig.engagement.activityBadges) to assign a stable id using generateId(), ensure each badge object includes this id, update all badge updates (number and label handlers) and deletes to preserve and operate on the id property, and change the .map key from index to badge.id so React can track rows stably; update the badge type usage (in the map and handlers) to include id to make these changes consistent with updateDraftConfig and generateId.web/src/components/dashboard/config-categories/ai-automation.tsx (2)
47-56:⚠️ Potential issue | 🟠 MajorSeed
activeTabwhen tabs arrive after mount.
useStatesnapshotsavailableTabs[0]only once. If visibility loads later,activeTabstaysnullbecause this effect only repairs invalid non-null tabs, so Line 171 can blank the entire category until remount.Also applies to: 170-172
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-categories/ai-automation.tsx` around lines 47 - 56, The initial state for activeTab can stay null but you must seed it when availableTabs becomes populated after mount: update the useEffect that currently checks only "if (activeTab && !visibleFeatureIds.has(activeTab))" to also set activeTab when it is null and availableTabs has at least one entry (e.g., if (!activeTab && availableTabs.length > 0) setActiveTab((availableTabs[0]?.id as ConfigFeatureId) ?? null)), and ensure the effect's dependencies reflect visibleFeatureIds and availableTabs.length (or recompute availableTabs inside the effect) so the effect runs when tabs arrive; reference variables: availableTabs, activeTab, setActiveTab, visibleFeatureIds, TABS.
266-281:⚠️ Potential issue | 🟠 MajorGuard these channel pickers until
guildIdresolves.The AI Chat pane already hides its selectors behind
guildId, but the Content Safety and Triage panes still renderChannelSelectorwith a potentially unresolved guild id. That can trigger failed fetches during initial guild bootstrap.Also applies to: 460-475
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-categories/ai-automation.tsx` around lines 266 - 281, The ChannelSelector components are rendered before guildId is guaranteed, causing fetch failures; guard these selectors by checking guildId before rendering or disabling them until it resolves. Wrap each ChannelSelector (e.g., the ones using draftConfig.aiAutoMod?.flagChannelId and the other occurrence) in a conditional like guildId && <ChannelSelector ... /> or pass disabled={saving || !guildId} and ensure onChange/updateAiAutoModField('flagChannelId', ...) only runs when guildId is present. Apply the same guard to the other ChannelSelector instances referenced in this file.web/src/components/dashboard/config-categories/support-integrations.tsx (1)
32-45:⚠️ Potential issue | 🟠 MajorSeed
activeTabwhen tabs become visible.If
visibleFeatureIdsis empty on the first render,useStatecapturesnulland this effect never repairs it. Lines 44-45 can keep the whole category blank even after the available tabs load.Suggested fix
useEffect(() => { - if (activeTab && !visibleFeatureIds.has(activeTab)) { - setActiveTab((availableTabs[0]?.id as ConfigFeatureId) ?? null); - } + if (!availableTabs.length) { + setActiveTab(null); + return; + } + if (!activeTab || !visibleFeatureIds.has(activeTab)) { + setActiveTab((availableTabs[0]?.id as ConfigFeatureId) ?? null); + } }, [visibleFeatureIds, activeTab, availableTabs]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-categories/support-integrations.tsx` around lines 32 - 45, The component seeds activeTab only on initial useState which can remain null if visibleFeatureIds is empty; modify the effect watching visibleFeatureIds/availableTabs to also set activeTab when it's null (or not set) by calling setActiveTab((availableTabs[0]?.id as ConfigFeatureId) ?? null) so that when availableTabs becomes non-empty the first tab is selected; update the useEffect that references visibleFeatureIds, activeTab, and availableTabs (and the initial useState for activeTab remains) to handle the case activeTab === null and availableTabs.length > 0.web/src/components/dashboard/config-categories/community-tools.tsx (1)
12-25:⚠️ Potential issue | 🟠 MajorThe
community-toolstab is still gated by a synthetic id.The first tab groups several real features, but Lines 33-36 only expose it when
visibleFeatureIdscontains the synthetic'community-tools'id. Combined with the one-shotactiveTabinitialization, Lines 44-46 can hide the whole category even though the underlying command features are available.Also applies to: 33-46
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-categories/community-tools.tsx` around lines 12 - 25, The tab visibility currently checks for the synthetic id 'community-tools' in visibleFeatureIds and also initializes activeTab to that synthetic id, which can hide a real-feature group; change the logic to derive visibility from the actual underlying feature ids instead of the synthetic group id. Create a small mapping for the grouped tab (e.g., for TABS entry id 'community-tools' map to an array of real feature ids) and replace checks like visibleFeatureIds.includes('community-tools') with a test that any of those mapped ids exist in visibleFeatureIds (Array.prototype.some). Also update the activeTab initialization and any tab-selection fallback logic (the activeTab state) to pick the first tab that passes this real-feature visibility test rather than defaulting to the synthetic id.
🤖 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-categories/config-category-layout.tsx`:
- Around line 57-66: The tab button removed the default focus indicator via the
'outline-none' class in the ConfigCategoryLayout component, so keyboard users
lose a visible focus cue; restore an accessible focus-visible style by replacing
'outline-none' with a focus-visible utility (e.g., add classes like
'focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2' or
similar) on the same button element that uses cn (the button rendering the tabs
that calls onTabChange with tab.id and uses isActive to determine styles) so
focused tabs show a clear visual ring while preserving current active styling.
In `@web/src/components/dashboard/config-categories/onboarding-growth.tsx`:
- Around line 843-875: The input onChange handler for the TL;DR fields
(reading/updating draftConfig.tldr via updateDraftConfig) currently ignores
cfg.min and cfg.max; fix it by parsing the incoming value, clamping it between
cfg.min and cfg.max (e.g., value = Math.max(cfg.min, Math.min(cfg.max,
parsedValue))) and writing that clamped value back into tldr[cfg.key]; also
adjust the input value expression to default to cfg.min (not a hardcoded 50)
when the stored value is undefined so the UI always reflects the declared
bounds.
---
Duplicate comments:
In `@web/src/components/dashboard/config-categories/ai-automation.tsx`:
- Around line 47-56: The initial state for activeTab can stay null but you must
seed it when availableTabs becomes populated after mount: update the useEffect
that currently checks only "if (activeTab && !visibleFeatureIds.has(activeTab))"
to also set activeTab when it is null and availableTabs has at least one entry
(e.g., if (!activeTab && availableTabs.length > 0)
setActiveTab((availableTabs[0]?.id as ConfigFeatureId) ?? null)), and ensure the
effect's dependencies reflect visibleFeatureIds and availableTabs.length (or
recompute availableTabs inside the effect) so the effect runs when tabs arrive;
reference variables: availableTabs, activeTab, setActiveTab, visibleFeatureIds,
TABS.
- Around line 266-281: The ChannelSelector components are rendered before
guildId is guaranteed, causing fetch failures; guard these selectors by checking
guildId before rendering or disabling them until it resolves. Wrap each
ChannelSelector (e.g., the ones using draftConfig.aiAutoMod?.flagChannelId and
the other occurrence) in a conditional like guildId && <ChannelSelector ... />
or pass disabled={saving || !guildId} and ensure
onChange/updateAiAutoModField('flagChannelId', ...) only runs when guildId is
present. Apply the same guard to the other ChannelSelector instances referenced
in this file.
In `@web/src/components/dashboard/config-categories/community-tools.tsx`:
- Around line 12-25: The tab visibility currently checks for the synthetic id
'community-tools' in visibleFeatureIds and also initializes activeTab to that
synthetic id, which can hide a real-feature group; change the logic to derive
visibility from the actual underlying feature ids instead of the synthetic group
id. Create a small mapping for the grouped tab (e.g., for TABS entry id
'community-tools' map to an array of real feature ids) and replace checks like
visibleFeatureIds.includes('community-tools') with a test that any of those
mapped ids exist in visibleFeatureIds (Array.prototype.some). Also update the
activeTab initialization and any tab-selection fallback logic (the activeTab
state) to pick the first tab that passes this real-feature visibility test
rather than defaulting to the synthetic id.
In `@web/src/components/dashboard/config-categories/moderation-safety.tsx`:
- Around line 61-70: The current effect only fixes activeTab when it is non-null
but hidden; to handle the case where visibleFeatureIds is empty on mount and
later becomes populated, update the effect watching
visibleFeatureIds/availableTabs to set activeTab to availableTabs[0]?.id when
either activeTab is null or activeTab is not in visibleFeatureIds. Concretely,
in the useEffect that references availableTabs, activeTab, visibleFeatureIds,
change the condition to something like: if ((!activeTab ||
!visibleFeatureIds.has(activeTab)) && availableTabs[0]) {
setActiveTab(availableTabs[0].id as ConfigFeatureId) } so the first visible tab
is seeded once tabs become available.
In `@web/src/components/dashboard/config-categories/onboarding-growth.tsx`:
- Around line 419-429: The textarea only persists dmStepsRaw to draftConfig
onBlur, so edits are lost if the component unmounts before blur; add a
synchronization on unmount (and optionally on dmStepsRaw changes) that parses
dmStepsRaw (split, trim, filter(Boolean)) and calls
updateWelcomeDmSequence('steps', parsed) and normalizes dmStepsRaw via
setDmStepsRaw(parsed.join('\n')) so typed steps are persisted; implement this in
a useEffect cleanup or an effect that runs on component unmount and references
dmStepsRaw, setDmStepsRaw, and updateWelcomeDmSequence to locate the logic.
- Around line 22-53: TABS defines a synthetic id 'tldr-afk' but availableTabs
only tests for that synthetic id, causing the tab to vanish if visibleFeatureIds
contains only 'tldr' or 'afk'; update the logic that computes availableTabs and
the effect that seeds tabs (referencing availableTabs and the effect using
visibleFeatureIds) to treat 'tldr-afk' as mapped to the real feature ids ('tldr'
OR 'afk') — i.e., consider the tab available when visibleFeatureIds includes
either 'tldr' or 'afk', and when seeding/setting visible tabs ensure you add the
'tldr-afk' tab if either real feature is present so the category doesn't blank
out.
- Around line 535-617: The activity badges use array index as React keys and new
badges are created without ids; change the add flow in the + Add Tier handler
(where you create the new badge in draftConfig.engagement.activityBadges) to
assign a stable id using generateId(), ensure each badge object includes this
id, update all badge updates (number and label handlers) and deletes to preserve
and operate on the id property, and change the .map key from index to badge.id
so React can track rows stably; update the badge type usage (in the map and
handlers) to include id to make these changes consistent with updateDraftConfig
and generateId.
In `@web/src/components/dashboard/config-categories/support-integrations.tsx`:
- Around line 32-45: The component seeds activeTab only on initial useState
which can remain null if visibleFeatureIds is empty; modify the effect watching
visibleFeatureIds/availableTabs to also set activeTab when it's null (or not
set) by calling setActiveTab((availableTabs[0]?.id as ConfigFeatureId) ?? null)
so that when availableTabs becomes non-empty the first tab is selected; update
the useEffect that references visibleFeatureIds, activeTab, and availableTabs
(and the initial useState for activeTab remains) to handle the case activeTab
=== null and availableTabs.length > 0.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 80323645-b98f-4d8b-a939-d8ee6dc98ea7
📒 Files selected for processing (6)
web/src/components/dashboard/config-categories/ai-automation.tsxweb/src/components/dashboard/config-categories/community-tools.tsxweb/src/components/dashboard/config-categories/config-category-layout.tsxweb/src/components/dashboard/config-categories/moderation-safety.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsxweb/src/components/dashboard/config-categories/support-integrations.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,ts,tsx}: Use single quotes for strings (except in JSON files); no double quotes
Always include semicolons at the end of statements
Use 2-space indentation (spaces, not tabs)
Always include trailing commas in multi-line arrays, objects, and function parameters
Maintain a maximum line width of 100 characters
Files:
web/src/components/dashboard/config-categories/community-tools.tsxweb/src/components/dashboard/config-categories/config-category-layout.tsxweb/src/components/dashboard/config-categories/support-integrations.tsxweb/src/components/dashboard/config-categories/ai-automation.tsxweb/src/components/dashboard/config-categories/moderation-safety.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsx
web/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Never use
console.*methods in web dashboard code; use appropriate logging mechanisms for React applications
Files:
web/src/components/dashboard/config-categories/community-tools.tsxweb/src/components/dashboard/config-categories/config-category-layout.tsxweb/src/components/dashboard/config-categories/support-integrations.tsxweb/src/components/dashboard/config-categories/ai-automation.tsxweb/src/components/dashboard/config-categories/moderation-safety.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: ESM only - do not use CommonJS modules
Use src/logger.js; do not use console.*
Files:
web/src/components/dashboard/config-categories/community-tools.tsxweb/src/components/dashboard/config-categories/config-category-layout.tsxweb/src/components/dashboard/config-categories/support-integrations.tsxweb/src/components/dashboard/config-categories/ai-automation.tsxweb/src/components/dashboard/config-categories/moderation-safety.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsx
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with reusable SettingsFeatureCard pattern (header + master toggle + Basic/Advanced blocks)
Applied to files:
web/src/components/dashboard/config-categories/community-tools.tsxweb/src/components/dashboard/config-categories/config-category-layout.tsxweb/src/components/dashboard/config-categories/support-integrations.tsxweb/src/components/dashboard/config-categories/ai-automation.tsxweb/src/components/dashboard/config-categories/moderation-safety.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard page titles should sync with route changes using DashboardTitleSync component mounted in dashboard-shell.tsx and canonical title string 'Volvox.Bot - AI Powered Discord Bot'
Applied to files:
web/src/components/dashboard/config-categories/community-tools.tsxweb/src/components/dashboard/config-categories/config-category-layout.tsxweb/src/components/dashboard/config-categories/support-integrations.tsxweb/src/components/dashboard/config-categories/ai-automation.tsxweb/src/components/dashboard/config-categories/moderation-safety.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to src/**/*.{js,ts} : Community features should be gated behind config.<feature>.enabled. Moderation commands are the exception.
Applied to files:
web/src/components/dashboard/config-categories/community-tools.tsxweb/src/components/dashboard/config-categories/ai-automation.tsxweb/src/components/dashboard/config-categories/moderation-safety.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to web/src/pages/dashboard/**/*.{ts,tsx} : Use shared title helpers from web/src/lib/page-titles.ts for setting browser titles in dashboard pages
Applied to files:
web/src/components/dashboard/config-categories/community-tools.tsxweb/src/components/dashboard/config-categories/config-category-layout.tsxweb/src/components/dashboard/config-categories/support-integrations.tsxweb/src/components/dashboard/config-categories/ai-automation.tsxweb/src/components/dashboard/config-categories/moderation-safety.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsx
📚 Learning: 2026-03-11T05:32:46.325Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T05:32:46.325Z
Learning: Applies to web/src/app/**/*.{ts,tsx} : Apply static metadata to server-rendered dashboard entry pages and use title template format for root app metadata
Applied to files:
web/src/components/dashboard/config-categories/config-category-layout.tsxweb/src/components/dashboard/config-categories/moderation-safety.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsx
📚 Learning: 2026-03-26T00:04:14.693Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T00:04:14.693Z
Learning: Applies to {web/src/lib/page-titles.ts,web/src/**/+page.ts,web/src/**/+page.svelte} : New dashboard routes need title wiring in web/src/lib/page-titles.ts: use createPageMetadata() for SSR and keep DashboardTitleSync aligned for client navigation.
Applied to files:
web/src/components/dashboard/config-categories/config-category-layout.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to web/src/app/dashboard/**/*.tsx : For dashboard routes, add a matcher entry to `dashboardTitleMatchers` in `web/src/lib/page-titles.ts`: use exact equality for leaf routes (`pathname === '/dashboard/my-route'`) and subtree checks (`pathname.startsWith('/dashboard/my-route/')`); export `metadata` using `createPageMetadata(title)` for SSR entry points
Applied to files:
web/src/components/dashboard/config-categories/config-category-layout.tsxweb/src/components/dashboard/config-categories/moderation-safety.tsxweb/src/components/dashboard/config-categories/onboarding-growth.tsx
📚 Learning: 2026-03-11T06:42:38.728Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T06:42:38.728Z
Learning: Applies to web/src/app/api/**/route.ts : Include guildId in signed WebSocket ticket payload when issuing tickets from dashboard endpoints
Applied to files:
web/src/components/dashboard/config-categories/ai-automation.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to src/api/ws/**/*.js : Enforce tenant scoping on WebSocket handlers: reject mismatched guildId in filters and require entry.guild_id === ws.guildId for broadcast matching
Applied to files:
web/src/components/dashboard/config-categories/ai-automation.tsx
📚 Learning: 2026-03-10T23:21:49.730Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T23:21:49.730Z
Learning: Applies to src/api/routes/*.js : Add adaptDeleteGuildIdParam in API route handlers to enforce guild moderation checks on DELETE requests without losing record id
Applied to files:
web/src/components/dashboard/config-categories/ai-automation.tsx
📚 Learning: 2026-03-12T02:03:36.493Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-12T02:03:36.493Z
Learning: Applies to config.json : All community features in `config.json` should be gated behind a `config.<feature>.enabled` flag; moderation commands are always available regardless of config settings
Applied to files:
web/src/components/dashboard/config-categories/moderation-safety.tsx
🔇 Additional comments (1)
web/src/components/dashboard/config-categories/moderation-safety.tsx (1)
251-266: Gate the channel and role selectors onguildId.This category always mounts
ChannelSelectorandRoleSelectorwith the rawguildId, while other panes in the same PR already wait for a resolved guild id. During guild bootstrap these controls can query against an invalid guild and come up empty or error.Also applies to: 548-555, 572-581, 656-664, 681-698, 708-725
web/src/components/dashboard/config-categories/config-category-layout.tsx
Show resolved
Hide resolved
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@claude review |
|
Claude finished @BillChirico's task in 7m 43s —— View job Claude Code Review — PR #390Issues Found1. This component uses React hooks ( Add as the very first line: 'use client';2. The last migration in the repo is
Rename to 3. This PR removes the three application services ( If the intent is to run these services another way (e.g. |























…and cinematic interactions
Describe your changes
Please describe your changes in detail.
Issue ticket number and link
What type of PR is this? (check all applicable)