Skip to content

Conversation

@elibosley
Copy link
Member

@elibosley elibosley commented Nov 24, 2025

  • Added PHP logic to determine if the current theme is dark and set a CSS variable accordingly.
  • Introduced a new function to retrieve the dark mode state from the CSS variable in JavaScript.
  • Updated the theme store to initialize dark mode based on the CSS variable, ensuring consistent theme application across the application.

This improves user experience by ensuring the correct theme is applied based on user preferences.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added theme switching capability with persistent storage to server configuration
    • Enhanced dark mode support with improved detection across CSS variables and DOM elements
    • Multi-source theme initialization from storage, cookies, and server settings
  • Improvements

    • Responsive banner gradient tuning with mobile-optimized display
    • Better theme synchronization across application components
    • Refined layout adjustments for improved mobile experience

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 24, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This pull request introduces a comprehensive theme management system featuring dark mode support, GraphQL-backed theme mutations, multi-source theme initialization from CSS variables and DOM state, and server-client synchronization via a new customization.setTheme GraphQL mutation endpoint. Updates span frontend store refactoring, backend resolver/service additions, theme utility functions, and dev infrastructure.

Changes

Cohort / File(s) Summary
GraphQL Schema & API Types
api/generated-schema.graphql, web/src/composables/gql/graphql.ts, web/src/composables/gql/gql.ts
Added CustomizationMutations type with setTheme mutation, ThemeName enum (azure, black, gray, white), and corresponding mutation variables/document types; extended Mutation root with customization field and InfoCpu with packages and topology fields.
Backend Resolvers & Services
api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts, api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts, api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts, api/src/unraid-api/graph/resolvers/customization/customization.service.ts, api/src/unraid-api/graph/resolvers/customization/customization.module.ts
Added CustomizationMutationsResolver and CustomizationMutations container; implemented setTheme mutation with permissions guard; added CustomizationService.setTheme() method updating config file and in-memory state; exposed resolver and service in module.
Theme Store & Initialization
web/src/store/theme.ts
Switched from useQuery to useLazyQuery with explicit fetchTheme action; introduced DOM/CSS-based initialization reading --theme-dark-mode CSS variable and .dark class; added isDarkModeActive integration and dark class synchronization on documentElement, body, and .unapi elements; updated bannerGradient computation to depend on CSS variable --banner-gradient and dark mode state.
Theme Component & Mutations
web/src/components/DevThemeSwitcher.standalone.vue, web/src/components/DevThemeSwitcher.mutation.ts
Completely refactored with GraphQL mutation-driven theme switching; implemented multi-source theme resolution (CSS variable, cookie, localStorage, server); added centralized applyThemeSelection flow handling DOM updates, CSS file switching, and GraphQL persistence; added isSaving and isQueryLoading UI states; exposed SET_THEME_MUTATION export.
Dark Mode Detection Utility
unraid-ui/src/lib/utils.ts, unraid-ui/src/lib/utils.test.ts
Added isDarkModeActive() function checking CSS variable --theme-dark-mode with fallbacks to documentElement/body .dark class and .unapi.dark selector; includes SSR safety. Comprehensive test suite covering CSS variable detection, class-based detection, and negative cases.
Teleport & Mount Dark Mode Support
unraid-ui/src/composables/useTeleport.ts, unraid-ui/src/composables/useTeleport.test.ts, web/src/components/Wrapper/mount-engine.ts
Added dark mode detection in teleport container initialization and mounted element class application; updated tests with async imports and dark mode test cases.
CSS & Styling Updates
web/src/assets/main.css, web/src/components/UserProfile.standalone.vue
Introduced --banner-gradient-stop CSS variable (default 30%, responsive 60% on max-width 768px); added responsive left padding to UserProfile root container.
Plugin & PHP
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php
Added isDarkMode computation and new CSS variables --theme-dark-mode and --theme-name; restricted banner gradient and header styles to only apply when banner is enabled.
Dev Infrastructure
web/vite-plugin-serve-static.ts, web/test-pages/partials/styles.njk, web/.prettierignore
Added /dev/theme GET/POST middleware for theme persistence via dynamix config; enhanced template rendering with resolvedTheme resolution (config → query → cookie precedence); injected development-time theme CSS variable and dark class synchronization; added dist/ to prettier ignore.
Test Mocks & Fixtures
web/__test__/store/theme.test.ts, web/__test__/components/ColorSwitcher.test.ts, web/__test__/components/UserProfile.test.ts, web/__test__/components/Wrapper/mount-engine.test.ts, web/__test__/mocks/ui-components.ts
Updated test setup with useLazyQuery mock, CSS variable initialization, and isDarkModeActive mock implementation; replaced direct state mutations with setTheme calls; added dark mode detection test cases and robust cleanup.
Formatting
web/src/composables/gql/index.ts
Changed export statement string literals from single to double quotes (no functional change).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • Theme store refactoring (web/src/store/theme.ts): Significant architectural change from immediate query to lazy query with DOM-based initialization; verify CSS variable and dark class synchronization logic across all scenarios
  • DevThemeSwitcher rewrite (web/src/components/DevThemeSwitcher.standalone.vue): Large functional changes with multi-source theme resolution and GraphQL mutation integration; verify cookie/localStorage persistence and DOM updates coordinate correctly
  • Backend theme mutation flow (customization.mutations.resolver.ts, customization.service.ts): New GraphQL mutation pathway and config file persistence; verify permissions guard and in-memory state updates are consistent
  • CSS variable initialization (store, component, plugin, test-pages): Multiple sources setting --theme-dark-mode, --theme-name, --banner-gradient-stop across frontend/backend; verify consistency and order of initialization
  • Dark mode detection utility (isDarkModeActive in utils.ts and mocks): Used across multiple components; verify CSS variable precedence and fallback logic is consistent across all usages
  • Test coverage (multiple test files): Significant mock updates and new async test patterns; verify all dark mode scenarios are adequately covered

Poem

🐰 A theme switcher hopped from query to lazy,
Dark modes dancing through CSS so hazy,
GraphQL mutations now set the tone,
DOM and server as one, fully grown!
Gradients shimmer, the banner's aflutter,
Configuration updated with barely a sputter.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: enhance dark mode support in theme handling' clearly and concisely summarizes the main focus of the changeset, which involves comprehensive dark mode detection, initialization, and propagation across PHP, JavaScript, CSS, and component layers.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/dark-mode

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 586d5c8 and 452bf57.

📒 Files selected for processing (2)
  • web/__test__/store/theme.test.ts (7 hunks)
  • web/src/store/theme.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Always use TypeScript imports with .js extensions for ESM compatibility
Never add comments unless they are needed for clarity of function
Never add comments for obvious things, and avoid commenting when starting and ending code blocks

Files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx,js,jsx}: Use VITEST for test suite, not jest
Use .rejects.toThrow() without arguments to test that functions throw errors, not exact error message strings

Files:

  • web/__test__/store/theme.test.ts
web/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Always run pnpm codegen for GraphQL code generation in the web directory

Files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
web/__test__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/__test__/**/*.test.{ts,tsx}: Test component behavior and output, not implementation details
Use mount from Vue Test Utils for component testing
Stub complex child components that aren't the focus of the test
Find elements with semantic queries like find('button') rather than data-test IDs
Use createPinia() and setActivePinia when testing Store files, only use createTestingPinia if you specifically need its testing features

Files:

  • web/__test__/store/theme.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never use the any type. Always prefer proper typing
Avoid using casting whenever possible, prefer proper typing from the start

Files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
**/*

📄 CodeRabbit inference engine (.cursor/rules/default.mdc)

Never add comments unless they are needed for clarity of function

Files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/*.test.ts: Use .rejects.toThrow() without arguments to test that functions throw errors. Don't test exact error message strings unless the message format is specifically what you're testing
Test what the code does, not implementation details like exact error message wording
Mock external services and API calls
Use vi.mock() for module-level mocks
Specify return values for component methods with vi.spyOn()
Reset mocks between tests with vi.clearAllMocks()
Always await async operations before making assertions

Files:

  • web/__test__/store/theme.test.ts
**/__test__/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/store/**/*.ts: Use createPinia and setActivePinia when testing Pinia store files
Use createPinia() instead of createTestingPinia() for most Pinia store tests
Only use createTestingPinia if you specifically need its testing features for Pinia stores
Let stores initialize with their natural default state instead of forcing initial state
Do not mock the store being tested in the test file when using createPinia()
Place all mock declarations at the top level in Pinia store tests
Use factory functions for module mocks in Pinia store tests to avoid hoisting issues
Test Pinia action side effects and state changes
Verify Pinia actions are called with correct parameters
Mock external dependencies appropriately in Pinia store tests
Test computed properties in Pinia stores by accessing them directly
Verify state changes after Pinia store actions
Test Pinia store getter dependencies are properly mocked
Test Pinia store interactions with other stores
Verify proper error handling in Pinia store tests
Test async operations completely in Pinia store tests
Override specific Pinia action implementations when needed in tests
Set initial state for focused Pinia store testing

Files:

  • web/__test__/store/theme.test.ts
**/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

Ensure Vue reactivity imports like computed, ref, and watchEffect are added to store files even with Nuxt auto-imports enabled

Files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
web/src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Ensure Vue reactivity imports are added to store files (computed, ref, watchEffect)

Files:

  • web/src/store/theme.ts
🧠 Learnings (41)
📓 Common learnings
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Mock external dependencies appropriately in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Use factory functions for module mocks in Pinia store tests to avoid hoisting issues

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia store getter dependencies are properly mocked

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Mock external dependencies and services in Vue component tests

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Place all mock declarations at the top level in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `createTestingPinia()` for mocking stores in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `await nextTick()` for DOM updates in Vue component tests

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Use `createPinia()` and `setActivePinia` when testing Store files, only use `createTestingPinia` if you specifically need its testing features

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-02-20T15:52:58.297Z
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:161-172
Timestamp: 2025-02-20T15:52:58.297Z
Learning: The banner gradient implementation in web/store/theme.ts doesn't require explicit error handling for hexToRgba as CSS gracefully handles invalid values by ignoring them.

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Verify proper error handling in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test computed properties in Pinia stores by accessing them directly

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Verify state changes after Pinia store actions

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Check for expected prop handling and event emissions in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Use `vi.mock()` for module-level mocks

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify that the expected elements are rendered in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/src/**/*.ts : Ensure Vue reactivity imports are added to store files (computed, ref, watchEffect)

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-08-09T01:03:29.676Z
Learnt from: elibosley
Repo: unraid/api PR: 1575
File: packages/unraid-shared/src/services/socket-config.service.spec.ts:10-13
Timestamp: 2025-08-09T01:03:29.676Z
Learning: Vitest is used for all testing across all repositories in the unraid organization, not Jest. Always use `vi` for mocking utilities, not `jest`.

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Reset mocks between tests with `vi.clearAllMocks()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: This is a Nuxt.js app but tests are run with vitest outside of the Nuxt environment

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-02-20T15:52:56.733Z
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:49-50
Timestamp: 2025-02-20T15:52:56.733Z
Learning: CSS variable names in the theme store should be concise and follow established patterns. For example, prefer '--gradient-start' over '--color-customgradient-start' to maintain consistency with other variable names.

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify element existence with `expect(element.exists()).toBe(true)`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Set initial state for focused Pinia store testing

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Specify return values for component methods with `vi.spyOn()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Do not mock the store being tested in the test file when using `createPinia()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia action side effects and state changes

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Let stores initialize with their natural default state instead of forcing initial state

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2024-12-17T14:59:32.458Z
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-02-24T14:51:21.328Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:0-0
Timestamp: 2025-02-24T14:51:21.328Z
Learning: In the Unraid API project's theme system, exact TypeScript type definitions are preferred over index signatures for theme variables to ensure better type safety.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-02-21T18:40:10.810Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:210-216
Timestamp: 2025-02-21T18:40:10.810Z
Learning: When updating theme-related CSS variables via `cssText`, preserve existing non-theme styles by filtering out only theme-related rules (those starting with '--') and combining them with the new theme styles.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:51:46.348Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/api-rules.mdc:0-0
Timestamp: 2025-11-24T17:51:46.348Z
Learning: Applies to api/**/*.test.{ts,tsx} : Prefer to not mock simple dependencies

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/store/**/*.ts : Ensure Vue reactivity imports like `computed`, `ref`, and `watchEffect` are added to store files even with Nuxt auto-imports enabled

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Nuxt is set to auto import so some Vue files may need `computed` or `ref` imported explicitly in store files

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:52:57.888Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:52:57.888Z
Learning: In the unraid/api project, Vue components are compiled into web components. The `setActivePinia(createPinia())` call at the module level in store files is intentional and ensures all web components share a single Pinia store instance, which is the desired behavior. This shared state approach is critical for the application's architecture to function correctly.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:52:57.888Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:52:57.888Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files ensures that all web components share a single Pinia store instance, which is the desired behavior. Without this initialization, each web component would have its own isolated store, breaking the intended architecture.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:33:13.215Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:33:13.215Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files would break the build by causing all web components to share a singular Pinia store instance. Each web component needs its own Pinia store instance to maintain proper isolation and encapsulation.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:33:13.215Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:33:13.215Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files would break the build by causing all web components to share a singular Pinia store instance. Each web component needs its own Pinia store instance to function correctly.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-06-13T17:14:21.739Z
Learnt from: pujitm
Repo: unraid/api PR: 1417
File: web/components/ConnectSettings/ConnectSettings.ce.vue:11-18
Timestamp: 2025-06-13T17:14:21.739Z
Learning: The project’s build tooling auto-imports common Vue/Pinia helpers such as `storeToRefs`, so explicit import statements for them are not required.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2024-12-09T15:45:46.492Z
Learnt from: pujitm
Repo: unraid/api PR: 975
File: web/components/Notifications/TabList.vue:1-4
Timestamp: 2024-12-09T15:45:46.492Z
Learning: In our Nuxt.js setup for the `web` project, it's not necessary to explicitly import `computed` from `vue` in Vue components, as it's globally available.

Applied to files:

  • web/src/store/theme.ts
🧬 Code graph analysis (1)
web/__test__/store/theme.test.ts (1)
api/src/store/index.ts (1)
  • store (10-16)
🔇 Additional comments (7)
web/__test__/store/theme.test.ts (2)

21-43: LGTM - Mock setup and cleanup improvements.

The mock implementations correctly match the external dependencies:

  • useLazyQuery mock includes all required properties (load, result, loading, callbacks)
  • isDarkModeActive mock properly derives state from CSS variables and DOM classes
  • Test cleanup thoroughly removes CSS properties and dark classes for proper isolation
  • Try-catch around app.unmount() prevents errors when app isn't mounted

Also applies to: 68-72, 85-91


126-161: LGTM - Comprehensive test coverage for CSS-variable-driven theme behavior.

The test suite thoroughly covers the new CSS-variable-driven initialization:

  • Tests darkMode computation from CSS variable for both '1' and '0' values
  • Tests bannerGradient computation when CSS variable is set vs. undefined
  • Tests reactive updates when theme changes (e.g., switching to 'gray' theme)
  • Tests initialization flow with mocked getComputedStyle to verify dark class application

The tests correctly enable theme flags before asserting gradient values and verify both documentElement and body class changes.

Based on learnings: Use createPinia() and setActivePinia when testing Store files, which is correctly implemented in this test suite.

Also applies to: 235-290

web/src/store/theme.ts (5)

42-72: LGTM - Well-structured helper functions with proper SSR guards.

The helper functions provide clean abstractions for DOM and CSS variable operations:

  • isDomAvailable() consistently guards against SSR environments
  • getCssVar() safely reads CSS variables from documentElement
  • syncDarkClass() properly applies dark classes to all relevant elements (documentElement, body, .unapi)
  • applyDarkClass() centralizes dark mode application by updating DOM, CSS variables, and the reactive ref
  • bootstrapDarkClass() handles initialization from isDarkModeActive()

The use of optional chaining for document.body?.classList is good defensive programming.


99-99: LGTM - Dark mode initialization correctly uses ref for reactivity.

The change from computed to ref<boolean> for darkMode addresses the previous concern about reactivity. The initialization sequence:

  1. Sets darkMode to a reactive ref
  2. Reads initial state from isDarkModeActive() (which checks CSS variable --theme-dark-mode and DOM classes)
  3. Applies dark class via bootstrapDarkClass() if needed

This ensures the store's darkMode state stays synchronized with DOM changes made by applyDarkClass().

Also applies to: 102-105


149-181: LGTM - Computed properties correctly integrate CSS variables with theme state.

The computed properties provide clean integration of CSS-variable-driven values:

  • themeName reads from --theme-name CSS variable with fallback to internal state
  • readBannerGradientVar defensively filters out invalid values ('null', 'none', 'undefined')
  • bannerGradient returns undefined in dark mode, which aligns with the intentional design (per learnings: "header is intentionally designed to have a light background with dark text in dark mode")

The multi-condition checks in bannerGradient ensure the gradient is only shown when all required conditions are met (dark mode off, flags enabled, CSS variable set).

Based on learnings: The header gradient behavior in dark mode is intentional.


219-232: LGTM - Watch and initialization sequence avoids timing conflicts.

The watch configuration with immediate: false correctly addresses the previous concern about overriding CSS-based initialization. The initialization sequence is well-ordered:

  1. Dark mode is initialized from DOM/CSS state (lines 102-105)
  2. Watch is defined but won't fire on initialization
  3. Theme name is read from --theme-name CSS variable (line 229)

The watch only triggers when theme name changes programmatically later (e.g., via dev tools), not during initial store setup. This ensures CSS-variable-driven initialization takes precedence.


234-247: LGTM - Store exports provide clean reactive API.

The return statement properly exposes:

  • darkMode as a readonly computed wrapping the internal ref for encapsulation
  • theme as a computed that merges internal state with CSS-variable-driven themeName (making CSS vars the source of truth for theme name)
  • fetchTheme action for triggering lazy theme load

The computed wrappers ensure external consumers get reactive values that update when underlying state or CSS variables change.

As per coding guidelines: Vue reactivity imports (computed, ref, watch) are correctly included at line 1.


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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 832e9d0 and 1063831.

📒 Files selected for processing (2)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php (1 hunks)
  • web/src/store/theme.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*

📄 CodeRabbit inference engine (.cursor/rules/default.mdc)

Never add comments unless they are needed for clarity of function

Files:

  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php
  • web/src/store/theme.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Always use TypeScript imports with .js extensions for ESM compatibility
Never add comments unless they are needed for clarity of function
Never add comments for obvious things, and avoid commenting when starting and ending code blocks

Files:

  • web/src/store/theme.ts
web/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Always run pnpm codegen for GraphQL code generation in the web directory

Files:

  • web/src/store/theme.ts
web/src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Ensure Vue reactivity imports are added to store files (computed, ref, watchEffect)

Files:

  • web/src/store/theme.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never use the any type. Always prefer proper typing
Avoid using casting whenever possible, prefer proper typing from the start

Files:

  • web/src/store/theme.ts
**/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

Ensure Vue reactivity imports like computed, ref, and watchEffect are added to store files even with Nuxt auto-imports enabled

Files:

  • web/src/store/theme.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:210-216
Timestamp: 2025-02-21T18:40:10.810Z
Learning: When updating theme-related CSS variables via `cssText`, preserve existing non-theme styles by filtering out only theme-related rules (those starting with '--') and combining them with the new theme styles.
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:0-0
Timestamp: 2025-02-24T14:51:21.328Z
Learning: In the Unraid API project's theme system, exact TypeScript type definitions are preferred over index signatures for theme variables to ensure better type safety.
📚 Learning: 2024-12-17T14:59:32.458Z
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.

Applied to files:

  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php
  • web/src/store/theme.ts
📚 Learning: 2025-02-21T18:40:10.810Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:210-216
Timestamp: 2025-02-21T18:40:10.810Z
Learning: When updating theme-related CSS variables via `cssText`, preserve existing non-theme styles by filtering out only theme-related rules (those starting with '--') and combining them with the new theme styles.

Applied to files:

  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php
  • web/src/store/theme.ts
📚 Learning: 2025-02-24T14:51:21.328Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:0-0
Timestamp: 2025-02-24T14:51:21.328Z
Learning: In the Unraid API project's theme system, exact TypeScript type definitions are preferred over index signatures for theme variables to ensure better type safety.

Applied to files:

  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php
  • web/src/store/theme.ts
📚 Learning: 2025-02-20T15:52:56.733Z
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:49-50
Timestamp: 2025-02-20T15:52:56.733Z
Learning: CSS variable names in the theme store should be concise and follow established patterns. For example, prefer '--gradient-start' over '--color-customgradient-start' to maintain consistency with other variable names.

Applied to files:

  • web/src/store/theme.ts
🧬 Code graph analysis (1)
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php (2)
api/src/unraid-api/graph/resolvers/customization/customization.resolver.ts (1)
  • theme (58-60)
api/src/unraid-api/graph/resolvers/vars/vars.resolver.ts (1)
  • vars (18-23)
🔇 Additional comments (3)
web/src/store/theme.ts (2)

53-60: LGTM!

The function correctly reads the CSS variable set by PHP and converts it to a boolean, with appropriate SSR handling.


67-67: LGTM!

Setting the CSS variable alongside the dark class creates a bidirectional sync, allowing the variable to stay in sync with runtime theme changes.

plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php (1)

212-214: Dark theme lists are synchronized.

Verification confirms both the PHP (web-components-extractor.php, line 212) and TypeScript (web/src/store/theme.ts, line 13) dark theme lists contain identical values ['gray', 'black']. The lists are currently in sync, and dark mode detection is consistent between backend and frontend.

@codecov
Copy link

codecov bot commented Nov 24, 2025

Codecov Report

❌ Patch coverage is 26.43172% with 334 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.84%. Comparing base (74df938) to head (452bf57).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
web/vite-plugin-serve-static.ts 0.00% 167 Missing ⚠️
web/src/components/DevThemeSwitcher.standalone.vue 0.00% 127 Missing ⚠️
web/__test__/mocks/ui-components.ts 0.00% 12 Missing ⚠️
...h/resolvers/customization/customization.service.ts 27.27% 8 Missing ⚠️
.../customization/customization.mutations.resolver.ts 63.15% 7 Missing ⚠️
web/src/store/theme.ts 91.42% 6 Missing ⚠️
web/src/components/DevThemeSwitcher.mutation.ts 0.00% 2 Missing and 1 partial ⚠️
...-api/graph/resolvers/mutation/mutation.resolver.ts 50.00% 2 Missing ⚠️
...aid-api/graph/resolvers/mutation/mutation.model.ts 75.00% 1 Missing ⚠️
web/src/components/Wrapper/mount-engine.ts 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1808      +/-   ##
==========================================
- Coverage   52.04%   51.84%   -0.20%     
==========================================
  Files         876      878       +2     
  Lines       50509    50848     +339     
  Branches     5023     5053      +30     
==========================================
+ Hits        26285    26361      +76     
- Misses      24149    24411     +262     
- Partials       75       76       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
web/__test__/store/theme.test.ts (1)

200-225: Dark‑mode init test looks solid; consider simplifying mock cleanup and comments

The new test correctly verifies initialization from --theme-dark-mode and the resulting dark classes on documentElement and body. Two small cleanups:

  • Since afterEach already calls vi.restoreAllMocks(), the explicit vi.restoreAllMocks() inside this test is redundant and can unexpectedly reset other spies (like requestAnimationFrame) mid‑test. Relying on the suite‑level cleanup keeps the lifecycle clearer.
  • The inline comments here add little beyond what the code already conveys and slightly conflict with the “no unnecessary comments” guideline; you can safely drop them.

Example minimal version:

-    it('should initialize dark mode from CSS variable on store creation', () => {
-      // Mock getComputedStyle to return dark mode
+    it('should initialize dark mode from CSS variable on store creation', () => {
       const originalGetComputedStyle = window.getComputedStyle;
       vi.spyOn(window, 'getComputedStyle').mockImplementation((el) => {
         const style = originalGetComputedStyle(el);
         if (el === document.documentElement) {
           return {
             ...style,
             getPropertyValue: (prop: string) => {
               if (prop === '--theme-dark-mode') {
                 return '1';
               }
               return style.getPropertyValue(prop);
             },
           } as CSSStyleDeclaration;
         }
         return style;
       });
 
       createStore();
 
-      // Should have added dark class to documentElement
       expect(document.documentElement.classList.add).toHaveBeenCalledWith('dark');
       expect(document.body.classList.add).toHaveBeenCalledWith('dark');
-
-      vi.restoreAllMocks();
     });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1063831 and 297a6a5.

📒 Files selected for processing (1)
  • web/__test__/store/theme.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Always use TypeScript imports with .js extensions for ESM compatibility
Never add comments unless they are needed for clarity of function
Never add comments for obvious things, and avoid commenting when starting and ending code blocks

Files:

  • web/__test__/store/theme.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx,js,jsx}: Use VITEST for test suite, not jest
Use .rejects.toThrow() without arguments to test that functions throw errors, not exact error message strings

Files:

  • web/__test__/store/theme.test.ts
web/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Always run pnpm codegen for GraphQL code generation in the web directory

Files:

  • web/__test__/store/theme.test.ts
web/__test__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/__test__/**/*.test.{ts,tsx}: Test component behavior and output, not implementation details
Use mount from Vue Test Utils for component testing
Stub complex child components that aren't the focus of the test
Find elements with semantic queries like find('button') rather than data-test IDs
Use createPinia() and setActivePinia when testing Store files, only use createTestingPinia if you specifically need its testing features

Files:

  • web/__test__/store/theme.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never use the any type. Always prefer proper typing
Avoid using casting whenever possible, prefer proper typing from the start

Files:

  • web/__test__/store/theme.test.ts
**/*

📄 CodeRabbit inference engine (.cursor/rules/default.mdc)

Never add comments unless they are needed for clarity of function

Files:

  • web/__test__/store/theme.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/*.test.ts: Use .rejects.toThrow() without arguments to test that functions throw errors. Don't test exact error message strings unless the message format is specifically what you're testing
Test what the code does, not implementation details like exact error message wording
Mock external services and API calls
Use vi.mock() for module-level mocks
Specify return values for component methods with vi.spyOn()
Reset mocks between tests with vi.clearAllMocks()
Always await async operations before making assertions

Files:

  • web/__test__/store/theme.test.ts
**/__test__/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/store/**/*.ts: Use createPinia and setActivePinia when testing Pinia store files
Use createPinia() instead of createTestingPinia() for most Pinia store tests
Only use createTestingPinia if you specifically need its testing features for Pinia stores
Let stores initialize with their natural default state instead of forcing initial state
Do not mock the store being tested in the test file when using createPinia()
Place all mock declarations at the top level in Pinia store tests
Use factory functions for module mocks in Pinia store tests to avoid hoisting issues
Test Pinia action side effects and state changes
Verify Pinia actions are called with correct parameters
Mock external dependencies appropriately in Pinia store tests
Test computed properties in Pinia stores by accessing them directly
Verify state changes after Pinia store actions
Test Pinia store getter dependencies are properly mocked
Test Pinia store interactions with other stores
Verify proper error handling in Pinia store tests
Test async operations completely in Pinia store tests
Override specific Pinia action implementations when needed in tests
Set initial state for focused Pinia store testing

Files:

  • web/__test__/store/theme.test.ts
**/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

Ensure Vue reactivity imports like computed, ref, and watchEffect are added to store files even with Nuxt auto-imports enabled

Files:

  • web/__test__/store/theme.test.ts
🧠 Learnings (18)
📓 Common learnings
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:210-216
Timestamp: 2025-02-21T18:40:10.810Z
Learning: When updating theme-related CSS variables via `cssText`, preserve existing non-theme styles by filtering out only theme-related rules (those starting with '--') and combining them with the new theme styles.
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia store getter dependencies are properly mocked

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Mock external dependencies appropriately in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Use factory functions for module mocks in Pinia store tests to avoid hoisting issues

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Place all mock declarations at the top level in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia action side effects and state changes

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia store interactions with other stores

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Do not mock the store being tested in the test file when using `createPinia()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Use `createPinia()` and `setActivePinia` when testing Store files, only use `createTestingPinia` if you specifically need its testing features

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Verify state changes after Pinia store actions

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Specify return values for component methods with `vi.spyOn()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Reset mocks between tests with `vi.clearAllMocks()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Use `vi.mock()` for module-level mocks

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify element existence with `expect(element.exists()).toBe(true)`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `createTestingPinia()` for mocking stores in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify that the expected elements are rendered in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Set initial state for focused Pinia store testing

Applied to files:

  • web/__test__/store/theme.test.ts

@elibosley elibosley marked this pull request as ready for review November 25, 2025 15:16
@github-actions
Copy link
Contributor

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +116 to 120
// Lazy query - only executes when explicitly called
const { load, onResult, onError } = useLazyQuery<GetThemeQuery>(GET_THEME_QUERY, null, {
fetchPolicy: 'cache-and-network',
nextFetchPolicy: 'cache-first',
});

Choose a reason for hiding this comment

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

P1 Badge Trigger theme query after switching to useLazyQuery

useThemeStore now instantiates useLazyQuery for GET_THEME_QUERY, but load()/fetchTheme() are never called anywhere (rg "fetchTheme" web only returns this definition), so the theme query no longer runs on startup. This regresses the previous eager fetch and leaves server-provided theme values (colors, banner flags, etc.) at defaults unless some other code happens to reload the page, meaning users’ configured theme is never applied in a single-page session.

Useful? React with 👍 / 👎.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
unraid-ui/src/composables/useTeleport.ts (1)

12-19: Dark class is applied at container creation but won't update dynamically.

The virtual container is a singleton created once. If the theme changes after the container is created (e.g., via dev tools), the dark class won't be updated. This may be acceptable if theme changes always trigger a page refresh in production, but could cause visual inconsistencies during development.

Consider adding a MutationObserver or exposing a method to sync the dark class if dynamic theme switching without refresh is needed.

web/src/store/theme.ts (1)

243-247: Consider extracting DOM initialization to a named function.

The inline initialization logic works, but extracting it to a named function (similar to bootstrapDarkClass) would improve readability and make the initialization sequence clearer.

+ const initializeThemeFromDom = () => {
+   const domThemeName = themeName.value;
+   if (domThemeName && domThemeName !== DEFAULT_THEME.name) {
+     theme.value.name = domThemeName;
+   }
+ };
+
  // Initialize dark mode from CSS variable set by PHP or any pre-applied .dark class
  bootstrapDarkClass();
+ initializeThemeFromDom();

- // Initialize theme from DOM on store creation
- const domThemeName = themeName.value;
- if (domThemeName && domThemeName !== DEFAULT_THEME.name) {
-   theme.value.name = domThemeName;
- }
web/__test__/store/theme.test.ts (2)

21-27: Mock onResult should accept and store a callback.

The store calls onResult(callback) to register a result handler. The current mock returns vi.fn() which accepts any args but doesn't do anything with them. For tests that need to simulate query results, you'll need a more sophisticated mock.

If you need to test query result handling in the future:

useLazyQuery: () => {
  const callbacks: Array<(result: { data: unknown }) => void> = [];
  return {
    load: vi.fn(),
    result: ref(null),
    loading: ref(false),
    onResult: vi.fn((cb) => callbacks.push(cb)),
    onError: vi.fn(),
    // Helper for tests to trigger result callbacks
    _triggerResult: (data: unknown) => callbacks.forEach(cb => cb({ data })),
  };
},

174-196: Test passes for the wrong reason - tests flag check, not CSS variable behavior.

This test expects undefined when the CSS variable is empty, but it actually returns undefined because theme.value.banner and theme.value.bannerGradient are both false by default. The CSS variable mock is never reached.

Consider clarifying the test name or adjusting to actually test the CSS variable fallback behavior.

- it('should return undefined when bannerGradient CSS variable is not set', () => {
+ it('should return undefined when banner flags are disabled', () => {
+   const store = createStore();
+   // Default theme has banner: false, bannerGradient: false
+   expect(store.bannerGradient).toBeUndefined();
+ });
+
+ it('should return undefined when CSS variable is not set but banner flags are enabled', () => {
    const originalGetComputedStyle = window.getComputedStyle;
    vi.spyOn(window, 'getComputedStyle').mockImplementation((el) => {
      // ... existing mock that returns empty string
    });

    const store = createStore();
+   store.setTheme({ banner: true, bannerGradient: true });
    expect(store.bannerGradient).toBeUndefined();

    vi.restoreAllMocks();
  });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 297a6a5 and 55742ac.

📒 Files selected for processing (5)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php (2 hunks)
  • unraid-ui/src/composables/useTeleport.ts (1 hunks)
  • web/__test__/store/theme.test.ts (3 hunks)
  • web/src/components/Wrapper/mount-engine.ts (1 hunks)
  • web/src/store/theme.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Always use TypeScript imports with .js extensions for ESM compatibility
Never add comments unless they are needed for clarity of function
Never add comments for obvious things, and avoid commenting when starting and ending code blocks

Files:

  • web/src/components/Wrapper/mount-engine.ts
  • unraid-ui/src/composables/useTeleport.ts
  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
web/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Always run pnpm codegen for GraphQL code generation in the web directory

Files:

  • web/src/components/Wrapper/mount-engine.ts
  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
web/src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Ensure Vue reactivity imports are added to store files (computed, ref, watchEffect)

Files:

  • web/src/components/Wrapper/mount-engine.ts
  • web/src/store/theme.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never use the any type. Always prefer proper typing
Avoid using casting whenever possible, prefer proper typing from the start

Files:

  • web/src/components/Wrapper/mount-engine.ts
  • unraid-ui/src/composables/useTeleport.ts
  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
**/*

📄 CodeRabbit inference engine (.cursor/rules/default.mdc)

Never add comments unless they are needed for clarity of function

Files:

  • web/src/components/Wrapper/mount-engine.ts
  • unraid-ui/src/composables/useTeleport.ts
  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx,js,jsx}: Use VITEST for test suite, not jest
Use .rejects.toThrow() without arguments to test that functions throw errors, not exact error message strings

Files:

  • web/__test__/store/theme.test.ts
web/__test__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/__test__/**/*.test.{ts,tsx}: Test component behavior and output, not implementation details
Use mount from Vue Test Utils for component testing
Stub complex child components that aren't the focus of the test
Find elements with semantic queries like find('button') rather than data-test IDs
Use createPinia() and setActivePinia when testing Store files, only use createTestingPinia if you specifically need its testing features

Files:

  • web/__test__/store/theme.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/*.test.ts: Use .rejects.toThrow() without arguments to test that functions throw errors. Don't test exact error message strings unless the message format is specifically what you're testing
Test what the code does, not implementation details like exact error message wording
Mock external services and API calls
Use vi.mock() for module-level mocks
Specify return values for component methods with vi.spyOn()
Reset mocks between tests with vi.clearAllMocks()
Always await async operations before making assertions

Files:

  • web/__test__/store/theme.test.ts
**/__test__/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/store/**/*.ts: Use createPinia and setActivePinia when testing Pinia store files
Use createPinia() instead of createTestingPinia() for most Pinia store tests
Only use createTestingPinia if you specifically need its testing features for Pinia stores
Let stores initialize with their natural default state instead of forcing initial state
Do not mock the store being tested in the test file when using createPinia()
Place all mock declarations at the top level in Pinia store tests
Use factory functions for module mocks in Pinia store tests to avoid hoisting issues
Test Pinia action side effects and state changes
Verify Pinia actions are called with correct parameters
Mock external dependencies appropriately in Pinia store tests
Test computed properties in Pinia stores by accessing them directly
Verify state changes after Pinia store actions
Test Pinia store getter dependencies are properly mocked
Test Pinia store interactions with other stores
Verify proper error handling in Pinia store tests
Test async operations completely in Pinia store tests
Override specific Pinia action implementations when needed in tests
Set initial state for focused Pinia store testing

Files:

  • web/__test__/store/theme.test.ts
**/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

Ensure Vue reactivity imports like computed, ref, and watchEffect are added to store files even with Nuxt auto-imports enabled

Files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
🧠 Learnings (36)
📓 Common learnings
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:210-216
Timestamp: 2025-02-21T18:40:10.810Z
Learning: When updating theme-related CSS variables via `cssText`, preserve existing non-theme styles by filtering out only theme-related rules (those starting with '--') and combining them with the new theme styles.
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `mount` from Vue Test Utils for component testing

Applied to files:

  • web/src/components/Wrapper/mount-engine.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Use `mount` from Vue Test Utils for component testing

Applied to files:

  • web/src/components/Wrapper/mount-engine.ts
📚 Learning: 2024-12-17T14:59:32.458Z
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.

Applied to files:

  • unraid-ui/src/composables/useTeleport.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Mock external dependencies appropriately in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia store getter dependencies are properly mocked

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Use factory functions for module mocks in Pinia store tests to avoid hoisting issues

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Place all mock declarations at the top level in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia action side effects and state changes

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Use `createPinia()` and `setActivePinia` when testing Store files, only use `createTestingPinia` if you specifically need its testing features

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia store interactions with other stores

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Do not mock the store being tested in the test file when using `createPinia()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `createTestingPinia()` for mocking stores in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-02-20T15:52:58.297Z
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:161-172
Timestamp: 2025-02-20T15:52:58.297Z
Learning: The banner gradient implementation in web/store/theme.ts doesn't require explicit error handling for hexToRgba as CSS gracefully handles invalid values by ignoring them.

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test computed properties in Pinia stores by accessing them directly

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Verify state changes after Pinia store actions

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-02-20T15:52:56.733Z
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:49-50
Timestamp: 2025-02-20T15:52:56.733Z
Learning: CSS variable names in the theme store should be concise and follow established patterns. For example, prefer '--gradient-start' over '--color-customgradient-start' to maintain consistency with other variable names.

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Specify return values for component methods with `vi.spyOn()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Reset mocks between tests with `vi.clearAllMocks()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Use `vi.mock()` for module-level mocks

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify element existence with `expect(element.exists()).toBe(true)`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Set initial state for focused Pinia store testing

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify that the expected elements are rendered in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-02-24T14:51:21.328Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:0-0
Timestamp: 2025-02-24T14:51:21.328Z
Learning: In the Unraid API project's theme system, exact TypeScript type definitions are preferred over index signatures for theme variables to ensure better type safety.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-02-21T18:40:10.810Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:210-216
Timestamp: 2025-02-21T18:40:10.810Z
Learning: When updating theme-related CSS variables via `cssText`, preserve existing non-theme styles by filtering out only theme-related rules (those starting with '--') and combining them with the new theme styles.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-06-13T17:14:21.739Z
Learnt from: pujitm
Repo: unraid/api PR: 1417
File: web/components/ConnectSettings/ConnectSettings.ce.vue:11-18
Timestamp: 2025-06-13T17:14:21.739Z
Learning: The project’s build tooling auto-imports common Vue/Pinia helpers such as `storeToRefs`, so explicit import statements for them are not required.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Nuxt is set to auto import so some Vue files may need `computed` or `ref` imported explicitly in store files

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:52:57.888Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:52:57.888Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files ensures that all web components share a single Pinia store instance, which is the desired behavior. Without this initialization, each web component would have its own isolated store, breaking the intended architecture.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:52:57.888Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:52:57.888Z
Learning: In the unraid/api project, Vue components are compiled into web components. The `setActivePinia(createPinia())` call at the module level in store files is intentional and ensures all web components share a single Pinia store instance, which is the desired behavior. This shared state approach is critical for the application's architecture to function correctly.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:33:13.215Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:33:13.215Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files would break the build by causing all web components to share a singular Pinia store instance. Each web component needs its own Pinia store instance to maintain proper isolation and encapsulation.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:33:13.215Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:33:13.215Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files would break the build by causing all web components to share a singular Pinia store instance. Each web component needs its own Pinia store instance to function correctly.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/store/**/*.ts : Ensure Vue reactivity imports like `computed`, `ref`, and `watchEffect` are added to store files even with Nuxt auto-imports enabled

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2024-12-09T15:45:46.492Z
Learnt from: pujitm
Repo: unraid/api PR: 975
File: web/components/Notifications/TabList.vue:1-4
Timestamp: 2024-12-09T15:45:46.492Z
Learning: In our Nuxt.js setup for the `web` project, it's not necessary to explicitly import `computed` from `vue` in Vue components, as it's globally available.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/src/**/*.ts : Ensure Vue reactivity imports are added to store files (computed, ref, watchEffect)

Applied to files:

  • web/src/store/theme.ts
🧬 Code graph analysis (1)
web/__test__/store/theme.test.ts (1)
api/src/store/index.ts (1)
  • store (10-16)
🪛 GitHub Actions: CI - Main (API)
web/__test__/store/theme.test.ts

[error] 150-170: Assertion failed: expected bannerGradient to have a gradient string, but received undefined in theme store tests.

🪛 GitHub Check: Test API
web/src/store/theme.ts

[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > enables gradient automatically when banner is enabled
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > updates theme store when switches change
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > updates theme store when color inputs change
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > updates theme store when theme selection changes
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > renders all form elements correctly
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24

🔇 Additional comments (4)
web/src/store/theme.ts (2)

180-196: Computed correctly guards CSS variable access with theme flags.

The logic properly checks banner and bannerGradient flags before reading the CSS variable. The pipeline failure in tests is due to tests not setting these flags to true before asserting the gradient value.


232-241: Good: immediate: false resolves the timing issue.

The watch no longer triggers immediately on initialization, which prevents the race condition flagged in the past review. The bootstrapDarkClass() call at line 114 handles the initial dark mode setup separately.

web/src/components/Wrapper/mount-engine.ts (1)

182-189: LGTM - Dark mode class propagation is correctly applied to mounted elements.

The detection logic correctly checks both the DOM class and CSS variable, consistent with the patterns in theme.ts. This ensures mounted web components inherit the dark mode styling.

web/__test__/store/theme.test.ts (1)

37-38: Variable store is correctly used throughout tests.

The past review comment about an unused variable appears to be outdated or a false positive. The store variable is properly used for cleanup in afterEach and populated via createStore().

Comment on lines +117 to 120
const { load, onResult, onError } = useLazyQuery<GetThemeQuery>(GET_THEME_QUERY, null, {
fetchPolicy: 'cache-and-network',
nextFetchPolicy: 'cache-first',
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Update existing Apollo mocks to include useLazyQuery.

The static analysis shows test failures in ColorSwitcher.test.ts because the Apollo mock doesn't export useLazyQuery. Any test file that mocks @vue/apollo-composable needs to include both useQuery and useLazyQuery.

The affected test file needs to update its mock:

 vi.mock('@vue/apollo-composable', () => ({
   useQuery: () => ({
     result: ref(null),
     loading: ref(false),
     onResult: vi.fn(),
     onError: vi.fn(),
   }),
+  useLazyQuery: () => ({
+    load: vi.fn(),
+    result: ref(null),
+    loading: ref(false),
+    onResult: vi.fn(),
+    onError: vi.fn(),
+  }),
 }));

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Check: Test API

[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > enables gradient automatically when banner is enabled
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > updates theme store when switches change
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > updates theme store when color inputs change
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > updates theme store when theme selection changes
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > renders all form elements correctly
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24

🤖 Prompt for AI Agents
In web/src/store/theme.ts around lines 117 to 120 the code uses useLazyQuery
from @vue/apollo-composable but the test mocks for that module only export
useQuery; update the test mocks that stub @vue/apollo-composable (e.g.,
ColorSwitcher.test.ts or the shared test mock file) to export both useQuery and
useLazyQuery with equivalent mock implementations and return shapes (including
load, onResult, onError, and reactive result/error/loading properties) so tests
that import useLazyQuery behave like the real composable.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
unraid-ui/src/composables/useTeleport.ts (1)

12-19: Inconsistent dark mode detection across files.

This detection logic differs from web/src/components/Wrapper/mount-engine.ts (lines 184-186), which checks the --theme-dark-mode CSS variable in addition to the documentElement class. The --theme-dark-mode variable is set by the theme store and serves as the source of truth for dark mode state.

Not checking the CSS variable here could cause the teleported container to miss dark mode in scenarios where the variable is set but the DOM classes haven't been applied yet.

Update the detection logic to align with mount-engine.ts:

-    const isDark =
-      document.documentElement.classList.contains('dark') ||
-      document.body?.classList.contains('dark') ||
-      Boolean(document.querySelector('.unapi.dark'));
+    const isDark =
+      document.documentElement.classList.contains('dark') ||
+      getComputedStyle(document.documentElement).getPropertyValue('--theme-dark-mode').trim() === '1';
web/src/store/theme.ts (1)

116-120: Critical: Theme query never executes.

The switch to useLazyQuery requires an explicit call to load() (exposed as fetchTheme), but fetchTheme is never invoked anywhere in the store initialization or by external code. This means the server theme is never fetched, and users will only see the default white theme regardless of their configured preferences.

Call fetchTheme() after the store initialization completes:

   // Initialize theme from DOM on store creation
   const domThemeName = themeName.value;
   if (domThemeName && domThemeName !== DEFAULT_THEME.name) {
     theme.value.name = domThemeName;
   }
+
+  // Fetch server theme to sync with backend
+  fetchTheme();

   return {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 297a6a5 and 55742ac.

📒 Files selected for processing (5)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php (2 hunks)
  • unraid-ui/src/composables/useTeleport.ts (1 hunks)
  • web/__test__/store/theme.test.ts (3 hunks)
  • web/src/components/Wrapper/mount-engine.ts (1 hunks)
  • web/src/store/theme.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Always use TypeScript imports with .js extensions for ESM compatibility
Never add comments unless they are needed for clarity of function
Never add comments for obvious things, and avoid commenting when starting and ending code blocks

Files:

  • web/src/components/Wrapper/mount-engine.ts
  • unraid-ui/src/composables/useTeleport.ts
  • web/src/store/theme.ts
  • web/__test__/store/theme.test.ts
web/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Always run pnpm codegen for GraphQL code generation in the web directory

Files:

  • web/src/components/Wrapper/mount-engine.ts
  • web/src/store/theme.ts
  • web/__test__/store/theme.test.ts
web/src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Ensure Vue reactivity imports are added to store files (computed, ref, watchEffect)

Files:

  • web/src/components/Wrapper/mount-engine.ts
  • web/src/store/theme.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never use the any type. Always prefer proper typing
Avoid using casting whenever possible, prefer proper typing from the start

Files:

  • web/src/components/Wrapper/mount-engine.ts
  • unraid-ui/src/composables/useTeleport.ts
  • web/src/store/theme.ts
  • web/__test__/store/theme.test.ts
**/*

📄 CodeRabbit inference engine (.cursor/rules/default.mdc)

Never add comments unless they are needed for clarity of function

Files:

  • web/src/components/Wrapper/mount-engine.ts
  • unraid-ui/src/composables/useTeleport.ts
  • web/src/store/theme.ts
  • web/__test__/store/theme.test.ts
**/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

Ensure Vue reactivity imports like computed, ref, and watchEffect are added to store files even with Nuxt auto-imports enabled

Files:

  • web/src/store/theme.ts
  • web/__test__/store/theme.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx,js,jsx}: Use VITEST for test suite, not jest
Use .rejects.toThrow() without arguments to test that functions throw errors, not exact error message strings

Files:

  • web/__test__/store/theme.test.ts
web/__test__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/__test__/**/*.test.{ts,tsx}: Test component behavior and output, not implementation details
Use mount from Vue Test Utils for component testing
Stub complex child components that aren't the focus of the test
Find elements with semantic queries like find('button') rather than data-test IDs
Use createPinia() and setActivePinia when testing Store files, only use createTestingPinia if you specifically need its testing features

Files:

  • web/__test__/store/theme.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/*.test.ts: Use .rejects.toThrow() without arguments to test that functions throw errors. Don't test exact error message strings unless the message format is specifically what you're testing
Test what the code does, not implementation details like exact error message wording
Mock external services and API calls
Use vi.mock() for module-level mocks
Specify return values for component methods with vi.spyOn()
Reset mocks between tests with vi.clearAllMocks()
Always await async operations before making assertions

Files:

  • web/__test__/store/theme.test.ts
**/__test__/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/store/**/*.ts: Use createPinia and setActivePinia when testing Pinia store files
Use createPinia() instead of createTestingPinia() for most Pinia store tests
Only use createTestingPinia if you specifically need its testing features for Pinia stores
Let stores initialize with their natural default state instead of forcing initial state
Do not mock the store being tested in the test file when using createPinia()
Place all mock declarations at the top level in Pinia store tests
Use factory functions for module mocks in Pinia store tests to avoid hoisting issues
Test Pinia action side effects and state changes
Verify Pinia actions are called with correct parameters
Mock external dependencies appropriately in Pinia store tests
Test computed properties in Pinia stores by accessing them directly
Verify state changes after Pinia store actions
Test Pinia store getter dependencies are properly mocked
Test Pinia store interactions with other stores
Verify proper error handling in Pinia store tests
Test async operations completely in Pinia store tests
Override specific Pinia action implementations when needed in tests
Set initial state for focused Pinia store testing

Files:

  • web/__test__/store/theme.test.ts
🧠 Learnings (34)
📓 Common learnings
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:49-50
Timestamp: 2025-02-20T15:52:56.733Z
Learning: CSS variable names in the theme store should be concise and follow established patterns. For example, prefer '--gradient-start' over '--color-customgradient-start' to maintain consistency with other variable names.
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:210-216
Timestamp: 2025-02-21T18:40:10.810Z
Learning: When updating theme-related CSS variables via `cssText`, preserve existing non-theme styles by filtering out only theme-related rules (those starting with '--') and combining them with the new theme styles.
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:161-172
Timestamp: 2025-02-20T15:52:58.297Z
Learning: The banner gradient implementation in web/store/theme.ts doesn't require explicit error handling for hexToRgba as CSS gracefully handles invalid values by ignoring them.
📚 Learning: 2024-12-17T14:59:32.458Z
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.

Applied to files:

  • unraid-ui/src/composables/useTeleport.ts
  • web/src/store/theme.ts
📚 Learning: 2025-02-24T14:51:21.328Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:0-0
Timestamp: 2025-02-24T14:51:21.328Z
Learning: In the Unraid API project's theme system, exact TypeScript type definitions are preferred over index signatures for theme variables to ensure better type safety.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-02-21T18:40:10.810Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:210-216
Timestamp: 2025-02-21T18:40:10.810Z
Learning: When updating theme-related CSS variables via `cssText`, preserve existing non-theme styles by filtering out only theme-related rules (those starting with '--') and combining them with the new theme styles.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-06-13T17:14:21.739Z
Learnt from: pujitm
Repo: unraid/api PR: 1417
File: web/components/ConnectSettings/ConnectSettings.ce.vue:11-18
Timestamp: 2025-06-13T17:14:21.739Z
Learning: The project’s build tooling auto-imports common Vue/Pinia helpers such as `storeToRefs`, so explicit import statements for them are not required.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Nuxt is set to auto import so some Vue files may need `computed` or `ref` imported explicitly in store files

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `createTestingPinia()` for mocking stores in Vue components

Applied to files:

  • web/src/store/theme.ts
  • web/__test__/store/theme.test.ts
📚 Learning: 2025-03-27T23:52:57.888Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:52:57.888Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files ensures that all web components share a single Pinia store instance, which is the desired behavior. Without this initialization, each web component would have its own isolated store, breaking the intended architecture.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:52:57.888Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:52:57.888Z
Learning: In the unraid/api project, Vue components are compiled into web components. The `setActivePinia(createPinia())` call at the module level in store files is intentional and ensures all web components share a single Pinia store instance, which is the desired behavior. This shared state approach is critical for the application's architecture to function correctly.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:33:13.215Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:33:13.215Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files would break the build by causing all web components to share a singular Pinia store instance. Each web component needs its own Pinia store instance to maintain proper isolation and encapsulation.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:33:13.215Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:33:13.215Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files would break the build by causing all web components to share a singular Pinia store instance. Each web component needs its own Pinia store instance to function correctly.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/store/**/*.ts : Ensure Vue reactivity imports like `computed`, `ref`, and `watchEffect` are added to store files even with Nuxt auto-imports enabled

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2024-12-09T15:45:46.492Z
Learnt from: pujitm
Repo: unraid/api PR: 975
File: web/components/Notifications/TabList.vue:1-4
Timestamp: 2024-12-09T15:45:46.492Z
Learning: In our Nuxt.js setup for the `web` project, it's not necessary to explicitly import `computed` from `vue` in Vue components, as it's globally available.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/src/**/*.ts : Ensure Vue reactivity imports are added to store files (computed, ref, watchEffect)

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Mock external dependencies appropriately in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia store getter dependencies are properly mocked

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Use factory functions for module mocks in Pinia store tests to avoid hoisting issues

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Place all mock declarations at the top level in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Do not mock the store being tested in the test file when using `createPinia()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Mock external dependencies and services in Vue component tests

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia store interactions with other stores

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia action side effects and state changes

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-02-20T15:52:58.297Z
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:161-172
Timestamp: 2025-02-20T15:52:58.297Z
Learning: The banner gradient implementation in web/store/theme.ts doesn't require explicit error handling for hexToRgba as CSS gracefully handles invalid values by ignoring them.

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test computed properties in Pinia stores by accessing them directly

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Verify state changes after Pinia store actions

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-02-20T15:52:56.733Z
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:49-50
Timestamp: 2025-02-20T15:52:56.733Z
Learning: CSS variable names in the theme store should be concise and follow established patterns. For example, prefer '--gradient-start' over '--color-customgradient-start' to maintain consistency with other variable names.

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Specify return values for component methods with `vi.spyOn()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Reset mocks between tests with `vi.clearAllMocks()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Use `vi.mock()` for module-level mocks

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify element existence with `expect(element.exists()).toBe(true)`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Set initial state for focused Pinia store testing

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify that the expected elements are rendered in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
🪛 GitHub Check: Test API
web/src/store/theme.ts

[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > enables gradient automatically when banner is enabled
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > updates theme store when switches change
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > updates theme store when color inputs change
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > updates theme store when theme selection changes
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24


[failure] 117-117: test/components/ColorSwitcher.test.ts > ColorSwitcher > renders all form elements correctly
Error: [vitest] No "useLazyQuery" export is defined on the "@vue/apollo-composable" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("@vue/apollo-composable"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/store/theme.ts:117:39
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:98
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:88
❯ EffectScope.run ../node_modules/.pnpm/@VUE[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:85:16
❯ ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:54
❯ fallbackRunWithContext ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1156:40
❯ createSetupStore ../node_modules/.pnpm/[email protected][email protected][email protected][email protected]_/node_modules/pinia/dist/pinia.mjs:1464:24

🔇 Additional comments (7)
web/src/store/theme.ts (6)

41-85: Well-structured helper functions.

The DOM availability checks and CSS variable reading helpers are well-organized. The syncDarkClass function correctly propagates the dark class to documentElement, body, and all .unapi elements, ensuring consistency across the application.


113-114: Good initialization pattern.

Calling bootstrapDarkClass() during store initialization ensures that dark mode is properly applied based on CSS variables or existing DOM state when the store is created.


158-168: CSS-variable-driven getters align with PR objectives.

The themeName and darkMode computed properties correctly read from CSS variables set by PHP (--theme-name and --theme-dark-mode), with appropriate fallbacks. This ensures consistent theme state between server-rendered HTML and client-side JavaScript.


180-196: Banner gradient logic correctly respects dark mode.

The implementation correctly returns undefined when darkMode is active, aligning with the learned behavior that banners should not show gradients in dark mode. The CSS variable reading includes proper normalization to handle empty, null, or undefined values.

Based on learnings, the header design intentionally differs between light and dark modes.


234-241: Watch with immediate: false prevents initialization conflict.

Setting immediate: false correctly prevents the watcher from overriding the dark class applied by bootstrapDarkClass() during initialization. The watcher now only applies the dark class when the theme name actually changes (e.g., via dev tools), which is the intended behavior per the comment.


254-257: Computed theme wrapper ensures CSS variable takes precedence.

Returning a computed object that overrides name with themeName.value ensures the theme name always reflects the CSS variable set by PHP, even if the internal theme ref has a different value. This maintains the CSS variable as the source of truth for theme name.

web/__test__/store/theme.test.ts (1)

100-294: Comprehensive test coverage for CSS-variable-driven theme.

The new tests effectively validate the CSS-variable-driven theme initialization and computed properties:

  • Dark mode detection from --theme-dark-mode (both '1' and '0' values)
  • Banner gradient reading from --banner-gradient
  • Proper handling of missing/empty CSS variables
  • Bootstrap dark class application on store creation

The mocking strategy using getComputedStyle is appropriate and aligns with the implementation.

@github-actions
Copy link
Contributor

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

2 similar comments
@github-actions
Copy link
Contributor

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

@github-actions
Copy link
Contributor

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
web/__test__/store/theme.test.ts (1)

106-128: Consider extracting repeated getComputedStyle mock to a helper.

The mock pattern for getComputedStyle is repeated across multiple tests with slight variations. A helper function could reduce duplication:

const mockCssVariables = (variables: Record<string, string>) => {
  const originalGetComputedStyle = window.getComputedStyle;
  vi.spyOn(window, 'getComputedStyle').mockImplementation((el) => {
    const style = originalGetComputedStyle(el);
    if (el === document.documentElement) {
      return {
        ...style,
        getPropertyValue: (prop: string) => variables[prop] ?? style.getPropertyValue(prop),
      } as CSSStyleDeclaration;
    }
    return style;
  });
};

Usage: mockCssVariables({ '--theme-dark-mode': '1' })

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8799ac9 and a930dee.

📒 Files selected for processing (3)
  • web/__test__/components/ColorSwitcher.test.ts (3 hunks)
  • web/__test__/components/UserProfile.test.ts (3 hunks)
  • web/__test__/store/theme.test.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Always use TypeScript imports with .js extensions for ESM compatibility
Never add comments unless they are needed for clarity of function
Never add comments for obvious things, and avoid commenting when starting and ending code blocks

Files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx,js,jsx}: Use VITEST for test suite, not jest
Use .rejects.toThrow() without arguments to test that functions throw errors, not exact error message strings

Files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
web/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Always run pnpm codegen for GraphQL code generation in the web directory

Files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
web/__test__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/__test__/**/*.test.{ts,tsx}: Test component behavior and output, not implementation details
Use mount from Vue Test Utils for component testing
Stub complex child components that aren't the focus of the test
Find elements with semantic queries like find('button') rather than data-test IDs
Use createPinia() and setActivePinia when testing Store files, only use createTestingPinia if you specifically need its testing features

Files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never use the any type. Always prefer proper typing
Avoid using casting whenever possible, prefer proper typing from the start

Files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
**/*

📄 CodeRabbit inference engine (.cursor/rules/default.mdc)

Never add comments unless they are needed for clarity of function

Files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/*.test.ts: Use .rejects.toThrow() without arguments to test that functions throw errors. Don't test exact error message strings unless the message format is specifically what you're testing
Test what the code does, not implementation details like exact error message wording
Mock external services and API calls
Use vi.mock() for module-level mocks
Specify return values for component methods with vi.spyOn()
Reset mocks between tests with vi.clearAllMocks()
Always await async operations before making assertions

Files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
**/__test__/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/store/**/*.ts: Use createPinia and setActivePinia when testing Pinia store files
Use createPinia() instead of createTestingPinia() for most Pinia store tests
Only use createTestingPinia if you specifically need its testing features for Pinia stores
Let stores initialize with their natural default state instead of forcing initial state
Do not mock the store being tested in the test file when using createPinia()
Place all mock declarations at the top level in Pinia store tests
Use factory functions for module mocks in Pinia store tests to avoid hoisting issues
Test Pinia action side effects and state changes
Verify Pinia actions are called with correct parameters
Mock external dependencies appropriately in Pinia store tests
Test computed properties in Pinia stores by accessing them directly
Verify state changes after Pinia store actions
Test Pinia store getter dependencies are properly mocked
Test Pinia store interactions with other stores
Verify proper error handling in Pinia store tests
Test async operations completely in Pinia store tests
Override specific Pinia action implementations when needed in tests
Set initial state for focused Pinia store testing

Files:

  • web/__test__/store/theme.test.ts
**/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

Ensure Vue reactivity imports like computed, ref, and watchEffect are added to store files even with Nuxt auto-imports enabled

Files:

  • web/__test__/store/theme.test.ts
**/__test__/components/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/components/**/*.ts: Use mount from Vue Test Utils for component testing
Stub complex child components that aren't the focus of the test
Mock external dependencies and services in Vue component tests
Test component behavior and output, not implementation details
Verify that the expected elements are rendered in Vue components
Test component interactions such as clicks and inputs
Check for expected prop handling and event emissions in Vue components
Use createTestingPinia() for mocking stores in Vue components
Use semantic queries like find('button') or find('[data-test="id"]') but prefer not to use data test ID's when finding elements
Find Vue components with findComponent(ComponentName)
Use findAll to check for multiple elements in Vue components
Assert on rendered text content with wrapper.text()
Assert on element attributes with element.attributes()
Verify element existence with expect(element.exists()).toBe(true)
Check component state through rendered output
Trigger events with await element.trigger('click')
Set input values with await input.setValue('value')
Test emitted events with wrapper.emitted()
Use await nextTick() for DOM updates in Vue component tests
Use flushPromises() for complex promise chains in Vue component tests

Files:

  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
🧠 Learnings (37)
📓 Common learnings
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:49-50
Timestamp: 2025-02-20T15:52:56.733Z
Learning: CSS variable names in the theme store should be concise and follow established patterns. For example, prefer '--gradient-start' over '--color-customgradient-start' to maintain consistency with other variable names.
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Mock external dependencies appropriately in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Use factory functions for module mocks in Pinia store tests to avoid hoisting issues

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia store getter dependencies are properly mocked

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Place all mock declarations at the top level in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Do not mock the store being tested in the test file when using `createPinia()`

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia store interactions with other stores

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Use `createPinia()` and `setActivePinia` when testing Store files, only use `createTestingPinia` if you specifically need its testing features

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Mock external dependencies and services in Vue component tests

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia action side effects and state changes

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-02-20T15:52:58.297Z
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:161-172
Timestamp: 2025-02-20T15:52:58.297Z
Learning: The banner gradient implementation in web/store/theme.ts doesn't require explicit error handling for hexToRgba as CSS gracefully handles invalid values by ignoring them.

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Verify proper error handling in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test computed properties in Pinia stores by accessing them directly

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Verify state changes after Pinia store actions

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `await nextTick()` for DOM updates in Vue component tests

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Use `vi.mock()` for module-level mocks

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `createTestingPinia()` for mocking stores in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Check for expected prop handling and event emissions in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify that the expected elements are rendered in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/src/**/*.ts : Ensure Vue reactivity imports are added to store files (computed, ref, watchEffect)

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-02-20T15:52:56.733Z
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:49-50
Timestamp: 2025-02-20T15:52:56.733Z
Learning: CSS variable names in the theme store should be concise and follow established patterns. For example, prefer '--gradient-start' over '--color-customgradient-start' to maintain consistency with other variable names.

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Specify return values for component methods with `vi.spyOn()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Reset mocks between tests with `vi.clearAllMocks()`

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify element existence with `expect(element.exists()).toBe(true)`

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Set initial state for focused Pinia store testing

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Use `createPinia` and `setActivePinia` when testing Pinia store files

Applied to files:

  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Use `createPinia()` instead of `createTestingPinia()` for most Pinia store tests

Applied to files:

  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Override specific Pinia action implementations when needed in tests

Applied to files:

  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Assert on rendered text content with `wrapper.text()`

Applied to files:

  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Check component state through rendered output

Applied to files:

  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Use `mount` from Vue Test Utils for component testing

Applied to files:

  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `mount` from Vue Test Utils for component testing

Applied to files:

  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Test component interactions such as clicks and inputs

Applied to files:

  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-08-09T01:03:29.676Z
Learnt from: elibosley
Repo: unraid/api PR: 1575
File: packages/unraid-shared/src/services/socket-config.service.spec.ts:10-13
Timestamp: 2025-08-09T01:03:29.676Z
Learning: Vitest is used for all testing across all repositories in the unraid organization, not Jest. Always use `vi` for mocking utilities, not `jest`.

Applied to files:

  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Only use `createTestingPinia` if you specifically need its testing features for Pinia stores

Applied to files:

  • web/__test__/components/ColorSwitcher.test.ts
🧬 Code graph analysis (2)
web/__test__/store/theme.test.ts (1)
api/src/store/index.ts (1)
  • store (10-16)
web/__test__/components/UserProfile.test.ts (1)
web/src/store/theme.ts (1)
  • useThemeStore (105-263)
🔇 Additional comments (12)
web/__test__/components/UserProfile.test.ts (3)

191-210: LGTM! Well-structured theme initialization.

The CSS variable setup and setTheme initialization properly align with the new CSS-variable-driven theme resolution. This approach ensures tests reflect production behavior where theme state is derived from CSS variables set by PHP.


336-357: Clean refactor to use public API.

The spread pattern { ...themeStore.theme, descriptionShow: ... } correctly preserves existing theme state while updating specific properties, replacing direct state mutations with proper setTheme calls.


368-398: LGTM!

Banner tests correctly use setTheme to toggle flags and verify bannerGradient computed behavior with CSS variables.

web/__test__/components/ColorSwitcher.test.ts (3)

25-31: Good addition of useLazyQuery mock.

This addresses the missing mock issue flagged in past reviews. The mock shape correctly mirrors the useLazyQuery return type with load, result, loading, onResult, and onError.


64-67: CSS variable setup enables proper theme store initialization.

Setting --theme-dark-mode and --banner-gradient before creating the store ensures the CSS-variable-driven computed properties initialize correctly during tests.


84-89: Improved cleanup robustness.

The guards prevent potential errors during teardown when modalDiv is already removed or consoleWarnSpy wasn't created.

web/__test__/store/theme.test.ts (6)

21-27: useLazyQuery mock added correctly.

This aligns with the mock added in ColorSwitcher.test.ts and addresses the past review comment about consistency across test files.


65-71: Defensive cleanup pattern.

The try-catch handles the case where the app was never mounted, preventing test failures during teardown.


106-152: Thorough CSS variable-based darkMode tests.

These tests properly verify the darkMode computed property reads from --theme-dark-mode CSS variable and returns the correct boolean value.


154-186: Good test for bannerGradient with CSS variable.

The test correctly sets banner: true and bannerGradient: true via setTheme before asserting the computed value, addressing the issue flagged in past reviews.


188-210: Correctly tests undefined gradient when CSS variable is empty.

This validates the edge case where --banner-gradient is not set or empty.


279-308: LGTM! Validates dark mode initialization from CSS variable.

This test confirms that bootstrapDarkClass correctly applies the dark class to documentElement and body when the CSS variable --theme-dark-mode is set to '1' at store creation time.

@elibosley elibosley requested a review from pujitm November 26, 2025 15:34
@github-actions
Copy link
Contributor

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (8)
web/src/components/DevThemeSwitcher.standalone.vue (3)

66-77: Remove unnecessary async keyword.

This function doesn't contain any await expressions, so the async modifier is unnecessary overhead.

-const resolveInitialTheme = async (): Promise<ThemeName> => {
+const resolveInitialTheme = (): ThemeName => {
   const candidates = [readCssTheme(), readCookieTheme(), readLocalStorageTheme(), theme.value?.name];

If this is intentionally async for future extensibility, consider adding a comment to clarify.


96-102: Redundant watch for query loading state.

The queryLoading ref from useQuery can be used directly instead of copying it to another ref via a watcher.

Remove the redundant watch and isQueryLoading ref:

-const isQueryLoading = ref(false);
...
-watch(
-  () => queryLoading.value,
-  (loading) => {
-    isQueryLoading.value = loading;
-  },
-  { immediate: true }
-);

Then in the template, use queryLoading directly:

-    :disabled="isSaving || isQueryLoading"
+    :disabled="isSaving || queryLoading"

191-216: Clarify the persist parameter semantics.

The persist parameter name is misleading—local persistence to cookie/localStorage always happens (line 198), while persist only controls the GraphQL server mutation.

Consider renaming for clarity:

 const applyThemeSelection = async (
   themeName: string | null | undefined,
-  { persist = false, skipStore = false }: { persist?: boolean; skipStore?: boolean } = {}
+  { persistToServer = false, skipStore = false }: { persistToServer?: boolean; skipStore?: boolean } = {}
 ) => {
   // ...
-  if (persist) {
+  if (persistToServer) {
web/test-pages/partials/styles.njk (1)

2-5: Dev theme bootstrap matches CSS-var/dark-mode flow; confirm azure dark-mode mapping

The dev-only logic looks good overall:

  • activeTheme precedence (resolvedThemequery.theme'white') and the explicit allowlist guard prevent invalid theme names from leaking into CSS vars.
  • The script keeps the <link id="dev-theme-css-link"> href in sync with the normalized theme.
  • --theme-name, --theme-dark-mode (as '1'/'0'), and data-theme on documentElement align with how the theme store expects to bootstrap state. Based on learnings, this should integrate cleanly with existing CSS-variable-driven theming.
  • syncDarkClass updating html, body, and .unapi mirrors how the app propagates the dark class to containers.

Two small follow-ups:

  1. isDark only treats 'black' and 'gray' as dark:

    var isDark = theme === 'black' || theme === 'gray';

    Please double-check whether azure should also be considered dark for your design system. If yes, add it here so --theme-dark-mode and the dark class stay consistent.

  2. The theme allowlist, themeCssMap keys, and isDark predicate all repeat the same set of theme names. If you find yourself tweaking themes often, consider centralizing this into a single map/object and deriving the allowlist and isDark from it to avoid drift.

Also applies to: 26-66

web/vite-plugin-serve-static.ts (4)

10-39: Tighten normalizeTheme typing to avoid unnecessary cast

The runtime logic is fine, but casting (theme ?? '').toLowerCase() directly to ThemeName is a bit loose for TS and goes against the “exact theme types, minimal casting” pattern used elsewhere in the theme system (see previous learnings). You can keep the same behavior while letting TypeScript infer the union from ALLOWED_THEMES:

-const normalizeTheme = (theme?: string | null): ThemeName => {
-  const normalized = (theme ?? '').toLowerCase() as ThemeName;
-  return ALLOWED_THEMES.includes(normalized) ? normalized : 'white';
-};
+const normalizeTheme = (theme?: string | null): ThemeName => {
+  const normalized = (theme ?? '').toLowerCase();
+  return ALLOWED_THEMES.find((t) => t === normalized) ?? 'white';
+};

This preserves guarantees that only whitelisted values flow out, without relying on a broad cast. Based on learnings, this matches the preference for precise theme typings.


40-58: Harden parseCookies against malformed encodings

The implementation is solid for normal cookies, but decodeURIComponent will throw if a cookie value contains invalid percent-encoding, which would currently take down the dev request.

If you want this helper to be more defensive (even in dev), consider wrapping the decode in a try/catch and skipping values that can’t be decoded:

-    acc[trimmedName] = decodeURIComponent(rest.join('=').trim());
+    const rawValue = rest.join('=').trim();
+    try {
+      acc[trimmedName] = decodeURIComponent(rawValue);
+    } catch {
+      // Ignore malformed cookie values
+    }

That way a single bad cookie header doesn’t break /test-pages rendering.


70-109: INI parsing and theme read behavior are minimal but adequate

parseIniSection is intentionally minimal (single-line key=value, no multi-line or advanced INI features) and readThemeFromConfig gracefully falls back to null on any error, which is appropriate for a dev helper.

One behavioral note: if [display] exists but has no theme, normalizeTheme(displaySection.theme) effectively defaults to 'white'. That’s probably fine for dev, but it does mean a misconfigured file silently falls back rather than surfacing an error. If you ever want stricter behavior, you could explicitly check for Object.hasOwn(displaySection, 'theme') before normalizing.


307-318: Theme resolution precedence is clear; confirm it matches intended UX

The resolution chain:

const cookies = parseCookies(req.headers.cookie);
const cookieTheme = cookies['unraid_dev_theme'];
const queryTheme = requestUrl.searchParams.get('theme');
const cfgTheme = readThemeFromConfig();
const resolvedTheme = normalizeTheme(cfgTheme || queryTheme || cookieTheme);

gives precedence config > query param > cookie, then defaults via normalizeTheme. That’s a reasonable choice for dev, but it does mean a remembered config will override ad‑hoc query testing unless config is cleared/changed.

If that’s the intended behavior for local testing, this looks good. If you expect query params to always win during manual testing, you may want to reorder to query > cookie > config.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a930dee and 8655575.

📒 Files selected for processing (11)
  • api/dev/configs/api.json (1 hunks)
  • api/generated-schema.graphql (2 hunks)
  • api/src/unraid-api/graph/resolvers/customization/customization.module.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts (2 hunks)
  • web/.prettierignore (1 hunks)
  • web/src/components/DevThemeSwitcher.standalone.vue (3 hunks)
  • web/test-pages/partials/styles.njk (2 hunks)
  • web/vite-plugin-serve-static.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
api/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer adding new files to the NestJS repo located at api/src/unraid-api/ instead of the legacy code

Files:

  • api/dev/configs/api.json
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.module.ts
  • api/generated-schema.graphql
**/*

📄 CodeRabbit inference engine (.cursor/rules/default.mdc)

Never add comments unless they are needed for clarity of function

Files:

  • api/dev/configs/api.json
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.module.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • web/vite-plugin-serve-static.ts
  • web/test-pages/partials/styles.njk
  • api/generated-schema.graphql
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Always use TypeScript imports with .js extensions for ESM compatibility
Never add comments unless they are needed for clarity of function
Never add comments for obvious things, and avoid commenting when starting and ending code blocks

Files:

  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.module.ts
  • web/vite-plugin-serve-static.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never use the any type. Always prefer proper typing
Avoid using casting whenever possible, prefer proper typing from the start

Files:

  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.module.ts
  • web/vite-plugin-serve-static.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 expects TTL values in milliseconds, not seconds (e.g., 600000 for 10 minutes, not 600)

Files:

  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.module.ts
web/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Always run pnpm codegen for GraphQL code generation in the web directory

Files:

  • web/src/components/DevThemeSwitcher.standalone.vue
  • web/vite-plugin-serve-static.ts
  • web/test-pages/partials/styles.njk
🧠 Learnings (18)
📓 Common learnings
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:49-50
Timestamp: 2025-02-20T15:52:56.733Z
Learning: CSS variable names in the theme store should be concise and follow established patterns. For example, prefer '--gradient-start' over '--color-customgradient-start' to maintain consistency with other variable names.
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:210-216
Timestamp: 2025-02-21T18:40:10.810Z
Learning: When updating theme-related CSS variables via `cssText`, preserve existing non-theme styles by filtering out only theme-related rules (those starting with '--') and combining them with the new theme styles.
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:0-0
Timestamp: 2025-02-24T14:51:21.328Z
Learning: In the Unraid API project's theme system, exact TypeScript type definitions are preferred over index signatures for theme variables to ensure better type safety.
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/src/**/*.ts : Ensure Vue reactivity imports are added to store files (computed, ref, watchEffect)

Applied to files:

  • web/.prettierignore
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/**/* : Always run `pnpm codegen` for GraphQL code generation in the web directory

Applied to files:

  • web/.prettierignore
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/**/*.query.ts : GraphQL queries must be placed in `.query.ts` files

Applied to files:

  • web/.prettierignore
📚 Learning: 2025-05-23T21:59:29.632Z
Learnt from: elibosley
Repo: unraid/api PR: 1408
File: web/components/ApiKey/PermissionCounter.vue:6-6
Timestamp: 2025-05-23T21:59:29.632Z
Learning: This codebase uses ESM (ECMAScript Modules) and requires .js extensions in import statements, even when importing from TypeScript files, as the imports refer to the compiled JavaScript output.

Applied to files:

  • web/.prettierignore
📚 Learning: 2025-11-24T17:52:00.572Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-11-24T17:52:00.572Z
Learning: Applies to web/**/*.{query.ts,mutation.ts} : All GraphQL under `web/` must follow the `.query.ts` and `.mutation.ts` naming convention

Applied to files:

  • web/.prettierignore
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/**/*.mutation.ts : GraphQL mutations must be placed in `.mutation.ts` files

Applied to files:

  • web/.prettierignore
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
📚 Learning: 2025-03-12T13:48:14.850Z
Learnt from: pujitm
Repo: unraid/api PR: 1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Applied to files:

  • web/.prettierignore
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Test component behavior and output, not implementation details

Applied to files:

  • web/.prettierignore
📚 Learning: 2025-03-14T16:10:38.562Z
Learnt from: pujitm
Repo: unraid/api PR: 1211
File: api/src/graphql/schema/types/connect/connect.graphql:142-146
Timestamp: 2025-03-14T16:10:38.562Z
Learning: The updateApiSettings mutation in api/src/unraid-api/graph/connect/connect.resolver.ts is protected with the UsePermissions decorator that requires UPDATE permission on the CONFIG resource.

Applied to files:

  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
📚 Learning: 2024-11-06T20:59:25.809Z
Learnt from: mdatelle
Repo: unraid/api PR: 942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in `api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts`, methods like `removeRoleFromApiKey` rely on service-level error handling.

Applied to files:

  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
📚 Learning: 2024-11-05T14:48:56.949Z
Learnt from: elibosley
Repo: unraid/api PR: 942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:41-47
Timestamp: 2024-11-05T14:48:56.949Z
Learning: Input types for GraphQL mutations can be pulled from the GraphQL codegen types, as they are generated into the API files. Therefore, explicit typing in the resolver files may be unnecessary.

Applied to files:

  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
📚 Learning: 2024-11-06T20:59:06.096Z
Learnt from: mdatelle
Repo: unraid/api PR: 942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:87-89
Timestamp: 2024-11-06T20:59:06.096Z
Learning: Error handling in mutations like `addRoleForUser` in `auth.resolver.ts` is performed at the service level, so additional error handling in the resolver is unnecessary.

Applied to files:

  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
📚 Learning: 2024-12-17T14:59:32.458Z
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.

Applied to files:

  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • web/vite-plugin-serve-static.ts
  • web/test-pages/partials/styles.njk
📚 Learning: 2025-04-23T20:19:42.542Z
Learnt from: pujitm
Repo: unraid/api PR: 1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom or extended implementation of NestJS ConfigService that includes a `set()` method for runtime configuration mutation, unlike the standard nestjs/config package which only provides getter methods.

Applied to files:

  • api/src/unraid-api/graph/resolvers/customization/customization.module.ts
📚 Learning: 2025-02-24T14:51:21.328Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:0-0
Timestamp: 2025-02-24T14:51:21.328Z
Learning: In the Unraid API project's theme system, exact TypeScript type definitions are preferred over index signatures for theme variables to ensure better type safety.

Applied to files:

  • web/src/components/DevThemeSwitcher.standalone.vue
  • web/vite-plugin-serve-static.ts
📚 Learning: 2025-03-27T23:52:57.888Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:52:57.888Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files ensures that all web components share a single Pinia store instance, which is the desired behavior. Without this initialization, each web component would have its own isolated store, breaking the intended architecture.

Applied to files:

  • web/src/components/DevThemeSwitcher.standalone.vue
📚 Learning: 2025-02-21T18:40:10.810Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:210-216
Timestamp: 2025-02-21T18:40:10.810Z
Learning: When updating theme-related CSS variables via `cssText`, preserve existing non-theme styles by filtering out only theme-related rules (those starting with '--') and combining them with the new theme styles.

Applied to files:

  • web/test-pages/partials/styles.njk
🧬 Code graph analysis (2)
api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts (3)
api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts (1)
  • Resolver (14-50)
packages/unraid-shared/src/use-permissions.directive.ts (1)
  • UsePermissions (81-128)
api/src/unraid-api/graph/resolvers/customization/customization.resolver.ts (1)
  • theme (58-60)
api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts (1)
web/composables/gql/graphql.ts (1)
  • Mutation (1199-1239)
🔇 Additional comments (19)
web/.prettierignore (1)

5-5: Adding dist/ to Prettier ignore list is sensible.

The change appropriately excludes the compiled/build output directory from Prettier formatting, which is a best practice to prevent formatting of generated files. The entry follows the existing naming convention with other directory-based ignores in the file.

web/src/components/DevThemeSwitcher.standalone.vue (7)

1-27: LGTM!

Good use of the generated ThemeName enum for type-safe theme options, aligning with the project's preference for exact TypeScript type definitions. Based on learnings, this approach ensures better type safety.


34-64: LGTM!

The helper functions are SSR-safe with appropriate guards for document and window. Cookie parsing correctly handles values containing = by using rest.join('=').


142-150: LGTM!

Cookie persistence with reasonable expiration and SameSite=Lax. The omission of the Secure flag is acceptable for a dev-only component that may run on localhost.


152-163: LGTM!

The CSS variable names --theme-name and --theme-dark-mode follow the concise naming pattern as per established conventions. The dark class synchronization across root, body, and .unapi elements ensures consistent theming. Based on learnings, this aligns with the project's approach to CSS variable naming.


227-244: LGTM!

The lifecycle management correctly:

  • Sets dev override on mount before applying theme
  • Watches store changes with skipStore: true to prevent circular updates
  • Separates user-initiated changes (with server persistence) from external sync

247-286: LGTM!

Template correctly binds to currentTheme and disables the select during saving/loading operations. The scoped styles appropriately handle the disabled state.


29-32: No case mismatch exists — review comment is incorrect.

The ThemeName enum values are lowercase strings ('azure', 'black', 'gray', 'white'), not uppercase. On line 27, themeValues is populated from themeOptions.map((option) => option.value), where each option.value is a ThemeName enum member with a lowercase string value. The normalizeTheme function correctly converts input to lowercase and checks membership against themeValues, which contains lowercase strings. The logic functions as intended—no fix is needed.

api/dev/configs/api.json (1)

2-2: Version bump looks consistent

Only the dev API config version was updated; structure and other fields are unchanged. As long as 4.27.2 matches the API build you’re shipping, this is good to go.

api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts (1)

27-30: CustomizationMutations container wiring matches existing mutation pattern

Defining CustomizationMutations and exposing it via RootMutations.customization mirrors the existing nested mutation containers (array/docker/vm/apiKey/parityCheck/rclone). Naming and descriptions are consistent.

Also applies to: 62-63

api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts (1)

1-25: setTheme mutation resolver is well-scoped and permissioned

The resolver cleanly delegates to CustomizationService.setTheme, uses ThemeName as the argument type and returns Theme, and applies UsePermissions({ action: AuthAction.UPDATE_ANY, resource: Resource.CUSTOMIZATIONS }) in line with existing mutation patterns. No additional error handling is needed here since it’s handled in the service.

api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts (1)

6-6: RootMutations.customization entry correctly exposes customization mutations

The new @Mutation(() => CustomizationMutations, { name: 'customization' }) method matches the existing container-style mutations and cleanly returns a new CustomizationMutations instance. Import updates are correct.

Also applies to: 41-44

api/src/unraid-api/graph/resolvers/customization/customization.module.ts (1)

3-3: CustomizationModule now cleanly wires mutations resolver and exports the service

Adding CustomizationMutationsResolver to providers and exporting CustomizationService aligns this module with how other resolver/service modules are structured. No issues.

Also applies to: 8-9

api/generated-schema.graphql (1)

947-963: Schema additions for customization/theming align with resolvers; ensure dependent codegen is updated

The generated SDL updates look internally consistent:

  • type CustomizationMutations with setTheme(theme: ThemeName!): Theme! matches CustomizationMutationsResolver.setTheme.
  • enum ThemeName { azure black gray white } aligns with the service logic that maps dynamix.display.theme to ThemeName.
  • Mutation.customization: CustomizationMutations! matches the new RootMutationsResolver.customization() entry.

Since this file is generated, the main follow-up is to ensure all dependent codegen is refreshed (e.g., web GraphQL TypeScript types via pnpm codegen in web/) so clients see ThemeName, CustomizationMutations, and the customization mutation root.

Also applies to: 2461-2461

api/src/unraid-api/graph/resolvers/customization/customization.service.ts (1)

12-15: Argument shape is correct; loadDynamixConfigFromDiskSync receives the right data structure

The review comment's concern about the loader argument is based on a misunderstanding. The code is correct as-is:

  • Line 50 in onModuleInit: this.configFile = paths['dynamix-config']?.[1] extracts a single file path string for use with updateCfgFile.
  • Line 478 in setTheme: loadDynamixConfigFromDiskSync(paths['dynamix-config']) correctly passes the full array to the loader.

The function signature requires configPaths: readonly (string | undefined | null)[] (an array), and paths['dynamix-config'] is exactly that. This matches the existing pattern in api/src/store/index.ts line 32, which also passes the full array: loadDynamixConfigFromDiskSync(configPaths) where configPaths = store.getState().paths['dynamix-config'] ?? [].

Passing this.configFile (a single string) would violate the type contract and break the function.

Optional: add error handling for reload/dispatch resilience

If the disk read or dispatch fails after updateCfgFile succeeds, the mutation currently rejects even though the config on disk is updated. Wrapping the reload/dispatch in a try-catch that logs but proceeds to getTheme() would make the endpoint more resilient, though this is optional.

web/vite-plugin-serve-static.ts (4)

60-69: Config path resolution order looks reasonable for dev

The candidate resolution favors explicit env vars (DEV_DYNAMIX_CFG, PATHS_DYNAMIX_CONFIG) before falling back to repo-relative paths, which is a sane precedence for a dev-only tool. The existsSync probe in findDynamixConfigPath is simple and should behave predictably across the small candidate list.

No changes needed from my side; just confirm this order matches how you expect to override local configs during development.


111-152: Config write logic is correct; consider concurrency edge cases only if needed

The writeThemeToConfig read–modify–write flow correctly:

  • Preserves existing sections.
  • Updates an existing theme= in [display] if present.
  • Otherwise inserts theme= into an existing [display] or creates the section at the end.

For a Vite dev server this is plenty. The only theoretical gap is concurrent writes (multiple /dev/theme POSTs) racing on the same file, but that’s unlikely and probably not worth guarding against here.

No required changes; just be aware of the race window in case this ever gets reused outside dev tooling.


153-162: Request body reader is fine for dev; no size limits

readRequestBody is straightforward and adequate for the small JSON payload expected on /dev/theme. If this ever grows beyond a tiny dev interface, you might want to:

  • Impose a max body size and reject larger payloads.
  • Handle req.aborted explicitly.

For now, I’d keep it as-is.


230-276: /dev/theme endpoint behavior looks correct and robust

The middleware cleanly handles:

  • GET → returns { theme: ThemeName | null } JSON from config.
  • POST → reads JSON body, validates theme as a string, normalizes against ALLOWED_THEMES, persists via writeThemeToConfig, and returns the result plus theme.
  • Other methods → 405 with an Allow header.
  • Malformed JSON → 400 with a structured error.

This aligns well with the PR’s goal of a small dev-only surface for theme persistence, and keeps responses consistently JSON-typed for the main flows.

No behavior changes to suggest here.

- Added PHP logic to determine if the current theme is dark and set a CSS variable accordingly.
- Introduced a new function to retrieve the dark mode state from the CSS variable in JavaScript.
- Updated the theme store to initialize dark mode based on the CSS variable, ensuring consistent theme application across the application.

This improves user experience by ensuring the correct theme is applied based on user preferences.
- Removed the previous test for applying dark mode classes to .unapi elements.
- Added a new test to verify that dark mode is initialized based on a CSS variable when the store is created.
- Mocked getComputedStyle to simulate dark mode and checked that the appropriate classes are added to document elements.

This enhances the test coverage for theme handling in the application.
- Removed the unnecessary assignment of the store variable in the theme store test.
- This change streamlines the test setup while maintaining the verification of dark mode class application.

This update enhances the clarity and efficiency of the test code.
- Updated the WebComponentsExtractor to set the theme name and conditionally apply gradient variables based on banner visibility.
- Improved the useTeleport and mount-engine functions to inherit dark mode from the document, ensuring consistency across components.
- Refactored the theme store to initialize dark mode based on CSS variables and streamline theme management, including lazy loading of theme queries.

These changes improve the user experience by ensuring that the correct theme and dark mode settings are applied consistently throughout the application.
- Updated the WebComponentsExtractor to utilize a CSS variable for the banner gradient stop, allowing for responsive adjustments.
- Modified theme store tests to reflect the new gradient format, ensuring consistency in gradient rendering.
- Added CSS rules to define the banner gradient stop, with a media query for different screen sizes.

These changes improve the flexibility and responsiveness of the banner gradient in the application.
- Introduced a new test to verify the correct application of the banner gradient when the theme is set.
- This enhancement ensures that the theme store's functionality for banner gradient customization is properly validated.

These changes improve the robustness of the theme store tests.
- Refactored UserProfile tests to replace direct theme property manipulations with the setTheme method, ensuring consistency in theme state management.
- Mocked getComputedStyle to simulate CSS variables for banner gradient and dark mode, enhancing test reliability.
- Improved test coverage for theme-related functionality, particularly around banner visibility and description rendering.

These changes enhance the robustness and maintainability of the UserProfile component tests.
- Updated ColorSwitcher and UserProfile tests to set CSS variables directly on the document element, improving test reliability and consistency.
- Refactored cleanup logic in theme store tests to handle unmounting more gracefully, ensuring robust test teardown.

These changes enhance the maintainability and clarity of the theme-related tests.
- Introduced CustomizationMutations to handle theme updates via GraphQL.
- Implemented setTheme mutation to update the UI theme and persist changes in the configuration.
- Updated the generated GraphQL schema to include the new mutations and theme enum.
- Enhanced the CustomizationService to manage theme persistence and in-memory updates.
- Updated the frontend to support theme selection and synchronization with the server.

These changes improve the user experience by allowing dynamic theme management and ensuring consistent application of user preferences.
@github-actions
Copy link
Contributor

github-actions bot commented Dec 9, 2025

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

- Added a new mutation, SET_THEME_MUTATION, to facilitate theme updates via GraphQL.
- Refactored DevThemeSwitcher.standalone.vue to utilize the new mutation for setting the theme.
- Updated GraphQL schema to include the setTheme mutation and its associated types.
- Enhanced gql.ts and graphql.ts to support the new mutation structure.

These changes enable dynamic theme switching, improving user experience by allowing real-time updates to the UI theme.
- Introduced a new utility function, isDarkModeActive, to streamline dark mode detection across the application.
- Refactored useTeleport, mount-engine, and theme store to utilize the new utility for consistent dark mode handling.
- Removed redundant dark mode checks from various components, enhancing code clarity and maintainability.

These changes improve the consistency of dark mode application and simplify future theme management.
@github-actions
Copy link
Contributor

github-actions bot commented Dec 9, 2025

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

- Updated useTeleport tests to include checks for dark mode application based on CSS variables and document classes.
- Refactored test setup to ensure consistent state before each test, including resetting dark mode classes and clearing the DOM.
- Added new tests to verify the correct behavior of the teleportTarget ref and the creation of the virtual container in both dark and light modes.

These changes improve the reliability and coverage of the useTeleport tests, ensuring accurate dark mode handling.

describe('isDarkModeActive', () => {
const originalGetComputedStyle = window.getComputedStyle;
const originalDocumentElement = document.documentElement;

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable originalDocumentElement.

Copilot Autofix

AI 2 days ago

The best way to fix this problem is to simply remove the declaration of the unused variable originalDocumentElement from line 6 in the file unraid-ui/src/lib/utils.test.ts. This prevents confusion, improves code readability, and may marginally reduce execution overhead or linting output. No other code needs editing, as there are no references to this variable anywhere in the file.


Suggested changeset 1
unraid-ui/src/lib/utils.test.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/unraid-ui/src/lib/utils.test.ts b/unraid-ui/src/lib/utils.test.ts
--- a/unraid-ui/src/lib/utils.test.ts
+++ b/unraid-ui/src/lib/utils.test.ts
@@ -3,7 +3,6 @@
 
 describe('isDarkModeActive', () => {
   const originalGetComputedStyle = window.getComputedStyle;
-  const originalDocumentElement = document.documentElement;
   const originalBody = document.body;
 
   beforeEach(() => {
EOF
@@ -3,7 +3,6 @@

describe('isDarkModeActive', () => {
const originalGetComputedStyle = window.getComputedStyle;
const originalDocumentElement = document.documentElement;
const originalBody = document.body;

beforeEach(() => {
Copilot is powered by AI and may make mistakes. Always verify output.
describe('isDarkModeActive', () => {
const originalGetComputedStyle = window.getComputedStyle;
const originalDocumentElement = document.documentElement;
const originalBody = document.body;

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable originalBody.

Copilot Autofix

AI 2 days ago

The best fix is to simply remove the declaration of the unused variable originalBody on line 7 in unraid-ui/src/lib/utils.test.ts. No other code should be changed, as there is no reference to that variable anywhere else in the file. Just one line can be deleted.

Suggested changeset 1
unraid-ui/src/lib/utils.test.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/unraid-ui/src/lib/utils.test.ts b/unraid-ui/src/lib/utils.test.ts
--- a/unraid-ui/src/lib/utils.test.ts
+++ b/unraid-ui/src/lib/utils.test.ts
@@ -4,7 +4,6 @@
 describe('isDarkModeActive', () => {
   const originalGetComputedStyle = window.getComputedStyle;
   const originalDocumentElement = document.documentElement;
-  const originalBody = document.body;
 
   beforeEach(() => {
     document.documentElement.classList.remove('dark');
EOF
@@ -4,7 +4,6 @@
describe('isDarkModeActive', () => {
const originalGetComputedStyle = window.getComputedStyle;
const originalDocumentElement = document.documentElement;
const originalBody = document.body;

beforeEach(() => {
document.documentElement.classList.remove('dark');
Copilot is powered by AI and may make mistakes. Always verify output.
@github-actions
Copy link
Contributor

github-actions bot commented Dec 9, 2025

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
unraid-ui/src/lib/utils.test.ts (1)

5-7: Remove unused originalDocumentElement / originalBody test variables.

These two constants are never read; only originalGetComputedStyle is used. You can simplify the setup and satisfy CodeQL by dropping them:

-  const originalGetComputedStyle = window.getComputedStyle;
-  const originalDocumentElement = document.documentElement;
-  const originalBody = document.body;
+  const originalGetComputedStyle = window.getComputedStyle;
🧹 Nitpick comments (4)
web/src/components/UserProfile.standalone.vue (1)

95-98: Verify header layout with new left padding across breakpoints

The added pl-[30%] md:pl-[160px] on an absolutely positioned, right-aligned root container changes its effective width. This is likely to grow the banner gradient footprint, but it’s also a fairly “magic” offset and could affect:

  • Very small viewports (e.g., mobile) with limited header width.
  • Cases with long server names or verbose localized descriptions.

Please sanity‑check on the smallest supported viewport and in a few locales to ensure:

  • No horizontal overflow or double scrollbars.
  • Header left‑side content (logo/nav) isn’t unintentionally overlapped.

If this offset needs to be reused or tuned later, consider moving it into a shared header layout utility or CSS variable instead of an inline arbitrary value for easier future adjustments.

Also, since this file is under web/, confirm pnpm codegen has been run if any GraphQL schema/operation changes are part of this PR, per the repo guidelines.

web/__test__/components/UserProfile.test.ts (1)

343-369: Use partial updates with themeStore.setTheme instead of spreading the whole theme.

setTheme already merges a partial payload into the existing theme, so you don’t need to spread themeStore.theme on every call. This keeps the tests less coupled to the full theme shape and easier to maintain. For example:

-    themeStore.setTheme({ ...themeStore.theme, descriptionShow: true });
+    themeStore.setTheme({ descriptionShow: true });-    themeStore.setTheme({
-      ...themeStore.theme,
-      banner: true,
-      bannerGradient: true,
-    });
+    themeStore.setTheme({ banner: true, bannerGradient: true });-    themeStore.setTheme({
-      ...themeStore.theme,
-      bannerGradient: false,
-    });
+    themeStore.setTheme({ bannerGradient: false });-    themeStore.setTheme({
-      ...themeStore.theme,
-      bannerGradient: true,
-    });
+    themeStore.setTheme({ bannerGradient: true });

Also applies to: 380-410

web/__test__/store/theme.test.ts (1)

106-152: CSS-variable-driven dark mode and bannerGradient tests correctly mirror store behavior

The new tests accurately exercise the theme store’s DOM-driven logic:

  • darkMode is derived from --theme-dark-mode when set to '1'/'0', independent of classes.
  • bannerGradient only returns a background-image string when:
    • not in dark mode,
    • theme.banner and theme.bannerGradient are true, and
    • --banner-gradient is a non-empty, non-sentinel value.
  • Initialization of dark mode from --theme-dark-mode/--theme-name verifies that .dark is applied to both documentElement and body, matching bootstrapDarkClass() semantics.

The explicit vi.restoreAllMocks() calls within these tests are redundant given the afterEach cleanup but don’t cause issues; you could drop them later for brevity if desired.

Also applies to: 154-210, 279-308

web/src/components/DevThemeSwitcher.standalone.vue (1)

4-31: Dev theme switcher's multi-source resolution and persistence flow looks solid

The refactor to use generated GraphQL types (ThemeName, SET_THEME_MUTATION) plus GET_THEME_QUERY and the centralized applyThemeSelection pipeline gives you a clean, single path that:

  • Normalizes all inputs (CSS var, cookie, localStorage, store, server) through ThemeName.
  • Keeps cookie, localStorage, DOM CSS vars, dark classes, and the Pinia theme store in sync.
  • Only calls the setTheme mutation when a user-initiated change occurs (persist: true), while still reflecting server-provided theme via useQuery.

One optional refinement you could consider:

Hoist themeCssMap to module scope
To avoid recreating the same mapping object on every call to updateThemeCssLink, you could lift it out of the function into a const at module scope. Not required, but slightly cleaner.

Functionally, the component matches the dark-mode and theme semantics of useThemeStore and the backend schema, so this is good to ship.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8655575 and 586d5c8.

📒 Files selected for processing (29)
  • api/generated-schema.graphql (2 hunks)
  • api/src/unraid-api/graph/resolvers/customization/customization.module.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts (2 hunks)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php (2 hunks)
  • unraid-ui/src/composables/useTeleport.test.ts (5 hunks)
  • unraid-ui/src/composables/useTeleport.ts (1 hunks)
  • unraid-ui/src/lib/utils.test.ts (1 hunks)
  • unraid-ui/src/lib/utils.ts (1 hunks)
  • web/.prettierignore (1 hunks)
  • web/__test__/components/ColorSwitcher.test.ts (3 hunks)
  • web/__test__/components/UserProfile.test.ts (4 hunks)
  • web/__test__/components/Wrapper/mount-engine.test.ts (1 hunks)
  • web/__test__/mocks/ui-components.ts (1 hunks)
  • web/__test__/store/theme.test.ts (4 hunks)
  • web/src/assets/main.css (1 hunks)
  • web/src/components/DevThemeSwitcher.mutation.ts (1 hunks)
  • web/src/components/DevThemeSwitcher.standalone.vue (3 hunks)
  • web/src/components/UserProfile.standalone.vue (1 hunks)
  • web/src/components/Wrapper/mount-engine.ts (2 hunks)
  • web/src/composables/gql/gql.ts (3 hunks)
  • web/src/composables/gql/graphql.ts (8 hunks)
  • web/src/composables/gql/index.ts (1 hunks)
  • web/src/helpers/dark-mode.ts (1 hunks)
  • web/src/store/theme.ts (5 hunks)
  • web/test-pages/partials/styles.njk (2 hunks)
  • web/vite-plugin-serve-static.ts (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • web/src/composables/gql/index.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • api/src/unraid-api/graph/resolvers/customization/customization.module.ts
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php
  • web/vite-plugin-serve-static.ts
  • web/.prettierignore
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Always use TypeScript imports with .js extensions for ESM compatibility
Never add comments unless they are needed for clarity of function
Never add comments for obvious things, and avoid commenting when starting and ending code blocks

Files:

  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • web/__test__/store/theme.test.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • web/src/components/DevThemeSwitcher.mutation.ts
  • unraid-ui/src/composables/useTeleport.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/mocks/ui-components.ts
  • web/__test__/components/UserProfile.test.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • web/src/components/Wrapper/mount-engine.ts
  • web/src/composables/gql/graphql.ts
  • unraid-ui/src/lib/utils.ts
  • web/src/store/theme.ts
  • web/src/composables/gql/gql.ts
  • web/src/helpers/dark-mode.ts
  • web/__test__/components/ColorSwitcher.test.ts
api/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer adding new files to the NestJS repo located at api/src/unraid-api/ instead of the legacy code

Files:

  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • api/generated-schema.graphql
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never use the any type. Always prefer proper typing
Avoid using casting whenever possible, prefer proper typing from the start

Files:

  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • web/__test__/store/theme.test.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • web/src/components/DevThemeSwitcher.mutation.ts
  • unraid-ui/src/composables/useTeleport.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/mocks/ui-components.ts
  • web/__test__/components/UserProfile.test.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • web/src/components/Wrapper/mount-engine.ts
  • web/src/composables/gql/graphql.ts
  • unraid-ui/src/lib/utils.ts
  • web/src/store/theme.ts
  • web/src/composables/gql/gql.ts
  • web/src/helpers/dark-mode.ts
  • web/__test__/components/ColorSwitcher.test.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 expects TTL values in milliseconds, not seconds (e.g., 600000 for 10 minutes, not 600)

Files:

  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
**/*

📄 CodeRabbit inference engine (.cursor/rules/default.mdc)

Never add comments unless they are needed for clarity of function

Files:

  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • web/__test__/store/theme.test.ts
  • web/src/assets/main.css
  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • web/src/components/DevThemeSwitcher.mutation.ts
  • web/test-pages/partials/styles.njk
  • unraid-ui/src/composables/useTeleport.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/mocks/ui-components.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • web/__test__/components/UserProfile.test.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • web/src/components/Wrapper/mount-engine.ts
  • web/src/composables/gql/graphql.ts
  • unraid-ui/src/lib/utils.ts
  • api/generated-schema.graphql
  • web/src/store/theme.ts
  • web/src/composables/gql/gql.ts
  • web/src/helpers/dark-mode.ts
  • web/__test__/components/ColorSwitcher.test.ts
  • web/src/components/UserProfile.standalone.vue
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx,js,jsx}: Use VITEST for test suite, not jest
Use .rejects.toThrow() without arguments to test that functions throw errors, not exact error message strings

Files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
web/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Always run pnpm codegen for GraphQL code generation in the web directory

Files:

  • web/__test__/store/theme.test.ts
  • web/src/assets/main.css
  • web/src/components/DevThemeSwitcher.mutation.ts
  • web/test-pages/partials/styles.njk
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/mocks/ui-components.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • web/__test__/components/UserProfile.test.ts
  • web/src/components/Wrapper/mount-engine.ts
  • web/src/composables/gql/graphql.ts
  • web/src/store/theme.ts
  • web/src/composables/gql/gql.ts
  • web/src/helpers/dark-mode.ts
  • web/__test__/components/ColorSwitcher.test.ts
  • web/src/components/UserProfile.standalone.vue
web/__test__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/__test__/**/*.test.{ts,tsx}: Test component behavior and output, not implementation details
Use mount from Vue Test Utils for component testing
Stub complex child components that aren't the focus of the test
Find elements with semantic queries like find('button') rather than data-test IDs
Use createPinia() and setActivePinia when testing Store files, only use createTestingPinia if you specifically need its testing features

Files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/*.test.ts: Use .rejects.toThrow() without arguments to test that functions throw errors. Don't test exact error message strings unless the message format is specifically what you're testing
Test what the code does, not implementation details like exact error message wording
Mock external services and API calls
Use vi.mock() for module-level mocks
Specify return values for component methods with vi.spyOn()
Reset mocks between tests with vi.clearAllMocks()
Always await async operations before making assertions

Files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
**/__test__/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/store/**/*.ts: Use createPinia and setActivePinia when testing Pinia store files
Use createPinia() instead of createTestingPinia() for most Pinia store tests
Only use createTestingPinia if you specifically need its testing features for Pinia stores
Let stores initialize with their natural default state instead of forcing initial state
Do not mock the store being tested in the test file when using createPinia()
Place all mock declarations at the top level in Pinia store tests
Use factory functions for module mocks in Pinia store tests to avoid hoisting issues
Test Pinia action side effects and state changes
Verify Pinia actions are called with correct parameters
Mock external dependencies appropriately in Pinia store tests
Test computed properties in Pinia stores by accessing them directly
Verify state changes after Pinia store actions
Test Pinia store getter dependencies are properly mocked
Test Pinia store interactions with other stores
Verify proper error handling in Pinia store tests
Test async operations completely in Pinia store tests
Override specific Pinia action implementations when needed in tests
Set initial state for focused Pinia store testing

Files:

  • web/__test__/store/theme.test.ts
**/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

Ensure Vue reactivity imports like computed, ref, and watchEffect are added to store files even with Nuxt auto-imports enabled

Files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
web/**/*.mutation.ts

📄 CodeRabbit inference engine (CLAUDE.md)

GraphQL mutations must be placed in .mutation.ts files

Files:

  • web/src/components/DevThemeSwitcher.mutation.ts
web/src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Ensure Vue reactivity imports are added to store files (computed, ref, watchEffect)

Files:

  • web/src/components/DevThemeSwitcher.mutation.ts
  • web/src/components/Wrapper/mount-engine.ts
  • web/src/composables/gql/graphql.ts
  • web/src/store/theme.ts
  • web/src/composables/gql/gql.ts
  • web/src/helpers/dark-mode.ts
web/**/*.{query.ts,mutation.ts}

📄 CodeRabbit inference engine (.cursor/rules/web-graphql.mdc)

All GraphQL under web/ must follow the .query.ts and .mutation.ts naming convention

Files:

  • web/src/components/DevThemeSwitcher.mutation.ts
**/__test__/components/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/components/**/*.ts: Use mount from Vue Test Utils for component testing
Stub complex child components that aren't the focus of the test
Mock external dependencies and services in Vue component tests
Test component behavior and output, not implementation details
Verify that the expected elements are rendered in Vue components
Test component interactions such as clicks and inputs
Check for expected prop handling and event emissions in Vue components
Use createTestingPinia() for mocking stores in Vue components
Use semantic queries like find('button') or find('[data-test="id"]') but prefer not to use data test ID's when finding elements
Find Vue components with findComponent(ComponentName)
Use findAll to check for multiple elements in Vue components
Assert on rendered text content with wrapper.text()
Assert on element attributes with element.attributes()
Verify element existence with expect(element.exists()).toBe(true)
Check component state through rendered output
Trigger events with await element.trigger('click')
Set input values with await input.setValue('value')
Test emitted events with wrapper.emitted()
Use await nextTick() for DOM updates in Vue component tests
Use flushPromises() for complex promise chains in Vue component tests

Files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
🧠 Learnings (78)
📓 Common learnings
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.
📚 Learning: 2025-02-24T14:51:21.328Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:0-0
Timestamp: 2025-02-24T14:51:21.328Z
Learning: In the Unraid API project's theme system, exact TypeScript type definitions are preferred over index signatures for theme variables to ensure better type safety.

Applied to files:

  • api/src/unraid-api/graph/resolvers/customization/customization.service.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • web/src/composables/gql/graphql.ts
  • unraid-ui/src/lib/utils.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Mock external dependencies appropriately in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/src/store/theme.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia store getter dependencies are properly mocked

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/src/store/theme.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Use factory functions for module mocks in Pinia store tests to avoid hoisting issues

Applied to files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/mocks/ui-components.ts
  • web/__test__/components/UserProfile.test.ts
  • web/src/store/theme.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Place all mock declarations at the top level in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Do not mock the store being tested in the test file when using `createPinia()`

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia store interactions with other stores

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test Pinia action side effects and state changes

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Use `createPinia()` and `setActivePinia` when testing Store files, only use `createTestingPinia` if you specifically need its testing features

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Mock external dependencies and services in Vue component tests

Applied to files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/mocks/ui-components.ts
  • web/__test__/components/UserProfile.test.ts
  • web/src/store/theme.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/mocks/ui-components.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-02-20T15:52:58.297Z
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:161-172
Timestamp: 2025-02-20T15:52:58.297Z
Learning: The banner gradient implementation in web/store/theme.ts doesn't require explicit error handling for hexToRgba as CSS gracefully handles invalid values by ignoring them.

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/assets/main.css
  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Verify proper error handling in Pinia store tests

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Test computed properties in Pinia stores by accessing them directly

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Verify state changes after Pinia store actions

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `await nextTick()` for DOM updates in Vue component tests

Applied to files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • web/src/store/theme.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Check for expected prop handling and event emissions in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • web/src/store/theme.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Use `vi.mock()` for module-level mocks

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/mocks/ui-components.ts
  • web/src/store/theme.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify that the expected elements are rendered in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `createTestingPinia()` for mocking stores in Vue components

Applied to files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/src/store/theme.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/src/**/*.ts : Ensure Vue reactivity imports are added to store files (computed, ref, watchEffect)

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-02-20T15:52:56.733Z
Learnt from: elibosley
Repo: unraid/api PR: 1155
File: web/store/theme.ts:49-50
Timestamp: 2025-02-20T15:52:56.733Z
Learning: CSS variable names in the theme store should be concise and follow established patterns. For example, prefer '--gradient-start' over '--color-customgradient-start' to maintain consistency with other variable names.

Applied to files:

  • web/__test__/store/theme.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Specify return values for component methods with `vi.spyOn()`

Applied to files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Reset mocks between tests with `vi.clearAllMocks()`

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Verify element existence with `expect(element.exists()).toBe(true)`

Applied to files:

  • web/__test__/store/theme.test.ts
  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Set initial state for focused Pinia store testing

Applied to files:

  • web/__test__/store/theme.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-03-14T16:10:38.562Z
Learnt from: pujitm
Repo: unraid/api PR: 1211
File: api/src/graphql/schema/types/connect/connect.graphql:142-146
Timestamp: 2025-03-14T16:10:38.562Z
Learning: The updateApiSettings mutation in api/src/unraid-api/graph/connect/connect.resolver.ts is protected with the UsePermissions decorator that requires UPDATE permission on the CONFIG resource.

Applied to files:

  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
📚 Learning: 2024-11-06T20:59:25.809Z
Learnt from: mdatelle
Repo: unraid/api PR: 942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in `api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts`, methods like `removeRoleFromApiKey` rely on service-level error handling.

Applied to files:

  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
📚 Learning: 2025-03-12T13:48:14.850Z
Learnt from: pujitm
Repo: unraid/api PR: 1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Applied to files:

  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • web/src/composables/gql/graphql.ts
  • web/src/composables/gql/gql.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/**/*.mutation.ts : GraphQL mutations must be placed in `.mutation.ts` files

Applied to files:

  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • web/src/components/DevThemeSwitcher.mutation.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • web/src/composables/gql/graphql.ts
  • web/src/composables/gql/gql.ts
📚 Learning: 2024-11-05T14:48:56.949Z
Learnt from: elibosley
Repo: unraid/api PR: 942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:41-47
Timestamp: 2024-11-05T14:48:56.949Z
Learning: Input types for GraphQL mutations can be pulled from the GraphQL codegen types, as they are generated into the API files. Therefore, explicit typing in the resolver files may be unnecessary.

Applied to files:

  • api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
  • web/src/composables/gql/gql.ts
📚 Learning: 2025-11-24T17:52:00.572Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-11-24T17:52:00.572Z
Learning: Applies to web/**/*.{query.ts,mutation.ts} : All GraphQL under `web/` must follow the `.query.ts` and `.mutation.ts` naming convention

Applied to files:

  • web/src/components/DevThemeSwitcher.mutation.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • web/src/composables/gql/gql.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/**/*.query.ts : GraphQL queries must be placed in `.query.ts` files

Applied to files:

  • web/src/components/DevThemeSwitcher.mutation.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • web/src/composables/gql/gql.ts
📚 Learning: 2025-02-21T18:40:10.810Z
Learnt from: elibosley
Repo: unraid/api PR: 1181
File: web/store/theme.ts:210-216
Timestamp: 2025-02-21T18:40:10.810Z
Learning: When updating theme-related CSS variables via `cssText`, preserve existing non-theme styles by filtering out only theme-related rules (those starting with '--') and combining them with the new theme styles.

Applied to files:

  • web/test-pages/partials/styles.njk
  • web/src/components/DevThemeSwitcher.standalone.vue
  • web/src/store/theme.ts
📚 Learning: 2024-12-17T14:59:32.458Z
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/store/theme.ts:46-49
Timestamp: 2024-12-17T14:59:32.458Z
Learning: In the `web/store/theme.ts` file of the Unraid web application, the header is intentionally designed to have a light background with dark text in dark mode, and a dark background with light text in light mode.

Applied to files:

  • web/test-pages/partials/styles.njk
  • web/__test__/mocks/ui-components.ts
  • web/src/components/DevThemeSwitcher.standalone.vue
  • web/src/components/Wrapper/mount-engine.ts
  • unraid-ui/src/lib/utils.ts
  • web/src/store/theme.ts
📚 Learning: 2025-04-25T13:29:07.868Z
Learnt from: elibosley
Repo: unraid/api PR: 1369
File: web/components/Activation/WelcomeModal.ce.vue:40-40
Timestamp: 2025-04-25T13:29:07.868Z
Learning: The `#confirmPassword` selector in the WelcomeModal.ce.vue component is intentionally looking for an element that exists in the Unraid OS login page (.login.php), not within the component itself. This is used as part of a font-size adjustment workaround to handle different CSS between login and authenticated pages.

Applied to files:

  • unraid-ui/src/composables/useTeleport.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Test component interactions such as clicks and inputs

Applied to files:

  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Use `mount` from Vue Test Utils for component testing

Applied to files:

  • unraid-ui/src/composables/useTeleport.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `mount` from Vue Test Utils for component testing

Applied to files:

  • unraid-ui/src/composables/useTeleport.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: This is a Nuxt.js app but tests are run with vitest outside of the Nuxt environment

Applied to files:

  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/src/components/Wrapper/mount-engine.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Use `flushPromises()` for complex promise chains in Vue component tests

Applied to files:

  • unraid-ui/src/composables/useTeleport.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Check component state through rendered output

Applied to files:

  • unraid-ui/src/composables/useTeleport.test.ts
  • unraid-ui/src/lib/utils.test.ts
  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Trigger events with `await element.trigger('click')`

Applied to files:

  • unraid-ui/src/composables/useTeleport.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Assert on rendered text content with `wrapper.text()`

Applied to files:

  • unraid-ui/src/composables/useTeleport.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Find elements with semantic queries like `find('button')` rather than data-test IDs

Applied to files:

  • unraid-ui/src/lib/utils.test.ts
📚 Learning: 2025-11-24T17:51:46.348Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/api-rules.mdc:0-0
Timestamp: 2025-11-24T17:51:46.348Z
Learning: Applies to api/**/*.test.{ts,tsx} : Use Vitest for the test suite, not Jest

Applied to files:

  • unraid-ui/src/lib/utils.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use VITEST for test suite, not jest

Applied to files:

  • unraid-ui/src/lib/utils.test.ts
📚 Learning: 2024-11-06T20:59:06.096Z
Learnt from: mdatelle
Repo: unraid/api PR: 942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:87-89
Timestamp: 2024-11-06T20:59:06.096Z
Learning: Error handling in mutations like `addRoleForUser` in `auth.resolver.ts` is performed at the service level, so additional error handling in the resolver is unnecessary.

Applied to files:

  • api/src/unraid-api/graph/resolvers/customization/customization.mutations.resolver.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Test emitted events with `wrapper.emitted()`

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/*.test.ts : Mock external services and API calls

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
📚 Learning: 2025-08-09T01:03:29.676Z
Learnt from: elibosley
Repo: unraid/api PR: 1575
File: packages/unraid-shared/src/services/socket-config.service.spec.ts:10-13
Timestamp: 2025-08-09T01:03:29.676Z
Learning: Vitest is used for all testing across all repositories in the unraid organization, not Jest. Always use `vi` for mocking utilities, not `jest`.

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:00.572Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-11-24T17:52:00.572Z
Learning: Always run `pnpm codegen` for GraphQL code generation in the web directory

Applied to files:

  • web/src/components/DevThemeSwitcher.standalone.vue
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/**/* : Always run `pnpm codegen` for GraphQL code generation in the web directory

Applied to files:

  • web/src/components/DevThemeSwitcher.standalone.vue
📚 Learning: 2025-03-12T13:35:43.900Z
Learnt from: pujitm
Repo: unraid/api PR: 1211
File: web/codegen.ts:14-14
Timestamp: 2025-03-12T13:35:43.900Z
Learning: The JSON scalar type in web/codegen.ts was temporarily changed from 'string' to 'any' for compatibility with JsonForms integration. This change facilitates the implementation of the Connect settings web component.

Applied to files:

  • web/src/components/DevThemeSwitcher.standalone.vue
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Applies to **/__test__/components/**/*.ts : Stub complex child components that aren't the focus of the test

Applied to files:

  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to web/__test__/**/*.test.{ts,tsx} : Stub complex child components that aren't the focus of the test

Applied to files:

  • web/__test__/components/UserProfile.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Use `createPinia` and `setActivePinia` when testing Pinia store files

Applied to files:

  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Use `createPinia()` instead of `createTestingPinia()` for most Pinia store tests

Applied to files:

  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/ColorSwitcher.test.ts
📚 Learning: 2025-04-02T21:21:29.168Z
Learnt from: elibosley
Repo: unraid/api PR: 1308
File: unraid-ui/src/components/common/loading/Error.vue:2-2
Timestamp: 2025-04-02T21:21:29.168Z
Learning: Components in the unraid-ui folder require explicit imports and are not autoloaded, unlike other parts of the project that may use Nuxt.js autoloading features.

Applied to files:

  • web/src/components/Wrapper/mount-engine.ts
📚 Learning: 2024-12-17T13:55:42.068Z
Learnt from: elibosley
Repo: unraid/api PR: 972
File: web/components/ColorSwitcher.ce.vue:1-2
Timestamp: 2024-12-17T13:55:42.068Z
Learning: In this Nuxt.js project, components used in templates are automatically imported by Nuxt, so explicit import statements for components are unnecessary.

Applied to files:

  • web/src/components/Wrapper/mount-engine.ts
📚 Learning: 2025-03-13T16:17:21.897Z
Learnt from: elibosley
Repo: unraid/api PR: 1211
File: unraid-ui/src/components/form/number/NumberFieldInput.vue:1-21
Timestamp: 2025-03-13T16:17:21.897Z
Learning: The unraid-ui is a UI library being published externally, where wrapping third-party components (like those from reka-ui) is an intentional design choice to maintain a consistent interface, standardize styling, and control the exposed API.

Applied to files:

  • web/src/components/Wrapper/mount-engine.ts
📚 Learning: 2024-12-09T15:47:29.325Z
Learnt from: pujitm
Repo: unraid/api PR: 975
File: web/components/Notifications/TabList.vue:1-24
Timestamp: 2024-12-09T15:47:29.325Z
Learning: In our Nuxt setup using Vue.js, components defined within the codebase are autoloaded and do not require explicit import statements in the script section. For example, components like `TabsList` and `TabsTrigger` used in `web/components/Notifications/TabList.vue` are automatically available without imports.

Applied to files:

  • web/src/components/Wrapper/mount-engine.ts
📚 Learning: 2025-03-27T23:52:57.888Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:52:57.888Z
Learning: In the unraid/api project, Vue components are compiled into web components. The `setActivePinia(createPinia())` call at the module level in store files is intentional and ensures all web components share a single Pinia store instance, which is the desired behavior. This shared state approach is critical for the application's architecture to function correctly.

Applied to files:

  • web/src/components/Wrapper/mount-engine.ts
  • web/src/store/theme.ts
📚 Learning: 2024-12-06T17:38:40.999Z
Learnt from: elibosley
Repo: unraid/api PR: 974
File: web/components/Loading/Error.vue:1-3
Timestamp: 2024-12-06T17:38:40.999Z
Learning: In Nuxt.js projects, components are automatically imported, so explicit import statements for components like `LoadingSpinner` in `web/components/Loading/Error.vue` are not necessary.

Applied to files:

  • web/src/components/Wrapper/mount-engine.ts
📚 Learning: 2024-12-06T17:34:16.133Z
Learnt from: pujitm
Repo: unraid/api PR: 974
File: web/components/Loading/Error.vue:50-50
Timestamp: 2024-12-06T17:34:16.133Z
Learning: In this project, the `Button` component from `~/components/shadcn/Button.vue` is autoloaded and does not need to be imported manually in components like `web/components/Loading/Error.vue`.

Applied to files:

  • web/src/components/Wrapper/mount-engine.ts
📚 Learning: 2025-03-27T23:52:57.888Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:52:57.888Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files ensures that all web components share a single Pinia store instance, which is the desired behavior. Without this initialization, each web component would have its own isolated store, breaking the intended architecture.

Applied to files:

  • web/src/components/Wrapper/mount-engine.ts
  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.907Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.907Z
Learning: Nuxt is set to auto import so some Vue files may need `computed` or `ref` imported explicitly in store files

Applied to files:

  • web/src/components/Wrapper/mount-engine.ts
  • web/src/store/theme.ts
📚 Learning: 2024-11-19T16:34:44.804Z
Learnt from: pujitm
Repo: unraid/api PR: 963
File: web/helpers/markdown.ts:1-41
Timestamp: 2024-11-19T16:34:44.804Z
Learning: In `web/store/updateOsChangelog.ts`, when custom logic is required, `Markdown.create` is used to obtain a custom `marked` instance. This is the correct and intended usage of the `Markdown` helper in such cases.

Applied to files:

  • unraid-ui/src/lib/utils.ts
📚 Learning: 2025-11-24T17:51:46.348Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/api-rules.mdc:0-0
Timestamp: 2025-11-24T17:51:46.348Z
Learning: Applies to api/**/*.test.{ts,tsx} : Prefer to not mock simple dependencies

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:33:13.215Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:33:13.215Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files would break the build by causing all web components to share a singular Pinia store instance. Each web component needs its own Pinia store instance to maintain proper isolation and encapsulation.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-03-27T23:33:13.215Z
Learnt from: zackspear
Repo: unraid/api PR: 0
File: :0-0
Timestamp: 2025-03-27T23:33:13.215Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files would break the build by causing all web components to share a singular Pinia store instance. Each web component needs its own Pinia store instance to function correctly.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-06-13T17:14:21.739Z
Learnt from: pujitm
Repo: unraid/api PR: 1417
File: web/components/ConnectSettings/ConnectSettings.ce.vue:11-18
Timestamp: 2025-06-13T17:14:21.739Z
Learning: The project’s build tooling auto-imports common Vue/Pinia helpers such as `storeToRefs`, so explicit import statements for them are not required.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/store/**/*.ts : Ensure Vue reactivity imports like `computed`, `ref`, and `watchEffect` are added to store files even with Nuxt auto-imports enabled

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2024-12-09T15:45:46.492Z
Learnt from: pujitm
Repo: unraid/api PR: 975
File: web/components/Notifications/TabList.vue:1-4
Timestamp: 2024-12-09T15:45:46.492Z
Learning: In our Nuxt.js setup for the `web` project, it's not necessary to explicitly import `computed` from `vue` in Vue components, as it's globally available.

Applied to files:

  • web/src/store/theme.ts
📚 Learning: 2025-09-02T19:35:14.427Z
Learnt from: elibosley
Repo: unraid/api PR: 1639
File: web/components/standalone-mount.ts:38-54
Timestamp: 2025-09-02T19:35:14.427Z
Learning: In the unraid/api project, exposing Apollo client and GraphQL utilities (apolloClient, gql, graphqlParse) on the global window object is intentional to allow developers to interact with GraphQL from the browser console without needing to be inside Vue components. Developer experience is prioritized over minimizing global exposure in this context.

Applied to files:

  • web/src/composables/gql/gql.ts
📚 Learning: 2025-11-24T17:52:26.908Z
Learnt from: CR
Repo: unraid/api PR: 0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-11-24T17:52:26.908Z
Learning: Applies to **/__test__/store/**/*.ts : Only use `createTestingPinia` if you specifically need its testing features for Pinia stores

Applied to files:

  • web/__test__/components/ColorSwitcher.test.ts
🧬 Code graph analysis (10)
api/src/unraid-api/graph/resolvers/customization/customization.service.ts (6)
api/src/unraid-api/graph/resolvers/customization/customization.resolver.ts (1)
  • theme (58-60)
web/composables/gql/graphql.ts (1)
  • Theme (2057-2073)
api/src/store/modules/paths.ts (1)
  • paths (114-118)
api/src/store/index.ts (2)
  • getters (50-55)
  • store (10-16)
api/src/store/actions/load-dynamix-config-file.ts (1)
  • loadDynamixConfigFromDiskSync (57-61)
api/src/store/modules/dynamix.ts (1)
  • updateDynamixConfig (20-22)
web/__test__/store/theme.test.ts (1)
api/src/store/index.ts (1)
  • store (10-16)
api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts (1)
web/src/composables/gql/graphql.ts (1)
  • CustomizationMutations (605-609)
web/src/components/DevThemeSwitcher.mutation.ts (1)
web/src/composables/gql/gql.ts (1)
  • graphql (300-302)
unraid-ui/src/composables/useTeleport.ts (2)
unraid-ui/src/lib/utils.ts (1)
  • isDarkModeActive (58-70)
web/src/helpers/dark-mode.ts (1)
  • isDarkModeActive (1-12)
api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts (1)
web/src/composables/gql/graphql.ts (1)
  • CustomizationMutations (605-609)
web/src/components/Wrapper/mount-engine.ts (2)
unraid-ui/src/lib/utils.ts (1)
  • isDarkModeActive (58-70)
web/src/helpers/dark-mode.ts (1)
  • isDarkModeActive (1-12)
unraid-ui/src/lib/utils.ts (1)
web/src/helpers/dark-mode.ts (1)
  • isDarkModeActive (1-12)
web/src/store/theme.ts (2)
unraid-ui/src/lib/utils.ts (1)
  • isDarkModeActive (58-70)
web/src/helpers/dark-mode.ts (1)
  • isDarkModeActive (1-12)
web/src/helpers/dark-mode.ts (1)
unraid-ui/src/lib/utils.ts (1)
  • isDarkModeActive (58-70)
🪛 ast-grep (0.40.0)
unraid-ui/src/composables/useTeleport.test.ts

[warning] 9-9: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: document.body.innerHTML = ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

🪛 GitHub Check: CodeQL
unraid-ui/src/lib/utils.test.ts

[notice] 6-6: Unused variable, import, function or class
Unused variable originalDocumentElement.


[notice] 7-7: Unused variable, import, function or class
Unused variable originalBody.

Comment on lines 6 to 24
const ensureVirtualContainer = () => {
if (!virtualModalContainer) {
virtualModalContainer = document.createElement('div');
virtualModalContainer.id = 'unraid-api-modals-virtual';
virtualModalContainer.className = 'unapi';
virtualModalContainer.style.position = 'relative';
virtualModalContainer.style.zIndex = '999999';
document.body.appendChild(virtualModalContainer);
const existing = document.getElementById('unraid-api-modals-virtual');
if (existing) {
virtualModalContainer = existing as HTMLDivElement;
} else {
virtualModalContainer = document.createElement('div');
virtualModalContainer.id = 'unraid-api-modals-virtual';
virtualModalContainer.className = 'unapi';
virtualModalContainer.style.position = 'relative';
virtualModalContainer.style.zIndex = '999999';
if (isDarkModeActive()) {
virtualModalContainer.classList.add('dark');
}
document.body.appendChild(virtualModalContainer);
}
}
return virtualModalContainer;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential stale dark mode state after theme switching.

The dark mode check (lines 17-19) only runs when the container is first created. Since virtualModalContainer is a module-scoped singleton that's reused, the dark class won't update if the user switches themes dynamically after the initial mount.

Consider one of these approaches if dynamic theme switching is a requirement:

Option 1: Check and sync dark mode on every call

 const ensureVirtualContainer = () => {
   if (!virtualModalContainer) {
     const existing = document.getElementById('unraid-api-modals-virtual');
     if (existing) {
       virtualModalContainer = existing as HTMLDivElement;
     } else {
       virtualModalContainer = document.createElement('div');
       virtualModalContainer.id = 'unraid-api-modals-virtual';
       virtualModalContainer.className = 'unapi';
       virtualModalContainer.style.position = 'relative';
       virtualModalContainer.style.zIndex = '999999';
-      if (isDarkModeActive()) {
-        virtualModalContainer.classList.add('dark');
-      }
       document.body.appendChild(virtualModalContainer);
     }
   }
+  // Sync dark mode on every call
+  virtualModalContainer.classList.toggle('dark', isDarkModeActive());
   return virtualModalContainer;
 };

Option 2: Document that theme changes require page reload

If theme switching doesn't need real-time updates, add a comment explaining the limitation.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ensureVirtualContainer = () => {
if (!virtualModalContainer) {
virtualModalContainer = document.createElement('div');
virtualModalContainer.id = 'unraid-api-modals-virtual';
virtualModalContainer.className = 'unapi';
virtualModalContainer.style.position = 'relative';
virtualModalContainer.style.zIndex = '999999';
document.body.appendChild(virtualModalContainer);
const existing = document.getElementById('unraid-api-modals-virtual');
if (existing) {
virtualModalContainer = existing as HTMLDivElement;
} else {
virtualModalContainer = document.createElement('div');
virtualModalContainer.id = 'unraid-api-modals-virtual';
virtualModalContainer.className = 'unapi';
virtualModalContainer.style.position = 'relative';
virtualModalContainer.style.zIndex = '999999';
if (isDarkModeActive()) {
virtualModalContainer.classList.add('dark');
}
document.body.appendChild(virtualModalContainer);
}
}
return virtualModalContainer;
};
const ensureVirtualContainer = () => {
if (!virtualModalContainer) {
const existing = document.getElementById('unraid-api-modals-virtual');
if (existing) {
virtualModalContainer = existing as HTMLDivElement;
} else {
virtualModalContainer = document.createElement('div');
virtualModalContainer.id = 'unraid-api-modals-virtual';
virtualModalContainer.className = 'unapi';
virtualModalContainer.style.position = 'relative';
virtualModalContainer.style.zIndex = '999999';
document.body.appendChild(virtualModalContainer);
}
}
// Sync dark mode on every call
virtualModalContainer.classList.toggle('dark', isDarkModeActive());
return virtualModalContainer;
};
🤖 Prompt for AI Agents
In unraid-ui/src/composables/useTeleport.ts around lines 6 to 24, the
module-scoped virtualModalContainer only gets the 'dark' class when first
created, so theme switches after initial mount leave the class stale; update
ensureVirtualContainer to re-check isDarkModeActive() on every call and add or
remove the 'dark' class accordingly (i.e., if virtualModalContainer exists, call
isDarkModeActive() and ensure classList contains 'dark' when true and does not
when false) so the container stays in sync with theme changes.

Comment on lines +183 to +185
if (isDarkModeActive()) {
element.classList.add('dark');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Code duplication: Two isDarkModeActive implementations exist.

While this implementation correctly uses isDarkModeActive from @unraid/ui, there's a duplicate implementation in web/src/helpers/dark-mode.ts with slightly different logic:

  • unraid-ui/src/lib/utils.ts (lines 58-70): checks cssVar === '1' AND cssVar === '0'
  • web/src/helpers/dark-mode.ts (lines 0-11): only checks cssVar === '1', not '0'

This inconsistency could lead to different behavior depending on which implementation is used.

Consider consolidating to a single implementation. If both packages need this utility, ensure they have identical logic or re-export from a shared location.

// Option 1: Remove web/src/helpers/dark-mode.ts and use @unraid/ui everywhere
import { isDarkModeActive } from '@unraid/ui';

// Option 2: Make web/src/helpers/dark-mode.ts re-export from @unraid/ui
export { isDarkModeActive } from '@unraid/ui';
🤖 Prompt for AI Agents
In web/src/components/Wrapper/mount-engine.ts around lines 183-185 there is a
duplicate dark-mode checker usage; the project also defines a slightly different
isDarkModeActive in web/src/helpers/dark-mode.ts which causes inconsistent
behavior — remove the duplicate implementation and consolidate to a single
source: either delete web/src/helpers/dark-mode.ts and update all imports to
import { isDarkModeActive } from '@unraid/ui', or change
web/src/helpers/dark-mode.ts to re-export the function from '@unraid/ui' (export
{ isDarkModeActive } from '@unraid/ui'); then update any local imports, run
typecheck/lint and ensure behavior is unchanged.

Comment on lines 1 to 12
export const isDarkModeActive = (): boolean => {
if (typeof document === 'undefined') return false;

const cssVar = getComputedStyle(document.documentElement).getPropertyValue('--theme-dark-mode').trim();
if (cssVar === '1') return true;

if (document.documentElement.classList.contains('dark')) return true;
if (document.body?.classList.contains('dark')) return true;
if (document.querySelector('.unapi.dark')) return true;

return false;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Honor --theme-dark-mode = "0" as an explicit “not dark” override.

Right now, if the CSS variable is "0", the function falls through to class-based checks and can still return true if .dark classes or .unapi.dark are present, even though the variable indicates dark mode is off. To keep the CSS variable as the source of truth (and to match the unraid-ui implementation), add an explicit false branch before the class checks:

  const cssVar = getComputedStyle(document.documentElement).getPropertyValue('--theme-dark-mode').trim();
  if (cssVar === '1') return true;
+ if (cssVar === '0') return false;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const isDarkModeActive = (): boolean => {
if (typeof document === 'undefined') return false;
const cssVar = getComputedStyle(document.documentElement).getPropertyValue('--theme-dark-mode').trim();
if (cssVar === '1') return true;
if (document.documentElement.classList.contains('dark')) return true;
if (document.body?.classList.contains('dark')) return true;
if (document.querySelector('.unapi.dark')) return true;
return false;
};
export const isDarkModeActive = (): boolean => {
if (typeof document === 'undefined') return false;
const cssVar = getComputedStyle(document.documentElement).getPropertyValue('--theme-dark-mode').trim();
if (cssVar === '1') return true;
if (cssVar === '0') return false;
if (document.documentElement.classList.contains('dark')) return true;
if (document.body?.classList.contains('dark')) return true;
if (document.querySelector('.unapi.dark')) return true;
return false;
};
🤖 Prompt for AI Agents
In web/src/helpers/dark-mode.ts around lines 1 to 12, the CSS variable check
treats only '1' as true but does not treat '0' as an explicit false, allowing
class-based checks to override the variable; change the logic so after reading
and trimming --theme-dark-mode you return true if it's '1' and return false if
it's '0' before running any class-based checks, otherwise fall back to existing
class and selector checks.

Comment on lines +100 to 104
// Lazy query - only executes when explicitly called
const { load, onResult, onError } = useLazyQuery<GetThemeQuery>(GET_THEME_QUERY, null, {
fetchPolicy: 'cache-and-network',
nextFetchPolicy: 'cache-first',
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's see the structure of the theme.ts file
echo "=== Checking theme.ts file structure ==="
wc -l web/src/store/theme.ts

echo ""
echo "=== Context around lines 100-104 ==="
sed -n '90,115n' web/src/store/theme.ts

echo ""
echo "=== Context around lines 208-210 ==="
sed -n '200,220n' web/src/store/theme.ts

Repository: unraid/api

Length of output: 197


🏁 Script executed:

#!/bin/bash
# Search for fetchTheme calls in the entire codebase
echo "=== Searching for fetchTheme calls ==="
rg "fetchTheme" --type ts --type tsx -C 3

echo ""
echo "=== Searching for useThemeStore().fetch or useThemeStore().load ==="
rg "useThemeStore\(\)\s*\.\s*(fetch|load)" --type ts --type tsx -C 2

Repository: unraid/api

Length of output: 234


🏁 Script executed:

#!/bin/bash
# Search for direct theme store imports and method calls
echo "=== Looking for theme store usage patterns ==="
rg "useThemeStore|themeStore" --type ts --type tsx -C 2 | head -100

Repository: unraid/api

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Read the theme.ts file
echo "=== Reading web/src/store/theme.ts ==="
cat -n web/src/store/theme.ts

Repository: unraid/api

Length of output: 9046


🏁 Script executed:

#!/bin/bash
# Search for fetchTheme without type restrictions
echo "=== Searching for fetchTheme ==="
rg "fetchTheme" --context 3

echo ""
echo "=== Searching for useThemeStore calls ==="
rg "useThemeStore" --context 2 | head -80

Repository: unraid/api

Length of output: 5390


🏁 Script executed:

#!/bin/bash
# Search specifically for fetchTheme() calls (with parentheses/invocations)
echo "=== Searching for fetchTheme() invocations ==="
rg "\.fetchTheme\(" --context 3

echo ""
echo "=== Searching for fetchTheme with store usage ==="
rg "fetchTheme" -C 2

echo ""
echo "=== Checking for app initialization, plugins, setup ==="
rg "useThemeStore" web/src -C 3 | grep -v "__test__" | head -150

Repository: unraid/api

Length of output: 10534


🏁 Script executed:

#!/bin/bash
# Read the themeInitializer.ts file
echo "=== Reading web/src/store/themeInitializer.ts ==="
cat -n web/src/store/themeInitializer.ts

Repository: unraid/api

Length of output: 2399


🏁 Script executed:

#!/bin/bash
# Search for initializeTheme calls to see if themeInitializer is even used
echo "=== Searching for initializeTheme calls ==="
rg "initializeTheme" --context 3

echo ""
echo "=== Verifying setTheme() logic from theme.ts lines 179-202 ==="
sed -n '179,202p' web/src/store/theme.ts

Repository: unraid/api

Length of output: 4325


The lazy query won't execute: fetchTheme() is never invoked anywhere in the codebase.

The useLazyQuery at lines 100-104 requires load() to be called to execute. While fetchTheme() wraps this call and is exported from the store, it's never actually invoked. The theme initialization in web/src/components/Wrapper/auto-mount.ts calls initializeTheme(), which in turn calls setTheme() without arguments—a no-op that returns early without triggering the GraphQL query. This means server-provided theme values are never fetched; the store remains at defaults with only CSS-based theme values available. Either call fetchTheme() during initialization, or clarify if server theme fetching is intentionally disabled.

🤖 Prompt for AI Agents
In web/src/store/theme.ts around lines 100-104: the useLazyQuery returns load()
(wrapped by fetchTheme) but fetchTheme() is never called, so the server theme
query never runs; update the app initialization path (e.g., in
web/src/components/Wrapper/auto-mount.ts where initializeTheme() is called) to
invoke fetchTheme() after store setup (or call load() directly) so the GraphQL
GET_THEME_QUERY actually executes; ensure you handle the Promise/result via
onResult/onError and preserve the early-return behavior of setTheme() when no
payload is present.

- Added a mock for dark mode detection in theme store tests, improving the accuracy of dark mode state verification.
- Refactored test setup to ensure proper initialization and cleanup of dark mode classes and CSS variables.
- Updated tests to check for reactive updates to dark mode based on theme changes, enhancing test coverage and reliability.

These changes improve the robustness of the theme store tests, ensuring consistent behavior in dark mode handling.
- Modified the banner gradient test to use a resolved CSS value instead of a nested variable, ensuring accurate rendering in tests.
- Updated expectations to reflect the new gradient format, enhancing the reliability of theme store tests.

These changes improve the clarity and correctness of the theme store's banner gradient functionality in tests.
@github-actions
Copy link
Contributor

github-actions bot commented Dec 9, 2025

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

@github-actions
Copy link
Contributor

github-actions bot commented Dec 9, 2025

This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:

https://preview.dl.unraid.net/unraid-api/tag/PR1808/dynamix.unraid.net.plg

Comment on lines +106 to +107
const expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toUTCString();
document.cookie = `${THEME_COOKIE_KEY}=${encodeURIComponent(themeName)}; path=/; SameSite=Lax; expires=${expires}`;
Copy link
Member

Choose a reason for hiding this comment

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

I'm assuming this sets expiration to 1 yr from now, but a comment would be nice!

Comment on lines +115 to +126
const syncDomForTheme = (themeName: ThemeName) => {
const root = document.documentElement;
const isDark = DARK_UI_THEMES.includes(themeName as (typeof DARK_UI_THEMES)[number]);
const method: 'add' | 'remove' = isDark ? 'add' : 'remove';
root.style.setProperty('--theme-name', themeName);
root.style.setProperty('--theme-dark-mode', isDark ? '1' : '0');
root.setAttribute('data-theme', themeName);
root.classList[method]('dark');
document.body?.classList[method]('dark');
document.querySelectorAll('.unapi').forEach((el) => el.classList[method]('dark'));
};
Copy link
Member

Choose a reason for hiding this comment

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

generally speaking, this is a lot of things we're having to change to set dark mode

$theme = strtolower(trim($display['theme'] ?? ''));
$darkThemes = ['gray', 'black'];
$isDarkMode = in_array($theme, $darkThemes, true);
$vars['--theme-dark-mode'] = $isDarkMode ? '1' : '0';
Copy link
Member

Choose a reason for hiding this comment

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

i get antipattern/red flag vibes from this (ie multiple ways to set dark mode, multiple places you have to consider to debug themes--which could step on each others' toes)

i suspect this is in the spirit of a css implementation of the dark mode recognition we were doing in js, though.

for my own context, what are the limitations of purely setting a css var palette based on theme? is it the use of dark: styles in our component code & component dependencies?

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.

3 participants