Skip to content

Add customizable theme color#582

Merged
Flaminel merged 13 commits into
mainfrom
add_themes
Apr 25, 2026
Merged

Add customizable theme color#582
Flaminel merged 13 commits into
mainfrom
add_themes

Conversation

@Flaminel

@Flaminel Flaminel commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Add user-configurable theme accent colors and update the UI to support accent presets and custom colors, including a refreshed brand palette and logo component.

New Features:

  • Introduce accent presets and custom accent support in the theme service with persistence and DOM bindings.
  • Add an Appearance settings page for choosing theme mode and accent color, wired into routing and navigation.
  • Add a reusable, theme-aware logo component and dark-variant provider icons for use across layouts and settings.

Enhancements:

  • Refine brand color palette and retint surfaces, orbs, and UI elements to derive styling from accent tokens instead of hardcoded purple values.
  • Update various components and animations to use accent-driven CSS variables for hover states, glows, and decorative effects.
  • Adjust sidebar, toolbar, and auth layout styling to better align with the new accent system and theme-aware logo behavior.

@Flaminel

Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

@Flaminel

Copy link
Copy Markdown
Contributor Author

@greptileai review

@Flaminel

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@sourcery-ai

sourcery-ai Bot commented Apr 25, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a customizable accent color system and Appearance settings UI, refactors theme binding and logo usage to be accent-aware, and updates global styles to derive surfaces and effects from accent variables instead of hard-coded purple values.

Sequence diagram for selecting a custom accent color

sequenceDiagram
  actor User
  participant AppearanceSettingsComponent
  participant ThemeService
  participant LocalStorage
  participant DOMRoot as DocumentElement

  User->>AppearanceSettingsComponent: Change color input
  AppearanceSettingsComponent->>AppearanceSettingsComponent: onCustomColorChange(event)
  AppearanceSettingsComponent->>ThemeService: setCustomAccent(hex)

  ThemeService->>ThemeService: _customAccent.set(hex)
  ThemeService->>LocalStorage: setItem(CUSTOM_ACCENT_KEY, hex)
  alt accent is not custom
    ThemeService->>ThemeService: setAccent("custom")
    ThemeService->>LocalStorage: setItem(ACCENT_KEY, "custom")
  end

  Note over ThemeService,DOMRoot: Angular signal effects already bound in bindToDom

  ThemeService-->>DOMRoot: effect data-accent="custom"
  ThemeService-->>DOMRoot: effect applyCustomAccent(hex)
  ThemeService->>DOMRoot: set CSS vars --brand-50..950, --accent-rgb
Loading

Class diagram for customizable theme color and appearance settings

classDiagram
  direction LR

  class ThemeService {
    <<injectable>>
    - _theme: Theme
    - _performanceMode: boolean
    - _fullWidth: boolean
    - _accent: Accent
    - _customAccent: string
    - static THEME_KEY: string
    - static PERFORMANCE_MODE_KEY: string
    - static FULL_WIDTH_KEY: string
    - static ACCENT_KEY: string
    - static CUSTOM_ACCENT_KEY: string
    - static DEFAULT_CUSTOM_ACCENT: string
    - static BRAND_SHADES: readonly number[]
    - static LIGHTNESS_STOPS: Record<number, number>
    + theme: Signal<Theme>
    + performanceMode: Signal<boolean>
    + fullWidth: Signal<boolean>
    + accent: Signal<Accent>
    + customAccent: Signal<string>
    + constructor()
    + setTheme(theme: Theme): void
    + setPerformanceMode(value: boolean): void
    + setFullWidth(value: boolean): void
    + setAccent(accent: Accent): void
    + setCustomAccent(hex: string): void
    - restoreFromStorage(): void
    - detectSystemPreferences(): void
    - bindToDom(): void
    - isAccent(value: string): boolean
    - applyCustomAccent(hex: string): void
    - clearInlineAccent(): void
  }

  class AppearanceSettingsComponent {
    <<standalone component>>
    - themeService: ThemeService
    + theme: Signal<Theme>
    + accent: Signal<Accent>
    + customAccent: Signal<string>
    + presetSwatches: AccentSwatch[]
    + constructor()
    + selectTheme(theme: Theme): void
    + selectAccent(accent: Accent): void
    + onCustomColorChange(event: Event): void
  }

  class AccentSwatch {
    <<interface>>
    + value: Accent
    + label: string
    + color: string
  }

  class LogoComponent {
    <<standalone component>>
    + ariaLabel: Input<string>
  }

  class NavSidebarComponent {
    <<standalone component>>
    - hub: AppHubService
    - auth: AuthService
    - themeService: ThemeService
    + theme: Signal<Theme>
    + collapsed: Input<boolean>
    + mobileOpen: Input<boolean>
    + themedIconSrc(src: string): string
    + logout(): void
  }

  class NotificationsComponent {
    <<component>>
    - api: NotificationApi
    - toast: ToastService
    - confirmService: ConfirmService
    - themeService: ThemeService
    + theme: Signal<Theme>
    + themedIconSrc(src: string): string
  }

  class Theme {
    <<type alias>>
    + dark
    + light
  }

  class Accent {
    <<type alias>>
    + default
    + blue
    + green
    + rose
    + amber
    + teal
    + custom
  }

  class AccentPreset {
    <<const union>>
    + default
    + blue
    + green
    + rose
    + amber
    + teal
  }

  ThemeService --> Theme : uses
  ThemeService --> Accent : manages
  ThemeService --> AccentPreset : presets

  AppearanceSettingsComponent --> ThemeService : injects
  AppearanceSettingsComponent --> Accent : selects
  AppearanceSettingsComponent --> AccentSwatch : builds

  NavSidebarComponent --> ThemeService : injects
  NavSidebarComponent --> LogoComponent : uses

  NotificationsComponent --> ThemeService : injects

  Accent <|-- AccentPreset
Loading

File-Level Changes

Change Details Files
Extend ThemeService and theming system to support accent presets and custom accent colors, persisting them and applying dynamic CSS variables.
  • Introduce Accent/AccentPreset types, localStorage keys, default custom accent, brand shade constants, and lightness stops for generating HSL-based scales.
  • Add accent and customAccent signals with setters that persist to storage and migrate legacy purple accent values.
  • Restore accent/customAccent from localStorage with validation, and bind data-accent plus inline brand/accent CSS variables via new effects.
  • Implement helpers to validate accent values, generate a full brand scale from a custom hex using RGB/HSL conversion utilities, and clear inline accent overrides.
code/frontend/src/app/core/services/theme.service.ts
Introduce accent presets and use accent variables instead of hard-coded purple in themes and global tokens.
  • Add _accents.scss to define per-preset brand scales and accent RGB triples, wired via data-accent attribute.
  • Update _tokens.scss to expose --accent-rgb tied to the default brand-500 color for translucent accent usage.
  • Refine brand violet palette in _variables.scss to a cooler purple scale that matches default custom accent.
  • Refactor _themes.scss, _glass.scss, _animations.scss, and root styles to compute surfaces, borders, glows, sidebars, scrollbars, and orbs from --brand-* and --accent-rgb instead of literal hexes.
code/frontend/src/styles/_accents.scss
code/frontend/src/styles/_tokens.scss
code/frontend/src/styles/_variables.scss
code/frontend/src/styles/_themes.scss
code/frontend/src/styles/_glass.scss
code/frontend/src/styles/_animations.scss
code/frontend/src/styles.scss
Add Appearance settings page for theme and accent selection, and wire it into routing and navigation.
  • Create AppearanceSettingsComponent with theme and accent signals from ThemeService and swatch metadata for presets plus a custom color input.
  • Build the Appearance settings template and styles, with theme toggles and accent swatch grid including a color picker for custom accents.
  • Register /settings/appearance route, add it to Settings navigation, and expose the palette icon in the app icon registry.
code/frontend/src/app/features/settings/appearance/appearance-settings.component.ts
code/frontend/src/app/features/settings/appearance/appearance-settings.component.html
code/frontend/src/app/features/settings/appearance/appearance-settings.component.scss
code/frontend/src/app/app.routes.ts
code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.ts
code/frontend/src/app/app.config.ts
Update logo usage to a new SVG LogoComponent that responds to theme and accent, and adjust layout styles accordingly.
  • Add reusable LogoComponent that renders the new SVG logo, controlled via CSS custom properties for body and accent colors.
  • Replace img-based logo usage in sidebar and auth layout templates with and adjust sizing, drop shadows, and text color to use accent-aware variables and sidebar tokens.
  • Introduce logo-related CSS variables (--logo-fg, --logo-accent) in themes for dark/light behavior and default accent override.
  • Update docs logo asset to match new branding (and add updated main logo asset).
code/frontend/src/app/ui/logo/logo.component.ts
code/frontend/src/app/ui/logo/logo.component.html
code/frontend/src/app/ui/logo/logo.component.scss
code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.html
code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.scss
code/frontend/src/app/layout/auth-layout/auth-layout.component.html
code/frontend/src/app/layout/auth-layout/auth-layout.component.scss
Logo/logo.svg
docs/static/img/cleanuparr.svg
Make various UI components accent-aware and retint effects, borders, and glows using --accent-rgb.
  • Replace hard-coded purple rgba/hex values in buttons, cards, badges, inputs, dashboard, events/logs/strikes/seeker-stats views, toolbar, list layouts, modals, and auth/setup flows with references to --accent-rgb or other accent-related tokens.
  • Adjust gradients, shadows, and progress fills to use brand/accent variables so changing the accent updates the whole UI consistently.
  • Tweak sidebar behavior (hover/active colors, fade gradient, error/logout item colors) to align with new theme tokens and accent model.
code/frontend/src/app/features/dashboard/dashboard.component.scss
code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.scss
code/frontend/src/app/features/auth/login/login.component.scss
code/frontend/src/app/ui/button/button.component.scss
code/frontend/src/app/features/auth/setup/setup.component.scss
code/frontend/src/app/features/events/events.component.scss
code/frontend/src/app/features/logs-component/logs.component.scss
code/frontend/src/styles/_animations.scss
code/frontend/src/app/ui/badge/badge.component.scss
code/frontend/src/app/ui/card/card.component.scss
code/frontend/src/app/ui/chip-input/chip-input.component.scss
code/frontend/src/styles/_glass.scss
code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.scss
code/frontend/src/app/layout/toolbar/toolbar.component.scss
code/frontend/src/app/ui/modal/modal.component.scss
code/frontend/src/app/ui/number-input/number-input.component.scss
code/frontend/src/app/ui/size-input/size-input.component.scss
code/frontend/src/styles/_list-layout.scss
code/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.scss
code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.scss
code/frontend/src/app/features/strikes/strikes.component.scss
code/frontend/src/styles.scss
Make provider and sidebar external icons theme-aware by swapping between light and dark SVG variants based on current theme.
  • Inject ThemeService into NavSidebarComponent and NotificationsComponent to expose reactive theme signals.
  • Add themedIconSrc helpers to pick dark vs light icon asset suffix based on theme, and update templates to use it for sidebar and notification provider icons.
  • Add dark SVG icon variants for all external providers used in notifications and sidebar.
code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.ts
code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.html
code/frontend/src/app/features/settings/notifications/notifications.component.ts
code/frontend/src/app/features/settings/notifications/notifications.component.html
code/frontend/public/icons/ext/apprise-dark.svg
code/frontend/public/icons/ext/discord-dark.svg
code/frontend/public/icons/ext/gotify-dark.svg
code/frontend/public/icons/ext/lidarr-dark.svg
code/frontend/public/icons/ext/notifiarr-dark.svg
code/frontend/public/icons/ext/ntfy-dark.svg
code/frontend/public/icons/ext/pushover-dark.svg
code/frontend/public/icons/ext/radarr-dark.svg
code/frontend/public/icons/ext/readarr-dark.svg
code/frontend/public/icons/ext/sonarr-dark.svg
code/frontend/public/icons/ext/telegram-dark.svg
code/frontend/public/icons/ext/whisparr-dark.svg

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The themedIconSrc helper relies on filenames ending in -light.svg and a simple string replacement to derive dark variants; consider either handling missing/other suffixes more defensively or passing in an explicit light/dark pair to avoid brittle coupling to file naming conventions.
  • Accent preset base colors are defined both in _accents.scss and in the PRESET_SWATCHES map in appearance-settings.component.ts; pulling these from a single source (or generating the swatch colors from CSS variables) would reduce the risk of the values drifting out of sync.
  • In ThemeService.applyCustomAccent, you recompute document.documentElement instead of reusing the cached root element from bindToDom; capturing and reusing a single reference would slightly simplify the code and avoid repeated DOM lookups.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `themedIconSrc` helper relies on filenames ending in `-light.svg` and a simple string replacement to derive dark variants; consider either handling missing/other suffixes more defensively or passing in an explicit light/dark pair to avoid brittle coupling to file naming conventions.
- Accent preset base colors are defined both in `_accents.scss` and in the `PRESET_SWATCHES` map in `appearance-settings.component.ts`; pulling these from a single source (or generating the swatch colors from CSS variables) would reduce the risk of the values drifting out of sync.
- In `ThemeService.applyCustomAccent`, you recompute `document.documentElement` instead of reusing the cached `root` element from `bindToDom`; capturing and reusing a single reference would slightly simplify the code and avoid repeated DOM lookups.

## Individual Comments

### Comment 1
<location path="code/frontend/src/app/core/services/theme.service.ts" line_range="98-101" />
<code_context>
+    localStorage.setItem(ACCENT_KEY, accent);
+  }
+
+  setCustomAccent(hex: string): void {
+    this._customAccent.set(hex);
+    localStorage.setItem(CUSTOM_ACCENT_KEY, hex);
+    if (this._accent() !== 'custom') {
+      this.setAccent('custom');
+    }
</code_context>
<issue_to_address>
**suggestion:** Consider validating custom accent values on write, not only on restore.

`setCustomAccent` stores any hex string without validation, while `restoreFromStorage` / `applyCustomAccent` validate via `hexToRgb`/regex. This can leave invalid values in localStorage that are never applied and give no feedback to the user. Please validate in `setCustomAccent` (e.g. reject/normalise invalid values) or fall back to `DEFAULT_CUSTOM_ACCENT` on failure so storage and runtime stay consistent.

Suggested implementation:

```typescript
  setCustomAccent(hex: string): void {
    const candidate = hex.trim();
    const isValid = this.hexToRgb(candidate) !== null;
    const valueToStore = isValid ? candidate : DEFAULT_CUSTOM_ACCENT;

    this._customAccent.set(valueToStore);
    localStorage.setItem(CUSTOM_ACCENT_KEY, valueToStore);

    if (this._accent() !== 'custom') {

```

This change assumes:
1. `this.hexToRgb` is already defined on `ThemeService` and returns `null` (or a falsy value) when the hex string is invalid, as implied by its use in `restoreFromStorage` / `applyCustomAccent`.
2. `DEFAULT_CUSTOM_ACCENT` is already defined in this file and matches the default that `restoreFromStorage` uses.

If `hexToRgb` has a different signature or behavior (e.g. throws instead of returning `null` on invalid input), you should adapt the `isValid` check accordingly (e.g. wrap the call in a try/catch and treat exceptions as invalid).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +98 to +101
setCustomAccent(hex: string): void {
this._customAccent.set(hex);
localStorage.setItem(CUSTOM_ACCENT_KEY, hex);
if (this._accent() !== 'custom') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Consider validating custom accent values on write, not only on restore.

setCustomAccent stores any hex string without validation, while restoreFromStorage / applyCustomAccent validate via hexToRgb/regex. This can leave invalid values in localStorage that are never applied and give no feedback to the user. Please validate in setCustomAccent (e.g. reject/normalise invalid values) or fall back to DEFAULT_CUSTOM_ACCENT on failure so storage and runtime stay consistent.

Suggested implementation:

  setCustomAccent(hex: string): void {
    const candidate = hex.trim();
    const isValid = this.hexToRgb(candidate) !== null;
    const valueToStore = isValid ? candidate : DEFAULT_CUSTOM_ACCENT;

    this._customAccent.set(valueToStore);
    localStorage.setItem(CUSTOM_ACCENT_KEY, valueToStore);

    if (this._accent() !== 'custom') {

This change assumes:

  1. this.hexToRgb is already defined on ThemeService and returns null (or a falsy value) when the hex string is invalid, as implied by its use in restoreFromStorage / applyCustomAccent.
  2. DEFAULT_CUSTOM_ACCENT is already defined in this file and matches the default that restoreFromStorage uses.

If hexToRgb has a different signature or behavior (e.g. throws instead of returning null on invalid input), you should adapt the isValid check accordingly (e.g. wrap the call in a try/catch and treat exceptions as invalid).

@greptile-apps

greptile-apps Bot commented Apr 25, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a customisable accent-color system to the frontend: six CSS presets (default, blue, green, rose, amber, teal) driven by [data-accent] attribute selectors in a new _accents.scss file, plus a free-form custom color picker backed by an HSL color-scale generator in ThemeService. A new Appearance settings page wires everything together, and a reusable LogoComponent replaces the static icon so the logo adapts to the active accent.

Confidence Score: 5/5

Safe to merge; only P2 findings remain after previous review threads addressed the main concerns.

No P0 or P1 issues found. The two P2 findings are a deliberate design departure from a CLAUDE.md gotcha (light-theme sidebar is now light-tinted instead of dark-purple) and a minor floating-point drift between --accent-rgb and --brand-500 for custom colors.

code/frontend/src/styles/_themes.scss — light-theme sidebar change should be verified against the documented design intent in CLAUDE.md

Important Files Changed

Filename Overview
code/frontend/src/app/core/services/theme.service.ts Adds accent preset + custom accent signals, DOM binding via effects, and pure-TS HSL color math for generating brand scale from a custom hex; logic is sound but --accent-rgb and --brand-500 can drift by one rounding step for custom colors
code/frontend/src/styles/_themes.scss Replaces hardcoded purple rgba values with accent-driven CSS variables and adds color-mix() surface tints with static fallbacks; light-theme sidebar changed from dark-purple to light-tinted, contradicting the documented CLAUDE.md architecture rule
code/frontend/src/styles/_accents.scss New file defining CSS attribute selectors for six accent presets; Tailwind-matching brand scale values are clean and correctly include --accent-rgb for every non-default preset
code/frontend/src/app/features/settings/appearance/appearance-settings.component.ts New appearance settings component; correctly builds preset swatch list from shared ACCENT_PRESETS/ACCENT_PRESET_HEX constants and delegates all state to ThemeService
code/frontend/src/styles/_variables.scss Brand palette refreshed from amethyst-purple to cooler violet (Tailwind violet-500 #8b5cf6); all shades updated consistently
code/frontend/src/styles/_tokens.scss Adds --accent-rgb: 139, 92, 246 to :root so rgba(var(--accent-rgb), X) works on the default preset without any attribute selector

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    U([User picks accent]) --> AP[AppearanceSettingsComponent]
    AP -->|selectAccent preset| SA[ThemeService.setAccent]
    AP -->|onCustomColorChange| SC[ThemeService.setCustomAccent]
    SA --> AS[_accent signal]
    SC --> CS[_customAccent signal]
    SC -->|if not already custom| SA
    AS --> EFF{effect — bindToDom}
    CS --> EFF
    EFF -->|preset| ATTR[setAttribute data-accent = preset]
    EFF -->|preset| CLR[clearInlineAccent — remove inline --brand-* and --accent-rgb]
    EFF -->|custom| ATTR2[setAttribute data-accent = custom]
    EFF -->|custom| APL[applyCustomAccent]
    APL --> HSL[hex → RGB → HSL]
    HSL --> SCALE[generate --brand-50..950 via LIGHTNESS_STOPS]
    SCALE --> DOM[write CSS custom props to documentElement.style]
    APL --> RGB[write --accent-rgb]
    ATTR --> CSS[_accents.scss preset block applies]
    CLR --> CSS2[_tokens.scss root values restored]
Loading

Reviews (2): Last reviewed commit: "extract themedIconSrc helper to theme se..." | Re-trigger Greptile

Comment thread code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.ts Outdated
Comment thread code/frontend/src/styles/_themes.scss Outdated
Comment thread code/frontend/src/app/core/services/theme.service.ts Outdated
@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds runtime accent presets and custom accent support, a new Appearance settings UI, a reusable Logo component, theme-aware icon handling, and replaces hardcoded purple values with a theme-driven --accent-rgb across styles and tokens.

Changes

Cohort / File(s) Summary
Core theme & tokens
code/frontend/src/app/core/services/theme.service.ts, code/frontend/src/styles/_tokens.scss, code/frontend/src/styles/_variables.scss, code/frontend/src/styles/_accents.scss, code/frontend/src/styles/_themes.scss, code/frontend/src/styles/_animations.scss
Extended ThemeService with accent, customAccent, ACCENT_PRESETS, persistence and color-conversion helpers; added --accent-rgb token, new accent presets stylesheet, updated brand palette and theme tokens to use --accent-rgb.
Appearance settings feature & routing
code/frontend/src/app/features/settings/appearance/appearance-settings.component.ts, code/frontend/src/app/features/settings/appearance/appearance-settings.component.html, code/frontend/src/app/features/settings/appearance/appearance-settings.component.scss, code/frontend/src/app/app.routes.ts
Added standalone AppearanceSettingsComponent (theme + accent UI, custom color input), and lazy route /settings/appearance.
Logo & UI barrel export
code/frontend/src/app/ui/logo/logo.component.ts, code/frontend/src/app/ui/logo/logo.component.html, code/frontend/src/app/ui/logo/logo.component.scss, code/frontend/src/app/ui/index.ts
New standalone LogoComponent with SVG, ariaLabel input; exported from UI index.
Layouts & navigation changes
code/frontend/src/app/layout/auth-layout/auth-layout.component.html, code/frontend/src/app/layout/auth-layout/auth-layout.component.ts, code/frontend/src/app/layout/auth-layout/auth-layout.component.scss, code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.html, code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.ts, code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.scss
Replaced static logo <img> with app-logo, injected ThemeService in sidebar, added Appearance nav entry (uses tablerPalette), and updated logo sizing/glow and sidebar styles to use --accent-rgb.
Icon registration
code/frontend/src/app/app.config.ts
Registered tablerPalette icon in app icon provider.
Theme-aware icon usage & notifications
code/frontend/src/app/features/settings/notifications/notifications.component.ts, code/frontend/src/app/features/settings/notifications/notifications.component.html
Injected ThemeService; template now uses themeService.themedIconSrc(...) for light/dark icon variants and marks decorative icons as alt="" aria-hidden="true".
UI components: accent updates (SCSS)
code/frontend/src/app/ui/badge/badge.component.scss, code/frontend/src/app/ui/button/button.component.scss, code/frontend/src/app/ui/card/card.component.scss, code/frontend/src/app/ui/chip-input/chip-input.component.scss, code/frontend/src/app/ui/modal/modal.component.scss, code/frontend/src/app/ui/number-input/number-input.component.scss, code/frontend/src/app/ui/size-input/size-input.component.scss
Replaced hardcoded purple RGBA values with rgba(var(--accent-rgb), ...) for shadows, borders, and gradients.
Feature styles: accent updates (SCSS)
code/frontend/src/app/features/auth/login/login.component.scss, code/frontend/src/app/features/auth/setup/setup.component.scss, code/frontend/src/app/features/dashboard/dashboard.component.scss, code/frontend/src/app/features/events/events.component.scss, code/frontend/src/app/features/logs-component/logs.component.scss, code/frontend/src/app/features/seeker-stats/..., code/frontend/src/app/features/strikes/strikes.component.scss
Systematic replacement of hardcoded accent color with --accent-rgb in hover glows, markers, animations, and gradients (includes seeker-stats tabs and dashboard).
Layout & toolbar visuals
code/frontend/src/app/layout/toolbar/toolbar.component.scss, code/frontend/src/app/layout/auth-layout/auth-layout.component.scss, code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.scss
Updated shine/glow and hover shadow colors to use --accent-rgb; adjusted logo drop-shadow and related sizing.
Global styles & mixins
code/frontend/src/styles.scss, code/frontend/src/styles/_glass.scss, code/frontend/src/styles/_list-layout.scss, code/frontend/src/styles/_animations.scss
Updated skeleton, glass shadows, list hover glows, and animation keyframes to derive tints from --accent-rgb.
Minor component bindings
code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.ts, code/frontend/src/app/features/settings/notifications/notifications.component.ts, code/frontend/src/app/layout/auth-layout/auth-layout.component.ts
Injected ThemeService where needed and exposed theme or used themedIconSrc helpers; added LogoComponent to standalone imports in affected components.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Poem

🐰 In a burrow of styles I hop and play,
Accents shifting colors day by day.
A logo springs, and settings bloom anew—
I nibble CSS, and change the hue. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add customizable theme color' directly and clearly summarizes the main objective of the PR: introducing user-configurable theme accent colors.
Description check ✅ Passed The description is well-structured and comprehensively related to the changeset, detailing new features, enhancements, and specific component updates aligned with the PR objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add_themes

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
code/frontend/src/app/ui/modal/modal.component.scss (1)

31-31: Consider fully tokenizing the modal border gradient.

One stop is now theme-driven, but the other tinted stop is still fixed blue. Making both stops theme-based will keep modal chroma consistent across custom accents.

Proposed tweak
-    background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.3), transparent 50%, rgba(59, 130, 246, 0.2));
+    background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.3), transparent 50%, rgba(var(--accent-rgb), 0.2));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/frontend/src/app/ui/modal/modal.component.scss` at line 31, The modal
gradient currently uses a theme token for the first stop
(rgba(var(--accent-rgb), 0.3)) but a hard-coded blue for the second stop
(rgba(59, 130, 246, 0.2)); update the background declaration in
modal.component.scss to use a theme-driven token for the second stop as well
(e.g., use a dedicated CSS variable like --accent-tint-rgb or reuse --accent-rgb
with a different alpha) and provide a sensible fallback so both gradient stops
are driven by theme values and keep modal chroma consistent across custom
accents.
code/frontend/src/app/features/settings/notifications/notifications.component.ts (1)

739-741: Theming helper implementation looks correct, but there's code duplication.

The themedIconSrc method logic is correct for swapping icon variants based on theme. However, this exact method is duplicated in NavSidebarComponent. Consider extracting this to a shared utility or the ThemeService itself to avoid duplication.

♻️ Optional: Extract to ThemeService
// In ThemeService
themedIconSrc(src: string): string {
  return this._theme() === 'dark' ? src : src.replace('-light.svg', '-dark.svg');
}

Then in components:

-themedIconSrc(src: string): string {
-  return this.theme() === 'dark' ? src : src.replace('-light.svg', '-dark.svg');
-}
+// Use this.themeService.themedIconSrc(src) in template
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@code/frontend/src/app/features/settings/notifications/notifications.component.ts`
around lines 739 - 741, The themedIconSrc implementation is duplicated between
NotificationsComponent and NavSidebarComponent; extract it to a single shared
place (preferably ThemeService or a small util) and have both components call
that single method instead of their own copies. Specifically, move the logic
from themedIconSrc(src: string) into ThemeService (e.g.,
ThemeService.themedIconSrc or export a util function), update
NotificationsComponent and NavSidebarComponent to import and call the new
method, and remove the duplicated themedIconSrc implementations; ensure
dependencies and imports are updated and unit/usage references to themedIconSrc
in both components are replaced with the centralized method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/frontend/src/app/core/services/theme.service.ts`:
- Around line 98-104: The setCustomAccent(hex: string) method stores the input
unvalidated which can leave UI state inconsistent; validate the hex before
calling this._customAccent.set and localStorage.setItem (e.g., ensure it matches
a hex color regex like /^#?[0-9A-Fa-f]{6}$/ or your accepted formats), normalize
it to a consistent form (prepend '#' if needed), and only then persist and call
this.setAccent('custom'); if validation fails, do not mutate _customAccent or
localStorage and return/handle gracefully.

In
`@code/frontend/src/app/features/settings/notifications/notifications.component.html`:
- Line 87: The template is passing provider.iconLightUrl into themedIconSrc but
the helper currently inverts variants; update the logic in
notifications.component.ts (themedIconSrc) so it chooses provider.iconDarkUrl
when the app is in dark mode and provider.iconLightUrl when in light mode (i.e.,
if isDarkTheme return iconDarkUrl else return iconLightUrl), or alternatively
change the template to pass provider.iconDarkUrl if you intended the helper to
receive the dark URL; adjust the mapping for the function themedIconSrc and
ensure references to provider.iconLightUrl / provider.iconDarkUrl are used
consistently.

In `@code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.html`:
- Line 72: The img in nav-sidebar.component template is decorative but currently
uses [alt]="item.label" causing duplicate announcements; update the <img> for
icon rendering (themedIconSrc) to be treated as decorative by removing the bound
alt text and using an empty alt attribute (or aria-hidden="true") instead of
item.label so screen readers only read the link text; locate the img with
themedIconSrc(item.iconSrc), item.label and class "sidebar__item-img" to make
this change.

In `@code/frontend/src/styles.scss`:
- Around line 21-22: The skeleton gradient still contains a hardcoded blue stop
(rgba(59, 130, 246, 0.06)); update the gradient to use the same CSS variable as
the other stop so the shimmer uses the site's accent color (i.e., replace the
hardcoded rgba(...) with an rgba using var(--accent-rgb) and the desired alpha),
making the second stop consistent with the existing rgba(var(--accent-rgb),
0.08) usage in styles.scss for the skeleton gradient.

---

Nitpick comments:
In
`@code/frontend/src/app/features/settings/notifications/notifications.component.ts`:
- Around line 739-741: The themedIconSrc implementation is duplicated between
NotificationsComponent and NavSidebarComponent; extract it to a single shared
place (preferably ThemeService or a small util) and have both components call
that single method instead of their own copies. Specifically, move the logic
from themedIconSrc(src: string) into ThemeService (e.g.,
ThemeService.themedIconSrc or export a util function), update
NotificationsComponent and NavSidebarComponent to import and call the new
method, and remove the duplicated themedIconSrc implementations; ensure
dependencies and imports are updated and unit/usage references to themedIconSrc
in both components are replaced with the centralized method.

In `@code/frontend/src/app/ui/modal/modal.component.scss`:
- Line 31: The modal gradient currently uses a theme token for the first stop
(rgba(var(--accent-rgb), 0.3)) but a hard-coded blue for the second stop
(rgba(59, 130, 246, 0.2)); update the background declaration in
modal.component.scss to use a theme-driven token for the second stop as well
(e.g., use a dedicated CSS variable like --accent-tint-rgb or reuse --accent-rgb
with a different alpha) and provide a sensible fallback so both gradient stops
are driven by theme values and keep modal chroma consistent across custom
accents.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c02a109d-3b54-4f70-9add-adcdf8843b81

📥 Commits

Reviewing files that changed from the base of the PR and between 02a07d4 and e886b0e.

⛔ Files ignored due to path filters (14)
  • Logo/logo.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/apprise-dark.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/discord-dark.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/gotify-dark.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/lidarr-dark.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/notifiarr-dark.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/ntfy-dark.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/pushover-dark.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/radarr-dark.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/readarr-dark.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/sonarr-dark.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/telegram-dark.svg is excluded by !**/*.svg
  • code/frontend/public/icons/ext/whisparr-dark.svg is excluded by !**/*.svg
  • docs/static/img/cleanuparr.svg is excluded by !**/*.svg
📒 Files selected for processing (43)
  • code/frontend/src/app/app.config.ts
  • code/frontend/src/app/app.routes.ts
  • code/frontend/src/app/core/services/theme.service.ts
  • code/frontend/src/app/features/auth/login/login.component.scss
  • code/frontend/src/app/features/auth/setup/setup.component.scss
  • code/frontend/src/app/features/dashboard/dashboard.component.scss
  • code/frontend/src/app/features/events/events.component.scss
  • code/frontend/src/app/features/logs-component/logs.component.scss
  • code/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.scss
  • code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.scss
  • code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.scss
  • code/frontend/src/app/features/settings/appearance/appearance-settings.component.html
  • code/frontend/src/app/features/settings/appearance/appearance-settings.component.scss
  • code/frontend/src/app/features/settings/appearance/appearance-settings.component.ts
  • code/frontend/src/app/features/settings/notifications/notifications.component.html
  • code/frontend/src/app/features/settings/notifications/notifications.component.ts
  • code/frontend/src/app/features/strikes/strikes.component.scss
  • code/frontend/src/app/layout/auth-layout/auth-layout.component.html
  • code/frontend/src/app/layout/auth-layout/auth-layout.component.scss
  • code/frontend/src/app/layout/auth-layout/auth-layout.component.ts
  • code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.html
  • code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.scss
  • code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.ts
  • code/frontend/src/app/layout/toolbar/toolbar.component.scss
  • code/frontend/src/app/ui/badge/badge.component.scss
  • code/frontend/src/app/ui/button/button.component.scss
  • code/frontend/src/app/ui/card/card.component.scss
  • code/frontend/src/app/ui/chip-input/chip-input.component.scss
  • code/frontend/src/app/ui/index.ts
  • code/frontend/src/app/ui/logo/logo.component.html
  • code/frontend/src/app/ui/logo/logo.component.scss
  • code/frontend/src/app/ui/logo/logo.component.ts
  • code/frontend/src/app/ui/modal/modal.component.scss
  • code/frontend/src/app/ui/number-input/number-input.component.scss
  • code/frontend/src/app/ui/size-input/size-input.component.scss
  • code/frontend/src/styles.scss
  • code/frontend/src/styles/_accents.scss
  • code/frontend/src/styles/_animations.scss
  • code/frontend/src/styles/_glass.scss
  • code/frontend/src/styles/_list-layout.scss
  • code/frontend/src/styles/_themes.scss
  • code/frontend/src/styles/_tokens.scss
  • code/frontend/src/styles/_variables.scss

Comment thread code/frontend/src/app/core/services/theme.service.ts
Comment thread code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.html Outdated
Comment thread code/frontend/src/styles.scss Outdated
@Flaminel

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@Flaminel

Copy link
Copy Markdown
Contributor Author

@greptileai review

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 the current code and only fix it if needed.

Inline comments:
In `@code/frontend/src/styles/_accents.scss`:
- Around line 1-12: Remove the empty single-line comment causing the
scss/comment-no-empty lint error by deleting the standalone "//" line in the
Accent Presets header block (the empty comment between the explanatory lines),
or replace it with a meaningful comment text; ensure the rest of the header
comments remain unchanged so the preset documentation and note about
ACCENT_PRESET_HEX are preserved.

In `@code/frontend/src/styles/_themes.scss`:
- Around line 10-15: The duplicate custom-property declarations (e.g.,
--surface-ground, --surface-section, --surface-card) must be split so the simple
fallback stays in the root block and the color-mix() value is moved into an
`@supports` (color-mix) override; i.e., leave the solid/fallback declaration
(e.g., --surface-ground: `#0c0614`) and remove the duplicate color-mix line from
that block, then add an `@supports` (selector(--color-mix) or `@supports`
(color-mix(in srgb, var(--brand-500) 8%, `#000`))) block that re-declares the same
custom properties using color-mix() so the browser that supports it will
override the fallback; apply the same change to the other duplicate pairs
mentioned (the other --surface-* declarations).
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d218743-a40d-4880-be3b-9be21914f097

📥 Commits

Reviewing files that changed from the base of the PR and between e886b0e and 4d0146e.

📒 Files selected for processing (10)
  • code/frontend/src/app/core/services/theme.service.ts
  • code/frontend/src/app/features/settings/appearance/appearance-settings.component.ts
  • code/frontend/src/app/features/settings/notifications/notifications.component.html
  • code/frontend/src/app/features/settings/notifications/notifications.component.ts
  • code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.html
  • code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.ts
  • code/frontend/src/app/ui/modal/modal.component.scss
  • code/frontend/src/styles.scss
  • code/frontend/src/styles/_accents.scss
  • code/frontend/src/styles/_themes.scss
✅ Files skipped from review due to trivial changes (1)
  • code/frontend/src/app/ui/modal/modal.component.scss
🚧 Files skipped from review as they are similar to previous changes (3)
  • code/frontend/src/app/layout/nav-sidebar/nav-sidebar.component.html
  • code/frontend/src/app/features/settings/notifications/notifications.component.html
  • code/frontend/src/app/features/settings/appearance/appearance-settings.component.ts

Comment thread code/frontend/src/styles/_accents.scss
Comment thread code/frontend/src/styles/_themes.scss
@Flaminel Flaminel merged commit 9f48d35 into main Apr 25, 2026
12 checks passed
@Flaminel Flaminel deleted the add_themes branch April 25, 2026 22:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant