Skip to content

Conversation

@Ajit-Mehrotra
Copy link
Collaborator

@Ajit-Mehrotra Ajit-Mehrotra commented Dec 29, 2025

Summary by CodeRabbit

  • New Features

    • Notification settings UI (position, stacking, duration, max), improved toasts, and richer notification interactions.
    • New Docker actions and an active-container view; streamlined Docker pages/components.
    • Centralized external navigation handling for consistent link behavior.
  • Bug Fixes / Improvements

    • More robust notification add/archive flows, retries, streaming loads and accurate overviews.
    • Scoped theme/CSS adjustments and UI framework upgrade; notifications list default page size increased.
    • Deployment retry resilience and unified clipboard/toast behavior.
  • Tests

    • Expanded fixtures and tests for file-modifier and CSS scoping changes.

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

pujitm and others added 30 commits December 23, 2025 20:54
fix context menu trigger area and dropdown styles

implement 'move to folder'

implement 'start / stop' bulk & row actions

implement pause / resume

implement column visibility customization

fix(organizer): over-eager addition of untracked containers to root folder

scaffold docker form service

docker container management prototype

use compacted table as sidebar tree

add drag and drop for re-ordering containers & folders

right click to reame & delete folders

add bottom padding to container to make it easier to drag items to bottom in sidebar

click overview row to open details

track active container in query param

refactor: extract composables

refactor: simplify organizer operations

refactor!: rm intermediate tree resolution of organizers

BREAKING CHANGE: ResolvedOrganizerView -> root is replaced by rootId +
flatEntries.

`root` resolved a tree representation of an organizer, but this required
clients to defined and use their own tree operations, thus increasing
client complexity.

Instead, `flatEntries` is more suitable for client operations and only
requires an initial mapping step on the client for efficiency.

fix: rm extra Root layer in table

map containers to their template files

feat: icon support

fix: container state badge

chore: fix formatting

fix: search filtering

fix: context menu

feat: filtering & bulk actions in compact mode

feat: critical notifications

feat: notifyIfUnique service method

tmp: critical notifications system

fix: nuxt ui portal styling

fix: notifications type check

fix api tests

fix: css

Revert "fix: css"

This reverts commit 234c2e2.

add docker constants

flatten css scopes

feat: file modification for replacing docker overview table

feat: navigate to container update page

feat: implement manage settings action

fix: column visibility toggle

fix: move update to a badge + popover

feat: save column visibility preferences across visits

fix: add feature flag to containers file mod

fix: circular dependency in docker service

add a flag to opt out of version check in super.shouldApply in file mods

fix: optimistic column toggle update

refactor: optimistic column toggle

feat: container start & stop order

feat: bulk toggle auto-start

fix: add background style reset, apply to button:hover as well

feat: add ENABLE_NEXT_DOCKER_RELEASE=true to staging environment

chore(api): add dev/notifications to gitignore

feat: container update actions

fix: container update logic

feat: bulk container updates

feat: container sizes

fix: container sizes modla overlay

fix: checkbox alignment

fix: revert color in main css

chore: ignore build output in lint & fix

feat: server-side container ip

add docker feature flag to .env.production

fix: container port duplication

feat: multi-value copyable badges

feat: make lanIpPorts a list, not a csv

feat: visit button

feat: include indexed search fields in filter input title

feat: sync userprefs.cfg for rollback compat

feat: port conflicts

refactor: port conflict frontend stuff

feat: update all containers bulk action

feat: docker logs

fix: use container name instead of id in 'view logs' modal

make webgui iframable

feat: re-add compact mode

manage settings > compact view

fix styles

feat: container stats

refactor: docker autostart service

refactor: docker log, event, and port services
- update VSCode settings for Tailwind CSS support
…rror messages

commit addresses the following two bugs/issues:
1. infinite network requests
2. make error messages more accurate

bug details:
- when scrolled all the way down in the notification pane (when api is down), unraid infinitely sends network requests.
- must be scrolled all the way to the bottom and stay at the bottom of the pane while the api is down

technical details:
- for infinite loop, added try/catch that sets a canLoadMore flag to false when it encounters an error, preventing infinite loop
- errors now look at non-standard locations as well

impact:
- performance benefits
- more graceful ux on failure
… improved consistency

- replaced Heroicons components with UIcon for better integration
- refactored Sidebar.vue to utilize USlideover and UButton for a cleaner UI
- removed unused imports and styles in main.css for better maintainability

NOTES:
- had to change main.css variables for it to work properly. Need to make sure this doesn't ruin other people's code.
- still needs to be further refactored to align with existing ui variables
…mproved UI consistency

- modified vite.config.ts to integrate app configuration into UI setup
- updated app.config.ts to include new button, tabs, and slideover variants for better theming
- cleaned up main.css by removing unused styles and ensuring proper imports
- refactored notification components to streamline structure and improve readability
> [!Note] This stubs the unraid-ui/src/components/common/toast. Initially created a shim to convert vue-sonnner toasts to nuxtui. However, since there weren't that many, I just did a clean replacement.

- replace router link with window.location.assign

The `UButton` component attempts to inject the Vue Router instance when the `:to` prop is used. In the standalone component environment (where the router is not installed), this caused a "TypeError: inject(...) is undefined" crash when rendering notifications with links.

This change replaces the `:to` prop with a standard `@click` handler that uses `window.location.assign`, ensuring navigation works correctly without requiring the router context.
…ng logic

Problem this solution addresses:
Basically, when users filtered by alert, warning, or info, results were being paginated first, then filtered by the requested importance, so filtered notifications were not working properly in some (a lot) of cases.

- added a new async generator method to load notifications in batches, enhancing performance and error handling.
- refactored the notification loading logic to utilize the generator, improving readability and maintainability.
- updated filtering logic to streamline the process of matching notifications based on importance and type.
- updated file watching logic to ignore initial files, improving performance.
- added duplicate check for archive notifications to prevent double counting.
- implemented retry mechanism for loading notifications, enhancing reliability.
- introduced handling for risky notifications to ensure proper file creation and avoid legacy script failures.
This change ensures that Nuxt UI notifications respect the display position configured in the legacy webGUI settings.

Backend:
- Added `NotificationSettings` to the GraphQL model.
- Exposed `settings` field on the `Notifications` resolver.
- Implemented `getSettings` in `NotificationsService` to read `notify.position` from the Dynamix store.

Frontend:
- Added `getNotificationSettings` GraphQL query.
- Updated `mount-engine.ts` to fetch settings before mounting.
- Mapped legacy position values (e.g., 'center') to Nuxt UI compatible values (e.g., 'top-center').
…on drift

This commit addresses several critical stability issues in the notification system spanning the legacy PHP script, the Node.js API, and the Vue frontend.

**Backend / API:**
- **Refactor `notify` script (PHP):** Added `-u` flag to accept a custom filename/ID from the caller. Added filename length sanitization (255 chars) to prevent filesystem errors.
- **Fix ID Mismatch:** The Node API now generates a UUID and passes it to the `notify` script via `-u`. This guarantees that the API, Frontend, and Filesystem all reference the same ID, removing the need for "Risky" notification logic.
- **Fix Counter Bugs:**
    - `handleNotificationAdd` no longer ignores duplicate files in the archive.
    - `archiveNotification` now checks if a file exists in the archive before moving. If it exists, it simply deletes the unread copy without double-counting.
    - `archiveAll` now leverages the robust single-archive logic.

**Frontend (Web):**
- **Fix Infinite Scroll "Drift":**
    - Switched `List.vue` to use a **Debounced Refetch** (500ms) for subscription updates instead of manual cache manipulation. This handles rapid-fire events (mass adds) without corruption.
    - Increased `pageSize` to `50` to minimize race conditions where new items shift pagination offsets.
    - Added **Drift Detection**: If `fetchMore` returns a full page of duplicates (indicating the list has shifted), the component now automatically triggers a full refetch to self-heal.
…e files

Problem:
ESLint was flagging auto-imported composables (like `useToast`) as undefined variables in `.vue` files. While TypeScript correctly identified these globals via `auto-imports.d.ts`, the default `no-undef` rule in our Vue ESLint config did not account for them, causing false positive linting errors.

Solution:
- Disable the `no-undef` rule specifically for `.vue` files in `eslint.config.mjs`.
- Rely on TypeScript (via `vue-tsc`) and `auto-imports.d.ts` to handle global variable validation, ensuring type safety without conflicting with the auto-import build process.
- Remove manual imports of `useToast` that were added as a workaround (p sure the path I used didn't even exist in node modules).
…posable in ConnectSettings, DockerContainersTable, and RCloneOverview components
…r external URL handling across multiple components
@coderabbitai coderabbitai bot requested review from pujitm and zackspear December 29, 2025 17:22
@github-actions
Copy link
Contributor

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

import { PrefixedID } from '@unraid/shared/prefixed-id-scalar.js';
import { GraphQLBigInt, GraphQLJSON, GraphQLPort } from 'graphql-scalars';

import { DataSlice } from '@app/unraid-api/types/json-forms.js';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import DataSlice.

Copilot Autofix

AI 2 days ago

To fix an unused import, the appropriate action is to remove the import line (or the unused specifier from a multi‑symbol import) so that the file only imports what it actually uses. This reduces clutter and avoids misleading future maintainers into thinking the imported symbol is relevant.

In this specific file api/src/unraid-api/graph/resolvers/docker/docker.model.ts, the best fix is to delete the import of DataSlice on line 16:

import { DataSlice } from '@app/unraid-api/types/json-forms.js';

No other changes are required, since DataSlice is not referenced anywhere in the provided snippet. We do not need to adjust any existing imports, add new imports, or modify class definitions: the functionality of the file remains exactly the same, just without the unused symbol.

Suggested changeset 1
api/src/unraid-api/graph/resolvers/docker/docker.model.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/api/src/unraid-api/graph/resolvers/docker/docker.model.ts b/api/src/unraid-api/graph/resolvers/docker/docker.model.ts
--- a/api/src/unraid-api/graph/resolvers/docker/docker.model.ts
+++ b/api/src/unraid-api/graph/resolvers/docker/docker.model.ts
@@ -13,8 +13,6 @@
 import { PrefixedID } from '@unraid/shared/prefixed-id-scalar.js';
 import { GraphQLBigInt, GraphQLJSON, GraphQLPort } from 'graphql-scalars';
 
-import { DataSlice } from '@app/unraid-api/types/json-forms.js';
-
 export enum ContainerPortType {
     TCP = 'TCP',
     UDP = 'UDP',
EOF
@@ -13,8 +13,6 @@
import { PrefixedID } from '@unraid/shared/prefixed-id-scalar.js';
import { GraphQLBigInt, GraphQLJSON, GraphQLPort } from 'graphql-scalars';

import { DataSlice } from '@app/unraid-api/types/json-forms.js';

export enum ContainerPortType {
TCP = 'TCP',
UDP = 'UDP',
Copilot is powered by AI and may make mistakes. Always verify output.
import Docker from 'dockerode';
import { execa } from 'execa';

import { AppError } from '@app/core/errors/app-error.js';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import AppError.

Copilot Autofix

AI 2 days ago

  • In general, unused imports should be removed to improve readability and avoid confusion about unused dependencies.
  • The best fix here is to delete the unused AppError import line from api/src/unraid-api/graph/resolvers/docker/docker.service.ts, leaving all other imports untouched. This does not change runtime behavior because the symbol is never referenced.
  • Specifically, remove line 8: import { AppError } from '@app/core/errors/app-error.js'; from the import section at the top of docker.service.ts.
  • No additional methods, imports, or definitions are needed; we are only removing an unused import.
Suggested changeset 1
api/src/unraid-api/graph/resolvers/docker/docker.service.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/api/src/unraid-api/graph/resolvers/docker/docker.service.ts b/api/src/unraid-api/graph/resolvers/docker/docker.service.ts
--- a/api/src/unraid-api/graph/resolvers/docker/docker.service.ts
+++ b/api/src/unraid-api/graph/resolvers/docker/docker.service.ts
@@ -5,7 +5,6 @@
 import Docker from 'dockerode';
 import { execa } from 'execa';
 
-import { AppError } from '@app/core/errors/app-error.js';
 import { pubsub, PUBSUB_CHANNEL } from '@app/core/pubsub.js';
 import { catchHandlers } from '@app/core/utils/misc/catch-handlers.js';
 import { sleep } from '@app/core/utils/misc/sleep.js';
EOF
@@ -5,7 +5,6 @@
import Docker from 'dockerode';
import { execa } from 'execa';

import { AppError } from '@app/core/errors/app-error.js';
import { pubsub, PUBSUB_CHANNEL } from '@app/core/pubsub.js';
import { catchHandlers } from '@app/core/utils/misc/catch-handlers.js';
import { sleep } from '@app/core/utils/misc/sleep.js';
Copilot is powered by AI and may make mistakes. Always verify output.

// Add bottom-center and top-center position options if not present
const positionSelectStart = '<select name="position">';
const positionSelectEnd = '</select>';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable positionSelectEnd.

Copilot Autofix

AI 2 days ago

In general, unused variables should be removed to improve readability and avoid confusion. Here, positionSelectEnd is defined but not used anywhere, while the other constants in that block are used. The safest, behavior‑preserving fix is to remove the unused declaration.

Concretely, in api/src/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.ts, within the applyToSource method, delete the line declaring const positionSelectEnd = '</select>'; and leave the remaining declarations (positionSelectStart, bottomCenterOption, topCenterOption) unchanged. No additional imports, methods, or definitions are needed.

Suggested changeset 1
api/src/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.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/api/src/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.ts b/api/src/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.ts
--- a/api/src/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.ts
+++ b/api/src/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.ts
@@ -37,7 +37,6 @@
 
         // Add bottom-center and top-center position options if not present
         const positionSelectStart = '<select name="position">';
-        const positionSelectEnd = '</select>';
         const bottomCenterOption =
             '  <?=mk_option($notify[\'position\'], "bottom-center", _("bottom-center"))?>';
         const topCenterOption = '  <?=mk_option($notify[\'position\'], "top-center", _("top-center"))?>';
EOF
@@ -37,7 +37,6 @@

// Add bottom-center and top-center position options if not present
const positionSelectStart = '<select name="position">';
const positionSelectEnd = '</select>';
const bottomCenterOption =
' <?=mk_option($notify[\'position\'], "bottom-center", _("bottom-center"))?>';
const topCenterOption = ' <?=mk_option($notify[\'position\'], "top-center", _("top-center"))?>';
Copilot is powered by AI and may make mistakes. Always verify output.
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: 15

Caution

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

⚠️ Outside diff range comments (2)
web/src/components/Common/BaseTreeTable.vue (1)

419-456: Remove development logging artifacts before merge.

The drag handle code contains multiple console.log statements used for debugging (lines 419-427, 430-442, 445-456). These development artifacts should not be shipped to production.

🔎 Proposed cleanup
             onMousedown: (e: MouseEvent) => {
-              console.log('[DragHandle] mousedown', {
-                rowId: enhancedRow.original.id,
-                rowName: enhancedRow.original.name,
-                isBusy,
-                isDraggingThis,
-                draggable: !isBusy && !isDraggingThis,
-                target: e.target,
-                currentTarget: e.currentTarget,
-              });
             },
             onDragstart: (e: DragEvent) => {
-              console.log('[DragHandle] dragstart fired', {
-                rowId: enhancedRow.original.id,
-                rowName: enhancedRow.original.name,
-                isBusy,
-                dataTransfer: e.dataTransfer,
-              });
               if (isBusy) {
-                console.log('[DragHandle] dragstart prevented - row is busy');
                 e.preventDefault();
                 return;
               }
               handleDragStart(e, enhancedRow.original);
-              console.log('[DragHandle] handleDragStart called, draggingIds:', draggingIds.value);
             },
             onDragend: (e: DragEvent) => {
-              console.log('[DragHandle] dragend fired', {
-                rowId: enhancedRow.original.id,
-                dropEffect: e.dataTransfer?.dropEffect,
-              });
               handleDragEnd();
             },
             onDrag: (e: DragEvent) => {
-              // Log occasionally during drag (throttled by checking if clientX changed significantly)
-              if (e.clientX % 50 < 5) {
-                console.log('[DragHandle] dragging...', { x: e.clientX, y: e.clientY });
-              }
             },
api/src/unraid-api/graph/resolvers/docker/docker-event.service.spec.ts (1)

54-79: Critical: Duplicate declaration of mockDockerClientInstance and duplicate mocks.

There are two declarations of mockDockerClientInstance:

  1. Lines 54-56: const mockDockerClientInstance = { getEvents: vi.fn() }
  2. Lines 69-74: vi.hoisted(() => { ... })

Additionally, docker-client.js is mocked twice (lines 57-59 and 77-79). This causes static analysis failures and will lead to unpredictable test behavior.

Keep only the hoisted version and remove the duplicate.

🔎 Proposed fix
-// Mock the docker client utility - this is what the service actually uses
-const mockDockerClientInstance = {
-    getEvents: vi.fn(),
-};
-vi.mock('./utils/docker-client.js', () => ({
-    getDockerClient: vi.fn(() => mockDockerClientInstance),
-}));
-
 // Mock DockerService
 vi.mock('./docker.service.js', () => ({
     DockerService: vi.fn().mockImplementation(() => ({
         clearContainerCache: vi.fn(),
         getAppInfo: vi.fn().mockResolvedValue({ info: { apps: { installed: 1, running: 1 } } }),
     })),
 }));

 const { mockDockerClientInstance } = vi.hoisted(() => {
     const mock = {
         getEvents: vi.fn(),
-    } as unknown as Docker;
+    };
     return { mockDockerClientInstance: mock };
 });

 // Mock the docker client util
 vi.mock('@app/unraid-api/graph/resolvers/docker/utils/docker-client.js', () => ({
     getDockerClient: vi.fn().mockReturnValue(mockDockerClientInstance),
 }));
🧹 Nitpick comments (24)
web/src/components/Common/MultiValueCopyBadges.vue (2)

186-196: Consider multi-line formatting for better readability.

The inline formatting creates very long lines (120+ characters) that are difficult to scan and maintain. Multi-line formatting for components with many attributes is standard practice in Vue codebases and makes it easier to:

  • Quickly identify which attributes are being passed
  • Review individual attribute changes in git diffs
  • Maintain consistent code style
💡 Example of multi-line formatting
-    <component :is="UBadge" v-for="entry in inlineEntries" :key="entry.key" :color="getBadgeColor(entry.key)"
-      :variant="getBadgeVariant(entry.key)" :size="size" :title="getBadgeTitle(entry.key)"
-      :aria-label="getBadgeAriaLabel(entry.key)" data-stop-row-click="true" role="button" tabindex="0"
-      class="max-w-[20ch] cursor-pointer items-center gap-1 truncate select-none" @click.stop="handleCopy(entry)"
-      @keydown="handleBadgeKeydown($event, entry)">
+    <component
+      :is="UBadge"
+      v-for="entry in inlineEntries"
+      :key="entry.key"
+      :color="getBadgeColor(entry.key)"
+      :variant="getBadgeVariant(entry.key)"
+      :size="size"
+      :title="getBadgeTitle(entry.key)"
+      :aria-label="getBadgeAriaLabel(entry.key)"
+      data-stop-row-click="true"
+      role="button"
+      tabindex="0"
+      class="max-w-[20ch] cursor-pointer items-center gap-1 truncate select-none"
+      @click.stop="handleCopy(entry)"
+      @keydown="handleBadgeKeydown($event, entry)"
+    >
       <span class="flex min-w-0 items-center gap-1">
-        <component :is="UIcon" v-if="isBadgeCopied(entry.key)" name="i-lucide-check"
-          class="h-4 w-4 flex-shrink-0 text-white/90" />
+        <component
+          :is="UIcon"
+          v-if="isBadgeCopied(entry.key)"
+          name="i-lucide-check"
+          class="h-4 w-4 flex-shrink-0 text-white/90"
+        />
         <span class="truncate">{{ entry.value }}</span>
       </span>
     </component>

208-218: Apply the same multi-line formatting for consistency.

The overflow badge rendering has the same inline formatting that reduces readability. Applying multi-line formatting here would maintain consistency with the suggestion for inline entries.

web/src/helpers/functions.ts (2)

7-20: Debug utility looks reasonable, minor simplification possible.

The JSON.parse(JSON.stringify(err)) on line 14 could be simplified to just JSON.stringify(err, null, 2) for logging purposes, unless you specifically need the parsed object representation in the console.

🔎 Optional simplification
  try {
-   console.log('json:', JSON.parse(JSON.stringify(err)));
+   console.log('json:', JSON.stringify(err, null, 2));
  } catch {
    console.log('json:', 'failed to parse');
-   console.log('json:', err);
  }

46-47: Redundant type assertions and non-null assertion can be simplified.

The networkError is already typed as { message?: string } on line 27, making the repeated casts unnecessary. The non-null assertion on line 47 can be avoided.

As per coding guidelines, casting should be avoided when possible.

🔎 Proposed simplification
-  } else if ((e?.networkError as { message?: string })?.message) {
-    message = (e.networkError as { message?: string }).message!;
+  } else if (e?.networkError?.message) {
+    message = e.networkError.message;
   } else if (
api/src/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.ts (1)

45-45: Consider more robust duplicate detection.

The current approach uses exact string matching to check if content already exists (e.g., checking for bottomCenterOption literal at line 45 and '_(Stack notifications)_:' at line 80). While this works for known Unraid file formats, it's fragile to whitespace or formatting variations. Consider using more flexible patterns (e.g., checking for key identifiers like "bottom-center" or name="expand") to make the duplicate detection more resilient.

Also applies to: 80-80

web/scripts/deploy-dev.sh (1)

16-33: Consider adding logging for better debugging.

The retry logic is correct, but consider logging retry attempts to help diagnose transient failures during deployment.

🔎 Proposed enhancement with logging
 # Simple retry helper: retry <attempts> <delay_seconds> <command...>
 retry() {
   local attempts="$1"; shift
   local delay_seconds="$1"; shift
   local try=1
   while true; do
     "$@"
     local exit_code=$?
     if [ $exit_code -eq 0 ]; then
       return 0
     fi
     if [ $try -ge $attempts ]; then
+      echo "Command failed after $attempts attempts: $*" >&2
       return $exit_code
     fi
+    echo "Attempt $try failed (exit code: $exit_code), retrying in ${delay_seconds}s..." >&2
     sleep "$delay_seconds"
     try=$((try + 1))
   done
 }
api/src/unraid-api/unraid-file-modifier/modifications/default-cfg.modification.ts (1)

27-33: Remove excessive comments per coding guidelines.

The comments at lines 27-33, 41-42, and 50-51 explain what the code does rather than why, which is evident from the code itself. Per the coding guidelines: "Never add comments unless they are needed for clarity of function" and "Never add comments for obvious things."

🔎 Proposed cleanup
-        // Target: [notify] section
-        // We want to insert:
-        // expand="true"
-        // duration="5000"
-        // max="3"
-        //
-        // Inserting after [notify] line seems safest.
-
         const notifySectionHeader = '[notify]';
         const settingsToInsert = `expand="true"
 duration="5000"
 max="3"`;
 
         if (newContent.includes(notifySectionHeader)) {
-            // Check if already present to avoid duplicates (idempotency)
-            // Using a simple check for 'expand="true"' might be enough, or rigorous regex
             if (!newContent.includes('expand="true"')) {
         } else {
-            // If [notify] missing, append it?
-            // Unlikely for default.cfg, but let's append at end if missing
             newContent += `\n${notifySectionHeader}\n${settingsToInsert}\n`;
         }

As per coding guidelines.

Also applies to: 41-42, 50-51

api/src/unraid-api/organizer/organizer.ts (1)

221-234: Parameter iconMap is accepted but unused.

The iconMap parameter is added to resolveOrganizerView and resolveOrganizer but is never used in the function body. If this is intentional scaffolding for future work, consider adding a TODO comment. Otherwise, defer adding the parameter until it's actually needed.

api/src/unraid-api/graph/resolvers/docker/docker-event.service.spec.ts (1)

88-93: Remove unused getDockerClient from mock implementation.

The mockDockerServiceImpl includes getDockerClient but DockerService doesn't expose this method—it's an internal detail. This mock property is unused and should be removed for clarity.

🔎 Proposed fix
         const mockDockerServiceImpl = {
-            getDockerClient: vi.fn(),
             clearContainerCache: vi.fn(),
             getAppInfo: vi.fn().mockResolvedValue({ info: { apps: { installed: 1, running: 1 } } }),
         };
api/src/unraid-api/graph/resolvers/docker/organizer/docker-organizer.service.ts (1)

53-57: Injected DockerTemplateIconService is unused.

The service is injected but never used in any method. If this is scaffolding for upcoming functionality, consider deferring the injection until it's needed, or add a TODO indicating planned usage.

api/src/unraid-api/unraid-file-modifier/modifications/notify-script.modification.ts (1)

23-196: Consider adding validation for critical string replacements.

This modification performs many string.replace() operations. If the source file doesn't match expected patterns (e.g., upstream changes), replacements silently fail and the patch may be incomplete or break functionality. Consider validating that critical replacements succeeded.

🔎 Example validation pattern
function replaceOrThrow(content: string, search: string, replacement: string, description: string): string {
    if (!content.includes(search)) {
        throw new Error(`Failed to find pattern for ${description}`);
    }
    return content.replace(search, replacement);
}
api/src/unraid-api/unraid-file-modifier/modifications/default-base-css.modification.spec.ts (1)

41-42: Mock setup is unused in this test.

The vi.mocked(readFile).mockResolvedValue(inputCss) on line 42 is never used because applyToSource is called directly with inputCss rather than going through generatePatch. This mock can be removed from this specific test.

🔎 Suggested cleanup
-        // Mock readFile to return our inputCss
-        vi.mocked(readFile).mockResolvedValue(inputCss);
-
         // @ts-expect-error accessing private method
         const result = modification.applyToSource(inputCss);
api/src/unraid-api/unraid-file-modifier/modifications/__test__/generic-modification.spec.ts (1)

182-197: Good fixture management pattern.

The ensureFixtureExists helper downloads fixtures on-demand with proper error handling. This avoids committing large fixture files while ensuring tests have the required data.

Consider adding a timeout to the fetch call to prevent tests from hanging if GitHub is unreachable.

🔎 Optional: Add fetch timeout
-        const response = await fetch(testCase.fileUrl);
+        const response = await fetch(testCase.fileUrl, { signal: AbortSignal.timeout(30000) });
api/src/unraid-api/unraid-file-modifier/modifications/default-base-css.modification.ts (1)

70-78: Comment numbering inconsistency.

The numbered comments skip from "2." to "4." (line 75 → 77). This appears to be a minor oversight.

🔎 Fix comment numbering
             // 1. .Theme--sidebar definition e.g. .Theme--sidebar {
             .replace(/(\.Theme--sidebar)(\s*\{)/g, ':scope$1$2')
             // 2. .Theme--sidebar #displaybox
             .replace(/(\.Theme--sidebar)(\s+#displaybox)/g, ':scope$1$2')
-            // 4. .Theme--width-boxed #displaybox
+            // 3. .Theme--width-boxed #displaybox
             .replace(/(\.Theme--width-boxed)(\s+#displaybox)/g, ':scope$1$2');
api/src/unraid-api/unraid-file-modifier/modifications/default-azure-css.modification.ts (1)

32-54: Consider extracting shared CSS scoping logic to a base class.

The applyToSource method is identical across multiple CSS modifications (DefaultAzure, DefaultBlack, DefaultGray, DefaultWhite, DefaultBase). Extracting this to a shared abstract base class (e.g., CssBodyScopeModification) would reduce duplication and centralize maintenance.

Additionally, source.indexOf('}', bodyOpenBraceIndex) finds the first closing brace after the body opening, which can fail if the body block contains nested braces (e.g., media queries). For simple body blocks this works, but consider a brace-counting approach if the CSS structure is ever more complex.

api/src/unraid-api/unraid-file-modifier/modifications/default-black-css.modification.ts (1)

26-54: Duplicate implementation across CSS modifications.

This entire file (generatePatch + applyToSource) is a near-copy of DefaultAzureCssModification, differing only in the id and filePath. Consider introducing a shared base class to encapsulate the common logic.

🔎 Example shared base class approach
// css-scope-modification.base.ts
export abstract class CssScopeModification extends FileModification {
    protected async generatePatch(overridePath?: string): Promise<string> {
        const fileContent = await readFile(this.filePath, 'utf-8');
        const newContent = this.applyToSource(fileContent);
        return this.createPatchWithDiff(overridePath ?? this.filePath, fileContent, newContent);
    }

    protected applyToSource(source: string): string {
        // shared body-scoping logic
    }
}

// default-black-css.modification.ts
export default class DefaultBlackCssModification extends CssScopeModification {
    id = 'default-black-css-modification';
    public readonly filePath = '/usr/local/emhttp/plugins/dynamix/styles/default-black.css';
}
web/src/components/RClone/RCloneOverview.vue (1)

39-55: Switch to useToast for delete feedback is solid; consider adding onError handling

Using const toast = useToast() and toast.add(...) for both the success path and the “deleteRCloneRemote returned false” case gives clear user feedback and avoids reliance on a global window.toast. You might optionally also register an onError handler on this mutation to surface network/GraphQL failures (which won’t hit onDeleteDone) so users see a message if the delete request itself fails.

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

7-8: Centralizing navigation via navigate() is good; align import with ESM .js convention

Replacing direct window.location.href writes in deny and returnToApp with the shared navigate(url) helper keeps all external redirects going through one place and makes this logic easier to test and evolve.

To stay consistent with the project’s ESM import guideline and with the other imports in this file (e.g., useAuthorizationLink.js, useClipboardWithToast.js, useApiKeyStore.js), consider changing the new import to:

import { navigate } from '~/helpers/external-navigation.js';

so you’re always using .js extensions from TypeScript.

Also applies to: 93-103, 107-113

web/src/composables/useDockerEditNavigation.ts (1)

2-2: Inconsistent import path alias.

This file uses @/helpers/external-navigation while other files in this PR use ~/helpers/external-navigation. Consider standardizing on one alias for consistency across the codebase.

🔎 Suggested fix for consistency
-import { navigate } from '@/helpers/external-navigation';
+import { navigate } from '~/helpers/external-navigation';
web/src/components/HeaderOsVersion.standalone.vue (1)

42-59: Theme-specific styling fix looks reasonable.

The implementation correctly handles header overlap issues for Azure/Gray themes in Unraid 7.0. The version check (version > 0 && version < 7.1) safely handles cases where version parsing might fail.

Optional: Consider more robust version parsing

The current parseFloat(osVersion.value || '0') approach works for simple versions but would fail on pre-release versions like "7.0.0-beta1". If pre-release versions need to be supported in the future, consider using a more robust version comparison library.

However, the current guard (version > 0) ensures the code won't break on invalid versions, making this a low-priority enhancement.

web/src/components/Notifications/constants.ts (1)

15-23: Consider the semantic mapping of INFO to 'success'.

The NOTIFICATION_TOAST_COLORS mapping uses 'success' for INFO importance notifications. While this may be intentional for UI consistency (green for informational messages), it could be semantically confusing since "success" and "info" typically represent different message types in most design systems.

If the toast component supports an 'info' variant, consider:

 export const NOTIFICATION_TOAST_COLORS: Record<
   NotificationImportance,
   'error' | 'warning' | 'info' | 'success'
 > = {
   [NotificationImportance.ALERT]: 'error',
   [NotificationImportance.WARNING]: 'warning',
-  [NotificationImportance.INFO]: 'success',
+  [NotificationImportance.INFO]: 'info',
 };

However, if this mapping is intentional for your design system (e.g., showing info notifications in green), the current implementation is acceptable.

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

177-189: Simplify position mapping logic.

The position mapping can be simplified using a more concise approach without the explicit map object.

🔎 Proposed refactor
       if (fetchedSettings.position) {
-        const map: Record<string, ToastPosition> = {
-          'top-left': 'top-left',
-          'top-right': 'top-right',
-          'bottom-left': 'bottom-left',
-          'bottom-right': 'bottom-right',
-          'bottom-center': 'bottom-center',
-          'top-center': 'top-center',
-        };
-        const mappedPosition = map[fetchedSettings.position];
-        if (mappedPosition) {
-          toasterSettings.position = mappedPosition;
-        }
+        const validPositions: ToastPosition[] = [
+          'top-left', 'top-right', 'bottom-left', 
+          'bottom-right', 'top-center', 'bottom-center'
+        ];
+        if (validPositions.includes(fetchedSettings.position as ToastPosition)) {
+          toasterSettings.position = fetchedSettings.position as ToastPosition;
+        }
       }

This reduces the redundant identity mapping and makes the validation more explicit.


168-171: Consider caching notification settings.

The network-only fetch policy means settings are fetched on every mount, which could impact performance. If notification settings don't change frequently, consider using cache-first or implementing a short-lived cache.

For example:

fetchPolicy: 'cache-first',

This would use cached settings when available while still allowing updates when settings change server-side.

web/src/components/Notifications/List.vue (1)

86-90: Remove debug console.log statements before merging.

Multiple console.log and console.warn statements are present for debugging. These should be removed or converted to a debug utility that can be disabled in production.

Also applies to: 107-108, 140-141, 163-164, 186-188, 193-193, 200-201, 221-227, 230-230

Comment on lines 70 to 74
state: {
type: 'string',
title: 'State',
enum: ['RUNNING', 'EXITED'],
},
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

Missing PAUSED state in enum.

The state enum only includes ['RUNNING', 'EXITED'], but ContainerState (from docker.model.ts) also defines PAUSED. This could cause validation issues or UI inconsistencies when a container is paused.

🔎 Proposed fix
                         state: {
                             type: 'string',
                             title: 'State',
-                            enum: ['RUNNING', 'EXITED'],
+                            enum: ['RUNNING', 'PAUSED', 'EXITED'],
                         },
📝 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
state: {
type: 'string',
title: 'State',
enum: ['RUNNING', 'EXITED'],
},
state: {
type: 'string',
title: 'State',
enum: ['RUNNING', 'PAUSED', 'EXITED'],
},
🤖 Prompt for AI Agents
In api/src/unraid-api/graph/resolvers/docker/docker-form.service.ts around lines
70 to 74, the state schema enum only lists ['RUNNING','EXITED'] while
ContainerState also defines 'PAUSED'; update the enum to include 'PAUSED' so
validation/UI accepts paused containers, and run/adjust any affected type checks
or UI form logic that relies on this schema.

Comment on lines +11 to +17
import { type Layout } from '@jsonforms/core';
import { Node } from '@unraid/shared/graphql.model.js';
import { PrefixedID } from '@unraid/shared/prefixed-id-scalar.js';
import { GraphQLBigInt, GraphQLJSON, GraphQLPort } from 'graphql-scalars';

import { DataSlice } from '@app/unraid-api/types/json-forms.js';

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

Remove unused imports.

Layout and DataSlice are imported but not used in this file. Static analysis confirms DataSlice is unused. These types may be intended for future use but should be added when actually needed.

🔎 Proposed fix
-import { type Layout } from '@jsonforms/core';
 import { Node } from '@unraid/shared/graphql.model.js';
 import { PrefixedID } from '@unraid/shared/prefixed-id-scalar.js';
 import { GraphQLBigInt, GraphQLJSON, GraphQLPort } from 'graphql-scalars';
-
-import { DataSlice } from '@app/unraid-api/types/json-forms.js';
-
📝 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
import { type Layout } from '@jsonforms/core';
import { Node } from '@unraid/shared/graphql.model.js';
import { PrefixedID } from '@unraid/shared/prefixed-id-scalar.js';
import { GraphQLBigInt, GraphQLJSON, GraphQLPort } from 'graphql-scalars';
import { DataSlice } from '@app/unraid-api/types/json-forms.js';
import { Node } from '@unraid/shared/graphql.model.js';
import { PrefixedID } from '@unraid/shared/prefixed-id-scalar.js';
import { GraphQLBigInt, GraphQLJSON, GraphQLPort } from 'graphql-scalars';
🧰 Tools
🪛 GitHub Check: CodeQL

[notice] 16-16: Unused variable, import, function or class
Unused import DataSlice.

🤖 Prompt for AI Agents
In api/src/unraid-api/graph/resolvers/docker/docker.model.ts around lines 11 to
17, the imports "Layout" and "DataSlice" are unused; remove these two imports
from the top of the file (delete "import { type Layout } from
'@jsonforms/core';" and "import { DataSlice } from
'@app/unraid-api/types/json-forms.js;") so only actually used symbols remain
imported, then run the linter/TS compile to confirm no remaining unused-import
warnings.

import Docker from 'dockerode';
import { execa } from 'execa';

import { AppError } from '@app/core/errors/app-error.js';
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

Remove unused import.

AppError is imported but never used in this file. Static analysis confirms this is dead code.

🔎 Proposed fix
-import { AppError } from '@app/core/errors/app-error.js';
📝 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
import { AppError } from '@app/core/errors/app-error.js';
🧰 Tools
🪛 GitHub Check: CodeQL

[notice] 8-8: Unused variable, import, function or class
Unused import AppError.

🤖 Prompt for AI Agents
In api/src/unraid-api/graph/resolvers/docker/docker.service.ts around line 8,
the import "AppError" from '@app/core/errors/app-error.js' is unused; remove
that import statement (or delete only the AppError specifier if multiple imports
exist on the same line) and ensure the file still compiles and lints cleanly by
running the TypeScript compiler and your linter after the change.

Comment on lines +12 to +24
async shouldApply({
checkOsVersion = true,
}: { checkOsVersion?: boolean } = {}): Promise<ShouldApplyWithReason> {
// Apply ONLY if version < 7.1.0
if (await this.isUnraidVersionLessThanOrEqualTo('7.1.0', { includePrerelease: false })) {
return super.shouldApply({ checkOsVersion: false });
}

return {
shouldApply: false,
reason: 'Patch only applies to Unraid versions < 7.1.0',
};
}
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

Inconsistency between comment/message and actual version check.

The comment on line 15 and the reason on line 22 both state < 7.1.0, but isUnraidVersionLessThanOrEqualTo includes version 7.1.0 itself. Clarify which is intended.

🔎 If the intent is to include 7.1.0, update the comments
-        // Apply ONLY if version < 7.1.0
+        // Apply ONLY if version <= 7.1.0
-            reason: 'Patch only applies to Unraid versions < 7.1.0',
+            reason: 'Patch only applies to Unraid versions <= 7.1.0',
📝 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
async shouldApply({
checkOsVersion = true,
}: { checkOsVersion?: boolean } = {}): Promise<ShouldApplyWithReason> {
// Apply ONLY if version < 7.1.0
if (await this.isUnraidVersionLessThanOrEqualTo('7.1.0', { includePrerelease: false })) {
return super.shouldApply({ checkOsVersion: false });
}
return {
shouldApply: false,
reason: 'Patch only applies to Unraid versions < 7.1.0',
};
}
async shouldApply({
checkOsVersion = true,
}: { checkOsVersion?: boolean } = {}): Promise<ShouldApplyWithReason> {
// Apply ONLY if version <= 7.1.0
if (await this.isUnraidVersionLessThanOrEqualTo('7.1.0', { includePrerelease: false })) {
return super.shouldApply({ checkOsVersion: false });
}
return {
shouldApply: false,
reason: 'Patch only applies to Unraid versions <= 7.1.0',
};
}
🤖 Prompt for AI Agents
In
api/src/unraid-api/unraid-file-modifier/modifications/default-azure-css.modification.ts
around lines 12 to 24, the inline comment and the returned reason say "< 7.1.0"
but the code calls isUnraidVersionLessThanOrEqualTo('7.1.0'), which includes
7.1.0; make them consistent by either changing the function call to exclude
7.1.0 (use a strict less-than check) or—preferably—update the comment and the
reason to state "<= 7.1.0" (or "≤ 7.1.0") and mention includePrerelease: false
if relevant so the text accurately reflects the implemented logic.

Comment on lines +12 to +24
async shouldApply({
checkOsVersion = true,
}: { checkOsVersion?: boolean } = {}): Promise<ShouldApplyWithReason> {
// Apply ONLY if version < 7.1.0
if (await this.isUnraidVersionLessThanOrEqualTo('7.1.0', { includePrerelease: false })) {
return super.shouldApply({ checkOsVersion: false });
}

return {
shouldApply: false,
reason: 'Patch only applies to Unraid versions < 7.1.0',
};
}
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

Same comment/message vs code discrepancy as in DefaultAzureCssModification.

The comment and reason string say < 7.1.0 but isUnraidVersionLessThanOrEqualTo includes 7.1.0. Align these with the intended behavior.

🔎 If the intent is to include 7.1.0, update the comments
-        // Apply ONLY if version < 7.1.0
+        // Apply ONLY if version <= 7.1.0
-            reason: 'Patch only applies to Unraid versions < 7.1.0',
+            reason: 'Patch only applies to Unraid versions <= 7.1.0',
📝 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
async shouldApply({
checkOsVersion = true,
}: { checkOsVersion?: boolean } = {}): Promise<ShouldApplyWithReason> {
// Apply ONLY if version < 7.1.0
if (await this.isUnraidVersionLessThanOrEqualTo('7.1.0', { includePrerelease: false })) {
return super.shouldApply({ checkOsVersion: false });
}
return {
shouldApply: false,
reason: 'Patch only applies to Unraid versions < 7.1.0',
};
}
async shouldApply({
checkOsVersion = true,
}: { checkOsVersion?: boolean } = {}): Promise<ShouldApplyWithReason> {
// Apply ONLY if version <= 7.1.0
if (await this.isUnraidVersionLessThanOrEqualTo('7.1.0', { includePrerelease: false })) {
return super.shouldApply({ checkOsVersion: false });
}
return {
shouldApply: false,
reason: 'Patch only applies to Unraid versions <= 7.1.0',
};
}

Comment on lines 1 to 12
import { gql } from '@apollo/client';

export const GET_DOCKER_CONTAINER_OVERVIEW_FORM = gql`
query GetDockerContainerOverviewForm($skipCache: Boolean = false) {
dockerContainerOverviewForm(skipCache: $skipCache) {
id
dataSchema
uiSchema
data
}
}
`;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use graphql helper for type generation consistency.

Similar to other .query.ts files in the codebase, this should use the graphql helper from ~/composables/gql/gql instead of gql from @apollo/client to ensure proper type generation and consistency with the established pattern.

🔎 Proposed fix
-import { gql } from '@apollo/client';
+import { graphql } from '~/composables/gql/gql';

-export const GET_DOCKER_CONTAINER_OVERVIEW_FORM = gql`
+export const GET_DOCKER_CONTAINER_OVERVIEW_FORM = graphql(/* GraphQL */ `
   query GetDockerContainerOverviewForm($skipCache: Boolean = false) {
     dockerContainerOverviewForm(skipCache: $skipCache) {
       id
       dataSchema
       uiSchema
       data
     }
   }
-`;
+`);

After making this change, run pnpm codegen in the web directory to regenerate types.

As per coding guidelines, GraphQL queries must use the graphql helper for proper code generation support.

📝 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
import { gql } from '@apollo/client';
export const GET_DOCKER_CONTAINER_OVERVIEW_FORM = gql`
query GetDockerContainerOverviewForm($skipCache: Boolean = false) {
dockerContainerOverviewForm(skipCache: $skipCache) {
id
dataSchema
uiSchema
data
}
}
`;
import { graphql } from '~/composables/gql/gql.js';
export const GET_DOCKER_CONTAINER_OVERVIEW_FORM = graphql(/* GraphQL */ `
query GetDockerContainerOverviewForm($skipCache: Boolean = false) {
dockerContainerOverviewForm(skipCache: $skipCache) {
id
dataSchema
uiSchema
data
}
}
`);
🤖 Prompt for AI Agents
In web/src/components/Docker/docker-overview.query.ts lines 1-12, the file
imports and uses gql from @apollo/client which breaks the repository's type
generation pattern; replace the import with the graphql helper from
~/composables/gql/gql and wrap the query with graphql(...) instead of gql`...`,
then run pnpm codegen in the web directory to regenerate types and verify the
generated types match usage.

Comment on lines +39 to +51
export const getNotificationSettings = gql`
query GetNotificationSettings {
notifications {
id
settings {
position
expand
duration
max
}
}
}
`;
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

Use graphql helper for type generation consistency.

This query uses gql from graphql-tag, while all other queries in this file use the graphql helper from ~/composables/gql/gql. This inconsistency prevents proper type generation and breaks the established pattern.

🔎 Proposed fix
-export const getNotificationSettings = gql`
+export const getNotificationSettings = graphql(/* GraphQL */ `
   query GetNotificationSettings {
     notifications {
       id
       settings {
         position
         expand
         duration
         max
       }
     }
   }
-`;
+`);

Also remove the unused import:

-import gql from 'graphql-tag';
-
 import { graphql } from '~/composables/gql/gql';

After making this change, run pnpm codegen in the web directory to regenerate types.

As per coding guidelines, GraphQL queries must use the graphql helper for proper code generation support.

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

🤖 Prompt for AI Agents
In web/src/components/Notifications/graphql/notification.query.ts around lines
39 to 51, replace the usage of gql from graphql-tag with the project's graphql
helper (imported from ~/composables/gql/gql) to match other queries and enable
proper type generation; remove the now-unused graphql-tag import; then run pnpm
codegen in the web directory to regenerate types.

Comment on lines 288 to 304
<div v-if="loading" class="grid place-content-center py-3">
<LoadingSpinner />
<!-- 3 skeletons to replace shadcn's LoadingSpinner -->
<div v-if="loading" class="space-y-4 py-3">
<div v-for="n in 3" :key="n" class="py-3">
<div class="flex items-center gap-2">
<USkeleton class="size-5 rounded-full" />
<USkeleton class="h-4 w-40" />
<div class="ml-auto">
<USkeleton class="h-3 w-24" />
</div>
</div>
<div class="mt-2">
<USkeleton class="h-3 w-3/4" />
</div>
</div>
</div>
</div>
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

Redundant nested loading check.

The outer <div v-if="loading"> at line 288 already guards this block, but there's another <div v-if="loading"> at line 290 inside it. Remove the redundant inner check.

🔎 Proposed fix
     <div v-if="loading" class="grid place-content-center py-3">
-      <!-- 3 skeletons to replace shadcn's LoadingSpinner -->
-      <div v-if="loading" class="space-y-4 py-3">
+      <div class="space-y-4 py-3">
         <div v-for="n in 3" :key="n" class="py-3">
📝 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
<div v-if="loading" class="grid place-content-center py-3">
<LoadingSpinner />
<!-- 3 skeletons to replace shadcn's LoadingSpinner -->
<div v-if="loading" class="space-y-4 py-3">
<div v-for="n in 3" :key="n" class="py-3">
<div class="flex items-center gap-2">
<USkeleton class="size-5 rounded-full" />
<USkeleton class="h-4 w-40" />
<div class="ml-auto">
<USkeleton class="h-3 w-24" />
</div>
</div>
<div class="mt-2">
<USkeleton class="h-3 w-3/4" />
</div>
</div>
</div>
</div>
<div v-if="loading" class="grid place-content-center py-3">
<div class="space-y-4 py-3">
<div v-for="n in 3" :key="n" class="py-3">
<div class="flex items-center gap-2">
<USkeleton class="size-5 rounded-full" />
<USkeleton class="h-4 w-40" />
<div class="ml-auto">
<USkeleton class="h-3 w-24" />
</div>
</div>
<div class="mt-2">
<USkeleton class="h-3 w-3/4" />
</div>
</div>
</div>
</div>
🤖 Prompt for AI Agents
In web/src/components/Notifications/List.vue around lines 288 to 304, there is a
redundant v-if="loading" on the inner div (line ~290) while the outer div
already has v-if="loading"; remove the inner v-if attribute so the skeleton
markup is only conditionally rendered once by the outer wrapper, keeping the
same structure and classes otherwise to preserve layout and behavior.

import { defineConfig } from 'vite';
import removeConsole from 'vite-plugin-remove-console';

import appConfig from './app.config';
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

Missing .js extension in import statement.

Per the coding guidelines, TypeScript imports should include .js extensions for ESM compatibility.

🔎 Proposed fix
-import appConfig from './app.config';
+import appConfig from './app.config.js';

Based on coding guidelines, always use TypeScript imports with .js extensions for ESM compatibility.

📝 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
import appConfig from './app.config';
import appConfig from './app.config.js';
🤖 Prompt for AI Agents
In web/vite.config.ts around line 11, the import statement "import appConfig
from './app.config';" is missing the required .js extension for ESM-compatible
TypeScript imports; update the import to include the .js extension (import from
'./app.config.js'), ensure any TypeScript build/tsconfig settings still resolve
the file, and run a quick build to verify no import resolution errors.

@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: 7

🧹 Nitpick comments (12)
web/src/components/Notifications/CriticalNotifications.standalone.vue (1)

126-143: Consider displaying user-facing error message when dismiss fails.

The current error handling logs to console but doesn't inform the user when the dismiss action fails. Based on learnings, when archive mutations fail, an error message should be shown below the action buttons to inform the user.

Consider adding a reactive error state to display dismissal failures to the user.

Based on learnings from previous reviews.

🔎 Suggested implementation
 const { mutate: archiveNotificationMutate } = useMutation(archiveNotification);
 const dismissing = reactive(new Set<string>());
+const dismissErrors = reactive(new Map<string, string>());

 const dismissNotification = async (notification: NotificationFragmentFragment) => {
   if (dismissing.has(notification.id)) {
     return;
   }
+  dismissErrors.delete(notification.id);
   dismissing.add(notification.id);
   try {
     await archiveNotificationMutate({
       id: notification.id,
     });
     criticalNotifications.value = criticalNotifications.value.filter(
       (current) => current.id !== notification.id
     );
   } catch (dismissError) {
     console.error('[CriticalNotifications] Failed to dismiss notification', dismissError);
+    dismissErrors.set(notification.id, 'Failed to dismiss notification. Please try again.');
   } finally {
     dismissing.delete(notification.id);
   }
 };

Then in the template, after the action buttons:

           {{ dismissing.has(notification.id) ? 'Dismissing…' : 'Dismiss' }}
         </button>
       </div>
+      <p v-if="dismissErrors.has(notification.id)" class="text-sm text-red-600">
+        {{ dismissErrors.get(notification.id) }}
+      </p>
     </li>
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix/Notifications.page (4)

25-59: Consider refactoring to reduce repetition.

The function contains significant repetition when computing composite flags and disabling checkboxes. A loop-based approach would be more maintainable.

🔎 Proposed refactoring
 function prepareNotify(form) {
-  form.entity.value = form.normal1.checked | form.warning1.checked | form.alert1.checked;
-  form.normal.value = form.normal1.checked*1 + form.normal2.checked*2 + form.normal3.checked*4;
-  form.warning.value = form.warning1.checked*1 + form.warning2.checked*2 + form.warning3.checked*4;
-  form.alert.value = form.alert1.checked*1 + form.alert2.checked*2 + form.alert3.checked*4;
-  form.unraid.value = form.unraid1.checked*1 + form.unraid2.checked*2 + form.unraid3.checked*4;
-  form.plugin.value = form.plugin1.checked*1 + form.plugin2.checked*2 + form.plugin3.checked*4;
-  form.docker_notify.value = form.docker_notify1.checked*1 + form.docker_notify2.checked*2 + form.docker_notify3.checked*4;
-  form.language_notify.value = form.language_notify1.checked*1 + form.language_notify2.checked*2 + form.language_notify3.checked*4;
-  form.report.value = form.report1.checked*1 + form.report2.checked*2 + form.report3.checked*4;
-  form.normal1.disabled = true;
-  form.normal2.disabled = true;
-  form.normal3.disabled = true;
-  form.warning1.disabled = true;
-  form.warning2.disabled = true;
-  form.warning3.disabled = true;
-  form.alert1.disabled = true;
-  form.alert2.disabled = true;
-  form.alert3.disabled = true;
-  form.unraid1.disabled = true;
-  form.unraid2.disabled = true;
-  form.unraid3.disabled = true;
-  form.plugin1.disabled = true;
-  form.plugin2.disabled = true;
-  form.plugin3.disabled = true;
-  form.docker_notify1.disabled = true;
-  form.docker_notify2.disabled = true;
-  form.docker_notify3.disabled = true;
-  form.language_notify1.disabled = true;
-  form.language_notify2.disabled = true;
-  form.language_notify3.disabled = true;
-  form.report1.disabled = true;
-  form.report2.disabled = true;
-  form.report3.disabled = true;
+  const categories = ['normal', 'warning', 'alert', 'unraid', 'plugin', 'docker_notify', 'language_notify', 'report'];
+  
+  // Compute entity flag
+  form.entity.value = form.normal1.checked | form.warning1.checked | form.alert1.checked;
+  
+  // Compute composite flags and disable checkboxes
+  categories.forEach(function(category) {
+    form[category].value = (form[category + '1'].checked ? 1 : 0) + 
+                           (form[category + '2'].checked ? 2 : 0) + 
+                           (form[category + '3'].checked ? 4 : 0);
+    form[category + '1'].disabled = true;
+    form[category + '2'].disabled = true;
+    form[category + '3'].disabled = true;
+  });
 }

60-62: Use strict equality and consider more specific selector.

Line 61 uses loose equality (==) and a generic .checkbox selector that could unintentionally affect other checkboxes.

🔎 Proposed improvements
 function prepareSystem(index) {
-  if (index==0) $('.checkbox').attr('disabled','disabled'); else $('.checkbox').removeAttr('disabled');
+  if (index === 0) {
+    $('input[type="checkbox"].checkbox').attr('disabled', 'disabled');
+  } else {
+    $('input[type="checkbox"].checkbox').removeAttr('disabled');
+  }
 }

72-91: Consider consolidating duplicate show/hide functions.

The five prepare* functions (prepareUnraid, preparePlugin, prepareDocker, prepareLanguage, prepareReport) share identical logic and could be consolidated.

🔎 Proposed refactoring
-function prepareUnraid(value) {
-  if (value=='') $('.unraid').hide(); else $('.unraid').show();
-  prepareTitle();
-}
-function preparePlugin(value) {
-  if (value=='') $('.plugin').hide(); else $('.plugin').show();
-  prepareTitle();
-}
-function prepareDocker(value) {
-  if (value=='') $('.docker').hide(); else $('.docker').show();
-  prepareTitle();
-}
-function prepareLanguage(value) {
-  if (value=='') $('.language').hide(); else $('.language').show();
-  prepareTitle();
-}
-function prepareReport(value) {
-  if (value=='') $('.report').hide(); else $('.report').show();
-  prepareTitle();
-}
+function prepareSection(sectionClass, value) {
+  if (value === '') {
+    $('.' + sectionClass).hide();
+  } else {
+    $('.' + sectionClass).show();
+  }
+  prepareTitle();
+}
+function prepareUnraid(value) { prepareSection('unraid', value); }
+function preparePlugin(value) { prepareSection('plugin', value); }
+function prepareDocker(value) { prepareSection('docker', value); }
+function prepareLanguage(value) { prepareSection('language', value); }
+function prepareReport(value) { prepareSection('report', value); }

115-144: Ensure consistent null coalescing for all settings.

The expand, duration, and max fields use the null coalescing operator (??) for defaults, but position (lines 117-122) does not. Consider adding a default for consistency.

🔎 Proposed improvement
 _(Display position)_:
 : <select name="position">
-  <?=mk_option($notify['position'], "top-left", _("top-left"))?>
-  <?=mk_option($notify['position'], "top-right", _("top-right"))?>
-  <?=mk_option($notify['position'], "bottom-left", _("bottom-left"))?>
-  <?=mk_option($notify['position'], "bottom-right", _("bottom-right"))?>
-  <?=mk_option($notify['position'], "bottom-center", _("bottom-center"))?>
-  <?=mk_option($notify['position'], "top-center", _("top-center"))?>
+  <?=mk_option($notify['position'] ?? 'top-right', "top-left", _("top-left"))?>
+  <?=mk_option($notify['position'] ?? 'top-right', "top-right", _("top-right"))?>
+  <?=mk_option($notify['position'] ?? 'top-right', "bottom-left", _("bottom-left"))?>
+  <?=mk_option($notify['position'] ?? 'top-right', "bottom-right", _("bottom-right"))?>
+  <?=mk_option($notify['position'] ?? 'top-right', "bottom-center", _("bottom-center"))?>
+  <?=mk_option($notify['position'] ?? 'top-right', "top-center", _("top-center"))?>
   </select>
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix/include/Notify.php (1)

17-17: Consider renaming the $notify variable after modification.

The $notify variable starts as the script path (line 17) but gets arguments appended in the loop (line 38), then " add" is appended (line 46). While functional, this dual-purpose use could be clearer with a distinct variable name like $notifyCmd for the built command.

🔎 Optional clarity improvement
 $notify = "$docroot/webGui/scripts/notify";
+$notifyCmd = $notify;
 
 switch ($_POST['cmd']??'') {
 ...
 case 'add':
   foreach ($_POST as $option => $value) {
     switch ($option) {
     case 'e':
     case 's':
     case 'd':
     case 'i':
     case 'm':
     case 'u':
-      $notify .= " -{$option} ".escapeshellarg($value);
+      $notifyCmd .= " -{$option} ".escapeshellarg($value);
       break;
     case 'x':
     case 't':
-      $notify .= " -{$option}";
+      $notifyCmd .= " -{$option}";
       break;
     }
   }
-  shell_exec("$notify add");
+  shell_exec("$notifyCmd add");
   break;

Also applies to: 38-38, 46-46

web/src/composables/gql/graphql.ts (2)

1690-1712: NotificationSettings GraphQL shape and wiring look consistent

NotificationSettings plus the non-null notifications.settings field and GetNotificationSettings query/document are all structurally consistent with the existing schema style and notification queries (fragments, overview, warningsAndAlerts). This should give the UI a clean, typed way to pull toaster configuration without impacting existing notification flows.

You may want to document (in schema/docs, not here) the expected value space for position (top-right, etc.) so downstream UI code can treat it as a constrained enum rather than a free-form string. Based on learnings, this ties into the unraid-toaster positioning config.

Also applies to: 3191-3195, 3410-3410


3068-3074: Docker folder rename and single-container update mutations are correctly typed

The new RenameDockerFolderMutation* and UpdateDockerContainerMutation* types and documents line up with the existing MutationRenameDockerFolderArgs and MutationUpdateContainerArgs definitions (string folderId/newName, PrefixedID for id) and follow the same shape as the other Docker organizer/update mutations.

No issues from the typing/codegen side.

If you find yourself duplicating the ResolvedOrganizerV1 selection set across multiple mutations, consider extracting a reusable fragment in the .graphql sources to keep future changes DRY.

Also applies to: 3135-3140, 3393-3393, 3403-3403

api/src/unraid-api/unraid-file-modifier/modifications/patches/notify-script.patch (2)

23-33: Filename sanitization and INI encode/decode helpers are solid and safer than the previous ad‑hoc format

  • safe_filename($string, $maxLength = 255) now:

    • Drops dangerous characters (including path separators),
    • Allows periods so real .notify extensions survive,
    • Normalizes spaces/hyphens to _, and
    • Truncates to a path-segment–safe length.
      This significantly reduces the risk of invalid or overlong filenames while still keeping them human-readable.
  • ini_encode_value, build_ini_string, and ini_decode_value give you a proper, reversible encoding for fields that may contain spaces, = or quotes, which addresses edge cases the previous key=value lines could not safely handle.

One minor caveat: substr operates on bytes, so multibyte UTF‑8 characters can be cut mid-sequence when truncating, but since the filesystem treats names as byte strings and these filenames are primarily for internal use, that’s acceptable in practice.

If you ever need user-facing display of these filenames, consider applying truncation earlier at the UI/API level, where you could be multibyte-aware.

Also applies to: 44-73


155-168: Robust read-path for notification files using bounded explode and INI decoding

Updating the get case to:

  • Limit explode('=', $field, 2) ensures values containing = remain intact.
  • Use ini_decode_value before assigning into $output[$i][…] keeps the JSON output compatible with pre-change consumers (they still see plain strings) while allowing you to safely store escaped/quoted values in the files.

The timestamp handling (first field per file) still works as before because you transform it into a formatted string before decoding, so there’s no behavioral regression there.

If you later introduce booleans or numeric types that matter to the caller, consider special-casing those keys on the read side to cast them back to native PHP types before JSON encoding.

api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/notify.modified.snapshot (1)

1-333: Snapshot matches the patched notify script and provides good regression coverage

The notify.modified.snapshot content lines up with the changes in notify-script.patch:

  • Updated usage with -u.
  • New safe_filename($string, $maxLength), INI helpers, and filename construction.
  • INI-style archive/unread writes and INI-aware reads.

This should give you solid regression coverage for the modification pipeline (downloaded file → apply patch → compare to snapshot). Just remember to update this snapshot whenever you tweak the patch or when the base notify script changes upstream.

If you later extend the notify format again (e.g., add new fields to the stored INI data), I can help draft updated fixtures and snapshots to keep the tests in lockstep.

api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/Notify.php.modified.snapshot.php (1)

39-42: Formatting: Remove unnecessary blank line.

The empty line 42 between the 'u' case and the 'x' case is unnecessary and inconsistent with the formatting of other cases in the switch statement.

🔎 Proposed fix
     case 'u':
       $notify .= " -{$option} ".escapeshellarg($value);
       break;
-    
     case 'x':
     case 't':
       $notify .= " -{$option}";

table.array_status tr>td{padding-left:8px;white-space:normal}
table.array_status tr>td:nth-child(1){width:33%}
table.array_status tr>td:nth-child(2){width:22%}
able.array_status.noshift{margin-top:0}
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

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and get line 141 content
if [ -f "api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/default-azure.css" ]; then
  echo "=== File found. Line 141 content ==="
  sed -n '141p' "api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/default-azure.css"
  echo ""
  echo "=== Context around line 141 (lines 138-143) ==="
  sed -n '138,143p' "api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/default-azure.css"
else
  echo "File not found at specified path"
  # List what directories exist
  find api/src/unraid-api -type d -name "__fixtures__" 2>/dev/null
fi

Repository: unraid/api

Length of output: 491


🏁 Script executed:

# Search for any occurrences of "able" in CSS selectors that might be typos
echo "=== Searching for 'able.array_status' pattern ==="
rg "able\.array_status" --type css -n

echo ""
echo "=== Searching for similar patterns to understand context ==="
rg "array_status" --type css -n | head -20

Repository: unraid/api

Length of output: 7975


🏁 Script executed:

# Check if there are any tests or comments about this fixture that explain its purpose
echo "=== Looking for test files related to this fixture ==="
find api/src/unraid-api/unraid-file-modifier/modifications -name "*test*" -o -name "*spec*" | head -10

echo ""
echo "=== Check for any README or documentation about fixtures ==="
find api/src/unraid-api/unraid-file-modifier/modifications/__test__ -type f \( -name "README*" -o -name "*.md" \) 2>/dev/null

Repository: unraid/api

Length of output: 417


Fix typo in CSS selector: able should be table

Line 141 contains a typo: able.array_status.noshift should be table.array_status.noshift. This is inconsistent with the same selector in all other theme fixtures (default-white.css, default-base.css, default-gray.css, default-black.css), which correctly use table.array_status.noshift.

🧰 Tools
🪛 Biome (2.1.2)

[error] 140-141: Unknown type selector is not allowed.

See MDN web docs for more details.
Consider replacing the unknown type selector with valid one.

(lint/correctness/noUnknownTypeSelector)

🤖 Prompt for AI Agents
In
api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/default-azure.css
around line 141, the CSS selector has a typo: "able.array_status.noshift" should
be "table.array_status.noshift"; replace the misspelled selector with the
correct "table.array_status.noshift" to match the other theme fixtures and
ensure the rule targets table elements as intended.

Comment on lines +82 to +123
$timestamp = time();
$ticket = $timestamp;
$mailtest = false;
$overrule = false;
$noBrowser = false;
+ $customFilename = false;

- $options = getopt("l:e:s:d:i:m:r:xtb");
+ $options = getopt("l:e:s:d:i:m:r:u:xtb");
foreach ($options as $option => $value) {
switch ($option) {
case 'e':
$event = $value;
break;
@@ -213,19 +248,53 @@
case 'l':
$nginx = (array)@parse_ini_file('/var/local/emhttp/nginx.ini');
$link = $value;
$fqdnlink = (strpos($link,"http") === 0) ? $link : ($nginx['NGINX_DEFAULTURL']??'').$link;
break;
+ case 'u':
+ $customFilename = $value;
+ break;
}
}

- $unread = "{$unread}/".safe_filename("{$event}-{$ticket}.notify");
- $archive = "{$archive}/".safe_filename("{$event}-{$ticket}.notify");
+ if ($customFilename) {
+ $filename = safe_filename($customFilename);
+ } else {
+ // suffix length: _{timestamp}.notify = 1+10+7 = 18 chars.
+ $suffix = "_{$ticket}.notify";
+ $max_name_len = 255 - strlen($suffix);
+ // sanitize event, truncating it to leave room for suffix
+ $clean_name = safe_filename($event, $max_name_len);
+ // construct filename with suffix (underscore separator matches safe_filename behavior)
+ $filename = "{$clean_name}{$suffix}";
+ }
+
+ $unread = "{$unread}/{$filename}";
+ $archive = "{$archive}/{$filename}";
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

New -u custom filename flow works but relies on callers providing a .notify suffix

The new -u option and filename logic look correct overall:

  • getopt("l:e:s:d:i:m:r:u:xtb") cleanly introduces the -u argument.
  • When -u is provided, $filename = safe_filename($customFilename) protects against path traversal and invalid characters.
  • When -u is not provided, you now:
    • Build a suffix like _<timestamp>.notify,
    • Compute max_name_len = 255 - strlen($suffix),
    • Sanitize the $event into $clean_name with that max length, and
    • Append the suffix, guaranteeing a final basename within 255 characters and preserving the .notify extension.

This also finally makes glob("$unread/*.notify") consistent with how filenames are created in the default path.

The only sharp edge is:

  • If a caller passes -u without including .notify in the value, those files won’t match the *.notify glob and will never be surfaced by notify get.

Given the help text labels -u as “API use only”, that’s probably fine as long as your API layer always appends .notify explicitly.

If you want to make this more foolproof, you could normalize the custom name to ensure it ends with .notify (e.g., append it if missing) before passing it through safe_filename.

🤖 Prompt for AI Agents
In
api/src/unraid-api/unraid-file-modifier/modifications/patches/notify-script.patch
around lines 82 to 123, the new -u custom filename flow accepts arbitrary names
but relies on callers to include the ".notify" extension, which breaks
glob("*.notify") if omitted; before calling safe_filename on the provided custom
name, normalize it by appending ".notify" when missing (ensure you check
case-insensitively and only append if not already present), then pass the
normalized name to safe_filename and use that for $unread and $archive.

*/
?>
<?
$events = explode('|', $notify['events'] ?? '');
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

Remove unused variable.

The $events variable is assigned but never used in the file.

🔎 Proposed fix
-$events = explode('|', $notify['events'] ?? '');
 $disabled = $notify['system'] ? '' : 'disabled';
📝 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
$events = explode('|', $notify['events'] ?? '');
$disabled = $notify['system'] ? '' : 'disabled';
🤖 Prompt for AI Agents
In
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix/Notifications.page
around line 21, the $events variable is assigned but never used; remove the
unused assignment line ($events = explode('|', $notify['events'] ?? '');) or, if
the intention was to use the parsed events, update the surrounding code to
reference $events where needed (e.g., iterate or check it) and ensure
$notify['events'] is handled safely.

Comment on lines +239 to +320
<span id="unraidTitle" class="unraid" style="display:none">&nbsp;</span>
: <span class="unraid" style="display:none">
<span class="notifications-checkbox-group-title">_(Unraid OS update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="unraid1"<?=($notify['unraid'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="unraid2"<?=($notify['unraid'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="unraid3"<?=($notify['unraid'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>

<span id="pluginTitle" class="plugin" style="display:none">&nbsp;</span>
: <span class="plugin" style="display:none">
<span class="notifications-checkbox-group-title">_(Plugins update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="plugin1"<?=($notify['plugin'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="plugin2"<?=($notify['plugin'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="plugin3"<?=($notify['plugin'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>

<span id="dockerTitle" class="docker" style="display:none">&nbsp;</span>
: <span class="docker" style="display:none">
<span class="notifications-checkbox-group-title">_(Docker update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="docker_notify1"<?=($notify['docker_notify'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="docker_notify2"<?=($notify['docker_notify'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="docker_notify3"<?=($notify['docker_notify'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>

<span id="languageTitle" class="language" style="display:none">&nbsp;</span>
: <span class="language" style="display:none">
<span class="notifications-checkbox-group-title">_(Language update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="language_notify1"<?=($notify['language_notify'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="language_notify2"<?=($notify['language_notify'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="language_notify3"<?=($notify['language_notify'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>

<span id="reportTitle" class="report" style="display:none">&nbsp;</span>
: <span class="report" style="display:none">
<span class="notifications-checkbox-group-title">_(Array status)_</span>
<span>
<span>
<input type="checkbox" name="report1"<?=($notify['report'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</span>
<span>
<input type="checkbox" name="report2"<?=($notify['report'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</span>
<span>
<input type="checkbox" name="report3"<?=($notify['report'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</span>
</span>
</span>
</span>

:notifications_agent_selection_help:
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

Standardize checkbox group HTML structure.

The report notification section (lines 306-316) has inconsistent HTML structure compared to other sections. It's missing <label> elements wrapping the checkboxes, which affects accessibility and may impact styling.

🔎 Proposed fix
 <span id="reportTitle" class="report" style="display:none">&nbsp;</span>
 : <span class="report" style="display:none">
     <span class="notifications-checkbox-group-title">_(Array status)_</span>
-      <span>
-        <span>
-          <input type="checkbox" name="report1"<?=($notify['report'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
-        </span>
-        <span>
-          <input type="checkbox" name="report2"<?=($notify['report'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
-        </span>
-        <span>
-          <input type="checkbox" name="report3"<?=($notify['report'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
-        </span>
-      </span>
+    <span class="inline-block">
+      <label class="inline-block">
+        <input type="checkbox" name="report1"<?=($notify['report'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
+      </label>
+      <label class="inline-block">
+        <input type="checkbox" name="report2"<?=($notify['report'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
+      </label>
+      <label class="inline-block">
+        <input type="checkbox" name="report3"<?=($notify['report'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
+      </label>
     </span>
   </span>
📝 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
<span id="unraidTitle" class="unraid" style="display:none">&nbsp;</span>
: <span class="unraid" style="display:none">
<span class="notifications-checkbox-group-title">_(Unraid OS update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="unraid1"<?=($notify['unraid'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="unraid2"<?=($notify['unraid'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="unraid3"<?=($notify['unraid'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>
<span id="pluginTitle" class="plugin" style="display:none">&nbsp;</span>
: <span class="plugin" style="display:none">
<span class="notifications-checkbox-group-title">_(Plugins update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="plugin1"<?=($notify['plugin'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="plugin2"<?=($notify['plugin'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="plugin3"<?=($notify['plugin'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>
<span id="dockerTitle" class="docker" style="display:none">&nbsp;</span>
: <span class="docker" style="display:none">
<span class="notifications-checkbox-group-title">_(Docker update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="docker_notify1"<?=($notify['docker_notify'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="docker_notify2"<?=($notify['docker_notify'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="docker_notify3"<?=($notify['docker_notify'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>
<span id="languageTitle" class="language" style="display:none">&nbsp;</span>
: <span class="language" style="display:none">
<span class="notifications-checkbox-group-title">_(Language update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="language_notify1"<?=($notify['language_notify'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="language_notify2"<?=($notify['language_notify'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="language_notify3"<?=($notify['language_notify'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>
<span id="reportTitle" class="report" style="display:none">&nbsp;</span>
: <span class="report" style="display:none">
<span class="notifications-checkbox-group-title">_(Array status)_</span>
<span>
<span>
<input type="checkbox" name="report1"<?=($notify['report'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</span>
<span>
<input type="checkbox" name="report2"<?=($notify['report'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</span>
<span>
<input type="checkbox" name="report3"<?=($notify['report'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</span>
</span>
</span>
</span>
:notifications_agent_selection_help:
<span id="unraidTitle" class="unraid" style="display:none">&nbsp;</span>
: <span class="unraid" style="display:none">
<span class="notifications-checkbox-group-title">_(Unraid OS update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="unraid1"<?=($notify['unraid'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="unraid2"<?=($notify['unraid'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="unraid3"<?=($notify['unraid'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>
<span id="pluginTitle" class="plugin" style="display:none">&nbsp;</span>
: <span class="plugin" style="display:none">
<span class="notifications-checkbox-group-title">_(Plugins update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="plugin1"<?=($notify['plugin'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="plugin2"<?=($notify['plugin'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="plugin3"<?=($notify['plugin'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>
<span id="dockerTitle" class="docker" style="display:none">&nbsp;</span>
: <span class="docker" style="display:none">
<span class="notifications-checkbox-group-title">_(Docker update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="docker_notify1"<?=($notify['docker_notify'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="docker_notify2"<?=($notify['docker_notify'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="docker_notify3"<?=($notify['docker_notify'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>
<span id="languageTitle" class="language" style="display:none">&nbsp;</span>
: <span class="language" style="display:none">
<span class="notifications-checkbox-group-title">_(Language update)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="language_notify1"<?=($notify['language_notify'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="language_notify2"<?=($notify['language_notify'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="language_notify3"<?=($notify['language_notify'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>
<span id="reportTitle" class="report" style="display:none">&nbsp;</span>
: <span class="report" style="display:none">
<span class="notifications-checkbox-group-title">_(Array status)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" name="report1"<?=($notify['report'] & 1)==1 ? ' checked' : ''?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="report2"<?=($notify['report'] & 2)==2 ? ' checked' : ''?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" name="report3"<?=($notify['report'] & 4)==4 ? ' checked' : ''?>>_(Agents)_ &nbsp;
</label>
</span>
</span>
:notifications_agent_selection_help:
🤖 Prompt for AI Agents
In
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix/Notifications.page
around lines 239 to 320, the "report" (Array status) checkbox group uses bare
input elements without <label> wrappers unlike the other groups; update the
report section so each checkbox is wrapped in a <label class="inline-block">
containing the <input ...><?=... ? ' checked' : ''?> followed by the localized
text and spacing, and match the surrounding span structure (use the same
inline-block classes and markup pattern used by unraid/plugin/docker/language
groups) to restore consistent HTML structure, accessibility, and styling.

Comment on lines +354 to +368
&nbsp;
: <span>
<span class="notifications-checkbox-group-title">_(Alerts)_</span>
<span>
<span>
<input type="checkbox" class="checkbox" name="alert1"<?=($notify['alert'] & 1)==1 ? " checked $disabled" : $disabled?>>_(Browser)_ &nbsp;
</span>
<span>
<input type="checkbox" class="checkbox" name="alert2"<?=($notify['alert'] & 2)==2 ? " checked $disabled" : $disabled?>>_(Email)_ &nbsp;
</span>
<span>
<input type="checkbox" class="checkbox" name="alert3"<?=($notify['alert'] & 4)==4 ? " checked $disabled" : $disabled?>>_(Agents)_ &nbsp;
</span>
</span>

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

Standardize Alerts checkbox group HTML structure.

The Alerts notification section has inconsistent HTML structure compared to Notices and Warnings sections. It's missing <label> elements and inline-block classes.

🔎 Proposed fix
 &nbsp;
 : <span>
     <span class="notifications-checkbox-group-title">_(Alerts)_</span>
-    <span>
-      <span>
-      <input type="checkbox" class="checkbox" name="alert1"<?=($notify['alert'] & 1)==1 ? " checked $disabled" : $disabled?>>_(Browser)_ &nbsp;
-    </span>
-    <span>
-      <input type="checkbox" class="checkbox" name="alert2"<?=($notify['alert'] & 2)==2 ? " checked $disabled" : $disabled?>>_(Email)_ &nbsp;
-    </span>
-    <span>
-      <input type="checkbox" class="checkbox" name="alert3"<?=($notify['alert'] & 4)==4 ? " checked $disabled" : $disabled?>>_(Agents)_ &nbsp;
+    <span class="inline-block">
+      <label class="inline-block">
+        <input type="checkbox" class="checkbox" name="alert1"<?=($notify['alert'] & 1)==1 ? " checked $disabled" : $disabled?>>_(Browser)_ &nbsp;
+      </label>
+      <label class="inline-block">
+        <input type="checkbox" class="checkbox" name="alert2"<?=($notify['alert'] & 2)==2 ? " checked $disabled" : $disabled?>>_(Email)_ &nbsp;
+      </label>
+      <label class="inline-block">
+        <input type="checkbox" class="checkbox" name="alert3"<?=($notify['alert'] & 4)==4 ? " checked $disabled" : $disabled?>>_(Agents)_ &nbsp;
+      </label>
     </span>
   </span>
📝 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
&nbsp;
: <span>
<span class="notifications-checkbox-group-title">_(Alerts)_</span>
<span>
<span>
<input type="checkbox" class="checkbox" name="alert1"<?=($notify['alert'] & 1)==1 ? " checked $disabled" : $disabled?>>_(Browser)_ &nbsp;
</span>
<span>
<input type="checkbox" class="checkbox" name="alert2"<?=($notify['alert'] & 2)==2 ? " checked $disabled" : $disabled?>>_(Email)_ &nbsp;
</span>
<span>
<input type="checkbox" class="checkbox" name="alert3"<?=($notify['alert'] & 4)==4 ? " checked $disabled" : $disabled?>>_(Agents)_ &nbsp;
</span>
</span>
&nbsp;
: <span>
<span class="notifications-checkbox-group-title">_(Alerts)_</span>
<span class="inline-block">
<label class="inline-block">
<input type="checkbox" class="checkbox" name="alert1"<?=($notify['alert'] & 1)==1 ? " checked $disabled" : $disabled?>>_(Browser)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" class="checkbox" name="alert2"<?=($notify['alert'] & 2)==2 ? " checked $disabled" : $disabled?>>_(Email)_ &nbsp;
</label>
<label class="inline-block">
<input type="checkbox" class="checkbox" name="alert3"<?=($notify['alert'] & 4)==4 ? " checked $disabled" : $disabled?>>_(Agents)_ &nbsp;
</label>
</span>
</span>
🤖 Prompt for AI Agents
In
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix/Notifications.page
around lines 354-368, the Alerts checkbox group lacks the standardized HTML
structure used by Notices and Warnings: missing <label> wrappers and missing the
inline-block class on the checkbox container. Update each input to be paired
with a <label> that contains the input and text (so clicking text toggles the
box), add the same inline-block class to the surrounding span/container as other
sections, and ensure the checked/disabled PHP logic remains on the input
element; keep markup and spacing consistent with the other notification groups.

Comment on lines +167 to +171
onClick: () => {
if (notification.link) {
navigate(notification.link);
}
},
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

Inconsistent navigation approach for notification links.

The toast action uses navigate(notification.link) (line 169), but the template uses a regular <a> tag with target="_blank" (lines 248-256). This creates an inconsistency in how notification links are opened.

Consider using the same navigation approach in both places for consistency. If navigate is intended for external link handling, apply it to both the toast action and the template link.

🔎 Suggested approach for consistent navigation

Option 1: Use navigate in both places:

-          <a
-            v-if="notification.link"
-            :href="notification.link"
-            class="inline-flex items-center gap-1 rounded-md border border-amber-500 px-3 py-1 text-sm font-medium text-amber-700 transition hover:bg-amber-50"
-            target="_blank"
-            rel="noreferrer"
-          >
-            View Details
-          </a>
+          <button
+            v-if="notification.link"
+            type="button"
+            class="inline-flex items-center gap-1 rounded-md border border-amber-500 px-3 py-1 text-sm font-medium text-amber-700 transition hover:bg-amber-50"
+            @click="navigate(notification.link)"
+          >
+            View Details
+          </button>

Option 2: Use standard <a> tags in both places (depending on what navigate does).

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

🤖 Prompt for AI Agents
In web/src/components/Notifications/CriticalNotifications.standalone.vue around
lines 167-171, the toast action calls navigate(notification.link) while the
template uses a plain <a target="_blank"> (lines ~248-256), causing inconsistent
link behavior; pick a single navigation strategy and apply it in both
places—either call the same navigate(notification.link) helper from the template
link click handler (and ensure it opens in a new tab if that's required by the
current UX and preserves rel="noopener noreferrer") or replace the toast action
to use a standard anchor open (window.open or native link behavior) so both
places behave identically; update the template and the toast onClick accordingly
and add a null-check for notification.link before invoking navigation.

Comment on lines +2976 to +2982
export type GetDockerActiveContainerQueryVariables = Exact<{
id: Scalars['PrefixedID']['input'];
}>;


export type GetDockerActiveContainerQuery = { __typename?: 'Query', docker: { __typename?: 'Docker', id: string, containers: Array<{ __typename?: 'DockerContainer', id: string, names: Array<string>, image: string, created: number, state: ContainerState, status: string, autoStart: boolean, networkSettings?: any | null, labels?: any | null, ports: Array<{ __typename?: 'ContainerPort', privatePort?: number | null, publicPort?: number | null, type: ContainerPortType }>, hostConfig?: { __typename?: 'ContainerHostConfig', networkMode: string } | null }> } };

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:

# Find the actual GraphQL document definition for GetDockerActiveContainer
rg -A 10 "GetDockerActiveContainerDocument" web/src/composables/gql/

Repository: unraid/api

Length of output: 43982


🏁 Script executed:

# Also search for the GraphQL query definition itself
rg -B 5 -A 15 "query GetDockerActiveContainer" web/src/composables/gql/

Repository: unraid/api

Length of output: 32640


🏁 Script executed:

# Check DockerContainerManagement.vue usage
rg -B 5 -A 5 "GetDockerActiveContainer" web/src/components/ | head -100

Repository: unraid/api

Length of output: 790


🏁 Script executed:

# Look at line 3381 to see what other issue applies
sed -n '3375,3390p' web/src/composables/gql/graphql.ts

Repository: unraid/api

Length of output: 33656


🏁 Script executed:

sed -n '3375,3390p' web/src/composables/gql/graphql.ts

Repository: unraid/api

Length of output: 33656


🏁 Script executed:

# Search for GraphQL schema or validation rules configuration
find web -name "*.graphql" -o -name "*schema*" -o -name "*graphql.config*" | head -20

Repository: unraid/api

Length of output: 36


🏁 Script executed:

# Look for Apollo client configuration and any validation rules
rg -l "apollo|graphql" web/src --type ts --type tsx | xargs rg -l "validate|rule" | head -10

Repository: unraid/api

Length of output: 352


🏁 Script executed:

# Check how GetDockerActiveContainerDocument is actually used
rg "GetDockerActiveContainer" web/src/components --type ts --type tsx -B 3 -A 3

Repository: unraid/api

Length of output: 81


🏁 Script executed:

# Also check for NoUnusedVariables or validation rules in codegen config
rg -i "unused|validation|nounused" web/ --type ts --type js --type json | grep -i "rule\|valid"

Repository: unraid/api

Length of output: 4498


🏁 Script executed:

# Get the exact line count to understand line numbers better
wc -l web/src/composables/gql/graphql.ts

Repository: unraid/api

Length of output: 93


🏁 Script executed:

# Search for what's around 3381 in a different way
rg -n "export const" web/src/composables/gql/graphql.ts | grep -E "^(338|339|340|341)"

Repository: unraid/api

Length of output: 50366


🏁 Script executed:

# Search for similar query pattern with unused variables
rg -A 3 "variableDefinitions.*\[\{" web/src/composables/gql/graphql.ts | head -50

Repository: unraid/api

Length of output: 50366


🏁 Script executed:

# Look for codegen config that might control validation rules
find web -name "codegen.ts" -o -name "codegen.js" -o -name ".graphqlconfig*" | xargs cat 2>/dev/null

Repository: unraid/api

Length of output: 888


🏁 Script executed:

# Search for the actual source GraphQL files before codegen
find web -name "*.graphql" -o -path "*queries*" -name "*.ts" | xargs rg "GetDockerActiveContainer" 2>/dev/null | head -20

Repository: unraid/api

Length of output: 36


Remove the unused $id variable from GetDockerActiveContainer query

The query declares $id: PrefixedID! but never uses it. The operation fetches all containers via docker { id containers { ... } } without filtering by the variable. Either integrate the variable into the query logic (e.g., docker { container(id: $id) { ... } }) or remove it entirely. Most GraphQL servers enforce the NoUnusedVariables validation rule, so this operation will fail validation.

🤖 Prompt for AI Agents
In web/src/composables/gql/graphql.ts around lines 2976 to 2982, the
GetDockerActiveContainer GraphQL operation declares a variable $id: PrefixedID!
that is never used; remove the unused $id variable from the operation signature
or, if the intent is to fetch a specific container, modify the query to pass $id
into the selection (e.g., call the appropriate field that accepts an id
argument) and update the TypeScript types accordingly so the operation no longer
violates NoUnusedVariables validation.

cleaning up some incorrectly rebased files (don't remember if it was automatic or manual...either way)
@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

@codecov
Copy link

codecov bot commented Dec 30, 2025

Codecov Report

❌ Patch coverage is 73.63481% with 309 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.87%. Comparing base (9ef1cf1) to head (18f3227).

Files with missing lines Patch % Lines
...h/resolvers/notifications/notifications.service.ts 71.32% 39 Missing ⚠️
...Notifications/CriticalNotifications.standalone.vue 0.00% 33 Missing ⚠️
web/src/helpers/functions.ts 4.34% 22 Missing ⚠️
...ier/modifications/default-base-css.modification.ts 66.12% 21 Missing ⚠️
web/src/components/Wrapper/mount-engine.ts 46.15% 21 Missing ⚠️
...er/modifications/default-white-css.modification.ts 64.44% 16 Missing ⚠️
...r/modifications/notifications-page.modification.ts 60.00% 16 Missing ⚠️
...er/modifications/default-azure-css.modification.ts 61.53% 15 Missing ⚠️
...er/modifications/default-black-css.modification.ts 61.53% 15 Missing ⚠️
...ier/modifications/default-gray-css.modification.ts 63.41% 15 Missing ⚠️
... and 20 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1860      +/-   ##
==========================================
+ Coverage   46.49%   46.87%   +0.38%     
==========================================
  Files         954      968      +14     
  Lines       59788    60608     +820     
  Branches     5552     5627      +75     
==========================================
+ Hits        27799    28411     +612     
- Misses      31870    32076     +206     
- Partials      119      121       +2     

☔ 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: 6

Caution

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

⚠️ Outside diff range comments (1)
api/src/unraid-api/graph/resolvers/vms/vms.service.spec.ts (1)

104-104: Replace any type with proper typing.

Using any violates the coding guideline for TypeScript files. Use the appropriate domain type from @unraid/libvirt instead.

As per coding guidelines, "Never use the any type. Always prefer proper typing."

🧹 Nitpick comments (9)
web/__test__/components/UserProfile.test.ts (1)

45-45: LGTM - Passthrough mock is appropriate for tests.

The createSharedComposable mock correctly returns the function as-is, which is the right approach for testing. The actual sharing behavior isn't needed in unit tests.

Optional: More specific typing

If you prefer stronger typing, you could make the passthrough type-safe:

- createSharedComposable: (fn: unknown) => fn,
+ createSharedComposable: <T extends (...args: any[]) => any>(fn: T): T => fn,

However, unknown is perfectly acceptable for a simple passthrough mock.

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

139-139: Consider making the Apollo mock more specific.

The mock intercepts ALL Apollo queries and returns notification settings for any query. While this provides a working default, it could mask issues where the wrong query is called or where different queries need different responses.

Consider:

  1. Making the mock query-specific (e.g., check the query name/operation)
  2. Verifying the notification settings structure matches the actual NotificationSettings type
  3. Adding a test that explicitly verifies the notification settings query is called during mount if this is critical behavior
Example: Query-specific mock
-mockApolloClient.query.mockResolvedValue({ data: { notifications: { settings: {} } } });
+mockApolloClient.query.mockImplementation((options) => {
+  if (options.query.definitions[0]?.name?.value === 'GetNotificationSettings') {
+    return Promise.resolve({ data: { notifications: { settings: {} } } });
+  }
+  return Promise.resolve({ data: {} });
+});
web/__test__/components/SsoButton.test.ts (1)

92-126: Add explicit mock clear for mockLocation.assign for consistency.

While vi.restoreAllMocks() should handle clearing, explicitly clearing mockLocation.assign would match the pattern used for fetch and mockUseQuery mocks and ensure no test pollution.

🔎 Suggested addition to beforeEach

After line 111, add:

     (fetch as Mock).mockClear();
     mockUseQuery.mockClear();
+    (mockLocation.assign as Mock).mockClear();
web/__test__/store/updateOs.test.ts (3)

140-140: Verify or remove the unused assign mock.

The assign: vi.fn() mock is added but never asserted. If the runtime code calls window.location.assign(), add an assertion to verify it was called with the expected URL. If it's not called, remove the mock to avoid confusion.

Example assertion if assign is used
  expect(mockSend).toHaveBeenCalledWith(
    expectedUrl,
    [
      {
        sha256: 'test-sha256',
        type: 'updateOs',
      },
    ],
    undefined,
    'forUpc'
  );
+
+ expect(window.location.assign).toHaveBeenCalled();

221-223: Clarify which navigation method is being tested.

The mock supports both window.location.href = value (via the setter) and window.location.assign(value), but the test only verifies the final hrefValue. This makes it unclear which navigation method the code actually uses. Add specific assertions to verify the expected method is called.

Example assertions

If the code uses assign:

  expect(WebguiUpdateCancel).toHaveBeenCalled();
+ expect(window.location.assign).toHaveBeenCalledWith('/Tools');
  expect(hrefValue).toBe('/Tools');

Or if the code uses the href setter, you could verify assign was NOT called:

  expect(WebguiUpdateCancel).toHaveBeenCalled();
+ expect(window.location.assign).not.toHaveBeenCalled();
  expect(hrefValue).toBe('/Tools');

171-173: Avoid testing exact error messages.

These tests verify exact error message strings, which makes them brittle and violates the project guidelines. Use .rejects.toThrow() without arguments unless the specific message format is what you're testing.

As per coding guidelines.

Suggested changes
- await expect(store.localCheckForUpdate()).rejects.toThrow(
-   '[localCheckForUpdate] Error checking for updates'
- );
+ await expect(store.localCheckForUpdate()).rejects.toThrow();
- await expect(store.cancelUpdate()).rejects.toThrow('Unable to cancel update');
+ await expect(store.cancelUpdate()).rejects.toThrow();
- await expect(store.cancelUpdate()).rejects.toThrow(
-   '[cancelUpdate] Error cancelling update with error: API error'
- );
+ await expect(store.cancelUpdate()).rejects.toThrow();

Also applies to: 245-245, 255-257

api/src/unraid-api/graph/resolvers/vms/vms.service.spec.ts (3)

54-63: Remove unused helper or integrate into skip condition.

The verifyQemuInstallation() function is defined but never called. If it was previously used in beforeAll to validate QEMU installation, consider either:

  1. Removing it as dead code, or
  2. Integrating its check into isQemuAvailable() to ensure the actual emulator executables exist (not just qemu-img)
Option 1: Remove unused function
-// Helper function to verify QEMU installation
-const verifyQemuInstallation = () => {
-    const archConfig = getArchConfig();
-    const qemuPath = archConfig.emulator;
-
-    if (!existsSync(qemuPath)) {
-        throw new Error(`QEMU not found at ${qemuPath}. Please install QEMU first.`);
-    }
-
-    return archConfig;
-};
-
Option 2: Integrate into skip condition
 // Check if qemu-img is available before running tests
 const isQemuAvailable = () => {
     try {
         execSync('qemu-img --version', { stdio: 'ignore' });
+        const archConfig = getArchConfig();
+        if (!existsSync(archConfig.emulator)) {
+            return false;
+        }
         return true;
     } catch (error) {
         return false;
     }
 };

152-161: Verify skip condition matches actual requirements.

The skip condition checks for qemu-img availability, but the tests require the qemu-system-* emulator executables (returned by getArchConfig()). While these are typically installed together, there's a potential mismatch that could cause confusing test failures if only qemu-img is available.

Consider enhancing isQemuAvailable() to also verify the emulator executable exists (see suggestion in the earlier comment).


165-185: Consider moving suite-level initialization into hooks.

The archConfig and domainXml constants are defined at the suite level, which means they execute even when the suite is skipped. While not functionally problematic, moving these into the beforeAll hook would prevent unnecessary execution when tests are skipped.

Proposed refactor
 describe.skipIf(!isQemuAvailable())('VmsService', () => {
     let service: VmsService;
     let hypervisor: Hypervisor;
     let testVm: VmDomain | null = null;
-    const archConfig = getArchConfig();
-    const domainXml = `
-        <domain type='qemu'>
-            <name>${TEST_VM_NAME}</name>
-            <memory unit='KiB'>524288</memory>
-            <vcpu>1</vcpu>
-            <os>
-                <type arch='${archConfig.arch}' machine='${archConfig.machine}'>hvm</type>
-                <boot dev='hd'/>
-            </os>
-            <devices>
-                <emulator>${archConfig.emulator}</emulator>
-                <disk type='file' device='disk'>
-                    <driver name='qemu' type='qcow2'/>
-                    <source file='${DISK_IMAGE}'/>
-                    <target dev='vda' bus='virtio'/>
-                </disk>
-                <console type='pty'/>
-            </devices>
-        </domain>
-    `;
+    let archConfig: ReturnType<typeof getArchConfig>;
+    let domainXml: string;
 
     beforeAll(async () => {
+        archConfig = getArchConfig();
+        domainXml = `
+            <domain type='qemu'>
+                <name>${TEST_VM_NAME}</name>
+                <memory unit='KiB'>524288</memory>
+                <vcpu>1</vcpu>
+                <os>
+                    <type arch='${archConfig.arch}' machine='${archConfig.machine}'>hvm</type>
+                    <boot dev='hd'/>
+                </os>
+                <devices>
+                    <emulator>${archConfig.emulator}</emulator>
+                    <disk type='file' device='disk'>
+                        <driver name='qemu' type='qcow2'/>
+                        <source file='${DISK_IMAGE}'/>
+                        <target dev='vda' bus='virtio'/>
+                    </disk>
+                    <console type='pty'/>
+                </devices>
+            </domain>
+        `;
+
         // Override the LIBVIRT_URI environment variable for testing
         process.env.LIBVIRT_URI = LIBVIRT_URI;
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ef00ce and ad2a5cc.

📒 Files selected for processing (16)
  • api/src/unraid-api/graph/resolvers/vms/vms.service.spec.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.set-password.php
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/DockerContainers.page
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/generic-modification.spec.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.set-password.php.modified.snapshot.php
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DockerContainers.page.modified.snapshot.php
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/default-base-css.patch
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/docker-containers-page.patch
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/set-password-modal.patch
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/UserProfile.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/store/updateOs.test.ts
  • web/auto-imports.d.ts
  • web/components.d.ts
  • web/src/components/Wrapper/component-registry.ts
✅ Files skipped from review due to trivial changes (1)
  • api/src/unraid-api/unraid-file-modifier/modifications/test/snapshots/DockerContainers.page.modified.snapshot.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • api/src/unraid-api/unraid-file-modifier/modifications/test/generic-modification.spec.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/default-base-css.patch
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{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__/components/SsoButton.test.ts
  • web/__test__/store/updateOs.test.ts
  • web/auto-imports.d.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/src/components/Wrapper/component-registry.ts
  • web/__test__/components/UserProfile.test.ts
  • api/src/unraid-api/graph/resolvers/vms/vms.service.spec.ts
  • web/components.d.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__/components/SsoButton.test.ts
  • web/__test__/store/updateOs.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.test.ts
web/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • web/__test__/components/SsoButton.test.ts
  • web/__test__/store/updateOs.test.ts
  • web/auto-imports.d.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/src/components/Wrapper/component-registry.ts
  • web/__test__/components/UserProfile.test.ts
  • web/components.d.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__/components/SsoButton.test.ts
  • web/__test__/store/updateOs.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.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__/components/SsoButton.test.ts
  • web/__test__/store/updateOs.test.ts
  • web/auto-imports.d.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/src/components/Wrapper/component-registry.ts
  • web/__test__/components/UserProfile.test.ts
  • api/src/unraid-api/graph/resolvers/vms/vms.service.spec.ts
  • web/components.d.ts
**/*

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

Never add comments unless they are needed for clarity of function

Files:

  • web/__test__/components/SsoButton.test.ts
  • web/__test__/store/updateOs.test.ts
  • web/auto-imports.d.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/src/components/Wrapper/component-registry.ts
  • web/__test__/components/UserProfile.test.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/docker-containers-page.patch
  • api/src/unraid-api/graph/resolvers/vms/vms.service.spec.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/set-password-modal.patch
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/DockerContainers.page
  • web/components.d.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__/components/SsoButton.test.ts
  • web/__test__/store/updateOs.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.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/SsoButton.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.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/updateOs.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/updateOs.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/component-registry.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/unraid-file-modifier/modifications/patches/docker-containers-page.patch
  • api/src/unraid-api/graph/resolvers/vms/vms.service.spec.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/set-password-modal.patch
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/DockerContainers.page
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/vms/vms.service.spec.ts
🧠 Learnings (57)
📓 Common learnings
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:24-27
Timestamp: 2025-01-31T22:01:22.708Z
Learning: The Unraid UI uses a modern notification system with a custom `unraid-toaster` component replacing the legacy jGrowl notifications. The system is backed by a comprehensive GraphQL API with real-time subscription support for notification updates.
Learnt from: pujitm
Repo: unraid/api PR: 941
File: web/components/Notifications/Item.vue:0-0
Timestamp: 2024-11-01T17:52:55.311Z
Learning: In the `web/components/Notifications/Item.vue` component, ensure that when delete or archive mutations fail, the notification remains displayed, and an error message is shown below the action buttons to inform the user.
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:30-54
Timestamp: 2025-01-31T22:01:02.725Z
Learning: The removal of jGrowl notifications from DefaultPageLayout.php is intentional as notifications are now handled on a separate page as part of the architectural design to override existing Unraid pages.
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:6-20
Timestamp: 2025-01-31T22:01:41.842Z
Learning: The default-page-layout.patch removes the old jGrowl notification system and is complemented by the unraid-toaster component implementation. The new system is added through the DefaultPageLayout modification which inserts the toaster component with proper position configuration based on user preferences.
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:6-20
Timestamp: 2025-01-31T22:01:41.842Z
Learning: The default-page-layout.patch is used to remove the old jGrowl notification system from Unraid pages, as notifications are handled by a new system implemented on a different page.
Learnt from: mdatelle
Repo: unraid/api PR: 1106
File: unraid-ui/src/components/index.ts:2-2
Timestamp: 2025-02-04T17:21:39.710Z
Learning: The unraid-ui package is undergoing a major refactoring process, and breaking changes are expected during this transition period.
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php:203-207
Timestamp: 2025-02-03T17:21:26.738Z
Learning: The project uses patches to override existing Unraid pages rather than modifying them directly.
📚 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/SsoButton.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__/components/SsoButton.test.ts
  • web/__test__/components/Wrapper/mount-engine.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 : Test Pinia action side effects and state changes

Applied to files:

  • web/__test__/components/SsoButton.test.ts
  • web/__test__/store/updateOs.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__/components/SsoButton.test.ts
  • web/__test__/components/Wrapper/mount-engine.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__/components/SsoButton.test.ts
  • web/__test__/store/updateOs.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 : Check component state through rendered output

Applied to files:

  • web/__test__/components/SsoButton.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__/components/SsoButton.test.ts
  • web/__test__/store/updateOs.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 state changes after Pinia store actions

Applied to files:

  • web/__test__/components/SsoButton.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 : Stub complex child components that aren't the focus of the test

Applied to files:

  • web/__test__/components/SsoButton.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:

  • web/__test__/components/SsoButton.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__/components/SsoButton.test.ts
  • web/__test__/store/updateOs.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__/components/SsoButton.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__/components/SsoButton.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/SsoButton.test.ts
  • web/__test__/store/updateOs.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • api/src/unraid-api/graph/resolvers/vms/vms.service.spec.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__/components/SsoButton.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.test.ts
  • api/src/unraid-api/graph/resolvers/vms/vms.service.spec.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/updateOs.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 : Place all mock declarations at the top level in Pinia store tests

Applied to files:

  • web/__test__/store/updateOs.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/updateOs.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__/store/updateOs.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/updateOs.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 **/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/auto-imports.d.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/auto-imports.d.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/auto-imports.d.ts
  • web/components.d.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/Wrapper/mount-engine.test.ts
  • web/src/components/Wrapper/component-registry.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/Wrapper/mount-engine.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 emitted events with `wrapper.emitted()`

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.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} : Prefer to not mock simple dependencies

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/UserProfile.test.ts
  • api/src/unraid-api/graph/resolvers/vms/vms.service.spec.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/Wrapper/mount-engine.test.ts
📚 Learning: 2025-08-11T15:41:23.573Z
Learnt from: elibosley
Repo: unraid/api PR: 1563
File: web/pages/login.vue:31-48
Timestamp: 2025-08-11T15:41:23.573Z
Learning: In the Unraid API codebase, debug/test views (such as the debug modal in `web/pages/login.vue`) may intentionally display sensitive information like passwords in plain text for debugging and testing purposes. This is an accepted practice for development tools in this project.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.set-password.php.modified.snapshot.php
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.set-password.php
📚 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:

  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.set-password.php.modified.snapshot.php
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.set-password.php
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/set-password-modal.patch
📚 Learning: 2025-04-28T20:35:32.980Z
Learnt from: elibosley
Repo: unraid/api PR: 1368
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:0-0
Timestamp: 2025-04-28T20:35:32.980Z
Learning: For Unraid GUI mode, automatic session creation with root privileges is an acceptable pattern since these pages are already protected and only visible to authenticated users.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.set-password.php.modified.snapshot.php
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.set-password.php
📚 Learning: 2025-03-07T17:35:50.406Z
Learnt from: mdatelle
Repo: unraid/api PR: 1219
File: api/src/unraid-api/main.ts:32-55
Timestamp: 2025-03-07T17:35:50.406Z
Learning: Helmet security configuration in the Unraid API is intentionally relaxed (with disabled CSP, CORS policies, and HSTS) to maintain compatibility with existing Unraid plugins. Stricter security settings might break current plugin functionality.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.set-password.php.modified.snapshot.php
📚 Learning: 2025-01-31T22:01:41.842Z
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:6-20
Timestamp: 2025-01-31T22:01:41.842Z
Learning: The default-page-layout.patch removes the old jGrowl notification system and is complemented by the unraid-toaster component implementation. The new system is added through the DefaultPageLayout modification which inserts the toaster component with proper position configuration based on user preferences.

Applied to files:

  • web/src/components/Wrapper/component-registry.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/docker-containers-page.patch
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/set-password-modal.patch
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/DockerContainers.page
📚 Learning: 2025-01-30T19:38:02.478Z
Learnt from: pujitm
Repo: unraid/api PR: 1075
File: unraid-ui/src/register.ts:15-34
Timestamp: 2025-01-30T19:38:02.478Z
Learning: In the web components registration process for unraid-ui, use a soft-fail approach (logging warnings/errors) instead of throwing errors, to ensure other components can still register successfully even if one component fails.

Applied to files:

  • web/src/components/Wrapper/component-registry.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/component-registry.ts
  • web/components.d.ts
📚 Learning: 2025-03-14T19:14:10.408Z
Learnt from: pujitm
Repo: unraid/api PR: 1211
File: plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/Connect.page:0-0
Timestamp: 2025-03-14T19:14:10.408Z
Learning: *.ce.vue components in `web/components` get built as custom elements and prefixed with `unraid-`, making them available as HTML custom elements at runtime.

Applied to files:

  • web/src/components/Wrapper/component-registry.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/component-registry.ts
  • web/components.d.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 : Find Vue components with `findComponent(ComponentName)`

Applied to files:

  • web/src/components/Wrapper/component-registry.ts
  • web/components.d.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/component-registry.ts
  • web/components.d.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/component-registry.ts
  • web/components.d.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/component-registry.ts
  • web/components.d.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/component-registry.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__/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 : Check for expected prop handling and event emissions in Vue components

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-02-03T17:21:26.738Z
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php:203-207
Timestamp: 2025-02-03T17:21:26.738Z
Learning: The project uses patches to override existing Unraid pages rather than modifying them directly.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/patches/docker-containers-page.patch
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/set-password-modal.patch
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/DockerContainers.page
📚 Learning: 2025-01-31T22:01:41.842Z
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:6-20
Timestamp: 2025-01-31T22:01:41.842Z
Learning: The default-page-layout.patch is used to remove the old jGrowl notification system from Unraid pages, as notifications are handled by a new system implemented on a different page.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/patches/docker-containers-page.patch
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/set-password-modal.patch
📚 Learning: 2025-02-03T18:57:53.577Z
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/__test__/generic-modification.spec.ts:80-80
Timestamp: 2025-02-03T18:57:53.577Z
Learning: In the unraid/api codebase, patch files generated for file modifications always use the .patch extension, regardless of the source file type being modified.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/patches/docker-containers-page.patch
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/set-password-modal.patch
📚 Learning: 2025-02-04T17:21:39.710Z
Learnt from: mdatelle
Repo: unraid/api PR: 1106
File: unraid-ui/src/components/index.ts:2-2
Timestamp: 2025-02-04T17:21:39.710Z
Learning: The unraid-ui package is undergoing a major refactoring process, and breaking changes are expected during this transition period.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/patches/docker-containers-page.patch
📚 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:

  • api/src/unraid-api/graph/resolvers/vms/vms.service.spec.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:

  • api/src/unraid-api/graph/resolvers/vms/vms.service.spec.ts
📚 Learning: 2025-05-08T19:28:54.365Z
Learnt from: elibosley
Repo: unraid/api PR: 1381
File: plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/verify_install.sh:19-24
Timestamp: 2025-05-08T19:28:54.365Z
Learning: The directory `/usr/local/emhttp/plugins/dynamix.my.servers` is a valid directory that exists as part of the Unraid API plugin installation and should be included in verification checks.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/patches/set-password-modal.patch
📚 Learning: 2025-07-24T18:48:43.590Z
Learnt from: mdatelle
Repo: unraid/api PR: 1533
File: web/components/Docker/Console.vue:22-29
Timestamp: 2025-07-24T18:48:43.590Z
Learning: The Docker Console component in web/components/Docker/Console.vue is temporary/dummy code with placeholder terminal responses, used for building out the UI layout while developing the real components in PR #1533.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/DockerContainers.page
📚 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/components.d.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/components.d.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 `findAll` to check for multiple elements in Vue components

Applied to files:

  • web/components.d.ts
🧬 Code graph analysis (2)
api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.set-password.php.modified.snapshot.php (1)
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php (1)
  • _ (159-159)
api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.set-password.php (1)
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php (1)
  • _ (159-159)
🔇 Additional comments (10)
web/__test__/components/SsoButton.test.ts (2)

68-70: LGTM! Proper navigation mock setup.

The assign mock correctly captures navigation calls and maintains consistency by updating mockLocationHref.


267-267: LGTM! Correct navigation verification pattern.

The tests properly verify navigation by asserting that location.assign was called with the expected URLs. This approach correctly tests external navigation without side effects.

Also applies to: 389-389, 436-436

api/src/unraid-api/unraid-file-modifier/modifications/patches/set-password-modal.patch (1)

1-13: LGTM!

The patch follows the project's established pattern for modifying Unraid pages. The single-line include addition is minimal and correctly placed just before the closing body/html tags. Based on learnings, this aligns with the architectural approach of using patches to override existing Unraid pages.

api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.set-password.php.modified.snapshot.php (1)

415-415: LGTM!

The welcome-modal include is correctly placed just before the closing body tag, matching the patch specification.

api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.set-password.php (1)

1-416: Fixture file noted.

This fixture represents the original upstream .set-password.php file before patch application. The same issues identified in the snapshot (debounce misuse, max vs maxlength, label mismatch, missing items-center class) exist here as well. If these issues originate from the upstream Unraid source, consider whether they should be addressed in the patch or tracked separately.

api/src/unraid-api/unraid-file-modifier/modifications/patches/docker-containers-page.patch (3)

204-206: Clean component-based architecture.

The replacement implementation is appropriately minimal, delegating all functionality to the custom <unraid-docker-container-overview> web component. The unapi class wrapper likely provides consistent styling and scoping for the new component system.


11-13: Nchan change is intentional and properly documented.

The removal of the :stop suffix and addition of Tabs="false" are intentional changes documented in api/docs/developer/docker.md. The :stop suffix was part of the legacy PHP page's emhttp/WebGUI integration pattern. The new implementation replaces the entire page with the <unraid-docker-container-overview> Vue component, which handles its own subscription lifecycle internally. The Tabs="false" attribute is a valid WebGUI page metadata format used in the modification system. The change is tested and feature-flagged for Unraid 7.3+.


15-203: Fix incomplete data binding in DockerContainerOverview.vue.

The replacement component structure is in place with proper supporting infrastructure (stats subscription, autostart settings, bulk actions composables), but the containers computed property returns an empty array instead of being bound to the GraphQL query result. Update the computed property to extract containers from result.value?.docker so the table receives data to render.

Similar to how flatEntries and viewPrefs are properly computed from the query result, containers needs to be wired to the actual container data from the GraphQL response.

⛔ Skipped due to learnings
Learnt from: mdatelle
Repo: unraid/api PR: 1533
File: web/components/Docker/Console.vue:22-29
Timestamp: 2025-07-24T18:48:43.590Z
Learning: The Docker Console component in web/components/Docker/Console.vue is temporary/dummy code with placeholder terminal responses, used for building out the UI layout while developing the real components in PR #1533.
Learnt from: mdatelle
Repo: unraid/api PR: 1533
File: web/components/Docker/Edit.vue:16-32
Timestamp: 2025-07-24T18:48:44.035Z
Learning: In web/components/Docker/Edit.vue, the hardcoded configuration values in the config ref are intentional temporary/dummy data used during initial UI development phase while building out the real Docker components, as clarified by mdatelle in PR #1533.
api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/DockerContainers.page (1)

1-196: Test fixture correctly represents the original Docker Containers page.

This fixture file appropriately captures the legacy DockerContainers.page structure that the patch will modify. The metadata, PHP logic, jQuery-based UI, and Nchan subscription format all match what the patch expects as its starting point.

web/components.d.ts (1)

132-158: Inconsistent Nuxt UI versions in auto-generated component declarations.

The component type file references both @nuxt/ui@4.2.1 and @nuxt/ui@4.0.0-alpha.0, with UButton, UIcon, USkeleton, USlideover, UTabs, and UTooltip remaining on the older alpha version while other components are on 4.2.1. This version mismatch can cause API incompatibilities and runtime issues.

Rebuild the project to regenerate the component type declarations. Component declarations are generated by Vite during the build process, not by pnpm codegen.

⛔ Skipped due to learnings
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
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
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
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.
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.
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.
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.
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
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`.
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.
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.
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
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 : Find Vue components with `findComponent(ComponentName)`

Comment on lines +319 to +320
<label for="password"><?= _('Username') ?></label>
<input name="username" type="text" value="root" disabled title="<?=_('Username not changeable')?>">
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

Fix mismatched for attribute on the Username label.

The label says "Username" but for="password" associates it with the password input. Since the username input is disabled and has no id, remove the for attribute or add an id to the username input.

🔎 Proposed fix
-                <label for="password"><?= _('Username') ?></label>
-                <input name="username" type="text" value="root" disabled title="<?=_('Username not changeable')?>">
+                <label for="username"><?= _('Username') ?></label>
+                <input id="username" name="username" type="text" value="root" disabled title="<?=_('Username not changeable')?>">
📝 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
<label for="password"><?= _('Username') ?></label>
<input name="username" type="text" value="root" disabled title="<?=_('Username not changeable')?>">
<label for="username"><?= _('Username') ?></label>
<input id="username" name="username" type="text" value="root" disabled title="<?=_('Username not changeable')?>">
🤖 Prompt for AI Agents
In
api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.set-password.php.modified.snapshot.php
around lines 319 to 320, the label for="password" is incorrectly targeting the
password field while the label text is "Username" and the username input has no
id; fix by either removing the label's for attribute (since the input is
disabled) or add a matching id to the username input and change the label's for
to that id so the label correctly targets the username input.

<label for="password"><?= _('Username') ?></label>
<input name="username" type="text" value="root" disabled title="<?=_('Username not changeable')?>">

<div class="flex flex-row items-center justify-between">
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

Missing CSS class items-center.

The items-center class is used but not defined in the embedded styles. Add it to the utility classes section.

🔎 Proposed fix - add to utility classes (around line 198)
     .justify-end { justify-content: flex-end }
     .invisible { visibility: hidden }
+    .items-center { align-items: center }

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

🤖 Prompt for AI Agents
In
api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.set-password.php.modified.snapshot.php
around line 322, the template uses the utility class "items-center" but that
class is not defined in the embedded styles; add a new utility-class definition
for "items-center" in the stylesheet section (around line 198 as suggested) that
sets align-items: center (matching the other utility patterns), ensuring it
follows the same naming and placement conventions as the other utility classes.

Comment on lines +334 to +337
<input id="password" name="password" type="password" max="128" autocomplete="new-password" autofocus required>

<label for="confirmPassword"><?=_('Confirm Password')?></label>
<input id="confirmPassword" name="confirmPassword" type="password" max="128" autocomplete="new-password" required>
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

Use maxlength instead of max for text/password inputs.

The max attribute is for numeric inputs (type="number"). For text and password inputs, use maxlength to enforce character limits in the browser.

🔎 Proposed fix
-                <input id="password" name="password" type="password" max="128" autocomplete="new-password" autofocus required>
+                <input id="password" name="password" type="password" maxlength="128" autocomplete="new-password" autofocus required>

                 <label for="confirmPassword"><?=_('Confirm Password')?></label>
-                <input id="confirmPassword" name="confirmPassword" type="password" max="128" autocomplete="new-password" required>
+                <input id="confirmPassword" name="confirmPassword" type="password" maxlength="128" autocomplete="new-password" required>
📝 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
<input id="password" name="password" type="password" max="128" autocomplete="new-password" autofocus required>
<label for="confirmPassword"><?=_('Confirm Password')?></label>
<input id="confirmPassword" name="confirmPassword" type="password" max="128" autocomplete="new-password" required>
<input id="password" name="password" type="password" maxlength="128" autocomplete="new-password" autofocus required>
<label for="confirmPassword"><?=_('Confirm Password')?></label>
<input id="confirmPassword" name="confirmPassword" type="password" maxlength="128" autocomplete="new-password" required>
🤖 Prompt for AI Agents
In
api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.set-password.php.modified.snapshot.php
around lines 334 to 337, the password and confirmPassword inputs use the numeric
attribute "max"; replace "max=\"128\"" with the correct text attribute
"maxlength=\"128\"" on both <input> elements so the browser enforces character
limits for password fields; leave other attributes (autocomplete, autofocus,
required) unchanged.

Comment on lines +408 to +413
$passInputs.forEach($el => {
$el.addEventListener('change', () => debounce(validate()));
$el.addEventListener('keyup', () => {
if (displayValidation) debounce(validate()); // Wait until displayValidation is swapped in a change event
});
});
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

Debounce is not working as intended.

debounce(validate()) immediately invokes validate() and passes its result to debounce(). The debounced function returned is never stored or called. Create the debounced function once outside the event handlers.

🔎 Proposed fix
+        const debouncedValidate = debounce(validate);
         // event 🦻
         $passInputs.forEach($el => {
-            $el.addEventListener('change', () => debounce(validate()));
+            $el.addEventListener('change', () => debouncedValidate());
             $el.addEventListener('keyup', () => {
-                if (displayValidation) debounce(validate()); // Wait until displayValidation is swapped in a change event
+                if (displayValidation) debouncedValidate(); // Wait until displayValidation is swapped in a change event
             });
         });

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

🤖 Prompt for AI Agents
In
api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.set-password.php.modified.snapshot.php
around lines 408 to 413, the code calls debounce(validate()) which invokes
validate immediately and passes its return value to debounce; instead, create a
single debounced function reference once (e.g. const debouncedValidate =
debounce(validate, <timeout>)) outside the event handlers and use
debouncedValidate in both addEventListener callbacks (call the reference, not
the result), ensuring the conditional uses debouncedValidate() when
displayValidation is true so the function is actually debounced and reused.

const extractShortcuts: typeof import('../node_modules/.pnpm/@nuxt+ui@4.0.0-alpha.0_@babel+parser@7.28.4_@netlify+blobs@9.1.2_change-case@5.4.4_db0@_655bac6707ae017754653173419b3890/node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts.js')['extractShortcuts']
const fieldGroupInjectionKey: typeof import('../node_modules/.pnpm/@nuxt+ui@4.0.0-alpha.0_@babel+parser@7.28.4_@netlify+blobs@9.1.2_change-case@5.4.4_db0@_655bac6707ae017754653173419b3890/node_modules/@nuxt/ui/dist/runtime/composables/useFieldGroup.js')['fieldGroupInjectionKey']
const formBusInjectionKey: typeof import('../node_modules/.pnpm/@nuxt+ui@4.0.0-alpha.0_@babel+parser@7.28.4_@netlify+blobs@9.1.2_change-case@5.4.4_db0@_655bac6707ae017754653173419b3890/node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js')['formBusInjectionKey']
const formErrorsInjectionKey: typeof import('../node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.4_@netlify+blobs@9.1.2_change-case@5.4.4_db0@0.3.2_em_abe87a60859daf93a7fe8018ff1a0969/node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formErrorsInjectionKey
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

🧩 Analysis chain

🏁 Script executed:

# First, check the structure and version references in auto-imports.d.ts
echo "=== Checking web/auto-imports.d.ts ===" 
wc -l web/auto-imports.d.ts

echo -e "\n=== Lines 1-10 (header) ===" 
head -10 web/auto-imports.d.ts

echo -e "\n=== Lines 15-30 (including the reported inconsistency) ===" 
sed -n '15,30p' web/auto-imports.d.ts

echo -e "\n=== Version pattern summary ===" 
rg '@nuxt\+ui@' web/auto-imports.d.ts | head -20

Repository: unraid/api

Length of output: 10853


🏁 Script executed:

# Check the specified Nuxt UI version in package.json
echo "=== Checking web/package.json for @nuxt/ui ===" 
grep -A 2 -B 2 '@nuxt/ui' web/package.json || echo "Not found in main section, checking full file"
cat web/package.json | jq '.dependencies["@nuxt/ui"], .devDependencies["@nuxt/ui"]' 2>/dev/null || cat web/package.json | rg '@nuxt/ui'

Repository: unraid/api

Length of output: 295


🏁 Script executed:

# Check pnpm-lock.yaml for resolved @nuxt/ui versions
echo "=== Checking pnpm-lock.yaml for @nuxt/ui versions ===" 
rg -A 3 '"@nuxt/ui"' web/pnpm-lock.yaml | head -30

Repository: unraid/api

Length of output: 183


🏁 Script executed:

# Check for pnpm-lock.yaml in repo root
echo "=== Looking for pnpm-lock.yaml ===" 
fd -t f "pnpm-lock.yaml" . 2>/dev/null

# Check the @nuxt/ui resolution in the root lockfile
echo -e "\n=== Checking root pnpm-lock.yaml for @nuxt/ui ===" 
rg -A 5 '"@nuxt/ui"' pnpm-lock.yaml 2>/dev/null | head -40

# Count how many distinct @nuxt/ui versions are in the lockfile
echo -e "\n=== Counting @nuxt/ui versions in lockfile ===" 
rg '@nuxt/ui@' pnpm-lock.yaml | cut -d'@' -f3 | sort -u

Repository: unraid/api

Length of output: 243


🏁 Script executed:

# Check the full @nuxt/ui entry in the lockfile to understand the resolution
echo "=== Full @nuxt/ui entry in lockfile ===" 
rg -B 2 -A 10 "/@nuxt/ui@4.2.1" pnpm-lock.yaml | head -30

# Check if there are references to @nuxt/ui@4.0.0-alpha.0 in the lockfile
echo -e "\n=== Looking for 4.0.0-alpha.0 references ===" 
rg "@nuxt/ui@4.0.0-alpha.0" pnpm-lock.yaml

# Check the auto-imports configuration that generates this file
echo -e "\n=== Checking for auto-imports configuration ===" 
fd -t f "*.config.*" web/ | head -10
rg "unplugin-auto-import" web/ -l

Repository: unraid/api

Length of output: 495


🏁 Script executed:

# Fix the fd command with proper glob syntax
echo "=== Checking for nuxt/auto-imports config ===" 
find web -name "*.config.*" -type f 2>/dev/null

# Search for unplugin-auto-import configuration
echo -e "\n=== Looking for auto-import plugin config ===" 
rg "unplugin-auto-import" . --type ts --type js --type json -l 2>/dev/null | head -10

# Check the nuxt config
echo -e "\n=== Checking nuxt.config ===" 
cat web/nuxt.config.ts 2>/dev/null | head -50

# Check what's actually importing from @nuxt/ui in the codebase  
echo -e "\n=== Checking what imports formErrorsInjectionKey ===" 
rg "formErrorsInjectionKey" web/src --type ts --type js

Repository: unraid/api

Length of output: 356


🏁 Script executed:

# Check for nuxt.config at repo root
echo "=== Looking for nuxt.config ===" 
find . -maxdepth 2 -name "nuxt.config.*" -type f 2>/dev/null

# Check the vite config in web directory
echo -e "\n=== Checking web/vite.config.ts ===" 
cat web/vite.config.ts | head -60

# Check if there are any build/generation scripts
echo -e "\n=== Checking package.json scripts in web ===" 
cat web/package.json | jq '.scripts' 2>/dev/null

# Check if @nuxt/ui is actually installed in node_modules
echo -e "\n=== Checking @nuxt/ui in node_modules ===" 
ls -la web/node_modules/@nuxt/ui 2>/dev/null | head -5 || echo "Not found in web/node_modules"
ls -la node_modules/@nuxt/ui 2>/dev/null | head -5 || echo "Not found in root node_modules"

Repository: unraid/api

Length of output: 3804


🏁 Script executed:

# Check the build-ui-if-needed.js script that's run before dev
echo "=== Checking build-ui-if-needed.js ===" 
cat web/scripts/build-ui-if-needed.js

# Check if auto-imports.d.ts is supposed to be committed
echo -e "\n=== Checking .gitignore for auto-imports ===" 
rg "auto-imports" .gitignore web/.gitignore 2>/dev/null

# Look for where the auto-imports generation is configured
echo -e "\n=== Checking for unplugin-auto-import in dependencies ===" 
cat web/package.json | jq '.dependencies, .devDependencies' | rg "unplugin"

# Check if there's an eslint or vite plugin config for auto-imports
echo -e "\n=== Checking vite.config.ts for auto-import plugin ===" 
cat web/vite.config.ts | rg -i "auto.import" -A 5 -B 5

Repository: unraid/api

Length of output: 2591


🏁 Script executed:

# Check if auto-imports.d.ts should be in git at all (it's in .gitignore)
echo "=== Git status of auto-imports.d.ts ===" 
git ls-files web/auto-imports.d.ts

# Look for references to auto-imports generation in the codebase
echo -e "\n=== Searching for auto-imports generation references ===" 
rg "auto-imports" web/ --type ts --type js -B 3 -A 3

# Check the @nuxt/ui plugin in vite.config.ts more carefully
echo -e "\n=== Full vite.config.ts to see @nuxt/ui/vite usage ===" 
cat web/vite.config.ts | grep -A 30 "defineConfig"

# Check if auto-imports.d.ts was recently added to git
echo -e "\n=== Checking git history of auto-imports.d.ts ===" 
git log --oneline web/auto-imports.d.ts 2>/dev/null | head -5

Repository: unraid/api

Length of output: 2002


🏁 Script executed:

# Check if there's a way to manually regenerate auto-imports.d.ts
echo "=== Searching for how auto-imports.d.ts gets generated ===" 
rg "auto.import" web/ --type ts --type mjs --type json -l

# Check app.config.ts to understand what's being auto-imported
echo -e "\n=== Checking app.config.ts ===" 
cat web/app.config.ts

# Check git diff to see if the file was modified in this branch
echo -e "\n=== Checking git status ===" 
git status web/auto-imports.d.ts 2>/dev/null || echo "No git status info"

# Check if there are multiple versions of @nuxt/ui in the lock file
echo -e "\n=== Detailed lockfile analysis for @nuxt/ui ===" 
grep -c "@nuxt/ui" pnpm-lock.yaml
</script>

Repository: unraid/api

Length of output: 2274


🏁 Script executed:

# Check what @nuxt/ui entries exist in the lockfile
echo "=== All @nuxt/ui entries in lockfile ===" 
grep "\"@nuxt/ui" pnpm-lock.yaml | head -10

# Check if there are conditional dependencies or different resolution paths
echo -e "\n=== Checking lockfile structure for @nuxt/ui ===" 
rg "\"@nuxt/ui" pnpm-lock.yaml -B 2 -A 8 | head -60

# Verify the actual node_modules structure
echo -e "\n=== Checking what @nuxt/ui versions are actually installed ===" 
find . -type d -name "@nuxt" 2>/dev/null | grep ui

# See if auto-imports.d.ts was edited manually or is purely generated
echo -e "\n=== Checking the full extent of 4.2.1 vs 4.0.0 in auto-imports.d.ts ===" 
grep -c "4.2.1" web/auto-imports.d.ts
grep -c "4.0.0-alpha.0" web/auto-imports.d.ts

Repository: unraid/api

Length of output: 286


Regenerate auto-imports after dependency upgrade.

The auto-generated auto-imports.d.ts file contains mixed @nuxt/ui versions: 43 imports reference @nuxt+ui@4.0.0-alpha.0 while only 3 (formErrorsInjectionKey, formStateInjectionKey, toastMaxInjectionKey) reference @nuxt+ui@4.2.1. This inconsistency contradicts package.json, which specifies @nuxt/ui@4.2.1, and indicates the file was not fully regenerated after the dependency upgrade.

Since this is an auto-generated file, regenerate it by running:

cd web
pnpm install

The unplugin-auto-import plugin will automatically update auto-imports.d.ts with consistent version references during the next build or dev startup.

Also applies to: 21-21, 26-26

🤖 Prompt for AI Agents
In web/auto-imports.d.ts around lines 16, 21 and 26, the file contains mixed
@nuxt/ui version references because it wasn’t regenerated after the dependency
upgrade; fix by regenerating the auto-imports: cd into web, run pnpm install,
then start a dev/build (pnpm dev or pnpm build) so unplugin-auto-import rewrites
auto-imports.d.ts with consistent @nuxt/ui@4.2.1 references, verify the three
entries (formErrorsInjectionKey, formStateInjectionKey, toastMaxInjectionKey)
now reference @nuxt/ui@4.2.1, and commit the updated auto-imports.d.ts.

Comment on lines +163 to +167
{
component: defineAsyncComponent(() => import('@nuxt/ui/components/Toast.vue')),
selector: ['unraid-toaster', 'uui-toaster'],
appId: 'toaster',
},
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
# Check if the project has node_modules and verify Toast component existence

# First, let's see what node_modules structure exists
if [ -d "web/node_modules/@nuxt/ui" ]; then
  echo "=== Searching for Toast.vue in @nuxt/ui ==="
  find web/node_modules/@nuxt/ui -name "Toast.vue" -o -name "toast*" 2>/dev/null | head -20
  
  echo -e "\n=== Checking @nuxt/ui dist structure ==="
  ls -la web/node_modules/@nuxt/ui/dist/runtime/components/ 2>/dev/null | head -20
else
  echo "node_modules not present in sandbox - checking source files instead"
fi

# Check the actual component-registry.ts file
echo -e "\n=== Content of component-registry.ts around lines 163-167 ==="
if [ -f "web/src/components/Wrapper/component-registry.ts" ]; then
  sed -n '160,170p' web/src/components/Wrapper/component-registry.ts
fi

# Check web/components.d.ts for patterns
echo -e "\n=== Checking web/components.d.ts for Nuxt UI import patterns ==="
if [ -f "web/components.d.ts" ]; then
  grep -n "@nuxt/ui" web/components.d.ts | head -10
fi

Repository: unraid/api

Length of output: 2973


🌐 Web query:

How does @nuxt/ui Toast component work - is it a direct component import or useToast() composable

💡 Result:

Short answer: nuxt/ui exposes toasts via the useToast() composable (auto-imported) to add/update/remove toasts; the visual renderer is provided by the App/Toaster or Notifications component you place in your app (you don't import a single "Toast" to show one). [1][2][3]

Sources:

  • useToast composable docs. [1]
  • Toast component docs (shows usage via useToast). [2]
  • Notifications / Toaster usage (place Notifications / UNotifications in app). [3]

Fix the Toast component import path. The import @nuxt/ui/components/Toast.vue is invalid. @nuxt/ui doesn't export a direct Toast component—toasts are managed via the useToast() composable. To display toasts, place the Toaster or Notifications component in your app layout instead of importing this non-existent component.

🤖 Prompt for AI Agents
In web/src/components/Wrapper/component-registry.ts around lines 163 to 167, the
registry entry is importing a non-existent Toast component from
'@nuxt/ui/components/Toast.vue'; remove this registry object (the
component/selector/appId block) so we no longer attempt to import that invalid
path. If you need toast UI, instead add the library's Toaster or Notifications
component to your app layout and use the useToast() composable for triggers;
ensure the registry does not reference or try to asynchronously import the
nonexistent Toast.vue.

@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

♻️ Duplicate comments (1)
api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts (1)

14-24: Version check inconsistency already flagged.

The mismatch between isUnraidVersionLessThanOrEqualTo('7.1.0') and the reason message stating "< 7.1.0" has already been identified in a previous review. Please address the inconsistency by either:

  • Changing the check to isUnraidVersionLessThan('7.1.0'), or
  • Updating the comment (line 15) and reason message (line 23) to reflect "<= 7.1.0"
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad2a5cc and 443335e.

📒 Files selected for processing (4)
  • api/src/unraid-api/unraid-file-modifier/modifications/default-azure-css.modification.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/default-black-css.modification.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • api/src/unraid-api/unraid-file-modifier/modifications/default-black-css.modification.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/default-azure-css.modification.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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/unraid-file-modifier/modifications/default-white-css.modification.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/unraid-file-modifier/modifications/default-white-css.modification.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/unraid-file-modifier/modifications/default-white-css.modification.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/unraid-file-modifier/modifications/default-white-css.modification.ts
**/*

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

Never add comments unless they are needed for clarity of function

Files:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:24-27
Timestamp: 2025-01-31T22:01:22.708Z
Learning: The Unraid UI uses a modern notification system with a custom `unraid-toaster` component replacing the legacy jGrowl notifications. The system is backed by a comprehensive GraphQL API with real-time subscription support for notification updates.
Learnt from: pujitm
Repo: unraid/api PR: 941
File: web/components/Notifications/Item.vue:0-0
Timestamp: 2024-11-01T17:52:55.311Z
Learning: In the `web/components/Notifications/Item.vue` component, ensure that when delete or archive mutations fail, the notification remains displayed, and an error message is shown below the action buttons to inform the user.
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:30-54
Timestamp: 2025-01-31T22:01:02.725Z
Learning: The removal of jGrowl notifications from DefaultPageLayout.php is intentional as notifications are now handled on a separate page as part of the architectural design to override existing Unraid pages.
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:6-20
Timestamp: 2025-01-31T22:01:41.842Z
Learning: The default-page-layout.patch removes the old jGrowl notification system and is complemented by the unraid-toaster component implementation. The new system is added through the DefaultPageLayout modification which inserts the toaster component with proper position configuration based on user preferences.
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:6-20
Timestamp: 2025-01-31T22:01:41.842Z
Learning: The default-page-layout.patch is used to remove the old jGrowl notification system from Unraid pages, as notifications are handled by a new system implemented on a different page.
Learnt from: mdatelle
Repo: unraid/api PR: 1106
File: unraid-ui/src/components/index.ts:2-2
Timestamp: 2025-02-04T17:21:39.710Z
Learning: The unraid-ui package is undergoing a major refactoring process, and breaking changes are expected during this transition period.
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php:203-207
Timestamp: 2025-02-03T17:21:26.738Z
Learning: The project uses patches to override existing Unraid pages rather than modifying them directly.
Learnt from: elibosley
Repo: unraid/api PR: 1746
File: web/src/components/Activation/ActivationTimezoneStep.vue:78-91
Timestamp: 2025-10-11T02:09:16.853Z
Learning: In Unraid setup/activation flows (e.g., ActivationTimezoneStep.vue), don't block user progression when non-critical configuration steps like timezone updates fail. The design prioritizes allowing users to access Unraid over enforcing complete setup step success.
📚 Learning: 2025-01-29T16:35:43.699Z
Learnt from: elibosley
Repo: unraid/api PR: 1082
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:39-41
Timestamp: 2025-01-29T16:35:43.699Z
Learning: The UnraidFileModifierService in the Unraid API provides comprehensive error handling for all FileModification implementations, including detailed error logging with stack traces and modification IDs. Individual FileModification implementations should focus on their core functionality without duplicating error handling.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts
📚 Learning: 2025-02-03T18:57:53.577Z
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/__test__/generic-modification.spec.ts:80-80
Timestamp: 2025-02-03T18:57:53.577Z
Learning: In the unraid/api codebase, patch files generated for file modifications always use the .patch extension, regardless of the source file type being modified.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts
📚 Learning: 2025-01-29T16:35:43.699Z
Learnt from: elibosley
Repo: unraid/api PR: 1082
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:39-41
Timestamp: 2025-01-29T16:35:43.699Z
Learning: In the Unraid API, FileModification implementations (apply/rollback methods) don't need to implement their own error handling as it's handled by the UnraidFileModifierService caller.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts
📚 Learning: 2025-02-03T17:21:26.738Z
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php:203-207
Timestamp: 2025-02-03T17:21:26.738Z
Learning: The project uses patches to override existing Unraid pages rather than modifying them directly.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts
📚 Learning: 2025-02-04T18:45:23.106Z
Learnt from: elibosley
Repo: unraid/api PR: 1111
File: api/src/unraid-api/unraid-file-modifier/file-modification.ts:182-187
Timestamp: 2025-02-04T18:45:23.106Z
Learning: In the FileModification class's patch handling:
- `results === false` indicates patch application failure
- Empty string (`results === ''`) is a valid patch result indicating the file should be deleted
- These are distinct conditions and should be handled differently

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts
📚 Learning: 2025-01-29T16:36:04.777Z
Learnt from: elibosley
Repo: unraid/api PR: 1082
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:33-37
Timestamp: 2025-01-29T16:36:04.777Z
Learning: In the Unraid API, FileModification implementations (like LogRotateModification) don't need to handle errors internally as error handling is managed at the UnraidFileModifierService level.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts
🧬 Code graph analysis (1)
api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts (1)
api/src/unraid-api/unraid-file-modifier/file-modification.ts (1)
  • ShouldApplyWithReason (15-24)
🔇 Additional comments (2)
api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts (2)

1-6: LGTM! Imports follow ESM conventions.

The imports are correctly structured with .js extensions for ESM compatibility and include only necessary dependencies.


27-31: LGTM! Patch generation follows expected pattern.

The generatePatch method correctly reads the file, applies transformations, and generates a diff. This pattern is consistent with similar CSS modifications in the PR.

const before = source.slice(0, insertIndex);
const after = source.slice(insertIndex);

return `${before}\n@scope (:root) to (.unapi) {${after.replace(/background-color:\s*#f2f2f2/g, 'background-color: var(--header-background-color)')}\n}`;
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

Make background-color regex case-insensitive.

The regex /background-color:\s*#f2f2f2/g should use the i flag to match both lowercase and uppercase hex colors (e.g., #F2F2F2).

🔎 Proposed fix
-        return `${before}\n@scope (:root) to (.unapi) {${after.replace(/background-color:\s*#f2f2f2/g, 'background-color: var(--header-background-color)')}\n}`;
+        return `${before}\n@scope (:root) to (.unapi) {${after.replace(/background-color:\s*#f2f2f2/gi, 'background-color: var(--header-background-color)')}\n}`;
📝 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
return `${before}\n@scope (:root) to (.unapi) {${after.replace(/background-color:\s*#f2f2f2/g, 'background-color: var(--header-background-color)')}\n}`;
return `${before}\n@scope (:root) to (.unapi) {${after.replace(/background-color:\s*#f2f2f2/gi, 'background-color: var(--header-background-color)')}\n}`;
🤖 Prompt for AI Agents
In
api/src/unraid-api/unraid-file-modifier/modifications/default-white-css.modification.ts
around line 60, the regex `/background-color:\s*#f2f2f2/g` only matches
lowercase hex and should be case-insensitive; update the regex to include the i
flag (e.g., use `/background-color:\s*#f2f2f2/gi`) so replacements also match
`#F2F2F2` or mixed-case variants.

@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

@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

@github-actions
Copy link
Contributor

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/PR1860/dynamix.unraid.net.plg

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)
api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.ts (1)

54-54: Consider adding validation for successful regex replacement.

The regex replacement assumes the exact format background-color:#121510 without whitespace. If the source CSS format changes (e.g., spaces added during minification process changes), the replacement would silently fail, and the modification would produce unexpected output.

🔎 Consider adding validation
-        after = after.replace(/(#header\s*\{[^}]*background-color:)#121510/, '$1#f2f2f2');
+        const replaced = after.replace(/(#header\s*\{[^}]*background-color:\s*)#121510/, '$1#f2f2f2');
+        if (replaced === after) {
+            throw new Error(`Could not find #header background-color to replace in ${this.filePath}`);
+        }
+        after = replaced;

Note: The regex is also updated to handle optional whitespace after the colon with \s*.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9504b4a and 18f3227.

📒 Files selected for processing (3)
  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/default-gray.css.modified.snapshot
  • api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.ts
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/default-gray-css-modification.patch
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/src/unraid-api/unraid-file-modifier/modifications/patches/default-gray-css-modification.patch
🧰 Additional context used
📓 Path-based instructions (5)
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/unraid-file-modifier/modifications/__test__/snapshots/default-gray.css.modified.snapshot
  • api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.ts
**/*

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

Never add comments unless they are needed for clarity of function

Files:

  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/default-gray.css.modified.snapshot
  • api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.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:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.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/unraid-file-modifier/modifications/default-gray-css.modification.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/unraid-file-modifier/modifications/default-gray-css.modification.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:24-27
Timestamp: 2025-01-31T22:01:22.708Z
Learning: The Unraid UI uses a modern notification system with a custom `unraid-toaster` component replacing the legacy jGrowl notifications. The system is backed by a comprehensive GraphQL API with real-time subscription support for notification updates.
Learnt from: pujitm
Repo: unraid/api PR: 941
File: web/components/Notifications/Item.vue:0-0
Timestamp: 2024-11-01T17:52:55.311Z
Learning: In the `web/components/Notifications/Item.vue` component, ensure that when delete or archive mutations fail, the notification remains displayed, and an error message is shown below the action buttons to inform the user.
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:30-54
Timestamp: 2025-01-31T22:01:02.725Z
Learning: The removal of jGrowl notifications from DefaultPageLayout.php is intentional as notifications are now handled on a separate page as part of the architectural design to override existing Unraid pages.
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:6-20
Timestamp: 2025-01-31T22:01:41.842Z
Learning: The default-page-layout.patch removes the old jGrowl notification system and is complemented by the unraid-toaster component implementation. The new system is added through the DefaultPageLayout modification which inserts the toaster component with proper position configuration based on user preferences.
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:6-20
Timestamp: 2025-01-31T22:01:41.842Z
Learning: The default-page-layout.patch is used to remove the old jGrowl notification system from Unraid pages, as notifications are handled by a new system implemented on a different page.
Learnt from: mdatelle
Repo: unraid/api PR: 1106
File: unraid-ui/src/components/index.ts:2-2
Timestamp: 2025-02-04T17:21:39.710Z
Learning: The unraid-ui package is undergoing a major refactoring process, and breaking changes are expected during this transition period.
Learnt from: pujitm
Repo: unraid/api PR: 1075
File: web/components/Notifications/Sidebar.vue:52-57
Timestamp: 2025-01-30T19:56:42.055Z
Learning: Notification links in the Unraid API are validated at creation time on the server side, making additional client-side validation unnecessary when receiving notifications through GraphQL subscriptions.
Learnt from: elibosley
Repo: unraid/api PR: 1746
File: web/src/components/Activation/ActivationTimezoneStep.vue:78-91
Timestamp: 2025-10-11T02:09:16.853Z
Learning: In Unraid setup/activation flows (e.g., ActivationTimezoneStep.vue), don't block user progression when non-critical configuration steps like timezone updates fail. The design prioritizes allowing users to access Unraid over enforcing complete setup step success.
📚 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/unraid-file-modifier/modifications/__test__/snapshots/default-gray.css.modified.snapshot
📚 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:

  • api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/default-gray.css.modified.snapshot
📚 Learning: 2025-01-29T16:35:43.699Z
Learnt from: elibosley
Repo: unraid/api PR: 1082
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:39-41
Timestamp: 2025-01-29T16:35:43.699Z
Learning: The UnraidFileModifierService in the Unraid API provides comprehensive error handling for all FileModification implementations, including detailed error logging with stack traces and modification IDs. Individual FileModification implementations should focus on their core functionality without duplicating error handling.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.ts
📚 Learning: 2025-02-03T18:57:53.577Z
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/__test__/generic-modification.spec.ts:80-80
Timestamp: 2025-02-03T18:57:53.577Z
Learning: In the unraid/api codebase, patch files generated for file modifications always use the .patch extension, regardless of the source file type being modified.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.ts
📚 Learning: 2025-01-29T16:35:43.699Z
Learnt from: elibosley
Repo: unraid/api PR: 1082
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:39-41
Timestamp: 2025-01-29T16:35:43.699Z
Learning: In the Unraid API, FileModification implementations (apply/rollback methods) don't need to implement their own error handling as it's handled by the UnraidFileModifierService caller.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.ts
📚 Learning: 2025-02-03T17:21:26.738Z
Learnt from: elibosley
Repo: unraid/api PR: 1101
File: api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php:203-207
Timestamp: 2025-02-03T17:21:26.738Z
Learning: The project uses patches to override existing Unraid pages rather than modifying them directly.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.ts
🧬 Code graph analysis (1)
api/src/unraid-api/unraid-file-modifier/modifications/default-gray-css.modification.ts (1)
api/src/unraid-api/unraid-file-modifier/file-modification.ts (1)
  • ShouldApplyWithReason (15-24)

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