Repository: raycast/extensions
Pull request #19348: Update Ext/window sizer
Author: login: the3ash
Labels: name: extension fix / improvement
description: Label for PRs with extension's fix improvements
name: OP is author
description: The OP of the PR is the author of the extension
name: extension: window-sizer
description: Issues related to the window-sizer extension
Created: 2025-05-22T16:37:53Z | Merged: 2025-05-23T15:25:56Z

fix: enhance stability of action icon display
fix: prevent crashes caused by missing GUI context in dev mode

## Checklist

- [x] I read the [extension guidelines](https://developers.raycast.com/basics/prepare-an-extension-for-store)
- [x] I read the [documentation about publishing](https://developers.raycast.com/basics/publish-an-extension)
- [x] I ran `npm run build` and [tested this distribution build in Raycast](https://developers.raycast.com/basics/prepare-an-extension-for-store#metadata-and-configuration)
- [x] I checked that files in the `assets` folder are used by the extension itself
- [x] I checked that assets used in the `README` are located outside the metadata folder if they were not generated with our [metadata tool](https://developers.raycast.com/basics/prepare-an-extension-for-store#how-to-use-it)



Diff:
diff --git a/extensions/window-sizer/CHANGELOG.md b/extensions/window-sizer/CHANGELOG.md
index cdf197b4be5..d9a46083cb0 100644
--- a/extensions/window-sizer/CHANGELOG.md
+++ b/extensions/window-sizer/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Window Sizer Changelog
 
+## [Fixes] - 2025-05-23
+
+- Fixed occasional issue with action icon not displaying
+
 ## [UI improvement] - 2025-05-21
 
 - Adjusted Action icon to only display when selected
diff --git a/extensions/window-sizer/src/components/DefaultResolutionsList.tsx b/extensions/window-sizer/src/components/DefaultResolutionsList.tsx
index ca1a0818bf0..dd77a4fc770 100644
--- a/extensions/window-sizer/src/components/DefaultResolutionsList.tsx
+++ b/extensions/window-sizer/src/components/DefaultResolutionsList.tsx
@@ -5,7 +5,7 @@ interface DefaultResolutionsListProps {
   predefinedResolutions: Resolution[];
   onResizeWindow: (width: number, height: number) => Promise<void>;
   onToggleStar: (resolution: Resolution) => Promise<void>;
-  selectedItemId?: string | null | undefined;
+  selectedItemId?: string;
   starredResolutions: Resolution[];
 }
 
diff --git a/extensions/window-sizer/src/components/ResolutionList.tsx b/extensions/window-sizer/src/components/ResolutionList.tsx
index 0fab887c939..daa552df803 100644
--- a/extensions/window-sizer/src/components/ResolutionList.tsx
+++ b/extensions/window-sizer/src/components/ResolutionList.tsx
@@ -1,6 +1,7 @@
-import { List, ActionPanel, Action, Icon, Color, Toast, showToast } from "@raycast/api";
+import { List, ActionPanel, Action, Icon, Color, Toast, showToast, getApplications } from "@raycast/api";
 import { Resolution } from "../types";
 import { showFailureToast } from "@raycast/utils";
+import { useMemo, useState, useEffect } from "react";
 
 interface ResolutionListProps {
   resolutions: Resolution[];
@@ -13,6 +14,25 @@ interface ResolutionListProps {
   starredResolutions?: Resolution[];
 }
 
+interface ListAccessory {
+  icon: {
+    source: string;
+    fallback: Icon;
+    tintColor: Color;
+  };
+  tooltip: string;
+}
+
+// Icon paths that need to be preloaded
+const ICON_PATHS = {
+  customSize: "icons/custom-size.svg",
+  defaultSize: "icons/default-size.svg",
+  clear: "icons/clear.svg",
+  unstar: "icons/unstar.svg",
+  star: "icons/star.svg",
+  starCheck: "icons/star-check.svg",
+} as const;
+
 /**
  * ResolutionList component displays a list of available resolutions
  */
@@ -26,63 +46,95 @@ export function ResolutionList({
   selectedItemId,
   starredResolutions = [],
 }: ResolutionListProps) {
+  const [isIconsReady, setIsIconsReady] = useState(false);
+
+  useEffect(() => {
+    const preloadIcons = async () => {
+      try {
+        await getApplications();
+        setIsIconsReady(true);
+      } catch (error) {
+        console.error("Failed to preload icons:", error);
+        setIsIconsReady(true);
+      }
+    };
+
+    preloadIcons();
+  }, []);
+
   // Helper function to check if a resolution is starred
   const isStarred = (resolution: Resolution) =>
     starredResolutions.some((r) => r.width === resolution.width && r.height === resolution.height);
 
-  return (
-    <List.Section title={sectionTitle}>
-      {resolutions.map((resolution, index) => {
+  // Pre-compute accessories for each resolution to ensure stable rendering
+  const resolutionAccessories = useMemo(() => {
+    return resolutions.reduce(
+      (acc, resolution, index) => {
         const itemId = `${resolution.isCustom ? "custom" : "default"}-${resolution.width}x${resolution.height}-${sectionTitle}-${index}`;
         const isSelected = itemId === selectedItemId;
         const resolutionIsStarred = isStarred(resolution);
 
-        return (
-          <List.Item
-            key={itemId}
-            id={itemId}
-            title={resolution.title}
-            icon={{
-              source: resolution.isCustom ? "icons/custom-size.svg" : "icons/default-size.svg",
-              fallback: Icon.AppWindow,
-              tintColor: Color.SecondaryText,
-            }}
-            accessories={
-              (isSelected &&
-                (showDeleteAction && resolution.isCustom
-                  ? [
-                      {
-                        icon: { source: "icons/clear.svg", fallback: Icon.Trash, tintColor: Color.SecondaryText },
-                        tooltip: "⌘ D",
-                      },
-                    ]
-                  : resolutionIsStarred
-                    ? [
-                        {
-                          icon: {
-                            source: "icons/unstar.svg",
-                            fallback: Icon.StarDisabled,
-                            tintColor: Color.SecondaryText,
-                          },
-                          tooltip: "⇧ ⌘ S",
-                        },
-                      ]
-                    : !resolution.isCustom
-                      ? [
-                          {
-                            icon: { source: "icons/star.svg", fallback: Icon.Star, tintColor: Color.SecondaryText },
-                            tooltip: "⌘ S",
-                          },
-                        ]
-                      : [])) ||
-              []
-            }
-            actions={
+        let accessories: ListAccessory[] = [];
+        if (isSelected) {
+          if (showDeleteAction && resolution.isCustom) {
+            accessories = [
+              {
+                icon: { source: ICON_PATHS.clear, fallback: Icon.Trash, tintColor: Color.SecondaryText },
+                tooltip: "⌘ D",
+              },
+            ];
+          } else if (resolutionIsStarred) {
+            accessories = [
+              {
+                icon: {
+                  source: ICON_PATHS.unstar,
+                  fallback: Icon.StarDisabled,
+                  tintColor: Color.SecondaryText,
+                },
+                tooltip: "⇧ ⌘ S",
+              },
+            ];
+          } else if (!resolution.isCustom) {
+            accessories = [
+              {
+                icon: { source: ICON_PATHS.star, fallback: Icon.Star, tintColor: Color.SecondaryText },
+                tooltip: "⌘ S",
+              },
+            ];
+          }
+        }
+
+        acc[itemId] = accessories;
+        return acc;
+      },
+      {} as Record<string, ListAccessory[]>,
+    );
+  }, [resolutions, selectedItemId, showDeleteAction, starredResolutions, sectionTitle]);
+
+  // Pre-compute list items to ensure stable rendering
+  const listItems = useMemo(() => {
+    return resolutions.map((resolution, index) => {
+      const itemId = `${resolution.isCustom ? "custom" : "default"}-${resolution.width}x${resolution.height}-${sectionTitle}-${index}`;
+      const resolutionIsStarred = isStarred(resolution);
+
+      return (
+        <List.Item
+          key={itemId}
+          id={itemId}
+          title={resolution.title}
+          icon={{
+            source: resolution.isCustom ? ICON_PATHS.customSize : ICON_PATHS.defaultSize,
+            fallback: Icon.AppWindow,
+            tintColor: Color.SecondaryText,
+          }}
+          accessories={resolutionAccessories[itemId] || []}
+          actions={
+            isIconsReady ? (
               <ActionPanel>
                 <Action
                   title={`Resize to ${resolution.title}`}
                   icon={{
-                    source: resolution.isCustom ? "icons/custom-size.svg" : "icons/default-size.svg",
+                    source: resolution.isCustom ? ICON_PATHS.customSize : ICON_PATHS.defaultSize,
                     fallback: Icon.AppWindow,
                     tintColor: Color.PrimaryText,
                   }}
@@ -102,7 +154,7 @@ export function ResolutionList({
                     <Action
                       title="Remove from Starred"
                       icon={{
-                        source: "icons/unstar.svg",
+                        source: ICON_PATHS.unstar,
                         fallback: Icon.StarDisabled,
                         tintColor: Color.PrimaryText,
                       }}
@@ -113,10 +165,6 @@ export function ResolutionList({
                         }
                         try {
                           await onToggleStar(resolution);
-                          await showToast({
-                            style: Toast.Style.Success,
-                            title: "Removed from Starred Sizes",
-                          });
                         } catch (error) {
                           await showFailureToast("Failed to remove from starred", {
                             message: error instanceof Error ? error.message : String(error),
@@ -128,7 +176,7 @@ export function ResolutionList({
                       <Action
                         title="Already Starred"
                         icon={{
-                          source: "icons/star-check.svg",
+                          source: ICON_PATHS.starCheck,
                           fallback: Icon.Star,
                           tintColor: Color.PrimaryText,
                         }}
@@ -146,7 +194,7 @@ export function ResolutionList({
                   <Action
                     title="Mark as Starred"
                     icon={{
-                      source: "icons/star.svg",
+                      source: ICON_PATHS.star,
                       fallback: Icon.Star,
                       tintColor: Color.PrimaryText,
                     }}
@@ -157,10 +205,6 @@ export function ResolutionList({
                       }
                       try {
                         await onToggleStar(resolution);
-                        await showToast({
-                          style: Toast.Style.Success,
-                          title: "Marked as Starred",
-                        });
                       } catch (error) {
                         await showFailureToast("Failed to mark as starred", {
                           message: error instanceof Error ? error.message : String(error),
@@ -169,34 +213,49 @@ export function ResolutionList({
                     }}
                   />
                 )}
-                {showDeleteAction && resolution.isCustom && onDeleteResolution && (
+                {showDeleteAction && resolution.isCustom && (
                   <Action
                     title="Delete Custom Size"
-                    icon={{ source: "icons/clear.svg", fallback: Icon.Trash, tintColor: Color.Red }}
-                    style={Action.Style.Destructive}
+                    icon={{
+                      source: ICON_PATHS.clear,
+                      fallback: Icon.Trash,
+                      tintColor: Color.PrimaryText,
+                    }}
                     shortcut={{ modifiers: ["cmd"], key: "d" }}
                     onAction={async () => {
+                      if (!onDeleteResolution) {
+                        return;
+                      }
                       try {
-                        // First remove from starred list if it's starred
+                        await onDeleteResolution(resolution);
                         if (resolutionIsStarred && onToggleStar) {
                           await onToggleStar(resolution);
                         }
-
-                        // Then delete the custom resolution
-                        await onDeleteResolution(resolution);
                       } catch (error) {
-                        await showFailureToast("Failed to delete size", {
+                        await showFailureToast("Failed to delete custom size", {
                           message: error instanceof Error ? error.message : String(error),
                         });
+                        return;
                       }
                     }}
                   />
                 )}
               </ActionPanel>
-            }
-          />
-        );
-      })}
-    </List.Section>
-  );
+            ) : null
+          }
+        />
+      );
+    });
+  }, [
+    resolutions,
+    resolutionAccessories,
+    sectionTitle,
+    onResizeWindow,
+    onToggleStar,
+    onDeleteResolution,
+    showDeleteAction,
+    isIconsReady,
+  ]);
+
+  return <List.Section title={sectionTitle}>{listItems}</List.Section>;
 }
diff --git a/extensions/window-sizer/src/components/StarredResolutionsList.tsx b/extensions/window-sizer/src/components/StarredResolutionsList.tsx
index b8f903da274..64ae66fdbaf 100644
--- a/extensions/window-sizer/src/components/StarredResolutionsList.tsx
+++ b/extensions/window-sizer/src/components/StarredResolutionsList.tsx
@@ -5,7 +5,7 @@ interface StarredResolutionsListProps {
   starredResolutions: Resolution[];
   onResizeWindow: (width: number, height: number) => Promise<void>;
   onToggleStar: (resolution: Resolution) => Promise<void>;
-  selectedItemId?: string | undefined;
+  selectedItemId?: string;
 }
 
 export function StarredResolutionsList({
diff --git a/extensions/window-sizer/src/hooks/useStarredResolutions.ts b/extensions/window-sizer/src/hooks/useStarredResolutions.ts
index 7737c5d5dc2..ad24fae4c3b 100644
--- a/extensions/window-sizer/src/hooks/useStarredResolutions.ts
+++ b/extensions/window-sizer/src/hooks/useStarredResolutions.ts
@@ -1,4 +1,4 @@
-import { LocalStorage } from "@raycast/api";
+import { LocalStorage, showToast, Toast } from "@raycast/api";
 import { showFailureToast } from "@raycast/utils";
 import { useState, useEffect } from "react";
 import { Resolution } from "../types";
@@ -81,8 +81,8 @@ export function useStarredResolutions() {
 
   // Function to toggle star status
   async function toggleStarResolution(resolution: Resolution) {
+    const isStarred = starredResolutions.some((r) => isSameResolution(r, resolution));
     try {
-      const isStarred = starredResolutions.some((r) => isSameResolution(r, resolution));
       const updatedResolutions = isStarred
         ? starredResolutions.filter((r) => !isSameResolution(r, resolution))
         : [...starredResolutions, { ...resolution, isStarred: true }];
@@ -92,6 +92,12 @@ export function useStarredResolutions() {
 
       // Only update state after storage is confirmed
       setStarredResolutions(updatedResolutions);
+
+      // Show toast notification
+      await showToast({
+        style: Toast.Style.Success,
+        title: isStarred ? "Removed from Starred Sizes" : "Marked as Starred",
+      });
     } catch (error) {
       console.error("Error toggling star status:", error);
       throw error;
diff --git a/extensions/window-sizer/swift/Sources/WindowSizerSwiftAPI.swift b/extensions/window-sizer/swift/Sources/WindowSizerSwiftAPI.swift
index 91297c2a9f1..de6dc4f437b 100644
--- a/extensions/window-sizer/swift/Sources/WindowSizerSwiftAPI.swift
+++ b/extensions/window-sizer/swift/Sources/WindowSizerSwiftAPI.swift
@@ -171,13 +171,17 @@ private func getActiveWindowData() -> (
   )
 }
 
-// Retrieve all screens and their resolution info
+private func isLaunchedByRaycast() -> Bool {
+  let env = ProcessInfo.processInfo.environment
+  return env.keys.contains(where: { $0.hasPrefix("RAYCAST_") })
+}
+
 @raycast func getScreensInfo() -> [String] {
-  let screens = NSScreen.screens
-  guard !screens.isEmpty else {
-    return ["Error"]
+  guard isLaunchedByRaycast() else {
+    return ["Error: This command must be run from Raycast"]
   }
 
+  let screens = NSScreen.screens
   var results: [String] = []
   for (index, screen) in screens.enumerated() {
     results.append(getScreenInfoString(screenIndex: index, screen: screen))


Reviews:
user: login: greptile-apps[bot]
body: 
## PR Summary

This PR enhances the Window Sizer extension with improved stability for action icons and prevention of GUI context crashes in dev mode, along with better organization of window sizing components.

- The new changelog entry `[UI Improvement]` needs to end with `{PR_MERGE_DATE}` template string
- The `ResolutionListContainer` component now properly handles loading states and selected items with better error management
- New `StarredResolutionsList` component introduced for managing starred window sizes with proper integration
- Swift API improvements include environment variable checks to ensure commands run within Raycast
- Improved state management in hooks prevents race conditions and ensures storage consistency

<sub>13 file(s) reviewed, 7 comment(s)</sub>
<sub>[Edit PR Review Bot Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews&utm_content=extensions_19348)</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T16:44:10Z
user: login: pernielsentikaer
body: Hi 👋 

Looks good to me, approved 🔥
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-23T05:17:52Z

Review comments:
diff_hunk: @@ -1,57 +1,29 @@
-import { List } from "@raycast/api";
 import { Resolution } from "../types";
 import { ResolutionList } from "./ResolutionList";
-import { OptionsList } from "./OptionsList";
-import { useStarredResolutions } from "../hooks/useStarredResolutions";
 
 interface DefaultResolutionsListProps {
   predefinedResolutions: Resolution[];
   onResizeWindow: (width: number, height: number) => Promise<void>;
-  onRestorePreviousSize: () => Promise<void>;
-  onGetCurrentWindowSize: () => Promise<void>;
-  onAddCustomResolution: () => Promise<void>;
-  onMaximizeWindow: () => Promise<void>;
-  isLoading?: boolean;
+  onToggleStar: (resolution: Resolution) => Promise<void>;
+  selectedItemId?: string | null | undefined;
path: extensions/window-sizer/src/components/DefaultResolutionsList.tsx
user: login: greptile-apps[bot]
body: style: selectedItemId type includes both null and undefined - consider using just one of these types for consistency
created_at: 2025-05-22T16:39:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,5 +1,9 @@
 # Window Sizer Changelog
 
+## [UI improvement] - 2025-05-21
path: extensions/window-sizer/CHANGELOG.md
user: login: greptile-apps[bot]
body: syntax: The changelog entry title must end with {PR_MERGE_DATE}

```suggestion
## [UI improvement] - {PR_MERGE_DATE}
```
created_at: 2025-05-22T16:39:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,31 @@
+import { Resolution } from "../types";
+import { ResolutionList } from "./ResolutionList";
+
+interface StarredResolutionsListProps {
+  starredResolutions: Resolution[];
+  onResizeWindow: (width: number, height: number) => Promise<void>;
+  onToggleStar: (resolution: Resolution) => Promise<void>;
+  selectedItemId?: string | undefined;
+}
+
+export function StarredResolutionsList({
+  starredResolutions,
+  onResizeWindow,
+  onToggleStar,
+  selectedItemId,
+}: StarredResolutionsListProps) {
+  if (starredResolutions.length === 0) {
+    return null;
+  }
+
+  return (
+    <ResolutionList
+      resolutions={starredResolutions}
+      onResizeWindow={onResizeWindow}
+      sectionTitle="Starred Sizes"
+      onToggleStar={onToggleStar}
+      selectedItemId={selectedItemId}
+      starredResolutions={starredResolutions}
+    />
path: extensions/window-sizer/src/components/StarredResolutionsList.tsx
user: login: greptile-apps[bot]
body: style: starredResolutions is passed twice - once as resolutions and once as starredResolutions. This is redundant and could cause confusion in maintenance. Consider removing the starredResolutions prop since it's the same data.

```suggestion
      resolutions={starredResolutions}
      onResizeWindow={onResizeWindow}
      sectionTitle="Starred Sizes"
      onToggleStar={onToggleStar}
      selectedItemId={selectedItemId}
    />
```
created_at: 2025-05-22T16:40:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,31 @@
+import { Resolution } from "../types";
+import { ResolutionList } from "./ResolutionList";
+
+interface StarredResolutionsListProps {
+  starredResolutions: Resolution[];
+  onResizeWindow: (width: number, height: number) => Promise<void>;
+  onToggleStar: (resolution: Resolution) => Promise<void>;
+  selectedItemId?: string | undefined;
path: extensions/window-sizer/src/components/StarredResolutionsList.tsx
user: login: greptile-apps[bot]
body: style: selectedItemId type has redundant undefined in union type (string | undefined is equivalent to string?)

```suggestion
  selectedItemId?: string;
```
created_at: 2025-05-22T16:40:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,8 +10,31 @@ interface ResolutionListProps {
   showDeleteAction?: boolean;
   onDeleteResolution?: (resolution: Resolution) => Promise<void>;
   onToggleStar?: (resolution: Resolution) => Promise<void>;
+  selectedItemId?: string | null | undefined;
+  starredResolutions?: Resolution[];
 }
 
+interface ListAccessory {
+  icon: {
+    source: string;
+    fallback: Icon;
+    tintColor: Color;
+  };
+  tooltip: string;
+}
+
+// Icon paths that need to be preloaded
+const ICON_PATHS = {
+  customSize: "icons/custom-size.svg",
+  defaultSize: "icons/default-size.svg",
+  clear: "icons/clear.svg",
+  unstar: "icons/unstar.svg",
+  star: "icons/star.svg",
+} as const;
+
+// Module-level icon preloading flag
+let iconsPreloaded = false;
path: extensions/window-sizer/src/components/ResolutionList.tsx
user: login: greptile-apps[bot]
body: logic: Module-level mutable variable could cause issues in hot reloading scenarios. Consider moving this into React state or context.
created_at: 2025-05-22T16:40:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,132 +45,224 @@ export function ResolutionList({
   showDeleteAction = false,
   onDeleteResolution,
   onToggleStar,
+  selectedItemId,
+  starredResolutions = [],
 }: ResolutionListProps) {
-  const { isResolutionStarred, removeStarredResolution } = useStarredResolutions();
+  const [isIconsReady, setIsIconsReady] = useState(iconsPreloaded);
+
+  useEffect(() => {
+    const preloadIcons = async () => {
+      if (iconsPreloaded) {
+        setIsIconsReady(true);
+        return;
+      }
+
+      try {
+        await getApplications();
+        iconsPreloaded = true;
+        setIsIconsReady(true);
+      } catch (error) {
+        console.error("Failed to preload icons:", error);
+        setIsIconsReady(true);
+      }
+    };
+
+    preloadIcons();
+  }, []);
+
+  // Helper function to check if a resolution is starred
+  const isStarred = (resolution: Resolution) =>
+    starredResolutions.some((r) => r.width === resolution.width && r.height === resolution.height);
 
-  return (
-    <List.Section title={sectionTitle}>
-      {resolutions.map((resolution) => (
+  // Pre-compute accessories for each resolution to ensure stable rendering
+  const resolutionAccessories = useMemo(() => {
+    return resolutions.reduce(
+      (acc, resolution, index) => {
+        const itemId = `${resolution.isCustom ? "custom" : "default"}-${resolution.width}x${resolution.height}-${sectionTitle}-${index}`;
+        const isSelected = itemId === selectedItemId;
+        const resolutionIsStarred = isStarred(resolution);
+
+        let accessories: ListAccessory[] = [];
+        if (isSelected) {
+          if (showDeleteAction && resolution.isCustom) {
+            accessories = [
+              {
+                icon: { source: ICON_PATHS.clear, fallback: Icon.Trash, tintColor: Color.SecondaryText },
+                tooltip: "⌘ D",
+              },
+            ];
+          } else if (resolutionIsStarred) {
+            accessories = [
+              {
+                icon: {
+                  source: ICON_PATHS.unstar,
+                  fallback: Icon.StarDisabled,
+                  tintColor: Color.SecondaryText,
+                },
+                tooltip: "⇧ ⌘ S",
+              },
+            ];
+          } else if (!resolution.isCustom) {
+            accessories = [
+              {
+                icon: { source: ICON_PATHS.star, fallback: Icon.Star, tintColor: Color.SecondaryText },
+                tooltip: "⌘ S",
+              },
+            ];
+          }
+        }
+
+        acc[itemId] = accessories;
+        return acc;
+      },
+      {} as Record<string, ListAccessory[]>,
+    );
+  }, [resolutions, selectedItemId, showDeleteAction, starredResolutions, sectionTitle]);
+
+  // Pre-compute list items to ensure stable rendering
+  const listItems = useMemo(() => {
+    return resolutions.map((resolution, index) => {
+      const itemId = `${resolution.isCustom ? "custom" : "default"}-${resolution.width}x${resolution.height}-${sectionTitle}-${index}`;
+      const resolutionIsStarred = isStarred(resolution);
+
+      return (
         <List.Item
-          key={`${resolution.isCustom ? "custom" : "default"}-${resolution.title}`}
+          key={itemId}
+          id={itemId}
           title={resolution.title}
           icon={{
-            source: resolution.isCustom ? "icons/custom-size.svg" : "icons/default-size.svg",
+            source: resolution.isCustom ? ICON_PATHS.customSize : ICON_PATHS.defaultSize,
             fallback: Icon.AppWindow,
             tintColor: Color.SecondaryText,
           }}
-          accessories={
-            showDeleteAction && resolution.isCustom
-              ? [
-                  {
-                    icon: { source: "icons/clear.svg", fallback: Icon.Trash, tintColor: Color.SecondaryText },
-                    tooltip: "⌘ D",
-                  },
-                ]
-              : resolution.isStarred
-                ? [
-                    {
-                      icon: { source: "icons/unstar.svg", fallback: Icon.StarDisabled, tintColor: Color.SecondaryText },
-                      tooltip: "⇧ ⌘ S",
-                    },
-                  ]
-                : []
-          }
+          accessories={resolutionAccessories[itemId] || []}
           actions={
-            <ActionPanel>
-              <Action
-                title={`Resize to ${resolution.title}`}
-                icon={{
-                  source: resolution.isCustom ? "icons/custom-size.svg" : "icons/default-size.svg",
-                  fallback: Icon.AppWindow,
-                  tintColor: Color.PrimaryText,
-                }}
-                shortcut={{ modifiers: ["cmd"], key: "return" }}
-                onAction={async () => {
-                  try {
-                    await onResizeWindow(resolution.width, resolution.height);
-                  } catch (error) {
-                    await showFailureToast("Failed to resize window", {
-                      message: error instanceof Error ? error.message : String(error),
-                    });
-                  }
-                }}
-              />
-              <Action
-                title={resolution.isStarred ? "Remove from Starred" : "Mark as Starred"}
-                icon={{
-                  source: resolution.isStarred ? "icons/unstar.svg" : "icons/star.svg",
-                  fallback: resolution.isStarred ? Icon.StarDisabled : Icon.Star,
-                  tintColor: Color.PrimaryText,
-                }}
-                shortcut={{
-                  modifiers: resolution.isStarred ? ["cmd", "shift"] : ["cmd"],
-                  key: "s",
-                }}
-                onAction={async () => {
-                  try {
-                    if (!onToggleStar) {
-                      return;
-                    }
-
-                    if (resolution.isStarred) {
-                      await onToggleStar(resolution);
-                      await showToast({
-                        style: Toast.Style.Success,
-                        title: "Removed from Starred",
-                      });
-                      return;
-                    }
-
-                    // Check if resolution already exists in starred list
-                    const starredStatus = await isResolutionStarred(resolution);
-                    if (starredStatus) {
-                      await showToast({
-                        style: Toast.Style.Failure,
-                        title: "Size already exists in Starred Sizes",
-                      });
-                      return;
-                    }
-
-                    await onToggleStar(resolution);
-                    await showToast({
-                      style: Toast.Style.Success,
-                      title: "Marked as Starred",
-                    });
-                  } catch (error) {
-                    await showFailureToast("Failed to toggle star status", {
-                      message: error instanceof Error ? error.message : String(error),
-                    });
-                  }
-                }}
-              />
-              {showDeleteAction && resolution.isCustom && onDeleteResolution && (
+            isIconsReady ? (
+              <ActionPanel>
                 <Action
-                  title="Delete Custom Size"
-                  icon={{ source: "icons/clear.svg", fallback: Icon.Trash, tintColor: Color.Red }}
-                  style={Action.Style.Destructive}
-                  shortcut={{ modifiers: ["cmd"], key: "d" }}
+                  title={`Resize to ${resolution.title}`}
+                  icon={{
+                    source: resolution.isCustom ? ICON_PATHS.customSize : ICON_PATHS.defaultSize,
+                    fallback: Icon.AppWindow,
+                    tintColor: Color.PrimaryText,
+                  }}
+                  shortcut={{ modifiers: ["cmd"], key: "return" }}
                   onAction={async () => {
                     try {
-                      // Check if the resolution is starred and remove it if necessary
-                      const isStarred = await isResolutionStarred(resolution);
-                      if (isStarred) {
-                        await removeStarredResolution(resolution);
-                      }
-
-                      await onDeleteResolution(resolution);
+                      await onResizeWindow(resolution.width, resolution.height);
                     } catch (error) {
-                      await showFailureToast("Failed to delete size", {
+                      await showFailureToast("Failed to resize window", {
                         message: error instanceof Error ? error.message : String(error),
                       });
                     }
                   }}
                 />
-              )}
-            </ActionPanel>
+                {resolutionIsStarred ? (
+                  <>
+                    <Action
+                      title="Remove from Starred"
+                      icon={{
+                        source: ICON_PATHS.unstar,
+                        fallback: Icon.StarDisabled,
+                        tintColor: Color.PrimaryText,
+                      }}
+                      shortcut={{ modifiers: ["cmd", "shift"], key: "s" }}
+                      onAction={async () => {
+                        if (!onToggleStar) {
+                          return;
+                        }
+                        try {
+                          await onToggleStar(resolution);
+                        } catch (error) {
+                          await showFailureToast("Failed to remove from starred", {
+                            message: error instanceof Error ? error.message : String(error),
+                          });
+                        }
+                      }}
+                    />
+                    {sectionTitle !== "Starred Sizes" && (
+                      <Action
+                        title="Already Starred"
+                        icon={{
+                          source: "icons/star-check.svg",
path: extensions/window-sizer/src/components/ResolutionList.tsx
user: login: greptile-apps[bot]
body: style: This icon path should use ICON_PATHS constant like other icons for consistency

```suggestion
                          source: ICON_PATHS.starCheck,
```
created_at: 2025-05-22T16:40:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,132 +45,224 @@ export function ResolutionList({
   showDeleteAction = false,
   onDeleteResolution,
   onToggleStar,
+  selectedItemId,
+  starredResolutions = [],
 }: ResolutionListProps) {
-  const { isResolutionStarred, removeStarredResolution } = useStarredResolutions();
+  const [isIconsReady, setIsIconsReady] = useState(iconsPreloaded);
+
+  useEffect(() => {
+    const preloadIcons = async () => {
+      if (iconsPreloaded) {
+        setIsIconsReady(true);
+        return;
+      }
+
+      try {
+        await getApplications();
+        iconsPreloaded = true;
+        setIsIconsReady(true);
+      } catch (error) {
+        console.error("Failed to preload icons:", error);
+        setIsIconsReady(true);
+      }
+    };
+
+    preloadIcons();
+  }, []);
+
+  // Helper function to check if a resolution is starred
+  const isStarred = (resolution: Resolution) =>
+    starredResolutions.some((r) => r.width === resolution.width && r.height === resolution.height);
 
-  return (
-    <List.Section title={sectionTitle}>
-      {resolutions.map((resolution) => (
+  // Pre-compute accessories for each resolution to ensure stable rendering
+  const resolutionAccessories = useMemo(() => {
+    return resolutions.reduce(
+      (acc, resolution, index) => {
+        const itemId = `${resolution.isCustom ? "custom" : "default"}-${resolution.width}x${resolution.height}-${sectionTitle}-${index}`;
+        const isSelected = itemId === selectedItemId;
+        const resolutionIsStarred = isStarred(resolution);
+
+        let accessories: ListAccessory[] = [];
+        if (isSelected) {
+          if (showDeleteAction && resolution.isCustom) {
+            accessories = [
+              {
+                icon: { source: ICON_PATHS.clear, fallback: Icon.Trash, tintColor: Color.SecondaryText },
+                tooltip: "⌘ D",
+              },
+            ];
+          } else if (resolutionIsStarred) {
+            accessories = [
+              {
+                icon: {
+                  source: ICON_PATHS.unstar,
+                  fallback: Icon.StarDisabled,
+                  tintColor: Color.SecondaryText,
+                },
+                tooltip: "⇧ ⌘ S",
+              },
+            ];
+          } else if (!resolution.isCustom) {
+            accessories = [
+              {
+                icon: { source: ICON_PATHS.star, fallback: Icon.Star, tintColor: Color.SecondaryText },
+                tooltip: "⌘ S",
+              },
+            ];
+          }
+        }
+
+        acc[itemId] = accessories;
+        return acc;
+      },
+      {} as Record<string, ListAccessory[]>,
+    );
+  }, [resolutions, selectedItemId, showDeleteAction, starredResolutions, sectionTitle]);
+
+  // Pre-compute list items to ensure stable rendering
+  const listItems = useMemo(() => {
+    return resolutions.map((resolution, index) => {
+      const itemId = `${resolution.isCustom ? "custom" : "default"}-${resolution.width}x${resolution.height}-${sectionTitle}-${index}`;
+      const resolutionIsStarred = isStarred(resolution);
+
+      return (
         <List.Item
-          key={`${resolution.isCustom ? "custom" : "default"}-${resolution.title}`}
+          key={itemId}
+          id={itemId}
           title={resolution.title}
           icon={{
-            source: resolution.isCustom ? "icons/custom-size.svg" : "icons/default-size.svg",
+            source: resolution.isCustom ? ICON_PATHS.customSize : ICON_PATHS.defaultSize,
             fallback: Icon.AppWindow,
             tintColor: Color.SecondaryText,
           }}
-          accessories={
-            showDeleteAction && resolution.isCustom
-              ? [
-                  {
-                    icon: { source: "icons/clear.svg", fallback: Icon.Trash, tintColor: Color.SecondaryText },
-                    tooltip: "⌘ D",
-                  },
-                ]
-              : resolution.isStarred
-                ? [
-                    {
-                      icon: { source: "icons/unstar.svg", fallback: Icon.StarDisabled, tintColor: Color.SecondaryText },
-                      tooltip: "⇧ ⌘ S",
-                    },
-                  ]
-                : []
-          }
+          accessories={resolutionAccessories[itemId] || []}
           actions={
-            <ActionPanel>
-              <Action
-                title={`Resize to ${resolution.title}`}
-                icon={{
-                  source: resolution.isCustom ? "icons/custom-size.svg" : "icons/default-size.svg",
-                  fallback: Icon.AppWindow,
-                  tintColor: Color.PrimaryText,
-                }}
-                shortcut={{ modifiers: ["cmd"], key: "return" }}
-                onAction={async () => {
-                  try {
-                    await onResizeWindow(resolution.width, resolution.height);
-                  } catch (error) {
-                    await showFailureToast("Failed to resize window", {
-                      message: error instanceof Error ? error.message : String(error),
-                    });
-                  }
-                }}
-              />
-              <Action
-                title={resolution.isStarred ? "Remove from Starred" : "Mark as Starred"}
-                icon={{
-                  source: resolution.isStarred ? "icons/unstar.svg" : "icons/star.svg",
-                  fallback: resolution.isStarred ? Icon.StarDisabled : Icon.Star,
-                  tintColor: Color.PrimaryText,
-                }}
-                shortcut={{
-                  modifiers: resolution.isStarred ? ["cmd", "shift"] : ["cmd"],
-                  key: "s",
-                }}
-                onAction={async () => {
-                  try {
-                    if (!onToggleStar) {
-                      return;
-                    }
-
-                    if (resolution.isStarred) {
-                      await onToggleStar(resolution);
-                      await showToast({
-                        style: Toast.Style.Success,
-                        title: "Removed from Starred",
-                      });
-                      return;
-                    }
-
-                    // Check if resolution already exists in starred list
-                    const starredStatus = await isResolutionStarred(resolution);
-                    if (starredStatus) {
-                      await showToast({
-                        style: Toast.Style.Failure,
-                        title: "Size already exists in Starred Sizes",
-                      });
-                      return;
-                    }
-
-                    await onToggleStar(resolution);
-                    await showToast({
-                      style: Toast.Style.Success,
-                      title: "Marked as Starred",
-                    });
-                  } catch (error) {
-                    await showFailureToast("Failed to toggle star status", {
-                      message: error instanceof Error ? error.message : String(error),
-                    });
-                  }
-                }}
-              />
-              {showDeleteAction && resolution.isCustom && onDeleteResolution && (
+            isIconsReady ? (
+              <ActionPanel>
                 <Action
-                  title="Delete Custom Size"
-                  icon={{ source: "icons/clear.svg", fallback: Icon.Trash, tintColor: Color.Red }}
-                  style={Action.Style.Destructive}
-                  shortcut={{ modifiers: ["cmd"], key: "d" }}
+                  title={`Resize to ${resolution.title}`}
+                  icon={{
+                    source: resolution.isCustom ? ICON_PATHS.customSize : ICON_PATHS.defaultSize,
+                    fallback: Icon.AppWindow,
+                    tintColor: Color.PrimaryText,
+                  }}
+                  shortcut={{ modifiers: ["cmd"], key: "return" }}
                   onAction={async () => {
                     try {
-                      // Check if the resolution is starred and remove it if necessary
-                      const isStarred = await isResolutionStarred(resolution);
-                      if (isStarred) {
-                        await removeStarredResolution(resolution);
-                      }
-
-                      await onDeleteResolution(resolution);
+                      await onResizeWindow(resolution.width, resolution.height);
                     } catch (error) {
-                      await showFailureToast("Failed to delete size", {
+                      await showFailureToast("Failed to resize window", {
                         message: error instanceof Error ? error.message : String(error),
                       });
                     }
                   }}
                 />
-              )}
-            </ActionPanel>
+                {resolutionIsStarred ? (
+                  <>
+                    <Action
+                      title="Remove from Starred"
+                      icon={{
+                        source: ICON_PATHS.unstar,
+                        fallback: Icon.StarDisabled,
+                        tintColor: Color.PrimaryText,
+                      }}
+                      shortcut={{ modifiers: ["cmd", "shift"], key: "s" }}
+                      onAction={async () => {
+                        if (!onToggleStar) {
+                          return;
+                        }
+                        try {
+                          await onToggleStar(resolution);
+                        } catch (error) {
+                          await showFailureToast("Failed to remove from starred", {
+                            message: error instanceof Error ? error.message : String(error),
+                          });
+                        }
+                      }}
+                    />
+                    {sectionTitle !== "Starred Sizes" && (
+                      <Action
+                        title="Already Starred"
+                        icon={{
+                          source: "icons/star-check.svg",
+                          fallback: Icon.Star,
+                          tintColor: Color.PrimaryText,
+                        }}
+                        shortcut={{ modifiers: ["cmd"], key: "s" }}
+                        onAction={async () => {
+                          await showToast({
+                            style: Toast.Style.Success,
+                            title: "Already Starred",
+                          });
+                        }}
+                      />
+                    )}
+                  </>
+                ) : (
+                  <Action
+                    title="Mark as Starred"
+                    icon={{
+                      source: ICON_PATHS.star,
+                      fallback: Icon.Star,
+                      tintColor: Color.PrimaryText,
+                    }}
+                    shortcut={{ modifiers: ["cmd"], key: "s" }}
+                    onAction={async () => {
+                      if (!onToggleStar) {
+                        return;
+                      }
+                      try {
+                        await onToggleStar(resolution);
+                      } catch (error) {
+                        await showFailureToast("Failed to mark as starred", {
+                          message: error instanceof Error ? error.message : String(error),
+                        });
+                      }
+                    }}
+                  />
+                )}
+                {showDeleteAction && resolution.isCustom && (
+                  <Action
+                    title="Delete Custom Size"
+                    icon={{
+                      source: ICON_PATHS.clear,
+                      fallback: Icon.Trash,
+                      tintColor: Color.PrimaryText,
+                    }}
+                    shortcut={{ modifiers: ["cmd"], key: "d" }}
+                    onAction={async () => {
+                      if (!onDeleteResolution) {
+                        return;
+                      }
+                      try {
+                        if (resolutionIsStarred && onToggleStar) {
+                          await onToggleStar(resolution);
+                        }
+                        await onDeleteResolution(resolution);
path: extensions/window-sizer/src/components/ResolutionList.tsx
user: login: greptile-apps[bot]
body: logic: Potential race condition if onToggleStar fails but onDeleteResolution still executes
created_at: 2025-05-22T16:40:57Z
author_association: CONTRIBUTOR

PR comments:
user: login: raycastbot
created_at: 2025-05-22T16:38:18Z
body: Thank you for the update! :tada:

You can expect an initial review within five business days.
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-05-23T15:37:21Z
body: <!-- add-pr-comment:add-pr-comment -->

**Published to the Raycast Store:**
https://raycast.com/the3ash/window-sizer
author_association: CONTRIBUTOR
user: login: raycastbot
created_at: 2025-05-23T15:37:23Z
body: :tada: :tada: :tada:

We've rewarded your Raycast account with some credits. You will soon be able to exchange them for some swag.
author_association: COLLABORATOR