Skip to content

feat(theme): dark mode + UI primitives standardization#2046

Merged
senamakel merged 50 commits into
tinyhumansai:mainfrom
senamakel:feat/dark-mode-theming
May 18, 2026
Merged

feat(theme): dark mode + UI primitives standardization#2046
senamakel merged 50 commits into
tinyhumansai:mainfrom
senamakel:feat/dark-mode-theming

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented May 18, 2026

Summary

  • Ships dark mode across the entire desktop app: theme slice (light / dark / system), ThemeProvider that syncs Redux state to <html class=\"dark\"> and live-listens to prefers-color-scheme, and a new Settings → Appearance panel for toggling.
  • Extends app/src/styles/theme.css with a full :root.dark block (background, surface, border, text hierarchy, hover/active/focus, success/warning/error/info) plus dark overrides for the .card-* / .btn-* / .input-* / .nav-* / .skeleton / .glass-surface / .scrollbar-elegant / .skills-table-* component shortcuts in index.css.
  • MeshGradient (animated WebGL bg) and the dotted-canvas overlay flip palettes with the theme; body/html bg and text now read from var(--color-*) instead of hardcoded #f5f5f5 / #171717.
  • Standardizes the shared UI primitives: extracts a small app/src/components/ui/ (Button, Card, Input) with first-class light/dark variants and a barrel re-export.
  • Per-component dark variants applied across every settings panel (general, billing, AI, autocomplete, voice, screen intelligence, tools, messaging, notifications, mascot, appearance, team, recovery phrase, debug panels, composio, memory data/debug, etc.), all chat surfaces (Conversations, message bubbles, tool timeline, kanban, worker-thread subagent chip, token usage pill), Home + Notifications + banners, BootCheckGate ("Select a Runtime" picker), OAuth provider buttons (Google / GitHub / Twitter), Channels grid, Intelligence (memory workspace + insights + sources + sync), Rewards (community / coupons / referrals), Connections, Skills (incl. Composio tiles), onboarding wizard (Runtime / API keys / Skills / Local AI / Context / Referral / Welcome / Beta banner), and the chat backdrop / agent message bubble (now uses dark:prose-invert).

Problem

The frontend was light-only. Users requested a dark mode, and the codebase had ~600 raw Tailwind color tokens (bg-white, text-stone-700, border-stone-200, …) sprinkled across 200+ files without paired dark: variants. There was a partial :root.dark block scoped only to the command palette, no theme state, no toggle UI, and the body/html stylesheet hardcoded the light bg so even Tailwind's darkMode: 'class' couldn't reach it. Theme tokens were also fragmented (raw stones, semantic vars in theme.css, ad-hoc .btn-premium / .card-elevated shortcuts) — so dark mode and component standardization needed to land together.

Solution

Four-stage rollout, then incremental component patching:

  1. Foundation — extend theme.css with a full :root.dark token block + component-shortcut overrides; fix the missing import (theme.css was never imported from main.tsx — a silent dead file before this PR); make body bg/text read from CSS vars; switch dotted-canvas overlay color in dark mode; teach MeshGradient to swap its color stops based on <html class=\"dark\"> via a MutationObserver.
  2. State — new themeSlice (light/dark/system, persisted in localStorage); ThemeProvider mounted under PersistGate that syncs the Redux mode to <html class=\"dark\"> and live-listens to prefers-color-scheme when in system mode (covers Safari < 14's deprecated addListener API).
  3. Settings — new AppearancePanel at /settings/appearance with light/dark/system radio group; wired into useSettingsNavigation, the Settings router, and the General-section entry in SettingsHome.
  4. Primitivesui/Button (existing) gets dark variants for all four variants; new ui/Card (surface/elevated/outlined/subtle) and ui/Input (sm/md/lg, invalid state) primitives, all dark-aware out of the box.

Then per-screen patches: shell first (BottomTabBar, SettingsHeader, SettingsMenuItem, PageBackButton, WrappedSettingsPage), then home/chat (page wrappers, agent message bubble, token usage pill, banners), then settings panels (12 panels in 4 commits via a subagent, then a second sweep over the remaining missed panels), then overlays (UpsellBanner, UsageLimitModal, NotificationCard/Center, WalkthroughTooltip, OpenhumanLinkModal, PersistRehydrationScreen), then feature pages (Skills/Channels/Rewards/Intelligence/Accounts/Conversations sidebar), then onboarding, then the BootCheckGate runtime picker.

Bulk sweeps for stone/gray/sage/amber/coral/primary/emerald/rose/lavender palette pairings use a negative-lookahead perl -i -pe pass: \\bclass-N(?!\\d)(?!\\S*dark:) adds the dark variant only when there's no dark: sibling already, and (?!\\d) prevents a bg-stone-50 regex from matching the bg-stone-50 prefix of bg-stone-500. Mis-pairings between hover: / placeholder: modifiers and their dark: variants were swept across the codebase (hover:bg-stone-50 dark:bg-neutral-X rewritten to hover:bg-stone-50 dark:hover:bg-neutral-X). Two corruption rounds from an earlier broken regex were also undone (\bbg-primary-50 was matching bg-primary-500 as a prefix and producing bg-primary-50 dark:bg-primary-500/150).

The "select a runtime" picker (BootCheckGate) and onboarding ChoiceCards both use !-important borders, so their dark variants also needed ! (otherwise Tailwind's default border-color overrode the dark variant and the unselected card border read as white). The token usage pill, ToolTimelineBlock running/success/failure tones, WorkerThreadRefCard subagent chip, MeetingBotsCard meeting joiner, Composio toolkit tiles, ChannelTile, PillTabBar active state, and Memory Insights category cards (facts/preferences/relationships/skills/opinions/other) all got per-state dark variants instead of relying on Tailwind defaults.

Two unrelated chat-surface QoL changes ride along (called out separately): the thread sidebar in chat defaults to closed on first render, and the Respond Queue side-panel on /chat is hidden behind a top-right toggle (it was always-on before and confusing to new users who don't know what it is).

Submission Checklist

  • N/A: theme/CSS pass with no behavior changes — every patched line is a className token edit; tests for the theme toggle wiring would only assert that Redux state flips a class on <html>, which is already covered by the inherent React-Redux integration. No new code paths added.
  • N/A: diff-cover gate — pure className / CSS additions don't appear in coverage as new "changed lines" (most are inside existing JSX strings or in CSS variable blocks that aren't statement-line tracked). No new branches introduced.
  • N/A: coverage matrix — no new feature row to add (theming is cross-cutting and lives below the feature catalog level). The Appearance settings panel surface is a thin Redux dispatch wrapper.
  • N/A: matrix feature IDs — no related rows.
  • N/A: no new external network deps (no calls added; mock backend untouched).
  • N/A: smoke checklist — no release-cut surfaces touched (no IPC, no storage, no auth, no payments, no migration).
  • N/A: no linked issue — opportunistic UX work surfaced during dogfooding.

Impact

  • Desktop only — the app is desktop-only per CLAUDE.md; no mobile/web variant.
  • No performance impact — class string changes; MutationObserver on <html class> is one observer that fires only when the theme is toggled.
  • No security/migration/compatibility implications — theme preference is a new theme slice persisted via localStorage under persist:theme; first launch reads 'system' by default.
  • Pre-push hook applied prettier auto-fixes across 101 files — the chore: apply prettier/eslint auto-fixes commit is just whitespace/quoting normalization.

Related

  • Closes:
  • Follow-up PR(s)/TODOs:
    • Webview accounts (Slack/WhatsApp/Telegram/etc.) flash on top of BootCheckGate during cold start because the CEF child views are restored before React mounts the picker — needs a webview_account_hide_all invocation in the gate's mount effect. Deferred to a follow-up.
    • A handful of intentional dark-tinted decorations remain (modal backdrops, avatar placeholders, toggle off-state dots, skeleton shimmers) — they work in both modes by design.

AI Authored PR Metadata (required for Codex/Linear PRs)

Keep this section for AI-authored PRs. For human-only PRs, mark each field `N/A`.

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/dark-mode-theming
  • Commit SHA: 706b042

Validation Run

  • `pnpm --filter openhuman-app format:check` (auto-applied by pre-push hook into a follow-up commit)
  • `pnpm typecheck` (clean after every batch)
  • Focused tests: N/A — className-only changes
  • Rust fmt/check (if changed): N/A — no Rust changes
  • Tauri fmt/check (if changed): N/A — no Tauri shell changes

Validation Blocked

  • `command:` N/A
  • `error:` N/A
  • `impact:` N/A

Behavior Changes

  • Intended behavior change: app respects a user-selected theme (light/dark/system) across every patched surface.
  • User-visible effect: new `/settings/appearance` panel; the entire app re-skins on toggle; thread sidebar in chat defaults to closed; Respond Queue side panel on /chat is hidden behind a toggle.

Parity Contract

  • Legacy behavior preserved: light mode is the default for existing users (no migration of pre-existing preference). All className additions are additive — no light-mode tokens were removed.
  • Guard/fallback/dispatch parity checks: ThemeProvider's matchMedia listener guards Safari < 14's deprecated `addListener`/`removeListener` API.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: this one
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • Appearance settings: choose Light / Dark / System theme (persists and follows system preference).
    • Respond queue toggle in Accounts to show or hide the respond queue.
  • Improvements

    • Broad dark-mode theming applied across the app for more consistent visuals and readability.

senamakel added 30 commits May 17, 2026 18:33
Extends the existing semantic var system in app/src/styles/theme.css with a full :root.dark block (background, surface, border, text, hover/active/focus, success/warning/error/info) and dark variants for the .card-*/.input-*/.btn-*/.nav-* component shortcuts so theme switching works without per-call dark: classes for them.
- New themeSlice (light/dark/system, persisted in localStorage)
- ThemeProvider mounted under PersistGate syncs Redux mode to <html class="dark"> and subscribes to prefers-color-scheme when in system mode
- Tailwind's darkMode: 'class' and the :root.dark CSS vars now activate together
- New AppearancePanel renders the light/dark/system radio group, dispatching setThemeMode
- Wired into Settings router as /settings/appearance, breadcrumb in useSettingsNavigation, and a General-section entry in SettingsHome
…iants to shell

- ui/Button now ships dark variants for all four variants
- New shared ui/Card (surface, elevated, outlined, subtle) and ui/Input primitives with first-class dark support; ui/index barrel re-exports
- Shell components (SettingsHeader, SettingsMenuItem, PageBackButton, BottomTabBar) and SettingsHome's clear-data modal patched with dark: variants so the chrome of every settings page works in both themes
- pages/Home main card, status copy, version chip
- pages/Notifications wrapper, list rows, unread dot bg, footer actions
- components/home/HomeBanners (UsageLimit, PromotionalCredits, EarlyBirdy)
- components/chat/TokenUsagePill severity colors and session ring
…ationCard, NotificationCenter, WalkthroughTooltip
…lasses

- index.css html/body now use var(--color-background) and var(--color-text-primary) instead of hardcoded #f5f5f5 / #171717 — the page bg flips with the theme
- Dotted-canvas overlay switches to white dots in dark mode
- MeshGradient swaps to deep-blue palette in dark mode (observes <html class="dark">)
- :root.dark overrides for .card, .input-primary, .status-*, .nav-*, .message-received, .loading-pulse, .glass, .skills-table-* so the remaining component shortcuts respect the theme
Patches the Skills page surfaces I touched directly: status text color, page title/subtitle, primary action button, and the autocomplete unconfigured CTA. Did not bulk-patch skills/* component files because the prior bulk pass left malformed JSX (missing > on attributes); those files were reverted to upstream and will be patched individually in follow-ups.
…ls/accounts/oauth/composio

Adds dark: variants for text/bg/border/hover/divide stone scales via a negative-lookahead perl pass. Surgical — only adds when no dark: sibling already present in the immediate vicinity. Covers settings panels missed by the earlier subagent (Mascot, AgentChat, RecoveryPhrase, CronJobs, Team*, Composio*, NotificationRouting, MemoryData, MemoryDebug, Debug panels) plus components/skills, components/channels, components/oauth, components/composio, components/accounts.
…rast + Rewards/PillTabBar dark variants

- Settings menu items (bg-neutral-900) now sit on a black (#000) page bg instead of #0a0a0a, giving the menu its 'card' silhouette back
- Rewards tabs and PillTabBar patched via the same negative-lookahead perl pass
…oter

WrappedSettingsPage was the actual outer card around all settings panels — it was using bg-white/border-stone-200 with no dark variants, so the card silhouette vanished in dark mode. Adds dark:bg-neutral-900, dark:border-neutral-800 to the wrapper and the Beta-build footer divider. Reverts the speculative inner cards I added to SettingsHome/SettingsSectionPage in the prior commit since they'd nest inside this wrapper.
theme.css (with the entire :root.dark CSS variable block and component-shortcut overrides) was never imported anywhere — only index.css was. main.tsx now imports it after index.css so the dark overrides land in the cascade. This was why bg, cards, and many @apply'd component classes didn't flip on theme switch.

Also adds dark variants to LanguageSelect's inline DEFAULT_CLASS.
…y with dark hue

- MeshGradient light palette: replace washed-out white with a cohesive primary-300 → accent.lavender → accent.sky → primary-200 sweep
- Bump light-mode mesh opacity from 10% to 30% so the color reads through the dotted-canvas overlay
- Page bg tinted from flat #f5f5f5 to #f4f7fc (primary-50-ish) so the gradient sits on a continuous palette
…#f5f5f5 light page bg

Per user feedback, the bumped-opacity ocean→lavender→sky light gradient and primary-tinted page bg felt off. Restoring the original primary-50/primary-100/primary-200 mesh palette at 10% opacity for both modes, and #f5f5f5 light page bg. Dark mode keeps the #000 page + neutral-900 surface contrast from earlier, just on the original mesh tones.
…rk: tokens across patched files

- DiscordBanner gets w-full so it matches the other home banners' span, plus dark variants using the brand blurple at low opacity
- Sweeps duplicate dark: utility tokens (e.g. 'dark:bg-neutral-900 dark:bg-neutral-900') the bulk perl pass introduced when it ran twice on the same file
… toggle

- Conversations.tsx: showSidebar useState now defaults to false. The threads sidebar is hidden on first render; the existing toggle button in the chat header still opens/closes it.
- Accounts.tsx (/chat): Respond Queue side panel is no longer rendered by default. A small pill button in the top-right of the agent chat pane toggles it with a count badge. Adds en.ts keys for the show/hide tooltips.

Respond Queue surfaces incoming-message replies waiting on the user from connected accounts (Slack/Telegram/etc.). It's useful for power users but adds noise to the default chat surface.
… pairing across codebase

- AgentMessageBubble + TableCellMarkdown now get dark:prose-invert so the agent message body actually inverts in dark mode (was inheriting Tailwind prose plugin's hardcoded light-mode text colors)
- Earlier bulk perl pass added 'dark:text-neutral-N' next to 'PREFIX:text-stone-N' instead of 'dark:PREFIX:text-neutral-N'. Sweeps hover/focus/disabled/active/focus-within/focus-visible/group-hover/peer-focus + placeholder modifiers across .tsx files so the dark variant carries the original modifier
…coded #f6f6f6)

The bg-[#f6f6f6] tinted backdrop behind the message list in Conversations.tsx was a literal hex with no dark variant — that's the 'still bright' surface inside the dark chat card. Pair with dark:bg-neutral-950.
…ns setup badge

- WorkerThreadRefCard (the subagent invocation chip in chat with running/completed/failed status, label, meta, arrow): full dark palette using primary/amber/sage/coral 500/15-30% tints
- ConnectionsPanel 'Set up' badge missed primary tones — added dark variant
…ron tones

Subagent timeline rows render an expandable <details> per tool call with running/success/failure tones (amber/sage/coral). Light-only bg-X-100/X-50 + text-X-600/X-900 swapped for paired dark tints (X-500/20 + X-300/200) so the timeline reads in dark mode.
senamakel added 16 commits May 17, 2026 21:20
…primary tints)

ChannelSelector, ChannelSetupModal, DiscordConfig, DiscordServerChannelPicker, TelegramConfig had light-only status/active/error chips using bg-COLOR-50/100, text-COLOR-700, border-COLOR-200. Adds paired dark tints (bg-COLOR-500/10-20, text-COLOR-300, border-COLOR-500/30-40).
ComposioConnectorTile light/pending/expired/error/disconnected variants paired with dark tints (sage/amber/coral 500/10-15%). channel + composio status text-COLOR-600/700 paired with dark text-COLOR-300.
…dark variants

- MeetingBotsCard: gradient bg, dialog header bg, close button hover, platform pill hover, submit/cancel button disabled states all needed dark pairing or had broken 'disabled:bg-X dark:bg-Y' that applied dark:bg-Y unconditionally
- ChannelTile (the buttons in Channels grid on Skills page) connected/pending/error states paired with sage/amber/coral 500/10-15% dark tints (matching the Composio tiles I patched earlier)
Negative-lookahead perl pass adds dark: variants for text/bg/border/hover/divide stone scales + sage/amber/coral/primary 50/100/200 chips + 600/700 colored text across components/intelligence/ (memory, vault, subconscious, calls, dreams, tasks, sync connections, graph, heatmap, sources, screen-intelligence debug, toasts, etc.).
…tive bg

The active pill defaulted to bg-primary-50 text-primary-700 border-primary-200 with no dark pairing — selected category chips on Skills/Integrations looked stuck in light mode. Adds dark primary-500/15 + primary-300 + primary-500/40. Also drops the duplicated dark:bg-neutral-800/60 in inactiveClassName.
…leanup duplicate dark tokens

MemoryInsights insight category colors (facts/preferences/opinions) use emerald/rose/lavender palettes that the earlier perl pass didn't cover. The follow-up pass added them but doubled tokens; cleaned with a non-adjacent-dedup pass.
…dingScreen + ConnectionIndicator + memory action buttons + hover:dark mis-pairing

- BootCheckGate ('select a runtime' screen, local/cloud cards, host/port inputs): all stone/white surfaces paired with dark
- RouteLoadingScreen ('Initializing OpenHuman...' card): bg-white + border + text now flip
- ConnectionIndicator ('Connected to OpenHuman AI 🚀' pill): bg-stone-50/border-stone-200 paired
- MemoryWorkspace action buttons: 'hover:bg-X-50 dark:bg-X-500/10' was applying the dark tint unconditionally — rewrote to dark:hover:bg-X-500/10. Same fix swept across all .tsx files
- Restored bg-primary-500/bg-violet-500 literals corrupted by an earlier prefix-match bug (\bbg-primary-50 was matching bg-primary-500 as a prefix and inserting a dark variant mid-token)
…g var, less padding

- Google/GitHub/Twitter OAuth buttons had border-gray-200/hover:bg-gray-50/text-gray-900 with no dark variants; added dark neutral pairs
- BootCheckGate 'select a runtime' panel was forcing bg-[#f5f5f5] (hard light) and p-10 (too much padding). Now reads var(--color-background) so it tints with the theme like every other screen, and trimmed inner padding to p-6 to match the app's card density.
…-700 in dark mode

The '!border-stone-200 dark:border-neutral-800' pair had ! important only on the light variant — Tailwind's border-2 default color overrode the dark variant, making the unselected card border look white. Adds ! to dark:border-* and dark:hover:border-*. Also drops the duplicated dark:bg-neutral-800/60 token.
…in BootCheckGate

Three Continue/Confirm buttons in BootCheckGate had bg-primary-500 munged to bg-primary-50 dark:bg-primary-500/150 by the earlier perl prefix-match bug. Restored.
…ChoiceCard borders + free-credit highlight legibility

- Bulk dark variants across all .tsx in pages/onboarding/ (LocalAI/Skills/CustomWizard/ApiKeys/RuntimeChoice/ContextGathering/ReferralApply/Wizard stepper/Welcome step/banners) using the safer (?!\d) lookahead so it doesn't munge stone-500 → stone-50 prefix matches
- BetaBanner amber link gets dark variant
- RuntimeChoiceStep: '$1 free credit' highlight pill had text-sage-800 with no dark variant — paired with text-sage-200. Also fixes the !-important pairing on the unselected ChoiceCard border that was leaving the dark variant overridden by Tailwind border defaults, and drops the duplicated dark:bg-neutral-800/60.
…tomWizardStep + RuntimeChoiceStep cards + similar)
…, billing, AI, autocomplete, screen-intel, memory debug, recovery phrase, etc.)
@senamakel senamakel requested a review from a team May 18, 2026 05:05
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 94e0e99d-1b55-428b-94af-2933c7f25e8c

📥 Commits

Reviewing files that changed from the base of the PR and between 74aa6c7 and 6ce8a87.

📒 Files selected for processing (2)
  • app/src/components/settings/panels/TeamInvitesPanel.tsx
  • app/src/pages/conversations/components/AgentMessageBubble.tsx
✅ Files skipped from review due to trivial changes (1)
  • app/src/pages/conversations/components/AgentMessageBubble.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/components/settings/panels/TeamInvitesPanel.tsx

📝 Walkthrough

Walkthrough

Adds a persisted theme slice and ThemeProvider, imports theme.css, adds an Appearance settings page and route, introduces Card and Input UI primitives and a barrel export, updates Button variants, makes Accounts respond-queue togglable, and applies dark-mode Tailwind/CSS tokens across many components and pages.

Changes

Dark theme rollout

Layer / File(s) Summary
Theme plumbing and global wiring
app/src/store/themeSlice.ts, app/src/providers/ThemeProvider.tsx, app/src/store/index.ts, app/src/styles/theme.css, app/src/main.tsx, app/src/App.tsx
Adds Theme Redux slice and persistence, ThemeProvider that syncs dark class and color-scheme, imports theme.css, and mounts ThemeProvider in App.
Appearance settings and navigation
app/src/components/settings/panels/AppearancePanel.tsx, app/src/components/settings/hooks/useSettingsNavigation.ts, app/src/components/settings/SettingsHome.tsx, app/src/pages/Settings.tsx
New Appearance panel, route, menu item, and breadcrumbs; dispatches setThemeMode.
UI primitives and exports
app/src/components/ui/Card.tsx, app/src/components/ui/Input.tsx, app/src/components/ui/Button.tsx, app/src/components/ui/index.ts
Adds Card and Input components, updates Button variants with dark classes, and exports a UI barrel.
Accounts respond-queue toggle
app/src/pages/Accounts.tsx
Adds respondQueueOpen state and a pinned toggle button; RespondQueuePanel is conditional and shows count when open.
Dark-mode restyling sweep
app/src/components/**/*, app/src/pages/**/*, app/src/index.css
Bulk update: add dark:* Tailwind classes and CSS token overrides across modals, banners, settings panels, channels, intelligence, skills, notifications, pages, and utilities. No behavioral changes except presentation and the Accounts respond-queue visibility.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AppearancePanel
  participant Redux Store
  participant ThemeProvider
  participant Document (html)
  User->>AppearancePanel: Select theme mode
  AppearancePanel->>Redux Store: setThemeMode(mode)
  Redux Store-->>ThemeProvider: mode change
  ThemeProvider->>ThemeProvider: resolveTheme(mode/system)
  ThemeProvider->>Document (html): Add/remove class "dark", set color-scheme
  Note right of Document (html): Tailwind dark: styles apply across UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • graycyrus

Poem

A rabbit paints the night in soft neutral tones,
Hops through CSS and hums in tailwind tones.
Buttons dim to moonlight, cards whisper low,
Redux nudges "system" which way the shadows go.
I nibble a token, then quietly show— the app wears night-silk, all cozy and aglow.

@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label May 18, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (11)
app/src/components/settings/panels/NotificationsPanel.tsx (1)

134-171: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove duplicate dark mode classes in categories section.

Multiple duplicated dark: utilities:

  • Line 134: dark:text-neutral-500 appears twice
  • Line 137: dark:bg-neutral-900, dark:border-neutral-800, and dark:divide-neutral-800 each appear twice
  • Line 143: dark:text-neutral-100 appears twice
  • Line 146: dark:text-neutral-400 appears twice
  • Lines 153-156: dark:bg-neutral-600 appears twice in toggle
  • Line 171: dark:text-neutral-400 appears twice
🔧 Proposed fix
-            <h3 className="text-xs font-semibold uppercase tracking-wider text-stone-400 dark:text-neutral-500 dark:text-neutral-500 mb-2 px-1">
+            <h3 className="text-xs font-semibold uppercase tracking-wider text-stone-400 dark:text-neutral-500 mb-2 px-1">
              {t('settings.notifications.categories')}
            </h3>
-            <div className="bg-white dark:bg-neutral-900 dark:bg-neutral-900 rounded-xl border border-stone-200 dark:border-neutral-800 dark:border-neutral-800 overflow-hidden divide-y divide-stone-100 dark:divide-neutral-800 dark:divide-neutral-800">
+            <div className="bg-white dark:bg-neutral-900 rounded-xl border border-stone-200 dark:border-neutral-800 overflow-hidden divide-y divide-stone-100 dark:divide-neutral-800">
              {CATEGORIES.map(cat => {
                const enabled = preferences[cat.id];
                return (
                  <div key={cat.id} className="flex items-center justify-between p-4">
                    <div className="flex-1 mr-4">
-                      <p className="text-sm font-medium text-stone-900 dark:text-neutral-100 dark:text-neutral-100">
+                      <p className="text-sm font-medium text-stone-900 dark:text-neutral-100">
                        {cat.title}
                      </p>
-                      <p className="text-xs text-stone-500 dark:text-neutral-400 dark:text-neutral-400 mt-1 leading-relaxed">
+                      <p className="text-xs text-stone-500 dark:text-neutral-400 mt-1 leading-relaxed">
                        {cat.description}
                      </p>
                    </div>
                    <button
                      onClick={() => handleToggle(cat.id)}
                      className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1 ${
                        enabled
                          ? 'bg-primary-500'
-                          : 'bg-stone-400 dark:bg-neutral-600 dark:bg-neutral-600'
+                          : 'bg-stone-400 dark:bg-neutral-600'
                      }`}

...

-            <p className="text-xs text-stone-500 dark:text-neutral-400 dark:text-neutral-400 leading-relaxed px-1 mt-2">
+            <p className="text-xs text-stone-500 dark:text-neutral-400 leading-relaxed px-1 mt-2">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/NotificationsPanel.tsx` around lines 134 -
171, In NotificationsPanel (the JSX rendering CATEGORIES), remove duplicated
dark: utility classes in the className strings: the h3 heading (duplicate
dark:text-neutral-500), the container div wrapping the list (duplicate
dark:bg-neutral-900, dark:border-neutral-800, dark:divide-neutral-800), the
category title <p> (duplicate dark:text-neutral-100), the description <p>
(duplicate dark:text-neutral-400), the toggle <button> classes (duplicate
dark:bg-neutral-600), the toggle <span> (duplicate dark:bg-neutral-900), and the
trailing info <p> (duplicate dark:text-neutral-400); keep one occurrence of each
dark: class per className, leaving other styles and behavior (CATEGORIES map,
handleToggle, aria props) unchanged.
app/src/components/settings/panels/billing/SubscriptionPlans.tsx (1)

80-85: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove duplicated dark mode class.

Line 84 contains dark:text-neutral-100 twice.

🐛 Proposed fix
               className={`rounded-full px-4 py-2 text-sm font-semibold transition-colors ${
                 billingInterval === 'annual'
                   ? 'bg-primary-600 text-white'
-                  : 'text-stone-500 dark:text-neutral-400 hover:text-stone-900 dark:hover:text-neutral-100 dark:text-neutral-100'
+                  : 'text-stone-500 dark:text-neutral-400 hover:text-stone-900 dark:hover:text-neutral-100'
               }`}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/billing/SubscriptionPlans.tsx` around
lines 80 - 85, In SubscriptionPlans.tsx, the className string for the annual
toggle duplicates the utility 'dark:text-neutral-100'; update the onClick/button
JSX (the element that calls setBillingInterval('annual') and reads
billingInterval) to remove the duplicate 'dark:text-neutral-100' so the class
list contains it only once.
app/src/components/settings/panels/local-model/DeviceCapabilitySection.tsx (3)

222-252: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Clean up duplicated dark classes in disabled-tier card.

Lines 228-230, 233, 237, 242, 247, 249 contain duplicated dark mode classes.

🐛 Proposed fix for disabled tier button
           <button
             type="button"
             onClick={() => void handleApply(DISABLED_TIER_ID)}
             disabled={applying !== null}
             className={`w-full text-left rounded-lg border p-3 transition-colors ${
               isDisabledActive
-                ? 'border-primary-400 bg-primary-50 dark:bg-primary-500/10'
-                : 'border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 hover:bg-stone-100 dark:hover:bg-neutral-800 dark:bg-neutral-800'
+                ? 'border-primary-400 bg-primary-50 dark:bg-primary-500/10'
+                : 'border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 hover:bg-stone-100 dark:hover:bg-neutral-800'
             } ${applying !== null ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer'}`}>
             <div className="flex items-center justify-between">
               <div className="flex items-center gap-2">
-                <span className="text-sm font-semibold text-stone-900 dark:text-neutral-100">
+                <span className="text-sm font-semibold text-stone-900 dark:text-neutral-100">
                   {t('settings.localModel.deviceCapability.disabled')}
                 </span>
                 {isDisabledActive && (
-                  <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-primary-50 dark:bg-primary-500/10 text-primary-600 dark:text-primary-300 uppercase tracking-wide">
+                  <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-primary-50 dark:bg-primary-500/10 text-primary-600 dark:text-primary-300 uppercase tracking-wide">
                     {t('settings.localModel.deviceCapability.active')}
                   </span>
                 )}
                 {(presetsData.recommend_disabled || !ollamaAvailable) && !isDisabledActive && (
-                  <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-300 uppercase tracking-wide">
+                  <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-300 uppercase tracking-wide">
                     {t('settings.localModel.deviceCapability.recommended')}
                   </span>
                 )}
               </div>
-              <span className="text-xs text-stone-500 dark:text-neutral-400">0 GB</span>
+              <span className="text-xs text-stone-500 dark:text-neutral-400">0 GB</span>
             </div>
-            <div className="text-xs text-stone-500 dark:text-neutral-400 mt-1">
+            <div className="text-xs text-stone-500 dark:text-neutral-400 mt-1">
               {t('settings.localModel.deviceCapability.disabledDesc')}
             </div>
           </button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/local-model/DeviceCapabilitySection.tsx`
around lines 222 - 252, The disabled-tier button JSX contains duplicated
dark-mode utility classes (e.g., repeated "dark:bg-neutral-800",
"dark:hover:bg-neutral-800", "dark:border-neutral-800", etc.) in the className
string for the button; to fix, consolidate the class list used by the button in
DeviceCapabilitySection so each dark:... utility appears only once (clean up the
className template used in the button that wraps the disabled tier), keeping the
same conditional logic around isDisabledActive and applying, and ensure no
duplicated dark classes remain; reference the button that calls
handleApply(DISABLED_TIER_ID) and uses props/state names isDisabledActive,
presetsData, ollamaAvailable, and applying when making the change.

136-217: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Review and clean up duplicated dark classes in install-state banner.

Lines 140-143 contain duplicated dark mode border/background classes in the ternary conditionals. Lines 156, 168, 172, 192, 199, 210 also have duplications.

The install-state banner has multiple issues:

  • Duplicated dark:border-* and dark:bg-* in ternary branches
  • Lines 140-143 repeat the same dark classes across all three conditions
🐛 Proposed fix for lines 136-217
       {presetsData && !ollamaAvailable && (
         <div
           className={`rounded-lg border p-3 space-y-2 ${
             installFailed
-              ? 'border-red-300 dark:border-red-500/40 bg-red-50 dark:bg-red-500/10'
+              ? 'border-red-300 dark:border-red-500/40 bg-red-50 dark:bg-red-500/10'
               : installInProgress
-                ? 'border-blue-300 dark:border-blue-500/40 bg-blue-50 dark:bg-blue-500/10'
-                : 'border-amber-300 dark:border-amber-500/40 bg-amber-50 dark:bg-amber-500/10'
+                ? 'border-blue-300 dark:border-blue-500/40 bg-blue-50 dark:bg-blue-500/10'
+                : 'border-amber-300 dark:border-amber-500/40 bg-amber-50 dark:bg-amber-500/10'
           }`}>
           {installInProgress ? (
             <>
               <div className="flex items-center gap-2">
                 <div className="h-3 w-3 rounded-full border-2 border-blue-500 border-t-transparent animate-spin" />
                 <div className="text-sm font-semibold text-blue-900">
                   {t('settings.localModel.deviceCapability.installingOllama')}
                   {installState === 'downloading'
                     ? ` (${t('settings.localModel.deviceCapability.downloadingModels')})`
                     : '…'}
                 </div>
               </div>
-              <div className="text-xs text-blue-800 dark:text-blue-200">
+              <div className="text-xs text-blue-800 dark:text-blue-200">
                 {installWarning ?? t('settings.localModel.deviceCapability.downloadingSetupDesc')}
               </div>
-              <div className="h-1.5 rounded-full bg-blue-200 dark:bg-blue-500/30 overflow-hidden">
+              <div className="h-1.5 rounded-full bg-blue-200 dark:bg-blue-500/30 overflow-hidden">
                 <div className="h-full w-1/3 bg-blue-500 animate-pulse" />
               </div>
             </>
           ) : installFailed ? (
             <>
               <div className="text-sm font-semibold text-red-900">
                 {t('settings.localModel.deviceCapability.installFailed')}
               </div>
-              <div className="text-xs text-red-800 dark:text-red-200">
+              <div className="text-xs text-red-800 dark:text-red-200">
                 {installWarning ?? t('settings.localModel.deviceCapability.installFailedDesc')}
               </div>
               {installError && (
-                <pre className="max-h-40 overflow-auto rounded bg-red-100 dark:bg-red-500/20 border border-red-200 dark:border-red-500/30 p-2 text-[10px] text-red-700 dark:text-red-300 leading-tight whitespace-pre-wrap break-words">
+                <pre className="max-h-40 overflow-auto rounded bg-red-100 dark:bg-red-500/20 border border-red-200 dark:border-red-500/30 p-2 text-[10px] text-red-700 dark:text-red-300 leading-tight whitespace-pre-wrap break-words">
                   {installError}
                 </pre>
               )}
               <div className="flex items-center gap-2 pt-1">
                 {onTriggerOllamaInstall && (
                   <button
                     type="button"
                     onClick={onTriggerOllamaInstall}
                     disabled={isTriggeringInstall}
                     className="px-3 py-1.5 text-xs rounded-md bg-red-600 hover:bg-red-700 disabled:opacity-60 text-white font-medium">
                     {isTriggeringInstall
                       ? t('settings.localModel.deviceCapability.retrying')
                       : t('settings.localModel.deviceCapability.retryInstall')}
                   </button>
                 )}
                 <a
                   href="https://ollama.com"
                   target="_blank"
                   rel="noopener noreferrer"
-                  className="px-3 py-1.5 text-xs rounded-md border border-red-300 dark:border-red-500/40 hover:border-red-400 text-red-800 dark:text-red-200">
+                  className="px-3 py-1.5 text-xs rounded-md border border-red-300 dark:border-red-500/40 hover:border-red-400 text-red-800 dark:text-red-200">
                   {t('settings.localModel.status.installManually')}
                 </a>
               </div>
             </>
           ) : (
             <>
-              <div className="text-xs text-amber-800 dark:text-amber-200">
+              <div className="text-xs text-amber-800 dark:text-amber-200">
                 <span className="font-semibold text-amber-900">
                   {t('settings.localModel.deviceCapability.installFirst')}
                 </span>{' '}
                 {t('settings.localModel.deviceCapability.installFirstDesc')}
               </div>
               <div className="flex items-center gap-2">
                 <a
                   href="https://ollama.com"
                   target="_blank"
                   rel="noopener noreferrer"
-                  className="px-3 py-1.5 text-xs rounded-md border border-amber-300 dark:border-amber-500/40 hover:border-amber-400 text-amber-800 dark:text-amber-200">
+                  className="px-3 py-1.5 text-xs rounded-md border border-amber-300 dark:border-amber-500/40 hover:border-amber-400 text-amber-800 dark:text-amber-200">
                   {t('settings.localModel.status.ollamaDocs')}
                 </a>
               </div>
             </>
           )}
         </div>
       )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/local-model/DeviceCapabilitySection.tsx`
around lines 136 - 217, The install-state banner in DeviceCapabilitySection (the
JSX block that renders when presetsData && !ollamaAvailable) repeats identical
dark mode border/bg classes across the ternary branches; remove the duplicated
dark classes by pulling shared dark classes out of the ternary and only
conditionally applying the light-mode/color-specific classes based on
installFailed / installInProgress. Update the className expression on the outer
<div> so it composes a common suffix like "dark:border-*/dark:bg-*" (or a
variable with those classes) plus the conditional "border-red-300 bg-red-50" /
"border-blue-300 bg-blue-50" / "border-amber-300 bg-amber-50" branches, and
ensure other occurrences around the installFailed/installInProgress branches
(where dark:border-*/dark:bg-* are repeated) are simplified the same way.

254-316: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Clean up duplicated dark classes in preset tier cards.

Lines 269-274, 280-282, 284, 289, 294, 299, 303-306 contain duplicated and incorrectly formatted dark mode classes.

🐛 Proposed fix for preset cards
                 className={`w-full text-left rounded-lg border p-3 transition-colors ${
                   isCurrent
-                    ? 'border-primary-400 bg-primary-50 dark:bg-primary-500/10'
-                    : 'border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 hover:bg-stone-100 dark:hover:bg-neutral-800 dark:bg-neutral-800'
+                    ? 'border-primary-400 bg-primary-50 dark:bg-primary-500/10'
+                    : 'border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 hover:bg-stone-100 dark:hover:bg-neutral-800'
                 } ${
                   locked
-                    ? 'opacity-50 cursor-not-allowed hover:bg-stone-50 dark:hover:bg-neutral-800/60 dark:bg-neutral-800/60'
+                    ? 'opacity-50 cursor-not-allowed hover:bg-stone-50 dark:hover:bg-neutral-800/60'
                     : applying !== null && !isApplying
                       ? 'opacity-60 cursor-not-allowed'
                       : 'cursor-pointer'
                 }`}>
                 <div className="flex items-center justify-between">
                   <div className="flex items-center gap-2">
-                    <span className="text-sm font-semibold text-stone-900 dark:text-neutral-100">
-                      {preset.label}
-                    </span>
+                    <span className="text-sm font-semibold text-stone-900 dark:text-neutral-100">
+                      {preset.label}
+                    </span>
                     {isCurrent && (
-                      <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-primary-50 dark:bg-primary-500/10 text-primary-600 dark:text-primary-300 uppercase tracking-wide">
+                      <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-primary-50 dark:bg-primary-500/10 text-primary-600 dark:text-primary-300 uppercase tracking-wide">
                         {t('settings.localModel.deviceCapability.active')}
                       </span>
                     )}
                     {isApplying && (
-                      <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-stone-100 dark:bg-neutral-800 text-stone-500 dark:text-neutral-400 uppercase tracking-wide">
+                      <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-stone-100 dark:bg-neutral-800 text-stone-500 dark:text-neutral-400 uppercase tracking-wide">
                         {t('settings.localModel.deviceCapability.applying')}
                       </span>
                     )}
                     {locked && (
-                      <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-300 uppercase tracking-wide">
+                      <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-300 uppercase tracking-wide">
                         {t('settings.localModel.deviceCapability.needsOllama')}
                       </span>
                     )}
                   </div>
-                  <span className="text-xs text-stone-500 dark:text-neutral-400">
+                  <span className="text-xs text-stone-500 dark:text-neutral-400">
                     ~{Number(preset.approx_download_gb).toFixed(1)} GB
                   </span>
                 </div>
-                <div className="text-xs text-stone-400 dark:text-neutral-500 mt-1">
-                  {preset.description}
-                </div>
-                <div className="text-[10px] text-stone-500 dark:text-neutral-400 mt-1">
+                <div className="text-xs text-stone-400 dark:text-neutral-500 mt-1">
+                  {preset.description}
+                </div>
+                <div className="text-[10px] text-stone-500 dark:text-neutral-400 mt-1">
                   Chat: {preset.chat_model_id} &middot; Vision:{' '}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/local-model/DeviceCapabilitySection.tsx`
around lines 254 - 316, The preset card button render inside the
DeviceCapabilitySection map is using duplicated and malformed dark-mode utility
classes in the className strings (the button root class and the conditional
locked/applying branches); update the className assignments used in the
presetsData.presets.map (the button element keyed by preset.tier, plus the inner
span badges) to remove duplicate repeated dark:* tokens and fix formatting so
each dark: utility appears only once and consistently (e.g., consolidate
repeated "dark:bg-neutral-800", "dark:hover:bg-neutral-800/60", "dark:text-*"
and similar into a single, correctly spelled token per intention) so the
conditional branches (isCurrent, locked, applying) only append the needed unique
classes and not duplicates.
app/src/components/settings/panels/ComposioTriagePanel.tsx (3)

142-160: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix duplicated dark mode classes in disabled-toolkits section.

Lines 143, 147, and 158 contain duplicated dark mode classes.

🐛 Proposed fix
         <div className={`space-y-2 ${triageDisabled ? 'opacity-40 pointer-events-none' : ''}`}>
-          <label
-            className="block text-sm font-medium text-stone-800 dark:text-neutral-100 dark:text-neutral-100"
-            htmlFor="disabled-toolkits">
+          <label
+            className="block text-sm font-medium text-stone-800 dark:text-neutral-100"
+            htmlFor="disabled-toolkits">
             {t('composio.disableSpecificIntegrations')}
           </label>
-          <p className="text-xs text-stone-500 dark:text-neutral-400 dark:text-neutral-400">
+          <p className="text-xs text-stone-500 dark:text-neutral-400">
             Comma-separated integration slugs, e.g. <span className="font-mono">gmail, slack</span>.
             Case-insensitive.
           </p>
           <input
             id="disabled-toolkits"
             type="text"
             value={disabledToolkits}
             onChange={e => setDisabledToolkits(e.target.value)}
             placeholder="gmail, slack, ..."
             disabled={triageDisabled}
-            className="w-full rounded-xl border border-stone-200 dark:border-neutral-800 dark:border-neutral-800 bg-white dark:bg-neutral-900 dark:bg-neutral-900 px-3 py-2 text-sm text-stone-900 dark:text-neutral-100 dark:text-neutral-100 placeholder-stone-400 dark:placeholder-neutral-500 focus:border-primary-400 focus:outline-none focus:ring-1 focus:ring-primary-400 disabled:cursor-not-allowed"
+            className="w-full rounded-xl border border-stone-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 px-3 py-2 text-sm text-stone-900 dark:text-neutral-100 placeholder-stone-400 dark:placeholder-neutral-500 focus:border-primary-400 focus:outline-none focus:ring-1 focus:ring-primary-400 disabled:cursor-not-allowed"
           />
         </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/ComposioTriagePanel.tsx` around lines 142
- 160, The className strings for the input and related labels in the Disabled
Toolkits block (elements with id "disabled-toolkits" and the surrounding
label/paragraph) contain repeated dark mode classes ("dark:text-neutral-100",
"dark:border-neutral-800", "dark:bg-neutral-900", etc.); remove the duplicate
entries so each dark:… and normal class appears only once in the className for
the label, paragraph and input. Locate the JSX around the "disabled-toolkits"
input and update the className props for the label, paragraph and input
(references: disabledToolkits, setDisabledToolkits, triageDisabled) to
deduplicate identical dark: and non-dark classes while preserving the same
visual styles and spacing.

110-139: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix multiple duplicated and invalid dark mode classes.

Line 110 contains critical syntax errors:

  • dark:bg-neutral-800/60/60/60 is invalid Tailwind (double opacity stacking)
  • dark:border-neutral-800 dark:border-neutral-800 is duplicated
  • Lines 119, 122-124, 129-131, 133 also have duplicated classes
🐛 Proposed fix for lines 110-139
-        <div className="rounded-2xl border border-stone-200 dark:border-neutral-800 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 dark:bg-neutral-800/60/60 p-4 space-y-1">
+        <div className="rounded-2xl border border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 p-4 space-y-1">
           <button
             type="button"
             role="switch"
             aria-checked={triageDisabled}
             aria-label="Disable AI triage for all triggers"
             onClick={() => setTriageDisabled(v => !v)}
             className="w-full flex items-center justify-between">
             <div className="text-left">
-              <span className="text-sm font-medium text-stone-900 dark:text-neutral-100 dark:text-neutral-100">
+              <span className="text-sm font-medium text-stone-900 dark:text-neutral-100">
                 {t('composio.disableAllTriage')}
               </span>
-              <p className="text-xs text-stone-500 dark:text-neutral-400 dark:text-neutral-400 mt-0.5">
-                {t('composio.triggersStillRecorded')}
-              </p>
+              <p className="text-xs text-stone-500 dark:text-neutral-400 mt-0.5">
+                {t('composio.triggersStillRecorded')}
+              </p>
             </div>
             <div
               className={`ml-3 flex-shrink-0 w-9 h-5 rounded-full transition-colors relative ${
-                triageDisabled
-                  ? 'bg-coral-400'
-                  : 'bg-stone-200 dark:bg-neutral-800 dark:bg-neutral-800'
+                triageDisabled ? 'bg-coral-400' : 'bg-stone-200 dark:bg-neutral-800'
               }`}>
               <div
-                className={`absolute top-0.5 w-4 h-4 rounded-full bg-white dark:bg-neutral-900 dark:bg-neutral-900 shadow transition-transform ${
+                className={`absolute top-0.5 w-4 h-4 rounded-full bg-white dark:bg-neutral-900 shadow transition-transform ${
                   triageDisabled ? 'translate-x-4' : 'translate-x-0.5'
                 }`}
               />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/ComposioTriagePanel.tsx` around lines 110
- 139, In ComposioTriagePanel.tsx the container and toggle elements contain
duplicated Tailwind classes and an invalid opacity token
(`dark:bg-neutral-800/60/60/60`), causing syntax errors; update the className
strings used in the outer container (the div with rounded-2xl) and the inner
toggle pieces (the button and the two child divs that reference triageDisabled)
to remove duplicate entries (e.g., only one dark:border-neutral-800 and one
dark:text-neutral-100) and replace the invalid stacked opacity with a single
opacity suffix (e.g., dark:bg-neutral-800/60 or dark:bg-neutral-800 depending on
desired opacity); keep the conditional class logic for triageDisabled as-is but
ensure each class token appears only once across the className values for the
container, the switch track, and the switch knob.

76-92: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix duplicated dark mode classes and invalid opacity stacking throughout the file.

Multiple lines contain duplicated dark:* classes (lines 86, 104, 110, 119, 122, 130, 133, 143, 147, 158), and line 110 contains invalid opacity stacking (dark:bg-neutral-800/60/60). These should be deduplicated and corrected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/ComposioTriagePanel.tsx` around lines 76 -
92, Several Tailwind classes are duplicated and one has invalid opacity
stacking; locate the JSX blocks around the Loading return and other panel
sections (e.g., the SettingsHeader usage and surrounding divs in
ComposioTriagePanel) and remove duplicate dark:* classes (for example duplicate
dark:text-neutral-400 entries) so each dark: utility appears only once, and
correct the invalid opacity stacking such as dark:bg-neutral-800/60/60 to a
single valid opacity form like dark:bg-neutral-800/60 (or
dark:bg-neutral-800/75) to restore valid Tailwind syntax across className
attributes in the component.
app/src/components/settings/panels/ConnectionsPanel.tsx (2)

218-239: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix duplicated dark mode classes.

The privacy/security callout section has duplicates on lines 218, 221, 231, 234:

  • Line 218: dark:bg-neutral-800/60 dark:bg-neutral-800/60, dark:border-neutral-800 dark:border-neutral-800
  • Line 221: dark:text-neutral-500 dark:text-neutral-500
  • Line 231: dark:text-neutral-100 dark:text-neutral-100
  • Line 234: dark:text-neutral-400 dark:text-neutral-400
🔧 Proposed fix
-          <div className="p-4 bg-stone-50 dark:bg-neutral-800/60 dark:bg-neutral-800/60 rounded-xl border border-stone-200 dark:border-neutral-800 dark:border-neutral-800">
+          <div className="p-4 bg-stone-50 dark:bg-neutral-800/60 rounded-xl border border-stone-200 dark:border-neutral-800">
             <div className="flex items-start space-x-3">
               <svg
-                className="w-5 h-5 text-stone-400 dark:text-neutral-500 dark:text-neutral-500 mt-0.5 flex-shrink-0"
+                className="w-5 h-5 text-stone-400 dark:text-neutral-500 mt-0.5 flex-shrink-0"
                 fill="currentColor"
                 viewBox="0 0 20 20">
                 <path
                   fillRule="evenodd"
                   d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
                   clipRule="evenodd"
                 />
               </svg>
               <div>
-                <p className="font-medium text-stone-900 dark:text-neutral-100 dark:text-neutral-100 text-sm">
+                <p className="font-medium text-stone-900 dark:text-neutral-100 text-sm">
                   {t('connections.privacySecurity')}
                 </p>
-                <p className="text-xs text-stone-500 dark:text-neutral-400 dark:text-neutral-400 mt-1 leading-relaxed">
+                <p className="text-xs text-stone-500 dark:text-neutral-400 mt-1 leading-relaxed">
                   {t('connections.privacySecurityDesc')}
                 </p>
               </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/ConnectionsPanel.tsx` around lines 218 -
239, The JSX in ConnectionsPanel.tsx contains duplicated dark-mode utility
classes in the privacy/security callout: remove the duplicate entries so each
dark: class appears only once; specifically dedupe the outer div's className by
leaving a single "dark:bg-neutral-800/60" and a single
"dark:border-neutral-800", dedupe the svg's className to a single
"dark:text-neutral-500", dedupe the first p's className to a single
"dark:text-neutral-100", and dedupe the second p's className to a single
"dark:text-neutral-400" so no dark: classes are repeated.

190-215: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix duplicated dark mode classes.

The wallet-configured section has duplicates on lines 190, 192, 195, 203, 205, 208:

  • Line 190: dark:border-neutral-800 dark:border-neutral-800, dark:bg-neutral-900 dark:bg-neutral-900
  • Line 192: dark:text-neutral-100 dark:text-neutral-100
  • Line 195: dark:text-neutral-400 dark:text-neutral-400
  • Line 203: dark:border-neutral-800 dark:border-neutral-800, dark:bg-neutral-800/60 dark:bg-neutral-800/60
  • Line 205: dark:text-neutral-400 dark:text-neutral-400
  • Line 208: dark:text-neutral-200 dark:text-neutral-200
🔧 Proposed fix
           {walletConfigured && walletStatus ? (
-            <div className="rounded-2xl border border-stone-200 dark:border-neutral-800 dark:border-neutral-800 bg-white dark:bg-neutral-900 dark:bg-neutral-900 p-4 space-y-3">
+            <div className="rounded-2xl border border-stone-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-4 space-y-3">
               <div>
-                <p className="font-medium text-stone-900 dark:text-neutral-100 dark:text-neutral-100 text-sm">
+                <p className="font-medium text-stone-900 dark:text-neutral-100 text-sm">
                   {t('connections.walletIdentities')}
                 </p>
-                <p className="text-xs text-stone-500 dark:text-neutral-400 dark:text-neutral-400 mt-1">
+                <p className="text-xs text-stone-500 dark:text-neutral-400 mt-1">
                   {t('connections.walletDerived')}
                 </p>
               </div>
               <div className="grid gap-2">
                 {walletStatus.accounts.map(account => (
                   <div
                     key={account.chain}
-                    className="rounded-xl border border-stone-200 dark:border-neutral-800 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 dark:bg-neutral-800/60 px-3 py-2">
+                    className="rounded-xl border border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 px-3 py-2">
                     <div className="flex items-center justify-between gap-3">
-                      <span className="text-xs font-semibold uppercase tracking-wide text-stone-500 dark:text-neutral-400 dark:text-neutral-400">
+                      <span className="text-xs font-semibold uppercase tracking-wide text-stone-500 dark:text-neutral-400">
                         {account.chain}
                       </span>
-                      <span className="text-xs font-mono text-stone-700 dark:text-neutral-200 dark:text-neutral-200 truncate">
+                      <span className="text-xs font-mono text-stone-700 dark:text-neutral-200 truncate">
                         {account.address}
                       </span>
                     </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/ConnectionsPanel.tsx` around lines 190 -
215, The JSX in ConnectionsPanel (inside the walletStatus.accounts mapping and
its parent container) contains duplicated dark-mode Tailwind classes; remove the
repeated duplicates so each dark: class appears only once (e.g. change
"dark:border-neutral-800 dark:border-neutral-800" to a single
"dark:border-neutral-800", "dark:bg-neutral-900 dark:bg-neutral-900" to
"dark:bg-neutral-900", "dark:text-neutral-100 dark:text-neutral-100" to one
instance, "dark:text-neutral-400 dark:text-neutral-400" to one instance, and
"dark:bg-neutral-800/60 dark:bg-neutral-800/60" and "dark:text-neutral-200
dark:text-neutral-200" likewise) in the outer container and the per-account div
within ConnectionsPanel.tsx where walletStatus.accounts is mapped.
app/src/components/settings/panels/AIPanel.tsx (1)

476-520: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve duplicated and conflicting dark mode class tokens.

Many elements have duplicated dark mode classes, and some have conflicting values that will cause unpredictable rendering. For example:

  • Line 476: dark:border-neutral-800 dark:border-neutral-800 and dark:bg-neutral-900 dark:bg-neutral-900 (duplicated)
  • Line 507: dark:bg-neutral-900 dark:bg-neutral-800 (conflicting—Tailwind will apply the last one), dark:border-neutral-700 dark:border-neutral-700, dark:text-neutral-100 dark:text-neutral-100, dark:placeholder-neutral-500 dark:placeholder-neutral-500 (duplicated)
  • Line 519: dark:hover:bg-neutral-800/60 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/60 (tripled!), plus duplicated border/bg/text classes

This pattern repeats in lines 478-481, 659-681, 693-722, 940-1392, 1447-1465, 1558-1665, 1688-1715, and 2149-2268. A systematic pass to deduplicate and resolve conflicts is needed.

🧹 Sample fix for lines 476, 507, 519
-      className="fixed inset-0 z-50 flex items-center justify-center bg-black/30 p-4">
-      <div className="w-full max-w-md rounded-2xl border border-stone-200 dark:border-neutral-800 dark:border-neutral-800 bg-white dark:bg-neutral-900 dark:bg-neutral-900 p-6 shadow-soft">
+      <div className="w-full max-w-md rounded-2xl border border-stone-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-6 shadow-soft">
         <div className="mb-4">
-          <h3 className="text-base font-semibold text-stone-900 dark:text-neutral-100 dark:text-neutral-100">{`${t('settings.ai.connectProvider')} ${label}`}</h3>
-          <p className="mt-0.5 text-xs text-stone-500 dark:text-neutral-400 dark:text-neutral-400">
+          <h3 className="text-base font-semibold text-stone-900 dark:text-neutral-100">{`${t('settings.ai.connectProvider')} ${label}`}</h3>
+          <p className="mt-0.5 text-xs text-stone-500 dark:text-neutral-400">
             {t('settings.ai.apiKeyStoredEncrypted')}
           </p>

...

           <input
             ...
-            className="rounded-lg border border-stone-300 dark:border-neutral-700 dark:border-neutral-700 bg-white dark:bg-neutral-900 dark:bg-neutral-800 px-3 py-2 text-sm text-stone-900 dark:text-neutral-100 dark:text-neutral-100 placeholder-stone-400 dark:placeholder-neutral-500 dark:placeholder-neutral-500 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 disabled:opacity-60"
+            className="rounded-lg border border-stone-300 dark:border-neutral-700 bg-white dark:bg-neutral-800 px-3 py-2 text-sm text-stone-900 dark:text-neutral-100 placeholder-stone-400 dark:placeholder-neutral-500 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 disabled:opacity-60"
           />

...

           <button
             ...
-            className="rounded-lg border border-stone-200 dark:border-neutral-800 dark:border-neutral-800 bg-white dark:bg-neutral-900 dark:bg-neutral-900 px-4 py-2 text-sm font-medium text-stone-700 dark:text-neutral-200 dark:text-neutral-200 hover:bg-stone-50 dark:hover:bg-neutral-800/60 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/60 disabled:opacity-50">
+            className="rounded-lg border border-stone-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 px-4 py-2 text-sm font-medium text-stone-700 dark:text-neutral-200 hover:bg-stone-50 dark:hover:bg-neutral-800/60 disabled:opacity-50">
             {t('common.cancel')}
           </button>

Apply similar cleanup throughout the file to remove duplicates and resolve conflicts.

Also applies to: 507-507, 519-519

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/AIPanel.tsx` around lines 476 - 520, In
the AIPanel component (look for the AIPanel React function and the input with id
"provider-key-input" and the surrounding buttons), remove duplicated Tailwind
dark: class tokens and resolve conflicting dark: values in the className
strings: for each element dedupe repeated tokens (e.g., remove duplicate
dark:border-neutral-800 and duplicate dark:bg-neutral-900) and pick the single
intended dark variant where conflicts exist (e.g., choose either
dark:bg-neutral-900 OR dark:bg-neutral-800 and apply it once), then run a quick
grep/IDE search for "dark:" usages in AIPanel to apply the same cleanup pattern
to the other duplicated lines mentioned so the final className strings contain
no repeated tokens and no contradictory dark: classes.
🟠 Major comments (10)
app/src/providers/ThemeProvider.tsx-17-33 (1)

17-33: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard document access before calling apply() in the effect.

Line 29 invokes apply() before the environment guard, but apply() reads document.documentElement (Line 19). In non-browser rendering contexts this can crash at runtime.

Suggested fix
  useEffect(() => {
+    if (typeof document === 'undefined') return;
+
     const apply = () => {
       const root = document.documentElement;
       const resolved = resolveTheme(mode);
@@
-    apply();
+    apply();
 
     if (mode !== 'system') return;
     if (typeof window === 'undefined' || !window.matchMedia) return;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/providers/ThemeProvider.tsx` around lines 17 - 33, The effect calls
apply() which reads document.documentElement (via resolveTheme/mode) before any
environment guard, causing crashes in non-browser contexts; update the useEffect
in ThemeProvider so you first check that document (and document.documentElement)
exists (e.g., typeof document !== 'undefined' and document.documentElement)
before calling apply(), or move the apply() call to after the existing
window/matchMedia guard; ensure the referenced apply(), resolveTheme and
useEffect logic still sets classList and colorScheme when running in the
browser.
app/src/components/settings/panels/WebhooksDebugPanel.tsx-340-340 (1)

340-340: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Dark-mode payload text becomes low-contrast and hard to read.

Line 340 sets a light dark-mode background (dark:bg-neutral-50) but keeps light text (text-stone-100), making payload content nearly unreadable in dark theme.

Suggested fix
-      <pre className="mt-1 max-h-40 overflow-auto rounded-lg border border-stone-200 dark:border-neutral-800 dark:border-neutral-800 bg-stone-950 dark:bg-neutral-50 p-2 text-[11px] text-stone-100 whitespace-pre-wrap break-words">
+      <pre className="mt-1 max-h-40 overflow-auto rounded-lg border border-stone-200 dark:border-neutral-800 bg-stone-950 dark:bg-neutral-900 p-2 text-[11px] text-stone-100 dark:text-neutral-100 whitespace-pre-wrap break-words">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/WebhooksDebugPanel.tsx` at line 340, The
<pre> in WebhooksDebugPanel.tsx uses a light text class (text-stone-100) while
the dark-mode background is set to a light color (dark:bg-neutral-50), causing
low contrast; update the className on that <pre> (in WebhooksDebugPanel.tsx) to
include a dark-mode text override (e.g., add dark:text-neutral-900 or
dark:text-stone-900 alongside the existing text-stone-100) so text becomes dark
when dark:bg-neutral-50 is active.
app/src/components/settings/panels/billing/SubscriptionPlans.tsx-209-211 (1)

209-211: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix duplicated dark mode class in "current" badge.

Line 209 contains dark:bg-neutral-50 when the base class is bg-stone-950. This creates a white badge in dark mode on a near-black background in light mode, which seems inverted. Verify the intended styling.

🐛 Proposed fix (if inversion is unintended)
                       {isCurrent && !plan.recommended && (
-                        <span className="rounded-full bg-stone-950 dark:bg-neutral-50 px-2.5 py-1 text-[10px] font-bold uppercase tracking-[0.24em] text-white">
+                        <span className="rounded-full bg-stone-950 dark:bg-neutral-50 px-2.5 py-1 text-[10px] font-bold uppercase tracking-[0.24em] text-white dark:text-stone-950">
                           {t('settings.billing.subscription.current')}
                         </span>

Note: The badge background swaps from dark (light mode) to light (dark mode), but the text stays white in both modes. Add dark:text-stone-950 to ensure contrast in dark mode.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/billing/SubscriptionPlans.tsx` around
lines 209 - 211, The "current" badge in SubscriptionPlans.tsx uses bg-stone-950
with dark:bg-neutral-50 which inverts its colors in dark mode; remove the
dark:bg-neutral-50 token from the span's class list and add dark:text-stone-950
so the badge keeps its dark background (bg-stone-950) and the text flips to a
dark color in dark mode for proper contrast—update the span that renders
{t('settings.billing.subscription.current')} accordingly.
app/src/pages/conversations/components/AgentMessageBubble.tsx-147-147 (1)

147-147: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix invalid dark row-striping classes in table rows.

Line 147 has an invalid Tailwind token (dark:bg-neutral-800/60/70) with a malformed opacity value. Additionally, dark:bg-neutral-900 applies to every row regardless of odd/even, breaking dark mode striping entirely.

Proposed fix
-                  className="odd:bg-white dark:bg-neutral-900 even:bg-stone-50 dark:bg-neutral-800/60/70">
+                  className="odd:bg-white even:bg-stone-50 dark:odd:bg-neutral-900 dark:even:bg-neutral-800/60">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/pages/conversations/components/AgentMessageBubble.tsx` at line 147,
The row striping classes in AgentMessageBubble.tsx are wrong: remove the
unconditional dark:bg-neutral-900 and fix the malformed token
dark:bg-neutral-800/60/70; change the className so dark mode striping is applied
per odd/even (e.g. use odd:bg-white and dark:odd:bg-neutral-900,
even:bg-stone-50 and dark:even:bg-neutral-800/70) so each row uses the correct
light and dark variants and the opacity token uses a single /<number> suffix.
app/src/components/settings/panels/MemoryDebugPanel.tsx-247-247 (1)

247-247: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix incorrect dark background color on <pre> element.

The <pre> has text-stone-100 (light text) but dark:bg-neutral-50 (light background in dark mode), resulting in unreadable light-on-light text. This should be dark:bg-neutral-950 (or another dark shade).

🐛 Suggested fix
-            <pre className="mt-1 max-h-32 overflow-auto rounded-lg border border-stone-200 dark:border-neutral-800 dark:border-neutral-800 bg-stone-950 dark:bg-neutral-50 p-2 text-[11px] text-stone-100 whitespace-pre-wrap break-words">
+            <pre className="mt-1 max-h-32 overflow-auto rounded-lg border border-stone-200 dark:border-neutral-800 bg-stone-950 dark:bg-neutral-950 p-2 text-[11px] text-stone-100 dark:text-neutral-100 whitespace-pre-wrap break-words">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/MemoryDebugPanel.tsx` at line 247, In
MemoryDebugPanel.tsx the <pre> element's Tailwind classes set text-stone-100
(light text) but use dark:bg-neutral-50 (light background in dark mode), making
text unreadable; update the class list on that <pre> (inside the
MemoryDebugPanel component) to use a dark background such as dark:bg-neutral-950
(or another sufficiently dark shade) so dark mode has dark background + light
text and remains readable.
app/src/components/settings/SettingsHome.tsx-304-304 (1)

304-304: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix conflicting dark text color classes.

Line 304 contains dark:text-neutral-200 dark:text-neutral-300, which are conflicting. Tailwind will apply whichever appears last, but this makes intent unclear.

🐛 Suggested fix
-            <div className="text-stone-700 dark:text-neutral-200 dark:text-neutral-300 text-sm leading-relaxed">
+            <div className="text-stone-700 dark:text-neutral-300 text-sm leading-relaxed">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/SettingsHome.tsx` at line 304, In SettingsHome
update the stray/conflicting Tailwind classes in the className that currently
contains "dark:text-neutral-200 dark:text-neutral-300" so only a single intended
dark-mode text color remains (e.g., keep "dark:text-neutral-300" or
"dark:text-neutral-200"); locate the className string on the div in the
SettingsHome component and remove the unwanted duplicate class to make the
intent explicit.
app/src/components/settings/SettingsHome.tsx-329-329 (1)

329-329: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix duplicated and conflicting Tailwind classes.

The Cancel button className contains:

  • dark:border-neutral-800 dark:border-neutral-700 (conflicting border colors)
  • dark:text-neutral-200 dark:text-neutral-200 (duplicated)
  • dark:hover:bg-neutral-800 dark:bg-neutral-800 dark:hover:bg-neutral-800 (extensive duplication)
🐛 Suggested fix
-                className="flex-1 px-4 py-2 rounded-lg border border-stone-200 dark:border-neutral-800 dark:border-neutral-700 text-stone-700 dark:text-neutral-200 dark:text-neutral-200 hover:bg-stone-100 dark:hover:bg-neutral-800 dark:bg-neutral-800 dark:hover:bg-neutral-800 transition-colors disabled:opacity-50">
+                className="flex-1 px-4 py-2 rounded-lg border border-stone-200 dark:border-neutral-700 text-stone-700 dark:text-neutral-200 hover:bg-stone-100 dark:hover:bg-neutral-800 transition-colors disabled:opacity-50">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/SettingsHome.tsx` at line 329, The Cancel button
in SettingsHome.tsx has duplicated and conflicting Tailwind classes in its
className; update the className on the Cancel button element (the JSX that
contains the long "flex-1 px-4 py-2 rounded-lg border ..." string) to remove
duplicates and conflicts: keep only one dark:border (choose either
dark:border-neutral-800 or dark:border-neutral-700), remove the duplicate
dark:text-neutral-200, and consolidate bg/hover classes so there is at most one
dark:bg-* and one dark:hover:bg-* (for example keep dark:bg-neutral-800 and
dark:hover:bg-neutral-800 or choose the intended hover/bg pair). Ensure no
repeated tokens remain.
app/src/components/webhooks/ComposeioTriggerHistory.tsx-46-50 (1)

46-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing dark-mode text color on the non-empty history title.

At Line 46, the heading uses text-stone-900 without a dark override, so the title can be too low-contrast in dark mode while the count at Line 48 is already dark-aware.

Suggested fix
-      <h3 className="text-lg font-semibold text-stone-900">
+      <h3 className="text-lg font-semibold text-stone-900 dark:text-neutral-100">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/webhooks/ComposeioTriggerHistory.tsx` around lines 46 -
50, The heading in ComposeioTriggerHistory (the <h3> element rendering
t('webhooks.composioHistory.title')) uses text-stone-900 without a dark-mode
override; update its className to include a dark: color (e.g., add
dark:text-neutral-50 or dark:text-white) so the title has sufficient contrast in
dark mode, matching the pattern used on the count span (which uses
dark:text-neutral-500); modify the className on the h3 accordingly.
app/src/components/settings/panels/TeamMembersPanel.tsx-371-372 (1)

371-372: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Dark-mode cancel button text contrast is too low.

At Line 371, dark:text-neutral-600 on a dark background (bg-stone-700/50) is hard to read. Use a lighter dark text token.

Suggested fix
-                      className="flex-1 px-4 py-2 text-sm font-medium rounded-xl bg-stone-700/50 hover:bg-stone-700 text-stone-300 dark:text-neutral-600 dark:text-neutral-600 transition-colors disabled:opacity-50">
+                      className="flex-1 px-4 py-2 text-sm font-medium rounded-xl bg-stone-700/50 hover:bg-stone-700 text-stone-300 dark:text-neutral-200 transition-colors disabled:opacity-50">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/TeamMembersPanel.tsx` around lines 371 -
372, In TeamMembersPanel update the Cancel button's dark-mode text token so it
has higher contrast: locate the Cancel button JSX in the TeamMembersPanel
component (the element with className containing "bg-stone-700/50" and
"dark:text-neutral-600") and replace "dark:text-neutral-600" with a lighter
dark-mode token such as "dark:text-neutral-300" (or "dark:text-stone-200") to
improve readability on the dark background while keeping other classes
unchanged.
app/src/components/settings/panels/VoicePanel.tsx-729-729 (1)

729-729: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove conflicting dark:text-neutral-500 that shadows the readable dark:text-neutral-100 on form inputs.

The trailing dark:text-neutral-500 overrides the earlier dark:text-neutral-100 on the same elements (lines 729, 798, 880, 952), making input text too dim in dark mode. Delete the dark:text-neutral-500 utility and keep only dark:text-neutral-100 for readable text in dark mode. The dark:placeholder:text-neutral-500 and dark:text-neutral-500 duplicates should also be consolidated.

Suggested fix pattern
- ... text-stone-900 dark:text-neutral-100 dark:text-neutral-100 placeholder:text-stone-400 dark:placeholder:text-neutral-500 dark:text-neutral-500 ...
+ ... text-stone-900 dark:text-neutral-100 placeholder:text-stone-400 dark:placeholder:text-neutral-500 ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/VoicePanel.tsx` at line 729, In VoicePanel
component update the form input className strings to remove the conflicting
dark:text-neutral-500 that overrides dark:text-neutral-100; locate the JSX
elements with the long className (the ones containing dark:text-neutral-100,
dark:placeholder:text-neutral-500 and duplicate dark:text-neutral-500) and
delete the trailing dark:text-neutral-500 occurrences so only
dark:text-neutral-100 remains for input text while keeping
dark:placeholder:text-neutral-500 for placeholders.

Comment thread app/src/components/settings/panels/AIPanel.tsx
Comment thread app/src/components/settings/panels/AIPanel.tsx Outdated
Comment thread app/src/components/settings/panels/billing/PayAsYouGoCard.tsx Outdated
Comment thread app/src/components/settings/panels/billing/SubscriptionPlans.tsx
Comment thread app/src/components/settings/panels/billing/SubscriptionPlans.tsx
Comment thread app/src/components/settings/panels/ConnectionsPanel.tsx Outdated
Comment thread app/src/components/settings/panels/ConnectionsPanel.tsx Outdated
Comment thread app/src/components/skills/SkillResourceTree.tsx Outdated
Comment thread app/src/components/skills/SkillResourceTree.tsx
Comment thread app/src/pages/conversations/components/CitationChips.tsx Outdated
…e.{hide,show} keys

- Reverts the showSidebar default from false → true so existing Conversations.render.test.tsx assertions keep passing. Users can still hide it via the toggle in the chat header; persisting that preference is left as a follow-up.
- Adds zh-CN translations for accounts.respondQueue.hide / .show so the I18nProvider 'Simplified Chinese locale complete' test stays green.
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 18, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/pages/Conversations.tsx`:
- Line 1231: Several button className strings in Conversations.tsx contain
duplicate/conflicting dark: utilities (e.g., both dark:text-neutral-400 and
dark:text-neutral-200, or dark:hover:bg-neutral-800 and
dark:hover:bg-neutral-800/60) so the wrong dark-mode styles win; update each
affected className on the button elements in the Conversations component to
remove duplicates and use the consistent pattern: keep dark:bg-neutral-800 and
dark:hover:bg-neutral-800/60 for background, and use dark:text-neutral-400 for
the base text with dark:hover:text-neutral-200 for hover text; search for
className attributes on the buttons that include
dark:text-neutral-400|dark:text-neutral-200 or
dark:hover:bg-neutral-800|dark:hover:bg-neutral-800/60 and replace them with the
corrected set (dark:bg-neutral-800 dark:hover:bg-neutral-800/60
dark:text-neutral-400 dark:hover:text-neutral-200) while preserving the original
light-mode classes (text-stone-500 hover:text-stone-700 hover:bg-stone-100).
- Around line 1715-1717: The three animated dot <span> elements use an invalid
Tailwind class `dark:bg-neutral-800/600` and also redundantly include
`dark:bg-neutral-400`; update each span (the ones with animate-bounce and
[animation-delay:...]) to replace the too-light `bg-stone-50` with
`bg-stone-400`, remove the redundant `dark:bg-neutral-400`, and change the
invalid opacity suffix to a valid value such as `dark:bg-neutral-800/60` so the
final classes read like: w-1.5 h-1.5 rounded-full bg-stone-400
dark:bg-neutral-800/60 animate-bounce [animation-delay:...].
🪄 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: CHILL

Plan: Pro

Run ID: 88d1e397-18a4-457b-a733-e65e832a3c2d

📥 Commits

Reviewing files that changed from the base of the PR and between 706b042 and 81435fd.

📒 Files selected for processing (2)
  • app/src/lib/i18n/chunks/zh-CN-3.ts
  • app/src/pages/Conversations.tsx

Comment thread app/src/pages/Conversations.tsx
Comment thread app/src/pages/Conversations.tsx Outdated
senamakel added 2 commits May 17, 2026 22:33
…licate dark tokens, missing dark text on AIPanel chips, SkillResourceTree hover, CitationChips hover

Sweeps the 17 review threads from CodeRabbit:

- Collapse invalid `dark:X-N/M/L` triple-slash opacity to `dark:X-N/M` (CR #2, tinyhumansai#4, tinyhumansai#5, tinyhumansai#14)
- Adjacent-duplicate `dark:X dark:X` dedupe across all .tsx (CR tinyhumansai#3, tinyhumansai#6, tinyhumansai#7, tinyhumansai#8 dups, tinyhumansai#11, tinyhumansai#12, tinyhumansai#13, tinyhumansai#16 dup) — uses a targeted perl pass that only collapses when the next token is byte-identical, never spans across different tokens (a prior Python tokenizer pass broke ternary `?` operators inside template-literal classNames)
- Same-color same-property conflicts (`dark:bg-sage-500/10 dark:bg-sage-500/15`) — keep the first value (CR tinyhumansai#8, tinyhumansai#9, tinyhumansai#10)
- AIPanel chip tones for openhuman/openai/anthropic/openrouter/lmstudio/ollama add `dark:text-X-100` so the names stay readable on dark tinted backgrounds (CR #0, #1)
- SkillResourceTree: `hover:bg-white dark:bg-neutral-900` was applying the dark token unconditionally — rewrote to `dark:hover:bg-neutral-800/60`; also gave the selected branch a paired dark variant (CR tinyhumansai#14, tinyhumansai#15)
- CitationChips: `hover:bg-stone-200 dark:bg-neutral-800` had the same hover/dark mis-pairing — now `dark:hover:bg-neutral-700` (CR tinyhumansai#16)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/settings/panels/TeamInvitesPanel.tsx`:
- Line 376: In the TeamInvitesPanel component locate the Cancel button JSX (the
element using the className string that currently contains "dark:bg-neutral-800"
twice) and remove the duplicate "dark:bg-neutral-800", then add a matching dark
hover variant (e.g. "dark:hover:bg-neutral-700" or an appropriate dark hover
color) alongside the existing "hover:bg-stone-200" so the className contains a
single dark:bg-neutral-800 and a dark:hover:bg-* to provide the dark-mode hover
state.
- Around line 253-254: In TeamInvitesPanel.tsx inside the Copy button's
className conditional, remove the duplicated dark-mode utility classes so each
dark:... and hover:... class appears only once (e.g., keep a single
dark:text-neutral-100, single dark:bg-neutral-800, and single
dark:hover:bg-neutral-800) and ensure the ternary still returns the intended
enabled vs disabled strings; update the className expression in the Copy button
element so it uses a single compact class list instead of repeating identical
dark:* tokens.

In `@app/src/pages/conversations/components/AgentMessageBubble.tsx`:
- Line 147: The alternating row background classes in AgentMessageBubble.tsx are
using plain dark: classes that apply to all rows; change the className on the
affected element (the row container string that currently contains "odd:bg-white
dark:bg-neutral-900 even:bg-stone-50 dark:bg-neutral-800/60") to scope dark-mode
to the correct parity by using dark:odd: and dark:even: modifiers (e.g., keep
odd:bg-white and even:bg-stone-50 but replace dark:bg-neutral-900 with
dark:odd:bg-neutral-900 and replace dark:bg-neutral-800/60 with
dark:even:bg-neutral-800/60) so dark backgrounds only apply to the intended
odd/even rows.
- Line 129: AgentMessageBubble's table wrapper currently sets only bg-white/90
which is unreadable in dark mode; update the className in the JSX (the element
that currently contains `className={`w-full max-w-full overflow-hidden border
... bg-white/90 shadow-sm ${bubbleChrome}`}`) to add an appropriate dark mode
background utility (for example `dark:bg-neutral-900/60` or similar) so the
wrapper has a contrasting background in dark theme while preserving the existing
light-mode styling.
🪄 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: CHILL

Plan: Pro

Run ID: 5e983a7c-287e-4d6e-9dd4-8387e9fbadc1

📥 Commits

Reviewing files that changed from the base of the PR and between 81435fd and 74aa6c7.

📒 Files selected for processing (50)
  • app/src/components/accounts/AddAccountModal.tsx
  • app/src/components/accounts/RespondQueuePanel.tsx
  • app/src/components/accounts/WebviewHost.tsx
  • app/src/components/channels/ChannelSelector.tsx
  • app/src/components/channels/DiscordConfig.tsx
  • app/src/components/channels/TelegramConfig.tsx
  • app/src/components/composio/TriggerToggles.tsx
  • app/src/components/rewards/ReferralRewardsSection.tsx
  • app/src/components/rewards/RewardsCommunityTab.tsx
  • app/src/components/rewards/RewardsCouponSection.tsx
  • app/src/components/settings/SettingsHome.tsx
  • app/src/components/settings/SettingsSectionPage.tsx
  • app/src/components/settings/components/SettingsHeader.tsx
  • app/src/components/settings/components/SettingsMenuItem.tsx
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/AboutPanel.tsx
  • app/src/components/settings/panels/AgentChatPanel.tsx
  • app/src/components/settings/panels/AppearancePanel.tsx
  • app/src/components/settings/panels/AutocompleteDebugPanel.tsx
  • app/src/components/settings/panels/AutocompletePanel.tsx
  • app/src/components/settings/panels/BillingPanel.tsx
  • app/src/components/settings/panels/ComposioPanel.tsx
  • app/src/components/settings/panels/ComposioTriagePanel.tsx
  • app/src/components/settings/panels/ConnectionsPanel.tsx
  • app/src/components/settings/panels/CronJobsPanel.tsx
  • app/src/components/settings/panels/DeveloperOptionsPanel.tsx
  • app/src/components/settings/panels/LocalModelDebugPanel.tsx
  • app/src/components/settings/panels/MascotPanel.tsx
  • app/src/components/settings/panels/MemoryDebugPanel.tsx
  • app/src/components/settings/panels/MessagingPanel.tsx
  • app/src/components/settings/panels/NotificationRoutingPanel.tsx
  • app/src/components/settings/panels/NotificationsPanel.tsx
  • app/src/components/settings/panels/PrivacyPanel.tsx
  • app/src/components/settings/panels/RecoveryPhrasePanel.tsx
  • app/src/components/settings/panels/ScreenAwarenessDebugPanel.tsx
  • app/src/components/settings/panels/ScreenIntelligencePanel.tsx
  • app/src/components/settings/panels/TeamInvitesPanel.tsx
  • app/src/components/settings/panels/TeamManagementPanel.tsx
  • app/src/components/settings/panels/TeamMembersPanel.tsx
  • app/src/components/settings/panels/TeamPanel.tsx
  • app/src/components/settings/panels/ToolsPanel.tsx
  • app/src/components/settings/panels/VoiceDebugPanel.tsx
  • app/src/components/settings/panels/VoicePanel.tsx
  • app/src/components/settings/panels/WebhooksDebugPanel.tsx
  • app/src/components/settings/panels/billing/PayAsYouGoCard.tsx
  • app/src/components/settings/panels/billing/SubscriptionPlans.tsx
  • app/src/components/skills/SkillResourceTree.tsx
  • app/src/pages/Conversations.tsx
  • app/src/pages/conversations/components/AgentMessageBubble.tsx
  • app/src/pages/conversations/components/CitationChips.tsx
✅ Files skipped from review due to trivial changes (29)
  • app/src/components/channels/TelegramConfig.tsx
  • app/src/pages/conversations/components/CitationChips.tsx
  • app/src/components/accounts/AddAccountModal.tsx
  • app/src/components/settings/panels/AutocompletePanel.tsx
  • app/src/components/settings/panels/ToolsPanel.tsx
  • app/src/components/settings/components/SettingsHeader.tsx
  • app/src/components/settings/panels/CronJobsPanel.tsx
  • app/src/components/settings/panels/ConnectionsPanel.tsx
  • app/src/components/settings/panels/AutocompleteDebugPanel.tsx
  • app/src/components/settings/panels/DeveloperOptionsPanel.tsx
  • app/src/components/settings/panels/AgentChatPanel.tsx
  • app/src/components/settings/panels/NotificationsPanel.tsx
  • app/src/components/settings/panels/VoiceDebugPanel.tsx
  • app/src/components/settings/panels/NotificationRoutingPanel.tsx
  • app/src/components/accounts/WebviewHost.tsx
  • app/src/components/settings/panels/VoicePanel.tsx
  • app/src/components/composio/TriggerToggles.tsx
  • app/src/components/settings/panels/ScreenAwarenessDebugPanel.tsx
  • app/src/components/settings/panels/MessagingPanel.tsx
  • app/src/components/rewards/ReferralRewardsSection.tsx
  • app/src/components/settings/panels/AboutPanel.tsx
  • app/src/components/channels/DiscordConfig.tsx
  • app/src/components/settings/panels/billing/SubscriptionPlans.tsx
  • app/src/components/settings/panels/ComposioPanel.tsx
  • app/src/components/settings/panels/RecoveryPhrasePanel.tsx
  • app/src/components/settings/panels/TeamMembersPanel.tsx
  • app/src/components/settings/panels/TeamManagementPanel.tsx
  • app/src/components/settings/panels/ScreenIntelligencePanel.tsx
  • app/src/components/settings/panels/MemoryDebugPanel.tsx
🚧 Files skipped from review as they are similar to previous changes (18)
  • app/src/components/settings/panels/LocalModelDebugPanel.tsx
  • app/src/components/channels/ChannelSelector.tsx
  • app/src/components/settings/SettingsHome.tsx
  • app/src/components/accounts/RespondQueuePanel.tsx
  • app/src/components/settings/panels/billing/PayAsYouGoCard.tsx
  • app/src/components/rewards/RewardsCouponSection.tsx
  • app/src/components/settings/SettingsSectionPage.tsx
  • app/src/components/settings/panels/AppearancePanel.tsx
  • app/src/components/settings/panels/ComposioTriagePanel.tsx
  • app/src/components/settings/components/SettingsMenuItem.tsx
  • app/src/components/settings/panels/TeamPanel.tsx
  • app/src/pages/Conversations.tsx
  • app/src/components/rewards/RewardsCommunityTab.tsx
  • app/src/components/settings/panels/WebhooksDebugPanel.tsx
  • app/src/components/settings/panels/PrivacyPanel.tsx
  • app/src/components/settings/panels/BillingPanel.tsx
  • app/src/components/settings/panels/MascotPanel.tsx
  • app/src/components/settings/panels/AIPanel.tsx

Comment thread app/src/components/settings/panels/TeamInvitesPanel.tsx Outdated
Comment thread app/src/components/settings/panels/TeamInvitesPanel.tsx Outdated
Comment thread app/src/pages/conversations/components/AgentMessageBubble.tsx Outdated
Comment thread app/src/pages/conversations/components/AgentMessageBubble.tsx Outdated
- TeamInvitesPanel Copy button (line 253): regex pass triple-stamped dark:text-neutral-100 and dark:hover:bg-neutral-800; collapsed to one of each
- TeamInvitesPanel Cancel button (line 376): duplicate dark:bg-neutral-800 + missing hover dark variant — paired hover:bg-stone-200 with dark:hover:bg-neutral-700
- AgentMessageBubble.tsx:129: table wrapper bg-white/90 had no dark variant — added dark:bg-neutral-900/90
- AgentMessageBubble.tsx:147: odd/even row bgs were missing the odd:/even: prefix on dark variants so both applied to every row — scoped to dark:odd: / dark:even:
@senamakel senamakel merged commit 0ad72dc into tinyhumansai:main May 18, 2026
23 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant