Skip to content

Comments

[WEB-5088] feat: Power K v2#7905

Merged
pushya22 merged 118 commits intopreviewfrom
feat/power-k-v2
Oct 29, 2025
Merged

[WEB-5088] feat: Power K v2#7905
pushya22 merged 118 commits intopreviewfrom
feat/power-k-v2

Conversation

@aaryan610
Copy link
Member

@aaryan610 aaryan610 commented Oct 4, 2025

Description

This PR brings a bunch of improvements to Power K.

Type of Change

  • Improvement (change that would cause existing functionality to not work as expected)

Summary by CodeRabbit

  • New Features
    • Introduced "Power K": a redesigned command palette with keyboard shortcuts, contextual actions, menus, and modal UI for fast navigation, creation, and preferences.
    • Expanded command sets (navigation, creation, account, help, miscellaneous) and richer context-based actions for work items, pages, modules, cycles, and projects.
    • Search improvements: selective-entity search, new intake search, consistent recency ordering, and clearer grouped search results.

vihar and others added 30 commits September 10, 2025 16:29
…ands, context-aware filtering, and reusable components. Add comprehensive documentation and integration guides. Enhance command execution with a dedicated executor and context provider. Introduce new command types and improve existing command definitions for better usability and maintainability.
cursor[bot]

This comment was marked as outdated.

Merge branch 'preview' of github.com:makeplane/plane into feat/power-k-v2
Copilot AI review requested due to automatic review settings October 27, 2025 14:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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

🧹 Nitpick comments (54)
apps/web/core/components/power-k/ui/modal/shortcuts-root.tsx (1)

23-27: Consider memoizing the commands array for better performance.

The getAllCommandsWithShortcuts() call runs on every render, including when the user types in the search box. Since the commands list doesn't change when the query changes (filtering is handled in ShortcutRenderer), this can be optimized.

Apply this diff to memoize the commands:

+import { useState, Fragment, useMemo } from "react";
-import { useState, Fragment } from "react";
  // store hooks
  const { commandRegistry } = usePowerK();

  // Get all commands from registry
- const allCommandsWithShortcuts = commandRegistry.getAllCommandsWithShortcuts();
+ const allCommandsWithShortcuts = useMemo(
+   () => commandRegistry.getAllCommandsWithShortcuts(),
+   [commandRegistry]
+ );
apps/web/core/components/power-k/ui/pages/issue-selection-page.tsx (2)

12-12: Remove unused import.

The useCommandPalette hook is imported but never used in the component. This appears to be a leftover from the command palette to PowerK migration.

Apply this diff to remove the unused import:

-import { useCommandPalette } from "@/hooks/store/use-command-palette";

54-83: Remove redundant .toString() calls.

The workspaceSlug and projectId parameters are already typed as string | undefined. After the truthiness checks, TypeScript knows they are strings, making the .toString() calls on lines 58, 73, and 77 redundant.

Apply this diff to simplify the code:

     workspaceService
-      .fetchWorkspaceRecents(workspaceSlug.toString(), "issue")
+      .fetchWorkspaceRecents(workspaceSlug, "issue")
       .then((res) =>
         setRecentIssues(res.map((r: TActivityEntityData) => r.entity_data as TIssueEntityData).slice(0, 10))
       )
       .catch(() => setRecentIssues([]));
   }, [workspaceSlug]);

   // Search issues based on search term
   useEffect(() => {
     if (!workspaceSlug || !debouncedSearchTerm) {
       setIssueResults([]);
       return;
     }

     workspaceService
-      .searchEntity(workspaceSlug.toString(), {
+      .searchEntity(workspaceSlug, {
         count: 10,
         query: debouncedSearchTerm,
         query_type: ["issue"],
-        ...(!isWorkspaceLevel && projectId ? { project_id: projectId.toString() } : {}),
+        ...(!isWorkspaceLevel && projectId ? { project_id: projectId } : {}),
       })
apps/web/ce/components/command-palette/modals/issue-level.tsx (1)

87-97: Consider removing redundant optional chaining for consistency.

Since the guard clause on line 87 already ensures both workItemId and workItemDetails are defined, the optional chaining on lines 93 and 95 is redundant.

Apply this diff for consistency:

         <DeleteIssueModal
           handleClose={() => toggleDeleteIssueModal(false)}
           isOpen={isDeleteIssueModalOpen}
           data={workItemDetails}
           onSubmit={() =>
-            handleDeleteIssue(workspaceSlug.toString(), workItemDetails.project_id!, workItemId?.toString())
+            handleDeleteIssue(workspaceSlug.toString(), workItemDetails.project_id!, workItemId.toString())
           }
-          isEpic={workItemDetails?.is_epic}
+          isEpic={workItemDetails.is_epic}
         />
apps/api/plane/app/views/search/base.py (2)

78-109: Add explicit ordering for consistency.

Unlike other filter methods (filter_cycles, filter_modules, etc.), this method doesn't explicitly call .order_by("-created_at") before .distinct(). While the model's Meta ordering might apply, explicit ordering ensures consistency across all filter methods.

Apply this diff to add explicit ordering:

         if workspace_search == "false" and project_id:
             issues = issues.filter(project_id=project_id)
 
-        return issues.distinct().values(
+        return (
+            issues.order_by("-created_at")
+            .distinct()
+            .values(
-            "name",
-            "id",
-            "sequence_id",
-            "project__identifier",
-            "project_id",
-            "workspace__slug",
-        )[:100]
+                "name",
+                "id",
+                "sequence_id",
+                "project__identifier",
+                "project_id",
+                "workspace__slug",
+            )[:100]
+        )

241-276: Add explicit ordering and consider reducing duplication.

This method has two concerns:

  1. Missing explicit ordering: Like filter_issues, this method should call .order_by("-created_at") before .distinct() for consistency with other filter methods.

  2. Code duplication: Lines 242-276 are nearly identical to filter_issues (lines 78-109), differing only in the queryset base (Issue.objects vs Issue.issue_objects) and the additional intake status filter at line 260.

For ordering, apply this diff:

         if workspace_search == "false" and project_id:
             issues = issues.filter(project_id=project_id)
 
         return (
-            issues.distinct()
+            issues.order_by("-created_at")
+            .distinct()
             .values(
                 "name",
                 "id",
                 "sequence_id",
                 "project__identifier",
                 "project_id",
                 "workspace__slug",
             )[:100]
         )

For duplication, consider extracting a helper method:

def _build_issue_query(self, query, slug, project_id, workspace_search, additional_filters=None):
    """Helper to build common issue query logic."""
    fields = ["name", "sequence_id", "project__identifier"]
    q = Q()
    if query:
        for field in fields:
            if field == "sequence_id":
                sequences = re.findall(r"\b\d+\b", query)
                for sequence_id in sequences:
                    q |= Q(**{"sequence_id": sequence_id})
            else:
                q |= Q(**{f"{field}__icontains": query})
    
    queryset = Issue.objects.filter(
        q,
        project__project_projectmember__member=self.request.user,
        project__project_projectmember__is_active=True,
        project__archived_at__isnull=True,
        workspace__slug=slug,
    )
    
    if additional_filters:
        queryset = queryset.filter(additional_filters)
    
    if workspace_search == "false" and project_id:
        queryset = queryset.filter(project_id=project_id)
    
    return queryset

def filter_issues(self, query, slug, project_id, workspace_search):
    issues = self._build_issue_query(query, slug, project_id, workspace_search)
    return issues.order_by("-created_at").distinct().values(...).[:100]

def filter_intakes(self, query, slug, project_id, workspace_search):
    intake_filter = Q(issue_intake__status=0) | Q(issue_intake__status=-2)
    issues = self._build_issue_query(query, slug, project_id, workspace_search, intake_filter)
    return issues.order_by("-created_at").distinct().values(...).[:100]
apps/web/ce/store/power-k.store.ts (1)

7-11: Verify the empty makeObservable configuration.

The makeObservable(this, {}) call with an empty configuration object is unusual. Since PowerKStore extends BasePowerKStore and adds no additional properties, this call may be unnecessary.

If no additional observables are needed in this subclass, consider removing the makeObservable call:

 export class PowerKStore extends BasePowerKStore implements IPowerKStore {
   constructor() {
     super();
-    makeObservable(this, {});
   }
 }

If future observables are planned, this is fine to keep.

apps/web/core/components/power-k/ui/pages/open-entity/projects-menu.tsx (1)

21-23: Consider using a type guard for clearer type narrowing.

The filter predicate doesn't provide explicit type narrowing. While functional, this could be more explicit.

Apply this diff for better type safety:

-  const projectsList = joinedProjectIds
-    ? joinedProjectIds.map((id) => getPartialProjectById(id)).filter((project) => project !== undefined)
-    : [];
+  const projectsList = joinedProjectIds
+    ? joinedProjectIds.map((id) => getPartialProjectById(id)).filter((project): project is IPartialProject => project !== undefined)
+    : [];

This makes the type narrowing explicit and ensures TypeScript understands the filtered array contains no undefined values.

apps/web/core/components/power-k/ui/pages/context-based/work-item/states-menu.tsx (1)

34-40: Remove redundant ?? undefined on optional properties.

Lines 36-37 use ?? undefined on properties that are already optional in the TIssue type, which is redundant.

Apply this diff to simplify:

     <Command.Group>
       <PowerKProjectStatesMenuItems
         {...props}
-        projectId={workItemDetails.project_id ?? undefined}
-        selectedStateId={workItemDetails.state_id ?? undefined}
+        projectId={workItemDetails.project_id}
+        selectedStateId={workItemDetails.state_id}
         states={filteredProjectStates}
         workspaceSlug={workspaceSlug?.toString()}
       />
     </Command.Group>
apps/web/core/components/power-k/config/help-commands.ts (1)

26-26: Normalize action signatures to accept ctx (future-proof with strictFunctionTypes).

Using (_ctx) keeps types robust if stricter TS settings are enabled later. Behavior unchanged.

-      action: () => toggleShortcutsListModal(true),
+      action: (_ctx) => toggleShortcutsListModal(true),
@@
-      action: () => {
+      action: (_ctx) => {
@@
-      action: () => {
+      action: (_ctx) => {
@@
-      action: () => {
+      action: (_ctx) => {
@@
-      action: () => toggleIntercom(true),
+      action: (_ctx) => toggleIntercom(true),

Also applies to: 37-37, 50-50, 63-63, 76-76

apps/web/core/components/power-k/ui/pages/open-entity/project-modules-menu.tsx (1)

24-28: Coerce fetched flag and narrow modules list type.

Prevent spinner due to undefined and provide IModule[] to menu.

-  const isFetched = projectId ? fetchedMap[projectId] : false;
+  const isFetched = projectId ? Boolean(fetchedMap[projectId]) : false;
@@
-  const modulesList = projectModuleIds
-    ? projectModuleIds.map((moduleId) => getModuleById(moduleId)).filter((module) => !!module)
-    : [];
+  const modulesList: IModule[] = projectModuleIds
+    ? projectModuleIds
+        .map((moduleId) => getModuleById(moduleId))
+        .filter((m): m is IModule => Boolean(m))
+    : [];
apps/web/core/components/power-k/ui/pages/context-based/work-item/estimates-menu.tsx (1)

65-66: Internationalize empty-state text.

Replace hard-coded "No estimate found" with a t(...) key for consistency.

-        <div className="text-center">No estimate found</div>
+        <div className="text-center">{t("power_k.estimates.no_estimate_found")}</div>

If this key doesn’t exist yet, please add it to i18n resources or reuse an existing equivalent.

apps/web/core/components/power-k/config/miscellaneous-commands.ts (1)

36-61: Stabilize commands array and align action signatures.

Return a memoized array and accept (_ctx) in actions to keep types future-proof.

-import { useCallback } from "react";
+import { useCallback, useMemo } from "react";
@@
-  return [
+  return useMemo(
+    () => [
     {
       id: "toggle_app_sidebar",
@@
-      action: () => toggleSidebar(),
+      action: (_ctx) => toggleSidebar(),
@@
     },
     {
       id: "copy_current_page_url",
@@
-      action: copyCurrentPageUrlToClipboard,
+      action: (_ctx) => copyCurrentPageUrlToClipboard(),
@@
-    },
-  ];
+    },
+  ],
+  [toggleSidebar, copyCurrentPageUrlToClipboard]);
apps/web/core/components/power-k/config/creation/command.ts (1)

68-68: Normalize action callbacks to accept ctx.

Keeps the config compatible with stricter TS settings and consistent with type definition.

-      action: () => toggleCreateIssueModal(true),
+      action: (_ctx) => toggleCreateIssueModal(true),
@@
-      action: () => toggleCreatePageModal({ isOpen: true }),
+      action: (_ctx) => toggleCreatePageModal({ isOpen: true }),
@@
-      action: () => toggleCreateViewModal(true),
+      action: (_ctx) => toggleCreateViewModal(true),
@@
-      action: () => toggleCreateCycleModal(true),
+      action: (_ctx) => toggleCreateCycleModal(true),
@@
-      action: () => toggleCreateModuleModal(true),
+      action: (_ctx) => toggleCreateModuleModal(true),
@@
-      action: () => toggleCreateProjectModal(true),
+      action: (_ctx) => toggleCreateProjectModal(true),

Also applies to: 80-80, 93-93, 108-108, 121-121, 134-134

apps/web/core/components/power-k/ui/pages/context-based/cycle/commands.ts (2)

33-44: Handle async favorite toggling and localize error

If store methods are async, current try/catch won’t catch rejections. Also error text isn’t localized.

-  const toggleFavorite = useCallback(() => {
+  const toggleFavorite = useCallback(async () => {
     if (!workspaceSlug || !cycleDetails || !cycleDetails.project_id) return;
     try {
-      if (isFavorite) removeCycleFromFavorites(workspaceSlug.toString(), cycleDetails.project_id, cycleDetails.id);
-      else addCycleToFavorites(workspaceSlug.toString(), cycleDetails.project_id, cycleDetails.id);
-    } catch {
+      if (isFavorite)
+        await removeCycleFromFavorites(workspaceSlug.toString(), cycleDetails.project_id, cycleDetails.id);
+      else
+        await addCycleToFavorites(workspaceSlug.toString(), cycleDetails.project_id, cycleDetails.id);
+    } catch {
       setToast({
         type: TOAST_TYPE.ERROR,
-        title: "Some error occurred",
+        title: t("common.error_generic"),
       });
     }
-  }, [addCycleFromFavorites, removeCycleFromFavorites, workspaceSlug, cycleDetails, isFavorite]);
+  }, [addCycleToFavorites, removeCycleFromFavorites, workspaceSlug, cycleDetails, isFavorite, t]);

46-62: Avoid suppressed deps; simplify URL; add t to deps

No need to construct URL; also keep hook deps correct and remove ESLint disable.

-  const copyCycleUrlToClipboard = useCallback(() => {
-    const url = new URL(window.location.href);
-    copyTextToClipboard(url.href)
+  const copyCycleUrlToClipboard = useCallback(() => {
+    copyTextToClipboard(window.location.href)
       .then(() => {
         setToast({
           type: TOAST_TYPE.SUCCESS,
           title: t("power_k.contextual_actions.cycle.copy_url_toast_success"),
         });
       })
       .catch(() => {
         setToast({
           type: TOAST_TYPE.ERROR,
           title: t("power_k.contextual_actions.cycle.copy_url_toast_error"),
         });
       });
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, []);
+  }, [t]);
apps/web/core/components/power-k/ui/pages/context-based/page/commands.ts (3)

52-63: Handle async favorite toggling and localize error

Same concern as cycle actions: catch async errors and avoid hardcoded copy.

-  const toggleFavorite = useCallback(() => {
+  const toggleFavorite = useCallback(async () => {
     try {
-      if (isFavorite) removePageFromFavorites?.();
-      else addToFavorites?.();
-    } catch {
+      if (isFavorite) await removePageFromFavorites?.();
+      else await addToFavorites?.();
+    } catch {
       setToast({
         type: TOAST_TYPE.ERROR,
-        title: "Some error occurred",
+        title: t("common.error_generic"),
       });
     }
-  }, [addToFavorites, removePageFromFavorites, isFavorite]);
+  }, [addToFavorites, removePageFromFavorites, isFavorite, t]);

64-81: Avoid suppressed deps; simplify URL; add t to deps

Keep callback stable with correct dependencies; no need for new URL().

-  const copyPageUrlToClipboard = useCallback(() => {
-    const url = new URL(window.location.href);
-    copyTextToClipboard(url.href)
+  const copyPageUrlToClipboard = useCallback(() => {
+    copyTextToClipboard(window.location.href)
       .then(() => {
         setToast({
           type: TOAST_TYPE.SUCCESS,
           title: t("power_k.contextual_actions.page.copy_url_toast_success"),
         });
       })
       .catch(() => {
         setToast({
           type: TOAST_TYPE.ERROR,
           title: t("power_k.contextual_actions.page.copy_url_toast_error"),
         });
       });
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, []);
+  }, [t]);

170-181: DRY: extract “copy current URL” helper

Cycle and page implement identical copy‑URL logic and toasts. Consider a shared utility/hook (e.g., useCopyCurrentUrlToClipboard(tKeySuccess, tKeyError)).

apps/web/core/components/power-k/ui/modal/command-item-shortcut-badge.tsx (3)

7-12: SSR/hydration: platform detection can mismatch on server vs client

Server render can’t know client OS; text may differ after hydration (Ctrl → ⌘). Consider defaulting to a neutral token on SSR and switching post‑mount, or wrap dynamic text with suppressHydrationWarning.


104-105: i18n: “then” should be localized

Use translations instead of hardcoded English.

-          {index < chars.length - 1 && <span className="text-[10px] text-custom-text-400">then</span>}
+          {index < chars.length - 1 && <span className="text-[10px] text-custom-text-400">{/* t("power_k.then") */}then</span>}

If acceptable, I can wire useTranslation and a proper key.


85-90: Dead code: formatter unused by badge

formatKeySequenceForDisplay isn’t used by KeySequenceBadge. Either use it for display or remove the export to avoid duplication.

apps/web/core/components/power-k/ui/pages/preferences/root.tsx (1)

21-28: Consider a more scalable rendering pattern.

The multiple if statements work correctly but could become harder to maintain as more preference pages are added. Consider using a mapping approach for better scalability.

Optional refactor using a component map:

+const PREFERENCE_MENUS: Record<TPowerKPageType, React.ComponentType<{ onSelect: (data: unknown) => void }>> = {
+  "update-theme": PowerKPreferencesThemesMenu,
+  "update-timezone": PowerKPreferencesTimezonesMenu,
+  "update-start-of-week": PowerKPreferencesStartOfWeekMenu,
+  "update-language": PowerKPreferencesLanguagesMenu,
+};
+
 export const PowerKAccountPreferencesPages: React.FC<Props> = observer((props) => {
   const { activePage, handleSelection } = props;
+  
+  if (!activePage) return null;
+  const MenuComponent = PREFERENCE_MENUS[activePage];
+  if (!MenuComponent) return null;

   return (
-    <>
-      {activePage === "update-theme" && <PowerKPreferencesThemesMenu onSelect={handleSelection} />}
-      {activePage === "update-timezone" && <PowerKPreferencesTimezonesMenu onSelect={handleSelection} />}
-      {activePage === "update-start-of-week" && <PowerKPreferencesStartOfWeekMenu onSelect={handleSelection} />}
-      {activePage === "update-language" && <PowerKPreferencesLanguagesMenu onSelect={handleSelection} />}
-    </>
+    <MenuComponent onSelect={handleSelection} />
   );
 });
apps/web/core/components/power-k/menus/views.tsx (1)

16-26: Consider using view-specific logos for consistency.

The component currently uses a fixed Layers icon for all views. However, IProjectView includes a logo_props field that could provide custom logos for each view. This would be consistent with PowerKProjectsMenu which respects project.logo_props.

Apply this diff to use view-specific logos:

+"use client";
+
+import React from "react";
+import { observer } from "mobx-react";
-import { Layers } from "lucide-react";
+// components
+import { Logo } from "@/components/common/logo";
 // plane imports
 import type { IProjectView } from "@plane/types";
 // local imports
 import { PowerKMenuBuilder } from "./builder";

 type Props = {
   views: IProjectView[];
   onSelect: (view: IProjectView) => void;
 };

 export const PowerKViewsMenu: React.FC<Props> = observer(({ views, onSelect }) => (
   <PowerKMenuBuilder
     items={views}
     getKey={(view) => view.id}
-    getIcon={() => Layers}
+    getIconNode={(view) => (
+      <span className="shrink-0">
+        <Logo logo={view.logo_props} size={14} />
+      </span>
+    )}
     getValue={(view) => view.name}
     getLabel={(view) => view.name}
     onSelect={onSelect}
     emptyText="No views found"
   />
 ));
apps/web/core/components/power-k/ui/pages/context-based/work-item/priorities-menu.tsx (1)

21-31: Optional: Use PowerKMenuBuilder for consistency with other menus.

Keeps item wiring standardized and reduces duplication.

apps/web/ce/components/command-palette/power-k/context-detector.ts (2)

1-1: Avoid importing types from Next.js internals.

next/dist/... is private API and may break across Next upgrades. Replace with a local alias.

Example:

-import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
+type Params = Record<string, string | string[] | undefined>;

5-5: Stub always returns null by type design.

With TPowerKContextTypeExtended = never, this can never return a non-null. Add a TODO and update the type once extended contexts exist.

apps/web/core/components/power-k/menus/settings.tsx (1)

8-13: Type name shadowing and overly strict icon typing.

This local TSettingItem collides in name with settings/sidebar’s TSettingItem and requires an icon, whereas other modules make it optional and sometimes a ReactNode. Rename and make icon optional for interoperability.

Apply:

-type TSettingItem = {
+type TPowerKSettingItem = {
   key: string;
-  icon: React.ComponentType<{ className?: string }>;
+  icon?: React.ComponentType<{ className?: string }>;
   label: string;
   href: string;
 };
 
-type Props = {
-  settings: TSettingItem[];
-  onSelect: (setting: TSettingItem) => void;
+type Props = {
+  settings: TPowerKSettingItem[];
+  onSelect: (setting: TPowerKSettingItem) => void;
 };
 
 export const PowerKSettingsMenu: React.FC<Props> = observer(({ settings, onSelect }) => (
   <PowerKMenuBuilder
     items={settings}
     getKey={(setting) => setting.key}
     getIcon={(setting) => setting.icon}
     getValue={(setting) => setting.label}
     getLabel={(setting) => setting.label}
     onSelect={onSelect}
     emptyText="No settings found"
   />
 ));
apps/web/app/(all)/[workspaceSlug]/(projects)/layout.tsx (1)

12-13: Provider ordering: consider mounting after auth/workspace guards.

Mounting ProjectsAppPowerKProvider before WorkspaceAuthWrapper may initialize context before user/workspace is resolved. Likely safe due to internal checks, but moving it inside the wrapper avoids unnecessary work on redirecting routes.

apps/web/core/components/power-k/config/creation/root.ts (1)

7-18: Consider simplifying the type annotation.

The explicit type annotation on line 8 is redundant since usePowerKCreationCommandsRecord() already returns Record<TPowerKCreationCommandKeys, TPowerKCommandConfig>.

You can simplify it:

 export const usePowerKCreationCommands = (): TPowerKCommandConfig[] => {
-  const optionsList: Record<TPowerKCreationCommandKeys, TPowerKCommandConfig> = usePowerKCreationCommandsRecord();
+  const optionsList = usePowerKCreationCommandsRecord();
   return [
     optionsList["create_work_item"],
apps/web/core/components/power-k/ui/modal/command-item.tsx (2)

9-19: Consider making value prop required for consistent search behavior.

The value prop is optional, but cmdk relies on it for search and filtering functionality. Without a value, the item won't be searchable in the command palette. Most use cases will need this prop.

Consider making it required or defaulting to label when not provided:

 type Props = {
   icon?: React.ComponentType<{ className?: string }>;
   iconNode?: React.ReactNode;
   isDisabled?: boolean;
   isSelected?: boolean;
   keySequence?: string;
   label: string | React.ReactNode;
   onSelect: () => void;
   shortcut?: string;
-  value?: string;
+  value: string;
 };

Alternatively, default the value:

 export const PowerKModalCommandItem: React.FC<Props> = (props) => {
-  const { icon: Icon, iconNode, isDisabled, isSelected, keySequence, label, onSelect, shortcut, value } = props;
+  const { icon: Icon, iconNode, isDisabled, isSelected, keySequence, label, onSelect, shortcut, value = typeof label === 'string' ? label : '' } = props;

31-32: Both icon and iconNode can render simultaneously.

If both icon and iconNode props are provided, they'll both render. While this may be intentional for flexibility, it could lead to visual confusion.

Consider adding a comment documenting this behavior, or add a guard:

       >
-        {Icon && <Icon className="shrink-0 size-3.5" />}
-        {iconNode}
+        {Icon ? <Icon className="shrink-0 size-3.5" /> : iconNode}
         {label}
apps/web/core/components/power-k/ui/modal/footer.tsx (1)

20-33: Consider removing the empty spacer div.

The empty <div /> on line 22 serves as a spacer for the justify-between layout but could be simplified using justify-end instead.

Apply this diff:

   return (
-    <div className="w-full flex items-center justify-between px-4 py-2 border-t border-custom-border-200 bg-custom-background-90/80 rounded-b-lg">
-      <div />
+    <div className="w-full flex items-center justify-end px-4 py-2 border-t border-custom-border-200 bg-custom-background-90/80 rounded-b-lg">
       <div className="flex items-center gap-2">
apps/web/core/components/power-k/config/navigation/root.ts (1)

7-45: Consider adding type safety for the key ordering.

The manual construction of the ordered array is clear and explicit about command priority. However, it's vulnerable to refactoring errors—if a key is renamed in TPowerKNavigationCommandKeys, this code will silently fail at runtime rather than produce a compile-time error.

Consider using a typed key array to maintain both the ordering and type safety:

export const usePowerKNavigationCommands = (): TPowerKCommandConfig[] => {
  const optionsList = usePowerKNavigationCommandsRecord();
  
  const orderedKeys: TPowerKNavigationCommandKeys[] = [
    "open_project_cycle",
    "open_project_module",
    // ... rest of keys
  ];
  
  return orderedKeys.map(key => optionsList[key]);
};

This approach provides the same explicit ordering while gaining compile-time validation that all keys exist in the type.

apps/web/core/components/power-k/ui/pages/open-entity/project-settings-menu.tsx (1)

26-36: Simplify the toString() conversions.

The filtering already checks that workspaceSlug and projectId exist (lines 28-29), but the subsequent toString() calls on lines 33-34 suggest these values might be arrays or undefined. Since the filter ensures they're truthy, the toString() conversions are redundant and could be simplified.

Consider this refactor:

  const settingsList = Object.values(PROJECT_SETTINGS).filter(
    (setting) =>
      context.params.workspaceSlug &&
      context.params.projectId &&
      allowPermissions(
        setting.access,
        EUserPermissionsLevel.PROJECT,
-       context.params.workspaceSlug?.toString(),
-       context.params.projectId?.toString()
+       String(context.params.workspaceSlug),
+       String(context.params.projectId)
      )
  );
apps/web/core/components/power-k/ui/pages/preferences/themes-menu.tsx (1)

32-34: Consider adding the value prop for search functionality.

PowerKModalCommandItem likely uses the value prop for command palette search and filtering. Without it, the theme options may not be searchable within the command palette.

Apply this diff:

  {THEME_OPTIONS.map((theme) => (
-   <PowerKModalCommandItem key={theme.value} onSelect={() => onSelect(theme.value)} label={t(theme.i18n_label)} />
+   <PowerKModalCommandItem 
+     key={theme.value} 
+     value={theme.value}
+     onSelect={() => onSelect(theme.value)} 
+     label={t(theme.i18n_label)} 
+   />
  ))}
apps/web/core/components/power-k/menus/members.tsx (1)

27-47: Explicitly return null for missing member details.

Line 29 implicitly returns undefined when memberDetails is falsy. While React handles this, explicitly returning null improves code clarity and intent.

Apply this diff:

  {userIds?.map((memberId) => {
    const memberDetails = getUserDetails(memberId);
-   if (!memberDetails) return;
+   if (!memberDetails) return null;

    return (
apps/web/core/components/power-k/projects-app-provider.tsx (1)

43-43: Simplify the type and remove redundant null coalescing.

The projectId variable is typed as string | string[] | undefined | null, but the ?? null operator is unnecessary since the type already permits undefined. The nullish coalescing only converts undefined to null without adding value.

Apply this diff:

-  const projectId: string | string[] | undefined | null = routerProjectId ?? workItemDetails?.project_id;
+  const projectId: string | string[] | undefined = routerProjectId ?? workItemDetails?.project_id;
apps/web/core/components/power-k/ui/pages/context-based/module/root.tsx (1)

37-42: Simplify the userIds prop.

Line 40 uses projectMemberIds ?? undefined, but projectMemberIds is already an array (either empty [] or populated) based on line 30. Since arrays are truthy, the nullish coalescing to undefined will never trigger. You can pass projectMemberIds directly.

Apply this diff:

       <PowerKMembersMenu
         handleSelect={handleSelection}
-        userIds={projectMemberIds ?? undefined}
+        userIds={projectMemberIds}
         value={moduleDetails.member_ids}
       />
apps/web/core/components/power-k/ui/modal/search-results.tsx (1)

58-63: Document or remove commented code.

The commented alternative implementation suggests either:

  • An incomplete feature that should be completed
  • Dead code from a previous approach that should be removed

Consider adding a TODO comment explaining the intention, or remove the commented code if it's no longer needed.

apps/web/core/components/power-k/ui/modal/header.tsx (1)

49-56: Add button type and accessibility label.

The clear button is missing type="button" and an aria-label attribute for screen readers.

Apply this diff:

         {searchTerm && (
           <button
+            type="button"
             onClick={() => onSearchChange("")}
             className="flex-shrink-0 rounded p-1 text-custom-text-400 hover:bg-custom-background-80 hover:text-custom-text-200"
+            aria-label="Clear search"
           >
             <X className="h-3 w-3" />
           </button>
apps/web/core/components/power-k/ui/pages/open-entity/workspace-settings-menu.tsx (1)

33-37: Consider optional chaining for icon lookup.

If a setting key doesn't have a corresponding icon in WORKSPACE_SETTINGS_ICONS, line 36 will be undefined. Consider adding optional chaining or a fallback icon to prevent potential runtime issues.

   const settingsListWithIcons = settingsList.map((setting) => ({
     ...setting,
     label: t(setting.i18n_label),
-    icon: WORKSPACE_SETTINGS_ICONS[setting.key as keyof typeof WORKSPACE_SETTINGS_ICONS],
+    icon: WORKSPACE_SETTINGS_ICONS[setting.key as keyof typeof WORKSPACE_SETTINGS_ICONS] ?? undefined,
   }));
apps/web/core/components/power-k/ui/modal/context-indicator.tsx (1)

33-42: Consider tabIndex for keyboard accessibility.

The clear button has tabIndex={-1}, which removes it from the keyboard tab order. While the button mentions "Backspace" as a shortcut in its label, users who rely on keyboard navigation but aren't aware of the shortcut may have difficulty clearing the context.

Consider either:

  1. Removing tabIndex={-1} to make the button accessible via Tab navigation
  2. Ensuring the Backspace shortcut is prominently documented and consistently implemented
         <button
           type="button"
           onClick={handleClearContext}
           className="shrink-0 grid place-items-center p-1 text-custom-text-200 hover:text-custom-text-100 transition-colors"
           title="Clear context (Backspace)"
           aria-label="Clear context (Backspace)"
-          tabIndex={-1}
         >
           <X className="size-2.5" />
         </button>
apps/web/core/components/power-k/config/preferences-commands.ts (1)

21-32: Consider adding success toast for consistency.

The handleUpdateTheme function only shows an error toast on failure (line 25-28), while the other update handlers show both success and error toasts. For consistent user feedback, consider adding a success toast after theme update.

   const handleUpdateTheme = useCallback(
     async (newTheme: string) => {
       setTheme(newTheme);
-      return updateUserTheme({ theme: newTheme }).catch(() => {
+      return updateUserTheme({ theme: newTheme })
+        .then(() => {
+          setToast({
+            type: TOAST_TYPE.SUCCESS,
+            title: "Theme updated successfully",
+          });
+        })
+        .catch(() => {
           setToast({
             type: TOAST_TYPE.ERROR,
             title: "Failed to save user theme settings!",
           });
         });
     },
     [setTheme, updateUserTheme]
   );
apps/web/core/components/power-k/ui/pages/context-based/work-item/modules-menu.tsx (1)

22-30: Narrow filtered modules to IModule[] and avoid undefined elements

Without a type guard, filteredModulesList remains (IModule | undefined)[], which can violate PowerKModulesMenu’s modules: IModule[] contract. Also, prefer default arrays and show spinner only while IDs are unresolved.

Apply:

-  const projectModuleIds = workItemDetails.project_id ? getProjectModuleIds(workItemDetails.project_id) : undefined;
-  const modulesList = projectModuleIds ? projectModuleIds.map((moduleId) => getModuleById(moduleId)) : undefined;
-  const filteredModulesList = modulesList ? modulesList.filter((module) => !!module) : undefined;
+  const projectModuleIds = workItemDetails.project_id
+    ? getProjectModuleIds(workItemDetails.project_id)
+    : undefined;
+  const modulesList = projectModuleIds?.map((moduleId) => getModuleById(moduleId)) ?? [];
+  const filteredModulesList: IModule[] = modulesList.filter((m): m is IModule => Boolean(m));
 
-  if (!filteredModulesList) return <Spinner />;
+  if (projectModuleIds === undefined) return <Spinner />;
apps/web/core/components/power-k/global-shortcuts.tsx (1)

39-59: Clear registry on unmount to avoid duplicated commands on remount

If this provider remounts, previously registered commands linger. Clear them on cleanup.

  useEffect(() => {
    commandRegistry.clear();
    commandRegistry.registerMultiple(commands);
- }, [commandRegistry, commands]);
+    return () => {
+      commandRegistry.clear();
+    };
+  }, [commandRegistry, commands]);

Optional: drop router from the dependency array in the shortcut handler effect since it isn’t referenced.

apps/web/core/components/power-k/ui/modal/search-menu.tsx (1)

31-36: Prevent race conditions on rapid typing (stale responses)

Older responses can overwrite newer results. Track the last request and ignore stale responses.

-  const debouncedSearchTerm = useDebounce(searchTerm, 500);
+  const debouncedSearchTerm = useDebounce(searchTerm, 500);
+  const [requestId, setRequestId] = useState(0);
 
   useEffect(() => {
     if (activePage || !workspaceSlug) return;
     setIsSearching(true);
 
     if (debouncedSearchTerm) {
+      const currentId = requestId + 1;
+      setRequestId(currentId);
       workspaceService
         .searchWorkspace(workspaceSlug.toString(), {
           ...(projectId ? { project_id: projectId.toString() } : {}),
           search: debouncedSearchTerm,
           workspace_search: !projectId ? true : isWorkspaceLevel,
         })
-        .then((results) => {
+        .then((results) => {
+          if (currentId !== requestId + 1) return; // stale
           setResults(results);
           const count = Object.keys(results.results).reduce(
             (accumulator, key) => results.results[key as keyof typeof results.results]?.length + accumulator,
             0
           );
           setResultsCount(count);
         })
         .catch(() => {
           setResults(WORKSPACE_DEFAULT_SEARCH_RESULT);
           setResultsCount(0);
         })
         .finally(() => setIsSearching(false));

Also applies to: 41-65

apps/web/core/components/power-k/ui/modal/wrapper.tsx.tsx (1)

139-159: Ensure command registry isn’t left with stale entries on modal unmount

If this wrapper unmounts while provider persists, clear or re-register appropriately. Consider clearing on close.

You can no-op if GlobalShortcutsProvider already clears on unmount, but co-locating a cleanup here improves isolation.

apps/web/core/components/power-k/ui/pages/context-based/module/commands.tsx (1)

117-129: Static status icon may mislead.

Icon always shows backlog regardless of target status. Consider a neutral glyph (e.g., Layers/Contrast) or render current module status.

apps/web/core/components/power-k/core/types.ts (1)

1-8: Import React types explicitly to avoid “Cannot find namespace React” in strict TS setups.

Some configs don’t expose the React namespace globally.

 import type { AppRouterProgressInstance } from "@bprogress/next";
+import type React from "react";

Also applies to: 67-74

apps/web/core/components/power-k/ui/modal/search-results-map.tsx (1)

84-99: Page routing fallback looks good; minor readability nit.

Inline selection logic is correct; consider extracting redirectProjectId calculation for clarity. No functional changes needed.

Also applies to: 91-97

apps/web/core/components/power-k/core/registry.ts (3)

36-44: Avoid double action-wrapping.

You both annotate methods as actions via makeObservable and wrap them with action(...). Pick one. Using class methods + makeObservable (diff above) is cleaner.

Also applies to: 50-56, 132-135


5-11: Public commands exposure invites unintended writes.

Consider removing commands from IPowerKCommandRegistry or declaring it as readonly and typed as ReadonlyMap if you must expose it. Prefer encapsulation and mutations only via register/clear.

Please confirm no external code mutates registry.commands. If it does, we should refactor callers to use register/clear.


83-111: Shortcut collisions silently override earlier entries.

If two visible commands share the same shortcut, latter wins with no signal. Add a dev-time warning to surface conflicts.

   this.getVisibleCommands(ctx).forEach((command) => {
     if (command.shortcut) {
-      shortcutMap.set(command.shortcut.toLowerCase(), command.id);
+      const key = command.shortcut.toLowerCase();
+      if (shortcutMap.has(key)) {
+        if (process.env.NODE_ENV !== "production") {
+          // eslint-disable-next-line no-console
+          console.warn(`[PowerK] Duplicate shortcut "${key}" between "${shortcutMap.get(key)}" and "${command.id}"`);
+        }
+      }
+      shortcutMap.set(key, command.id);
     }
   });

Repeat similarly for keySequence and modifierShortcut maps.

Comment on lines +23 to +29
<PowerKModalCommandItem
key={priority.key}
iconNode={<PriorityIcon priority={priority.key} />}
label={priority.title}
isSelected={priority.key === workItemDetails.priority}
onSelect={() => handleSelect(priority.key)}
/>
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

Missing Command.Item value breaks filtering and may fail type-checks.

PowerKModalCommandItem forwards its value to Command.Item; here it’s not provided. Add a stable value (key or title).

Apply:

         <PowerKModalCommandItem
           key={priority.key}
           iconNode={<PriorityIcon priority={priority.key} />}
+          value={priority.title}
           label={priority.title}
           isSelected={priority.key === workItemDetails.priority}
           onSelect={() => handleSelect(priority.key)}
         />
📝 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
<PowerKModalCommandItem
key={priority.key}
iconNode={<PriorityIcon priority={priority.key} />}
label={priority.title}
isSelected={priority.key === workItemDetails.priority}
onSelect={() => handleSelect(priority.key)}
/>
<PowerKModalCommandItem
key={priority.key}
iconNode={<PriorityIcon priority={priority.key} />}
value={priority.title}
label={priority.title}
isSelected={priority.key === workItemDetails.priority}
onSelect={() => handleSelect(priority.key)}
/>
🤖 Prompt for AI Agents
In
apps/web/core/components/power-k/ui/pages/context-based/work-item/priorities-menu.tsx
around lines 23 to 29, the PowerKModalCommandItem is missing a value prop which
breaks Command.Item filtering and may fail type-checks; add a stable value
(e.g., value={priority.key}) to the PowerKModalCommandItem so it forwards a
consistent identifier to Command.Item, ensuring the prop type matches the
expected Command value type.

Comment on lines +55 to +61
{activePage === "update-work-item-assignee" && (
<PowerKMembersMenu
handleSelect={handleSelection}
userIds={projectMemberIds ?? undefined}
value={entityDetails.assignee_ids}
/>
)}
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

Prevent crash in Members menu: ensure value is an array

PowerKMembersMenu calls value.includes; passing undefined can crash.

-          value={entityDetails.assignee_ids}
+          value={entityDetails.assignee_ids ?? []}
📝 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
{activePage === "update-work-item-assignee" && (
<PowerKMembersMenu
handleSelect={handleSelection}
userIds={projectMemberIds ?? undefined}
value={entityDetails.assignee_ids}
/>
)}
{activePage === "update-work-item-assignee" && (
<PowerKMembersMenu
handleSelect={handleSelection}
userIds={projectMemberIds ?? undefined}
value={entityDetails.assignee_ids ?? []}
/>
)}
🤖 Prompt for AI Agents
In apps/web/core/components/power-k/ui/pages/context-based/work-item/root.tsx
around lines 55 to 61, PowerKMembersMenu expects value to be an array and calls
value.includes, so passing undefined can crash; ensure you always pass an array
by normalizing entityDetails.assignee_ids (e.g., coerce to an empty array when
undefined or not an array) before passing it into PowerKMembersMenu so value is
never undefined.

cursor[bot]

This comment was marked as outdated.

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

Caution

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

⚠️ Outside diff range comments (1)
apps/web/ce/components/command-palette/helpers.tsx (1)

91-97: Implement missing /wiki route or verify workspace-level page routing exists.

The code change from /pages/{id} to /wiki/{id} introduces a breaking change: no /wiki route was found in the app router, and the workspace-level pages route also appears to be missing. Additionally, the recents widget component still uses the old /pages/ pattern, indicating an incomplete refactor.

Required verifications:

  1. Confirm the target /wiki/[pageId] route is implemented or will be added in this PR
  2. Verify the old /pages/ workspace route still exists for backward compatibility or is properly redirected
  3. Update the recents widget and any other components using the old path pattern to match the new routing structure
🧹 Nitpick comments (46)
apps/web/ce/store/power-k.store.ts (2)

5-5: Type alias is currently redundant but acceptable for future extensibility.

The IPowerKStore alias doesn't extend or modify IBasePowerKStore, making it functionally redundant. However, this pattern is acceptable for maintaining separate CE/core type hierarchies and allowing future divergence.


7-12: Remove unnecessary makeObservable call.

The makeObservable(this, {}) call with an empty annotations object is unnecessary. In MobX 6+, derived classes only need to call makeObservable when adding new observables or actions. Since PowerKStore doesn't declare any new reactive properties beyond those in BasePowerKStore, this call has no effect.

Apply this diff to remove the redundant call:

 export class PowerKStore extends BasePowerKStore implements IPowerKStore {
   constructor() {
     super();
-    makeObservable(this, {});
   }
 }

If the constructor becomes empty, you can remove it entirely:

 export class PowerKStore extends BasePowerKStore implements IPowerKStore {
-  constructor() {
-    super();
-    makeObservable(this, {});
-  }
 }
+export class PowerKStore extends BasePowerKStore implements IPowerKStore {}
apps/web/core/components/power-k/ui/modal/shortcuts-root.tsx (1)

23-27: Consider memoizing the commands list.

The allCommandsWithShortcuts is recomputed on every render. While this is likely not a performance issue for a modal, consider using useMemo to avoid unnecessary recalculations:

+ import { useState, Fragment, useMemo } from "react";
  // store hooks
  const { commandRegistry } = usePowerK();

  // Get all commands from registry
- const allCommandsWithShortcuts = commandRegistry.getAllCommandsWithShortcuts();
+ const allCommandsWithShortcuts = useMemo(
+   () => commandRegistry.getAllCommandsWithShortcuts(),
+   [commandRegistry]
+ );
apps/web/ce/components/command-palette/modals/issue-level.tsx (1)

87-96: Consider removing redundant null checks.

Since line 87's condition already verifies workItemId, workItemDetails, and workItemDetails.project_id exist, the optional chaining and non-null assertions on lines 93 and 95 are unnecessary.

Apply this diff to simplify:

         <DeleteIssueModal
           handleClose={() => toggleDeleteIssueModal(false)}
           isOpen={isDeleteIssueModalOpen}
           data={workItemDetails}
           onSubmit={() =>
-            handleDeleteIssue(workspaceSlug.toString(), workItemDetails.project_id!, workItemId?.toString())
+            handleDeleteIssue(workspaceSlug.toString(), workItemDetails.project_id, workItemId.toString())
           }
-          isEpic={workItemDetails?.is_epic}
+          isEpic={workItemDetails.is_epic}
         />
apps/web/core/components/power-k/config/help-commands.ts (1)

18-81: Consider extracting repeated predicates.

All five commands use identical isEnabled: () => true, isVisible: () => true, and closeOnSelect: true. While this is clear as-is, extracting these as constants would reduce duplication and make future updates easier.

const ALWAYS_ENABLED = () => true;
const ALWAYS_VISIBLE = () => true;

export const usePowerKHelpCommands = (): TPowerKCommandConfig[] => {
  // ... existing code ...
  
  return [
    {
      id: "open_keyboard_shortcuts",
      // ... other props ...
      isEnabled: ALWAYS_ENABLED,
      isVisible: ALWAYS_VISIBLE,
      closeOnSelect: true,
    },
    // ... other commands with same pattern
  ];
};
apps/web/core/components/power-k/config/preferences-commands.ts (1)

76-137: Consider extracting repeated predicates.

Similar to other command configuration files, all four commands use identical isEnabled: () => true, isVisible: () => true, and closeOnSelect: true patterns. Extracting these as shared constants would reduce duplication.

apps/web/core/components/power-k/ui/pages/context-based/cycle/commands.ts (1)

17-25: Harden param parsing and simplify URL copy.

Normalize cycleId that may be string[]; copy window.location.href directly.

-  const { workspaceSlug, cycleId } = useParams();
+  const { workspaceSlug, cycleId } = useParams();
+  const normalizedCycleId = Array.isArray(cycleId) ? cycleId[0] : cycleId;
-  const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : null;
+  const cycleDetails = normalizedCycleId ? getCycleById(normalizedCycleId) : null;
-  const copyCycleUrlToClipboard = useCallback(() => {
-    const url = new URL(window.location.href);
-    copyTextToClipboard(url.href)
+  const copyCycleUrlToClipboard = useCallback(() => {
+    copyTextToClipboard(window.location.href)
       .then(() => {
         setToast({
           type: TOAST_TYPE.SUCCESS,
           title: t("power_k.contextual_actions.cycle.copy_url_toast_success"),
         });
       })

Also applies to: 46-62

apps/web/core/components/power-k/ui/pages/context-based/work-item/estimates-menu.tsx (3)

42-44: Select “No estimate” for null or undefined.

Covers items with no estimate set yet.

-        isSelected={workItemDetails.estimate_point === null}
+        isSelected={workItemDetails.estimate_point == null}

55-58: Guard TIME value parsing to avoid NaN.

Ensure a numeric fallback when estimatePoint.value is null/empty.

-                  ? convertMinutesToHoursMinutesString(Number(estimatePoint.value))
+                  ? convertMinutesToHoursMinutesString(Number(estimatePoint.value ?? 0))
                   : estimatePoint.value

65-66: Localize empty state.

Replace hard‑coded “No estimate found” with i18n.

-        <div className="text-center">No estimate found</div>
+        <div className="text-center">{t("project_settings.estimates.no_estimate_found")}</div>

If this key doesn’t exist, add it to i18n or reuse an existing common empty-state key.

apps/web/core/components/power-k/ui/modal/command-item-shortcut-badge.tsx (3)

92-105: Make “then” label translatable or injectable.

Avoid hard‑coded English; allow callers to pass a localized label.

-export const KeySequenceBadge = ({ sequence }: { sequence: string | undefined }) => {
+type KeySequenceBadgeProps = { sequence: string | undefined; thenLabel?: string };
+export const KeySequenceBadge = ({ sequence, thenLabel = "then" }: KeySequenceBadgeProps) => {
@@
-          {index < chars.length - 1 && <span className="text-[10px] text-custom-text-400">then</span>}
+          {index < chars.length - 1 && <span className="text-[10px] text-custom-text-400">{thenLabel}</span>}

Then pass t("common.then") from the caller (e.g., PowerKModalCommandItem).


64-78: Render tokens, not characters, for multi-letter keys.

“Ctrl”, “Alt” etc. render as four separate keycaps; prefer single-keycap per token.

Consider splitting formatted string into tokens (e.g., using a regex that groups symbols vs words) and render one per token instead of per character. I can provide a concrete snippet if you want to adopt this.


27-33: Optional: distinguish Delete vs Backspace on macOS.

Both map to “⌫”; forward delete is “⌦”. If you care about accuracy, map “delete” → “⌦” and “backspace” → “⌫”.

apps/web/core/components/power-k/ui/pages/context-based/work-item/commands.ts (2)

141-157: Guard copy ID; avoid copying “undefined-undefined”.

Show error when data missing; keep success path otherwise.

-  const copyWorkItemIdToClipboard = useCallback(() => {
-    const id = `${projectDetails?.identifier}-${entityDetails?.sequence_id}`;
-    copyTextToClipboard(id)
+  const copyWorkItemIdToClipboard = useCallback(() => {
+    const identifier = projectDetails?.identifier;
+    const seq = entityDetails?.sequence_id;
+    if (!identifier || !seq) {
+      setToast({ type: TOAST_TYPE.ERROR, title: t("toast.error"), message: t("common.error.message") });
+      return;
+    }
+    const id = `${identifier}-${seq}`;
+    copyTextToClipboard(id)
       .then(() => {
         setToast({
           type: TOAST_TYPE.SUCCESS,
           title: t("power_k.contextual_actions.work_item.copy_id_toast_success"),
         });
       })
@@
-  }, [entityDetails?.sequence_id, projectDetails?.identifier]);
+  }, [entityDetails?.sequence_id, projectDetails?.identifier, t]);

176-192: Minor: simplify URL copy.

No need to construct URL instance for window.location.href.

-    const url = new URL(window.location.href);
-    copyTextToClipboard(url.href)
+    copyTextToClipboard(window.location.href)
apps/web/core/components/power-k/config/creation/command.ts (1)

60-72: Optional: add keywords for discoverability.

Add keywords to creation commands (e.g., ["new", "add", "create"]) to improve search relevance in Power‑K.

apps/web/core/components/power-k/config/creation/root.ts (1)

7-18: Memoize and derive from a single keys source to avoid redundant work and accidental omissions

Returning a new array every render can cause avoidable re-renders downstream. Also, enumerating keys inline risks drift. Prefer a single ordered key list + useMemo.

+import { useMemo } from "react";
 // types
 import type { TPowerKCommandConfig } from "@/components/power-k/core/types";
 // local imports
 import { usePowerKCreationCommandsRecord } from "./command";
 import type { TPowerKCreationCommandKeys } from "./command";

 export const usePowerKCreationCommands = (): TPowerKCommandConfig[] => {
-  const optionsList: Record<TPowerKCreationCommandKeys, TPowerKCommandConfig> = usePowerKCreationCommandsRecord();
-  return [
-    optionsList["create_work_item"],
-    optionsList["create_page"],
-    optionsList["create_view"],
-    optionsList["create_cycle"],
-    optionsList["create_module"],
-    optionsList["create_project"],
-    optionsList["create_workspace"],
-  ];
+  const optionsList = usePowerKCreationCommandsRecord();
+  const creationOrder: TPowerKCreationCommandKeys[] = [
+    "create_work_item",
+    "create_page",
+    "create_view",
+    "create_cycle",
+    "create_module",
+    "create_project",
+    "create_workspace",
+  ];
+  return useMemo(() => creationOrder.map((k) => optionsList[k]), [optionsList]);
 };
apps/web/core/components/power-k/ui/pages/issue-selection-page.tsx (2)

53-64: Guard against state updates after unmount; add a cancellation flag in the recents effect

Current effect may set state after unmount or after a newer run. Add a simple active flag to prevent stale updates.

   useEffect(() => {
-    if (!workspaceSlug) return;
+    if (!workspaceSlug) return;
+    let active = true;

     workspaceService
       .fetchWorkspaceRecents(workspaceSlug.toString(), "issue")
-      .then((res) =>
-        setRecentIssues(res.map((r: TActivityEntityData) => r.entity_data as TIssueEntityData).slice(0, 10))
-      )
-      .catch(() => setRecentIssues([]));
+      .then((res) => {
+        if (!active) return;
+        setRecentIssues(res.map((r: TActivityEntityData) => r.entity_data as TIssueEntityData).slice(0, 10));
+      })
+      .catch(() => {
+        if (!active) return;
+        setRecentIssues([]);
+      });
+
+    return () => {
+      active = false;
+    };
   }, [workspaceSlug]);

66-84: Debounced search still risks race conditions; ignore stale responses

When searchTerm changes quickly, older responses can overwrite newer ones. Use an active flag or request id to ignore stale results.

   useEffect(() => {
     if (!workspaceSlug || !debouncedSearchTerm) {
       setIssueResults([]);
       return;
     }
+    let active = true;

     workspaceService
       .searchEntity(workspaceSlug.toString(), {
         count: 10,
         query: debouncedSearchTerm,
         query_type: ["issue"],
         ...(!isWorkspaceLevel && projectId ? { project_id: projectId.toString() } : {}),
       })
-      .then((res) => {
-        setIssueResults(res.issue || []);
-      })
-      .catch(() => setIssueResults([]));
+      .then((res) => {
+        if (!active) return;
+        setIssueResults(res.issue || []);
+      })
+      .catch(() => {
+        if (!active) return;
+        setIssueResults([]);
+      });
+
+    return () => {
+      active = false;
+    };
   }, [debouncedSearchTerm, workspaceSlug, projectId, isWorkspaceLevel]);
apps/web/core/components/power-k/config/account-commands.ts (1)

23-32: Add 't' to dependency array or document the omission.

The handleSignOut callback uses t (lines 27-28) but doesn't include it in the dependency array. While t is typically stable, including it ensures correctness and avoids potential stale closure issues.

Apply this diff:

-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [signOut]);
+  }, [signOut, t]);

Alternatively, if the omission is intentional, document why t can be safely excluded.

apps/web/core/components/power-k/config/miscellaneous-commands.ts (1)

18-34: Add 't' to dependency array or document the omission.

The copyCurrentPageUrlToClipboard callback uses t (lines 24, 30) but doesn't include it in the dependency array. While t is typically stable, including it ensures correctness and avoids potential stale closure issues.

Apply this diff:

-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, []);
+  }, [t]);

Alternatively, if the omission is intentional, document why t can be safely excluded.

apps/web/core/components/power-k/ui/pages/context-based/module/commands.tsx (3)

20-28: Normalize route params; stop using .toString() on possibly-array values

Use a small helper and reuse normalized values to avoid subtle ID bugs.

-import { useParams } from "next/navigation";
+import { useParams } from "next/navigation";
@@
-  const { workspaceSlug, projectId, moduleId } = useParams();
+  const { workspaceSlug, projectId, moduleId } = useParams();
+  const normalizeParam = (p: string | string[] | undefined) => (Array.isArray(p) ? p[0] : p);
+  const ws = normalizeParam(workspaceSlug);
+  const pid = normalizeParam(projectId);
+  const mid = normalizeParam(moduleId);
@@
-  const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : null;
+  const moduleDetails = mid ? getModuleById(mid) : null;
@@
-      if (!workspaceSlug || !projectId || !moduleDetails) return;
-      await updateModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleDetails.id, formData).catch(
+      if (!ws || !pid || !moduleDetails) return;
+      await updateModuleDetails(ws, pid, moduleDetails.id, formData).catch(
         () => {

Also applies to: 36-41, 49-50


56-61: Avoid in-place mutation of member_ids; use immutable update

Mutating store-backed arrays can cause subtle MobX issues. Build a new collection.

-      const updatedMembers = moduleDetails.member_ids ?? [];
-      if (updatedMembers.includes(memberId)) updatedMembers.splice(updatedMembers.indexOf(memberId), 1);
-      else updatedMembers.push(memberId);
-
-      handleUpdateModule({ member_ids: updatedMembers });
+      const next = new Set(moduleDetails.member_ids ?? []);
+      next.has(memberId) ? next.delete(memberId) : next.add(memberId);
+      handleUpdateModule({ member_ids: Array.from(next) });

115-121: Show current status icon instead of a static one

Use the module’s current status for clearer context.

-      iconNode: <ModuleStatusIcon status="backlog" className="shrink-0 size-3.5" />,
+      iconNode: <ModuleStatusIcon status={(moduleDetails?.status ?? "backlog") as TModuleStatus} className="shrink-0 size-3.5" />,
apps/web/core/components/power-k/ui/pages/context-based/module/status-menu.tsx (1)

26-33: Provide Command.Item value for proper cmdk filtering

Without value, items may not be searchable/selectable as expected.

         <PowerKModalCommandItem
           key={status.value}
+          value={status.value}
           iconNode={<ModuleStatusIcon status={status.value} className="shrink-0 size-3.5" />}
           label={t(status.i18n_label)}
           isSelected={status.value === value}
           onSelect={() => handleSelect(status.value)}
         />
apps/web/core/components/power-k/hooks/use-context-indicator.ts (1)

21-29: Harden param handling and return null instead of empty string

  • Normalize string|string[] params; avoid .toString() on undefined/arrays.
  • Convert empty string indicators to null.
-  const { workItem: workItemIdentifier, cycleId, moduleId, pageId } = useParams();
+  const { workItem: workItemIdentifier, cycleId, moduleId, pageId } = useParams();
+  const normalizeParam = (p: string | string[] | undefined) => (Array.isArray(p) ? p[0] : p);
@@
     case "work-item": {
-      indicator = workItemIdentifier.toString();
+      indicator = normalizeParam(workItemIdentifier);
       break;
     }
     case "cycle": {
-      const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : null;
+      const cycleDetails = normalizeParam(cycleId) ? getCycleById(normalizeParam(cycleId)!) : null;
       indicator = cycleDetails?.name;
       break;
     }
     case "module": {
-      const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : null;
+      const moduleDetails = normalizeParam(moduleId) ? getModuleById(normalizeParam(moduleId)!) : null;
       indicator = moduleDetails?.name;
       break;
     }
     case "page": {
-      const pageInstance = pageId ? getPageById(pageId.toString()) : null;
+      const pageInstance = normalizeParam(pageId) ? getPageById(normalizeParam(pageId)!) : null;
       indicator = getPageName(pageInstance?.name);
       break;
     }
@@
-  return indicator ?? null;
+  return indicator && indicator.trim() ? indicator : null;

Also applies to: 34-50, 57-58

apps/web/ce/components/command-palette/power-k/search/no-results-command.tsx (1)

1-8: Consider marking as a client component for consistency

Add "use client" to avoid accidental server import regressions if usage changes.

+ "use client";
+
 import { Command } from "cmdk";
 import { Search } from "lucide-react";
apps/web/core/components/power-k/ui/pages/context-based/work-item/modules-menu.tsx (1)

22-24: Type narrowing may require explicit assertion.

The filter on Line 24 removes falsy values, but TypeScript may not automatically narrow (IModule | undefined)[] to IModule[]. Consider using a type predicate or explicit type assertion to ensure type safety downstream.

Apply this diff to add a type predicate for better type safety:

-  const filteredModulesList = modulesList ? modulesList.filter((module) => !!module) : undefined;
+  const filteredModulesList = modulesList ? modulesList.filter((module): module is IModule => !!module) : undefined;
apps/web/core/components/power-k/ui/pages/open-entity/root.tsx (1)

14-34: Consider a map-based approach for better scalability.

The series of conditional renders works correctly but could become harder to maintain as more entity types are added. A lookup map approach would be more scalable:

const ENTITY_MENU_MAP = {
  "open-workspace": (props) => <PowerKOpenWorkspaceMenu {...props} />,
  "open-project": (props) => <PowerKOpenProjectMenu {...props} />,
  // ... etc
} as const;

export const PowerKOpenEntityPages: React.FC<TPowerKOpenEntityActionsProps> = (props) => {
  const { activePage, context, handleSelection } = props;
  
  if (!activePage) return null;
  const MenuComponent = ENTITY_MENU_MAP[activePage];
  
  return MenuComponent ? <MenuComponent context={context} handleSelect={handleSelection} /> : null;
};

However, the current implementation is clear and readable, so this is purely optional.

apps/web/core/components/power-k/ui/pages/context-based/work-item/cycles-menu.tsx (1)

22-24: Type narrowing may require explicit assertion.

Similar to the modules menu, the filter on Line 24 removes falsy values but TypeScript may not automatically narrow (ICycle | undefined)[] to ICycle[]. Use a type predicate for better type safety.

Apply this diff:

-  const filteredCyclesList = cyclesList ? cyclesList.filter((cycle) => !!cycle) : undefined;
+  const filteredCyclesList = cyclesList ? cyclesList.filter((cycle): cycle is ICycle => !!cycle) : undefined;
apps/web/core/components/power-k/ui/pages/open-entity/projects-menu.tsx (1)

21-23: Consider explicit type assertion for clarity.

The filter removes undefined entries, but TypeScript may not automatically narrow the array type. Consider using a type guard or explicit type assertion to ensure projectsList is typed as IPartialProject[].

Apply this diff to improve type safety:

-  const projectsList = joinedProjectIds
-    ? joinedProjectIds.map((id) => getPartialProjectById(id)).filter((project) => project !== undefined)
-    : [];
+  const projectsList = (joinedProjectIds
+    ? joinedProjectIds.map((id) => getPartialProjectById(id)).filter((project): project is IPartialProject => project !== undefined)
+    : []) as IPartialProject[];
apps/web/core/components/power-k/ui/pages/context-based/module/root.tsx (1)

40-40: Remove redundant nullish coalescing.

Line 30 already ensures projectMemberIds defaults to [], so projectMemberIds ?? undefined is unnecessary.

Apply this diff:

-          userIds={projectMemberIds ?? undefined}
+          userIds={projectMemberIds}
apps/web/core/components/power-k/global-shortcuts.tsx (1)

46-59: Effect re-runs frequently due to context dependency.

The context object from the provider's useMemo likely changes on every render where any dependency updates. This causes the shortcut handler to be recreated frequently. While the getter pattern (() => context) is good, you could further optimize by extracting only the necessary primitive dependencies if performance becomes a concern.

apps/web/core/components/power-k/ui/pages/context-based/root.tsx (1)

38-46: Consider validating command ID uniqueness.

While the current implementation aggregates commands from multiple sources, there's no validation to ensure command IDs are unique. If two sources accidentally return commands with the same ID, it could lead to unexpected behavior.

Add a development-time check:

 export const usePowerKContextBasedActions = (): TPowerKCommandConfig[] => {
   const workItemCommands = usePowerKWorkItemContextBasedCommands();
   const cycleCommands = usePowerKCycleContextBasedActions();
   const moduleCommands = usePowerKModuleContextBasedActions();
   const pageCommands = usePowerKPageContextBasedActions();
   const extendedCommands = usePowerKContextBasedExtendedActions();
 
-  return [...workItemCommands, ...cycleCommands, ...moduleCommands, ...pageCommands, ...extendedCommands];
+  const allCommands = [...workItemCommands, ...cycleCommands, ...moduleCommands, ...pageCommands, ...extendedCommands];
+  
+  if (process.env.NODE_ENV === 'development') {
+    const ids = allCommands.map(cmd => cmd.id);
+    const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
+    if (duplicates.length > 0) {
+      console.warn('Duplicate PowerK command IDs detected:', duplicates);
+    }
+  }
+  
+  return allCommands;
 };
apps/web/core/components/power-k/ui/pages/context-based/work-item/states-menu.tsx (1)

32-43: Simplify redundant null coalescing.

The ?? undefined operators on lines 36-37 are redundant since these properties are already optional/nullable in the type definition.

      <PowerKProjectStatesMenuItems
        {...props}
-       projectId={workItemDetails.project_id ?? undefined}
-       selectedStateId={workItemDetails.state_id ?? undefined}
+       projectId={workItemDetails.project_id}
+       selectedStateId={workItemDetails.state_id}
        states={filteredProjectStates}
        workspaceSlug={workspaceSlug?.toString()}
      />
apps/web/core/components/power-k/ui/pages/open-entity/workspace-settings-menu.tsx (2)

3-3: Consider moving WORKSPACE_SETTINGS_ICONS to a shared location.

Importing from the app/(all)/[workspaceSlug]/(settings)/... directory into a core component creates a coupling between the component layer and the app routing structure. If the route structure changes, this import will break.

Move WORKSPACE_SETTINGS_ICONS to a constants file:

// In packages/constants/src/workspace.ts or similar
export const WORKSPACE_SETTINGS_ICONS = {
  general: Building,
  members: Users,
  export: ArrowUpToLine,
  "billing-and-plans": CreditCard,
  webhooks: Webhook,
};

Then import from the constants package:

-import { WORKSPACE_SETTINGS_ICONS } from "app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar";
+import { WORKSPACE_SETTINGS_ICONS } from "@plane/constants";

33-37: Consider adding a fallback icon or validation.

If WORKSPACE_SETTINGS_ICONS is missing a key that exists in WORKSPACE_SETTINGS, the icon will be undefined. While the types currently match, this coupling could break during refactoring.

Add a fallback or development-time warning:

  const settingsListWithIcons = settingsList.map((setting) => ({
    ...setting,
    label: t(setting.i18n_label),
-   icon: WORKSPACE_SETTINGS_ICONS[setting.key as keyof typeof WORKSPACE_SETTINGS_ICONS],
+   icon: WORKSPACE_SETTINGS_ICONS[setting.key as keyof typeof WORKSPACE_SETTINGS_ICONS] || Settings,
  }));
apps/web/core/components/power-k/ui/modal/search-menu.tsx (1)

41-65: Optimize isSearching state updates.

Setting isSearching(true) on line 43 occurs even when no search will be performed (empty debouncedSearchTerm). This causes a brief flash of the loading state before immediately resetting to false on line 63.

Move the state update inside the search condition:

  useEffect(() => {
    if (activePage || !workspaceSlug) return;
-   setIsSearching(true);

    if (debouncedSearchTerm) {
+     setIsSearching(true);
      workspaceService
        .searchWorkspace(workspaceSlug.toString(), {
          ...(projectId ? { project_id: projectId.toString() } : {}),
          search: debouncedSearchTerm,
          workspace_search: !projectId ? true : isWorkspaceLevel,
        })
        .then((results) => {
          setResults(results);
          const count = Object.keys(results.results).reduce(
            (accumulator, key) => results.results[key as keyof typeof results.results]?.length + accumulator,
            0
          );
          setResultsCount(count);
        })
        .finally(() => setIsSearching(false));
    } else {
      setResults(WORKSPACE_DEFAULT_SEARCH_RESULT);
-     setIsSearching(false);
+     setResultsCount(0);
    }
  }, [debouncedSearchTerm, isWorkspaceLevel, projectId, workspaceSlug, activePage]);
apps/web/core/components/power-k/ui/pages/context-based/page/commands.ts (3)

52-62: Localize error toast for consistency.

Hardcoded "Some error occurred" bypasses i18n; prefer t(...) like other toasts.

Apply:

-      setToast({
-        type: TOAST_TYPE.ERROR,
-        title: "Some error occurred",
-      });
+      setToast({
+        type: TOAST_TYPE.ERROR,
+        title: t("common.error_generic"),
+      });

If no suitable key exists, add one to i18n.


64-80: Fix stale translation in copyPageUrlToClipboard deps.

t is used but excluded from deps via eslint disable; this can stale when language changes.

Apply:

-  const copyPageUrlToClipboard = useCallback(() => {
+  const copyPageUrlToClipboard = useCallback(() => {
     const url = new URL(window.location.href);
     copyTextToClipboard(url.href)
       .then(() => {
         setToast({
           type: TOAST_TYPE.SUCCESS,
           title: t("power_k.contextual_actions.page.copy_url_toast_success"),
         });
       })
       .catch(() => {
         setToast({
           type: TOAST_TYPE.ERROR,
           title: t("power_k.contextual_actions.page.copy_url_toast_error"),
         });
       });
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, []);
+  }, [t]);

82-182: Memoize actions array to avoid re‑creating configurations every render.

Stable identities help downstream memoization and avoid needless re-renders.

Example:

-  return [
+  return React.useMemo(() => ([
     { /* ...commands... */ },
     { /* ... */ },
-  ];
+  ]), [
+    isLocked, canCurrentUserLockPage,
+    access, canCurrentUserChangeAccess,
+    archived_at, canCurrentUserArchivePage,
+    isFavorite, canCurrentUserFavoritePage,
+    toggleFavorite, copyPageUrlToClipboard,
+  ]);
apps/web/core/components/power-k/ui/modal/wrapper.tsx.tsx (1)

102-113: Clear the reset timer on unmount to avoid setState after unmount.

Store timer id and clear it in cleanup.

Apply:

-  useEffect(() => {
-    if (!isOpen) {
-      setTimeout(() => {
+  useEffect(() => {
+    let t: number | undefined;
+    if (!isOpen) {
+      t = window.setTimeout(() => {
         setSearchTerm("");
         setActivePage(null);
         context.setActiveCommand(null);
         context.setShouldShowContextBasedActions(true);
-      }, 200);
+      }, 200);
     }
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [isOpen]);
+    return () => {
+      if (t) window.clearTimeout(t);
+    };
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [isOpen]);
apps/web/core/components/power-k/core/shortcut-handler.ts (2)

140-149: Prefer global timers to avoid window coupling.

Using globalThis.setTimeout/clearTimeout works in more environments and eases testing.

Apply:

-    this.sequenceTimeout = window.setTimeout(() => {
+    this.sequenceTimeout = globalThis.setTimeout(() => {
       this.resetSequence();
     }, 1000);

And similarly replace window.clearTimeout with globalThis.clearTimeout in this file.


109-135: Guard against key auto‑repeat advancing sequences.

Holding a key can spam repeats and accidentally trigger sequences.

Add at top of handleKeyOrSequence:

+    if ((e as any).repeat) {
+      return;
+    }

Or debounce via a short per-key repeat suppression if you still want long‑press behavior elsewhere.

apps/web/core/components/power-k/core/registry.ts (2)

50-56: Guard against duplicate command IDs.

Silent overwrites make debugging hard.

Apply:

   register: IPowerKCommandRegistry["register"] = action((command) => {
-    this.commands.set(command.id, command);
+    if (this.commands.has(command.id)) {
+      // eslint-disable-next-line no-console
+      console.warn(`[PowerK] Duplicate command id "${command.id}" — overriding existing registration.`);
+    }
+    this.commands.set(command.id, command);
   });

132-134: Optional: expose unregister(id) for dev/hot‑reload tooling.

Helps cleanly replace commands when modules HMR.

Example:

 export class PowerKCommandRegistry implements IPowerKCommandRegistry {
   // ...
+  unregister(id: string): void {
+    this.commands.delete(id);
+  }
 }

And add to interface as needed.

dheeru0198
dheeru0198 previously approved these changes Oct 28, 2025
@pushya22 pushya22 changed the title feat: Power K v2 [WEB-5088] feat: Power K v2 Oct 29, 2025
@makeplane
Copy link

makeplane bot commented Oct 29, 2025

Linked to Plane Work Item(s)

This comment was auto-generated by Plane

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
apps/web/ce/components/command-palette/modals/work-item-level.tsx (1)

52-63: Don’t swallow delete errors; rethrow so the modal shows the error toast.

DeleteIssueModal relies on onSubmit rejecting to surface error toasts. Catching without rethrow marks failures as success.

     try {
       const isEpic = workItemDetails?.is_epic;
       const deleteAction = isEpic ? removeEpic : removeWorkItem;
       const redirectPath = `/${workspaceSlug}/projects/${projectId}/${isEpic ? "epics" : "issues"}`;

       await deleteAction(projectId, issueId);
       router.push(redirectPath);
     } catch (error) {
-      console.error("Failed to delete issue:", error);
+      console.error("Failed to delete issue:", error);
+      throw error; // let modal handle error UI
     }
♻️ Duplicate comments (9)
apps/web/core/components/power-k/ui/pages/context-based/work-item/commands.ts (5)

23-24: Unify EUserPermissions import source for consistency

Import EUserPermissions from @plane/constants (matches rest of codebase and co-locates permission enums).

-import { EIssueServiceType, EUserPermissions } from "@plane/types";
+import { EIssueServiceType } from "@plane/types";
+import { EUserPermissions } from "@plane/constants";

102-106: Prefer immutable toggle without splice for clarity

Use filter/concat to compute the next array; avoids indexOf+splice and reads cleaner. Functionally equivalent.

-      const updatedAssignees = [...(entityDetails.assignee_ids ?? [])];
-      if (updatedAssignees.includes(assigneeId)) updatedAssignees.splice(updatedAssignees.indexOf(assigneeId), 1);
-      else updatedAssignees.push(assigneeId);
+      const current = entityDetails.assignee_ids ?? [];
+      const updatedAssignees = current.includes(assigneeId)
+        ? current.filter((id) => id !== assigneeId)
+        : [...current, assigneeId];
-        const updatedLabels = [...(entityDetails.label_ids ?? [])];
-        if (updatedLabels.includes(labelId)) updatedLabels.splice(updatedLabels.indexOf(labelId), 1);
-        else updatedLabels.push(labelId);
+        const current = entityDetails.label_ids ?? [];
+        const updatedLabels = current.includes(labelId)
+          ? current.filter((id) => id !== labelId)
+          : [...current, labelId];

Also applies to: 373-379


141-157: Guard against copying “undefined-undefined” for ID

If identifier or sequence_id is missing, bail with an error toast before copying.

-  const id = `${projectDetails?.identifier}-${entityDetails?.sequence_id}`;
-  copyTextToClipboard(id)
+  const pid = projectDetails?.identifier;
+  const seq = entityDetails?.sequence_id;
+  if (!pid || !seq) {
+    setToast({
+      type: TOAST_TYPE.ERROR,
+      title: t("power_k.contextual_actions.work_item.copy_id_toast_error"),
+    });
+    return;
+  }
+  const id = `${pid}-${seq}`;
+  copyTextToClipboard(id)
       .then(() => {

298-324: Await async cycle add/remove so errors are caught

Without await, try/catch won’t handle promise rejections.

-      onSelect: (data) => {
+      onSelect: async (data) => {
         const cycleId = (data as ICycle)?.id;
         if (!workspaceSlug || !entityDetails || !entityDetails.project_id) return;
         if (entityDetails.cycle_id === cycleId) return;
@@
-        try {
-          if (cycleId) {
-            addCycleToEntity(workspaceSlug.toString(), entityDetails.project_id, cycleId, entityDetails.id);
-          } else {
-            removeCycleFromEntity(
+        try {
+          if (cycleId) {
+            await addCycleToEntity(
+              workspaceSlug.toString(),
+              entityDetails.project_id,
+              cycleId,
+              entityDetails.id
+            );
+          } else {
+            await removeCycleFromEntity(
               workspaceSlug.toString(),
               entityDetails.project_id,
               entityDetails.cycle_id ?? "",
               entityDetails.id
             );
           }
         } catch {

338-361: Module add/remove: guard moduleId and await the async update

Prevent undefined from flowing into store call; ensure catch handles rejections.

-      onSelect: (data) => {
+      onSelect: async (data) => {
         const moduleId = (data as IModule)?.id;
-        if (!workspaceSlug || !entityDetails || !entityDetails.project_id) return;
+        if (!workspaceSlug || !entityDetails || !entityDetails.project_id || !moduleId) return;
         // handlers
         const changeModulesInEntity = entityDetails.is_epic ? changeModulesInEpic : changeModulesInIssue;
         try {
           if (entityDetails.module_ids?.includes(moduleId)) {
-            changeModulesInEntity(workspaceSlug.toString(), entityDetails.project_id, entityDetails.id, [], [moduleId]);
+            await changeModulesInEntity(
+              workspaceSlug.toString(),
+              entityDetails.project_id,
+              entityDetails.id,
+              [],
+              [moduleId]
+            );
           } else {
-            changeModulesInEntity(workspaceSlug.toString(), entityDetails.project_id, entityDetails.id, [moduleId], []);
+            await changeModulesInEntity(
+              workspaceSlug.toString(),
+              entityDetails.project_id,
+              entityDetails.id,
+              [moduleId],
+              []
+            );
           }
         } catch {
apps/web/ce/components/command-palette/modals/work-item-level.tsx (2)

65-70: Fix unsafe guard; prevents crash when workItemDetails is undefined.

As written, both newIssue.parent_id and workItemDetails?.id can be undefined, causing the guard to pass and line 69 to dereference workItemDetails.id and crash. Also, workspaceSlug is already checked, so ?. is unnecessary in the fetch call. Apply:

-    if (!workspaceSlug || !newIssue.project_id || !newIssue.id || newIssue.parent_id !== workItemDetails?.id) return;
+    if (!workspaceSlug || !newIssue.project_id || !newIssue.id || !workItemDetails || newIssue.parent_id !== workItemDetails.id) return;

-    await fetchAction(workspaceSlug?.toString(), newIssue.project_id, workItemDetails.id);
+    await fetchAction(workspaceSlug.toString(), newIssue.project_id, workItemDetails.id);

92-95: Fix TS type mismatch when passing issueId.

workItemId?.toString() is string | undefined but handleDeleteIssue expects string. Given the enclosing guard, it’s safe to assert/normalize.

-            handleDeleteIssue(workspaceSlug.toString(), workItemDetails.project_id!, workItemId?.toString())
+            handleDeleteIssue(workspaceSlug.toString(), workItemDetails.project_id!, String(workItemId))
apps/web/core/components/power-k/ui/pages/work-item-selection-page.tsx (1)

87-171: Blank page: the entire UI is commented out; show at least a minimal empty state now.

Users currently see nothing. Re-enable a minimal fallback so Power‑K isn’t a no‑op while you wire the list UI.

Apply this minimal stopgap:

-  return (
-    <>
-      {/* ...full UI commented out... */}
-    </>
-  );
+  return (
+    <>
+      {!isLoading && !isSearching && (
+        <div className="flex flex-col items-center justify-center px-3 py-8 text-center">
+          <SimpleEmptyState title={t("command_k.empty_state.search.title")} assetPath={resolvedPath} />
+        </div>
+      )}
+    </>
+  );
apps/web/core/components/power-k/ui/modal/wrapper.tsx (1)

51-62: Await change-page onSelect before closing.

onSelect may be async. Not awaiting can close early and hide errors. Capture cmd, await, then close in finally.

-  const handlePageDataSelection = useCallback(
-    (data: unknown) => {
-      if (context.activeCommand?.type === "change-page") {
-        context.activeCommand.onSelect(data, context);
-      }
-      // Go back to main page
-      if (context.activeCommand?.closeOnSelect === true) {
-        context.closePalette();
-      }
-    },
-    [context]
-  );
+  const handlePageDataSelection = useCallback(
+    async (data: unknown) => {
+      const cmd = context.activeCommand;
+      if (cmd?.type === "change-page") {
+        try {
+          await cmd.onSelect(data, context);
+        } finally {
+          if (cmd.closeOnSelect === true) context.closePalette();
+        }
+      }
+    },
+    [context]
+  );
🧹 Nitpick comments (13)
apps/web/core/components/power-k/config/preferences-commands.ts (3)

43-44: Consider including t in the dependency array.

The t function is used within the callback but omitted from the dependency array. While translation functions are typically stable, the eslint-disable comment may be hiding this oversight. The same issue appears in other callbacks (lines 65-66, 87-88).

Apply this pattern to all three callbacks:

  },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [setTheme, updateUserTheme]
+ [setTheme, updateUserTheme, t]
);

69-89: Generic toast messages reduce user feedback clarity.

Previous reviews correctly identified that this handler serves both start_of_week (line 131) and language (line 146) updates. While the hardcoded "Language" text has been replaced with generic i18n keys, users now receive identical success/error messages regardless of which preference they updated, making it unclear what changed.

Consider accepting a message key parameter to provide specific feedback:

const handleUpdateUserProfile = useCallback(
- (payload: Partial<TUserProfile>) => {
+ (payload: Partial<TUserProfile>, messageKey: string) => {
    updateUserProfile(payload)
      .then(() => {
        setToast({
          type: TOAST_TYPE.SUCCESS,
          title: t("toast.success"),
-         message: t("power_k.preferences_actions.toast.generic.success"),
+         message: t(`power_k.preferences_actions.toast.${messageKey}.success`),
        });
      })
      .catch(() => {
        setToast({
          type: TOAST_TYPE.ERROR,
          title: t("toast.error"),
-         message: t("power_k.preferences_actions.toast.generic.error"),
+         message: t(`power_k.preferences_actions.toast.${messageKey}.error`),
        });
      });
  },
  [updateUserProfile, t]
);

Then update the call sites:

onSelect: (data) => {
  const startOfWeek = data as EStartOfTheWeek;
- handleUpdateUserProfile({ start_of_the_week: startOfWeek });
+ handleUpdateUserProfile({ start_of_the_week: startOfWeek }, "start_of_week");
},
onSelect: (data) => {
  const language = data as string;
- handleUpdateUserProfile({ language });
+ handleUpdateUserProfile({ language }, "language");
},

99-101: Consider adding runtime type validation for type assertions.

The onSelect handlers use type assertions (as string, as EStartOfTheWeek) without validating the incoming data. While the Power-K system likely ensures type correctness, defensive runtime checks would prevent unexpected failures.

Example for the theme handler:

onSelect: (data) => {
  const theme = data as string;
+ if (typeof theme !== "string") {
+   console.error("Invalid theme data type:", data);
+   return;
+ }
  handleUpdateTheme(theme);
},

Also applies to: 114-116, 129-131, 144-146

packages/i18n/src/locales/en/translations.ts (3)

2487-2487: De-hyphenate “Un-assign” → “Unassign” and mirror “Assign to me”

Keep copy consistent and concise.

-        unassign_from_me: "Un-assign from me",
+        unassign_from_me: "Unassign me",

2593-2593: Grammar: add “the” → “first day of the week”

Small clarity fix; appears in two places.

-      update_start_of_week: "Change first day of week",
+      update_start_of_week: "Change first day of the week",
-      update_start_of_week: "Change first day of week",
+      update_start_of_week: "Change first day of the week",

Also applies to: 2636-2636


2648-2648: Title consistency: “Navigate” → “Navigation”

Other group titles are nouns (Settings, Preferences). This aligns casing/part of speech.

-      navigation: "Navigate",
+      navigation: "Navigation",
apps/web/ce/components/command-palette/modals/work-item-level.tsx (2)

18-19: Prefer optional prop over string | undefined.

Cleaner and idiomatic TS.

-export type TWorkItemLevelModalsProps = {
-  workItemIdentifier: string | undefined;
-};
+export type TWorkItemLevelModalsProps = {
+  workItemIdentifier?: string;
+};

32-35: Optional: memoize derived lookups.

If getIssueIdByIdentifier/getIssueById are non-trivial, memoize to avoid redundant work and keep referential stability across renders.

Example:

import { useMemo } from "react";

const workItemId = useMemo(
  () => (workItemIdentifier ? getIssueIdByIdentifier(workItemIdentifier) : undefined),
  [workItemIdentifier, getIssueIdByIdentifier]
);

const workItemDetails = useMemo(
  () => (workItemId ? getIssueById(workItemId) : undefined),
  [workItemId, getIssueById]
);
apps/web/core/components/power-k/ui/pages/work-item-selection-page.tsx (2)

54-63: Guard recents fetch against unmount and stale setState.

Mirror the search effect’s “active” guard to avoid setState after unmount.

 useEffect(() => {
   if (!workspaceSlug) return;
 
-  workspaceService
+  let active = true;
+  workspaceService
     .fetchWorkspaceRecents(workspaceSlug.toString(), "issue")
     .then((res) =>
-      setRecentIssues(res.map((r: TActivityEntityData) => r.entity_data as TIssueEntityData).slice(0, 10))
+      active && setRecentIssues(res.map((r: TActivityEntityData) => r.entity_data as TIssueEntityData).slice(0, 10))
     )
-    .catch(() => setRecentIssues([]));
+    .catch(() => {
+      if (active) setRecentIssues([]);
+    });
+  return () => {
+    active = false;
+  };
 }, [workspaceSlug]);

57-59: Nit: .toString() on already‑string values is redundant.

workspaceSlug is typed as string | undefined and checked; same for projectId. Dropping .toString() reduces noise.

Example:

- .fetchWorkspaceRecents(workspaceSlug.toString(), "issue")
+ .fetchWorkspaceRecents(workspaceSlug, "issue")

- .searchEntity(workspaceSlug.toString(), {
+ .searchEntity(workspaceSlug, {

- ...(!isWorkspaceLevel && projectId ? { project_id: projectId.toString() } : {}),
+ ...(!isWorkspaceLevel && projectId ? { project_id: projectId } : {}),

Also applies to: 72-74

apps/web/core/components/power-k/ui/modal/wrapper.tsx (3)

171-176: Normalize projectId from params (string | string[]).

.toString() on arrays yields comma-joined values. Extract first element when array to avoid surprises.

 export const ProjectsAppPowerKModalWrapper = observer((props: Props) => {
   const { commandsListComponent: CommandsListComponent, context, hideFooter = false, isOpen, onClose } = props;
   // states
   const [searchTerm, setSearchTerm] = useState("");
   const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false);
   // store hooks
   const { activePage, setActivePage } = usePowerK();
+  // normalize projectId from route params
+  const projectId =
+    Array.isArray(context.params.projectId) ? context.params.projectId[0] : context.params.projectId;
-                    <PowerKModalFooter
-                      isWorkspaceLevel={isWorkspaceLevel}
-                      projectId={context.params.projectId?.toString()}
-                      onWorkspaceLevelChange={setIsWorkspaceLevel}
-                    />
+                    <PowerKModalFooter
+                      isWorkspaceLevel={isWorkspaceLevel}
+                      projectId={projectId}
+                      onWorkspaceLevelChange={setIsWorkspaceLevel}
+                    />

Also applies to: 23-31


142-158: Add accessible dialog title.

Dialog lacks an accessible name. Add a visually hidden title for screen readers.

               <Dialog.Panel className="relative flex w-full max-w-2xl transform flex-col items-center justify-center divide-y divide-custom-border-200 divide-opacity-10 rounded-lg bg-custom-background-100 shadow-custom-shadow-md transition-all">
                 <Command
                   filter={(i18nValue: string, search: string) => {
                     if (i18nValue === "no-results") return 1;
                     if (i18nValue.toLowerCase().includes(search.toLowerCase())) return 1;
                     return 0;
                   }}
                   shouldFilter={searchTerm.length > 0}
                   onKeyDown={handleKeyDown}
                   className="w-full"
                 >
+                  <Dialog.Title className="sr-only">Command palette</Dialog.Title>
                   <PowerKModalHeader
                     activePage={activePage}
                     context={context}
                     onSearchChange={setSearchTerm}
                     searchTerm={searchTerm}
                   />

102-114: Option B is feasible; both fixes address the setTimeout leak.

The review is accurate. Verification confirms:

  • @headlessui/react ^1.7.19 is installed and supports afterLeave
  • The setTimeout at lines 102-114 lacks a cleanup function, creating a potential memory leak and "state update on unmounted component" warnings
  • The Transition.Root component structure supports Option B implementation
  • The 200ms timeout aligns with the leave transition duration, making Option B a natural fit

Both options are valid:

  • Option A (cleanup): Minimal fix, safe to apply
  • Option B (afterLeave): Preferred approach, leverages Headless UI lifecycle semantics
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8de609 and 53aae45.

📒 Files selected for processing (10)
  • apps/web/ce/components/command-palette/modals/index.ts (0 hunks)
  • apps/web/ce/components/command-palette/modals/work-item-level.tsx (4 hunks)
  • apps/web/core/components/power-k/config/help-commands.ts (1 hunks)
  • apps/web/core/components/power-k/config/preferences-commands.ts (1 hunks)
  • apps/web/core/components/power-k/projects-app-provider.tsx (1 hunks)
  • apps/web/core/components/power-k/ui/modal/search-menu.tsx (1 hunks)
  • apps/web/core/components/power-k/ui/modal/wrapper.tsx (1 hunks)
  • apps/web/core/components/power-k/ui/pages/context-based/work-item/commands.ts (1 hunks)
  • apps/web/core/components/power-k/ui/pages/work-item-selection-page.tsx (1 hunks)
  • packages/i18n/src/locales/en/translations.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/ce/components/command-palette/modals/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/core/components/power-k/projects-app-provider.tsx
  • apps/web/core/components/power-k/config/help-commands.ts
  • apps/web/core/components/power-k/ui/modal/search-menu.tsx
🧰 Additional context used
🧬 Code graph analysis (5)
apps/web/core/components/power-k/ui/pages/context-based/work-item/commands.ts (5)
apps/web/core/components/power-k/core/types.ts (1)
  • TPowerKCommandConfig (67-108)
packages/types/src/issues/issue.ts (1)
  • TIssue (83-97)
packages/propel/src/toast/toast.tsx (1)
  • setToast (198-218)
apps/web/core/store/issue/helpers/base-issues.store.ts (2)
  • moduleId (266-268)
  • changeModulesInIssue (1087-1146)
packages/types/src/module/modules.ts (1)
  • IModule (49-89)
apps/web/core/components/power-k/ui/pages/work-item-selection-page.tsx (3)
apps/web/core/hooks/use-app-router.tsx (1)
  • useAppRouter (4-4)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
apps/web/core/hooks/store/use-power-k.ts (1)
  • usePowerK (7-11)
apps/web/core/components/power-k/config/preferences-commands.ts (6)
apps/web/core/components/power-k/core/types.ts (1)
  • TPowerKCommandConfig (67-108)
apps/admin/core/hooks/store/use-theme.tsx (1)
  • useTheme (6-10)
apps/space/core/hooks/store/use-user-profile.ts (1)
  • useUserProfile (7-11)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
packages/propel/src/toast/toast.tsx (1)
  • setToast (198-218)
packages/types/src/users.ts (1)
  • TUserProfile (57-84)
apps/web/ce/components/command-palette/modals/work-item-level.tsx (1)
apps/web/core/components/issues/delete-issue-modal.tsx (1)
  • DeleteIssueModal (30-128)
apps/web/core/components/power-k/ui/modal/wrapper.tsx (5)
apps/web/core/components/power-k/ui/modal/commands-list.tsx (1)
  • TPowerKCommandsListProps (6-14)
apps/web/core/components/power-k/core/types.ts (2)
  • TPowerKContext (11-27)
  • TPowerKCommandConfig (67-108)
apps/web/core/hooks/store/use-power-k.ts (1)
  • usePowerK (7-11)
apps/web/core/components/power-k/ui/modal/header.tsx (1)
  • PowerKModalHeader (20-60)
apps/web/core/components/power-k/ui/modal/footer.tsx (1)
  • PowerKModalFooter (15-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
apps/web/core/components/power-k/ui/pages/context-based/work-item/commands.ts (1)

64-64: Good fix: safe check for assignee_ids

Optional chaining added on assignee_ids prevents runtime crash. LGTM.

Comment on lines +31 to +48
// Handle command selection
const handleCommandSelect = useCallback(
(command: TPowerKCommandConfig) => {
if (command.type === "action") {
// Direct action - execute and potentially close
command.action(context);
if (command.closeOnSelect === true) {
context.closePalette();
}
} else if (command.type === "change-page") {
// Opens a selection page
context.setActiveCommand(command);
setActivePage(command.page);
setSearchTerm("");
}
},
[context, setActivePage]
);
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

Await action commands; close after completion.

action can return Promise. Await it to avoid race conditions and swallowed errors.

-  const handleCommandSelect = useCallback(
-    (command: TPowerKCommandConfig) => {
+  const handleCommandSelect = useCallback(
+    async (command: TPowerKCommandConfig) => {
       if (command.type === "action") {
-        // Direct action - execute and potentially close
-        command.action(context);
-        if (command.closeOnSelect === true) {
-          context.closePalette();
-        }
+        // Direct action - execute and potentially close
+        try {
+          await command.action(context);
+        } finally {
+          if (command.closeOnSelect === true) context.closePalette();
+        }
       } else if (command.type === "change-page") {
         // Opens a selection page
         context.setActiveCommand(command);
         setActivePage(command.page);
         setSearchTerm("");
       }
     },
     [context, setActivePage]
   );
📝 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
// Handle command selection
const handleCommandSelect = useCallback(
(command: TPowerKCommandConfig) => {
if (command.type === "action") {
// Direct action - execute and potentially close
command.action(context);
if (command.closeOnSelect === true) {
context.closePalette();
}
} else if (command.type === "change-page") {
// Opens a selection page
context.setActiveCommand(command);
setActivePage(command.page);
setSearchTerm("");
}
},
[context, setActivePage]
);
// Handle command selection
const handleCommandSelect = useCallback(
async (command: TPowerKCommandConfig) => {
if (command.type === "action") {
// Direct action - execute and potentially close
try {
await command.action(context);
} finally {
if (command.closeOnSelect === true) context.closePalette();
}
} else if (command.type === "change-page") {
// Opens a selection page
context.setActiveCommand(command);
setActivePage(command.page);
setSearchTerm("");
}
},
[context, setActivePage]
);
🤖 Prompt for AI Agents
In apps/web/core/components/power-k/ui/modal/wrapper.tsx around lines 31 to 48,
the handler executes action commands synchronously even though command.action
may return a Promise; change handleCommandSelect to be async, await
command.action(context) and wrap the await in a try/catch to surface/log errors,
then call context.closePalette() only after the awaited action completes (if
command.closeOnSelect === true). Ensure the useCallback still lists required
dependencies (context, setActivePage) and adjust any callers if they expect a
synchronous function.

Comment on lines +159 to +175
const copyWorkItemTitleToClipboard = useCallback(() => {
copyTextToClipboard(entityDetails?.name ?? "")
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: t("power_k.contextual_actions.work_item.copy_title_toast_success"),
});
})
.catch(() => {
setToast({
type: TOAST_TYPE.ERROR,
title: t("power_k.contextual_actions.work_item.copy_title_toast_error"),
});
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [entityDetails?.name]);

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

Copy title: avoid copying empty string

Add a guard; show error toast if title is missing/blank.

-  copyTextToClipboard(entityDetails?.name ?? "")
+  const title = entityDetails?.name?.trim() ?? "";
+  if (!title) {
+    setToast({
+      type: TOAST_TYPE.ERROR,
+      title: t("power_k.contextual_actions.work_item.copy_title_toast_error"),
+    });
+    return;
+  }
+  copyTextToClipboard(title)
       .then(() => {
🤖 Prompt for AI Agents
In apps/web/core/components/power-k/ui/pages/context-based/work-item/commands.ts
around lines 159 to 175, the copyWorkItemTitleToClipboard function currently
attempts to copy even when the title is empty; add a guard at the top that
checks if entityDetails?.name is missing or blank (trim() === ""), and if so
call setToast with TOAST_TYPE.ERROR and the existing i18n key
t("power_k.contextual_actions.work_item.copy_title_toast_error") and return
early; otherwise proceed to call copyTextToClipboard as before and keep the
success/error promise handlers.

Comment on lines +66 to +83
useEffect(() => {
if (!workspaceSlug || !debouncedSearchTerm) {
setIssueResults([]);
return;
}

workspaceService
.searchEntity(workspaceSlug.toString(), {
count: 10,
query: debouncedSearchTerm,
query_type: ["issue"],
...(!isWorkspaceLevel && projectId ? { project_id: projectId.toString() } : {}),
})
.then((res) => {
setIssueResults(res.issue || []);
})
.catch(() => setIssueResults([]));
}, [debouncedSearchTerm, workspaceSlug, projectId, isWorkspaceLevel]);
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

Prevent stale search results from overwriting newer ones.

Fast typing can cause out‑of‑order responses to clobber the latest results; also avoid setState after unmount.

 useEffect(() => {
   if (!workspaceSlug || !debouncedSearchTerm) {
     setIssueResults([]);
     return;
   }
 
+  let active = true;
+  const term = debouncedSearchTerm;
   workspaceService
     .searchEntity(workspaceSlug.toString(), {
       count: 10,
       query: debouncedSearchTerm,
       query_type: ["issue"],
       ...(!isWorkspaceLevel && projectId ? { project_id: projectId.toString() } : {}),
     })
     .then((res) => {
-        setIssueResults(res.issue || []);
+        if (active && term === debouncedSearchTerm) setIssueResults(res.issue || []);
     })
-    .catch(() => setIssueResults([]));
+    .catch(() => {
+      if (active) setIssueResults([]);
+    });
+  return () => {
+    active = false;
+  };
 }, [debouncedSearchTerm, workspaceSlug, projectId, isWorkspaceLevel]);

Based on relevant_code_snippets.

📝 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
useEffect(() => {
if (!workspaceSlug || !debouncedSearchTerm) {
setIssueResults([]);
return;
}
workspaceService
.searchEntity(workspaceSlug.toString(), {
count: 10,
query: debouncedSearchTerm,
query_type: ["issue"],
...(!isWorkspaceLevel && projectId ? { project_id: projectId.toString() } : {}),
})
.then((res) => {
setIssueResults(res.issue || []);
})
.catch(() => setIssueResults([]));
}, [debouncedSearchTerm, workspaceSlug, projectId, isWorkspaceLevel]);
useEffect(() => {
if (!workspaceSlug || !debouncedSearchTerm) {
setIssueResults([]);
return;
}
let active = true;
const term = debouncedSearchTerm;
workspaceService
.searchEntity(workspaceSlug.toString(), {
count: 10,
query: debouncedSearchTerm,
query_type: ["issue"],
...(!isWorkspaceLevel && projectId ? { project_id: projectId.toString() } : {}),
})
.then((res) => {
if (active && term === debouncedSearchTerm) setIssueResults(res.issue || []);
})
.catch(() => {
if (active) setIssueResults([]);
});
return () => {
active = false;
};
}, [debouncedSearchTerm, workspaceSlug, projectId, isWorkspaceLevel]);

Comment on lines +147 to +159
onSelect={(issue) => {
if (!issue.project_id) return;
togglePowerKModal(false);
router.push(
generateWorkItemLink({
workspaceSlug: workspaceSlug.toString(),
projectId: issue.project_id,
issueId: issue.id,
projectIdentifier: issue.project__identifier,
sequenceId: issue.sequence_id,
})
);
}}
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

Selection may build an invalid link when fields are missing; harden guards.

Render guards treat project__identifier and sequence_id as optional, but the onSelect only checks project_id. This can produce an invalid route.

           onSelect={(issue) => {
-            if (!issue.project_id) return;
+            if (!issue.project_id || !issue.project__identifier || !issue.sequence_id) return;
             togglePowerKModal(false);
             router.push(
               generateWorkItemLink({
                 workspaceSlug: workspaceSlug.toString(),
                 projectId: issue.project_id,
                 issueId: issue.id,
                 projectIdentifier: issue.project__identifier,
                 sequenceId: issue.sequence_id,
               })
             );
           }}

To confirm required fields for generateWorkItemLink, run:


🏁 Script executed:

#!/bin/bash
fd -a generateWorkItemLink -t f
rg -nP -C3 'export\s+(const|function)\s+generateWorkItemLink\s*\('
rg -nP -C2 '\bgenerateWorkItemLink\s*\(\s*{'

Length of output: 15531






🏁 Script executed:

#!/bin/bash
# Find the import statement for generateWorkItemLink in work-item-selection-page.tsx
rg -nP "import.*generateWorkItemLink" "apps/web/core/components/power-k/ui/pages/work-item-selection-page.tsx"

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Search for where generateWorkItemLink is defined (likely in a utils or helper file)
rg -nP "^export\s+(const|function).*generateWorkItemLink|^const\s+generateWorkItemLink.*=|function\s+generateWorkItemLink\s*\(" --type=ts --type=tsx

Length of output: 27






🏁 Script executed:

#!/bin/bash
# Find @plane/utils package location and the generateWorkItemLink function
fd -t d -n "utils" | head -20

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Search for generateWorkItemLink in all files (fixing the rg syntax)
rg -nP "generateWorkItemLink\s*[=:]|export.*generateWorkItemLink" -g "*.ts" -g "*.tsx" | head -50

Length of output: 79






🏁 Script executed:

#!/bin/bash
# View the generateWorkItemLink function definition
cat packages/utils/src/work-item/base.ts | sed -n '329,380p'

Length of output: 1215


🏁 Script executed:

#!/bin/bash
# View the render guards in work-item-selection-page.tsx around the onSelect handler
cat "apps/web/core/components/power-k/ui/pages/work-item-selection-page.tsx" | sed -n '100,165p'

Length of output: 2674


Add field guards in onSelect to prevent invalid link generation.

The function generateWorkItemLink accepts all parameters as optional (typed string | undefined | null), and the default return path uses projectIdentifier and sequenceId directly in a URL template. The render guards at line 136 confirm these fields can be missing, but the onSelect handler at line 147–159 only checks project_id. This will produce invalid routes like /{workspaceSlug}/browse/undefined-undefined/ when either field is missing.

          onSelect={(issue) => {
-            if (!issue.project_id) return;
+            if (!issue.project_id || !issue.project__identifier || !issue.sequence_id) return;
             togglePowerKModal(false);
             router.push(
               generateWorkItemLink({
                 workspaceSlug: workspaceSlug.toString(),
                 projectId: issue.project_id,
                 issueId: issue.id,
                 projectIdentifier: issue.project__identifier,
                 sequenceId: issue.sequence_id,
               })
             );
           }}
📝 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
onSelect={(issue) => {
if (!issue.project_id) return;
togglePowerKModal(false);
router.push(
generateWorkItemLink({
workspaceSlug: workspaceSlug.toString(),
projectId: issue.project_id,
issueId: issue.id,
projectIdentifier: issue.project__identifier,
sequenceId: issue.sequence_id,
})
);
}}
onSelect={(issue) => {
if (!issue.project_id || !issue.project__identifier || !issue.sequence_id) return;
togglePowerKModal(false);
router.push(
generateWorkItemLink({
workspaceSlug: workspaceSlug.toString(),
projectId: issue.project_id,
issueId: issue.id,
projectIdentifier: issue.project__identifier,
sequenceId: issue.sequence_id,
})
);
}}
🤖 Prompt for AI Agents
In apps/web/core/components/power-k/ui/pages/work-item-selection-page.tsx around
lines 147–159, the onSelect handler only guards project_id but passes
project__identifier and sequence_id directly to generateWorkItemLink, which can
produce invalid routes; update the handler to check that
issue.project__identifier and issue.sequence_id are present (and non-empty)
before calling togglePowerKModal/ router.push, and if either is missing either
return early or route to a safe fallback (e.g., open a details view modal or
show an error), ensuring generateWorkItemLink is never invoked with
undefined/null values.

Comment on lines +2477 to +2657
power_k: {
contextual_actions: {
work_item: {
title: "Work item actions",
indicator: "Work item",
change_state: "Change state",
change_priority: "Change priority",
change_assignees: "Assign to",
assign_to_me: "Assign to me",
unassign_from_me: "Un-assign from me",
change_estimate: "Change estimate",
add_to_cycle: "Add to cycle",
add_to_modules: "Add to modules",
add_labels: "Add labels",
subscribe: "Subscribe to notifications",
unsubscribe: "Unsubscribe from notifications",
delete: "Delete",
copy_id: "Copy ID",
copy_id_toast_success: "Work item ID copied to clipboard.",
copy_id_toast_error: "Some error occurred while copying the work item ID to clipboard.",
copy_title: "Copy title",
copy_title_toast_success: "Work item title copied to clipboard.",
copy_title_toast_error: "Some error occurred while copying the work item title to clipboard.",
copy_url: "Copy URL",
copy_url_toast_success: "Work item URL copied to clipboard.",
copy_url_toast_error: "Some error occurred while copying the work item URL to clipboard.",
},
cycle: {
title: "Cycle actions",
indicator: "Cycle",
add_to_favorites: "Add to favorites",
remove_from_favorites: "Remove from favorites",
copy_url: "Copy URL",
copy_url_toast_success: "Cycle URL copied to clipboard.",
copy_url_toast_error: "Some error occurred while copying the cycle URL to clipboard.",
},
module: {
title: "Module actions",
indicator: "Module",
add_remove_members: "Add/remove members",
change_status: "Change status",
add_to_favorites: "Add to favorites",
remove_from_favorites: "Remove from favorites",
copy_url: "Copy URL",
copy_url_toast_success: "Module URL copied to clipboard.",
copy_url_toast_error: "Some error occurred while copying the module URL to clipboard.",
},
page: {
title: "Page actions",
indicator: "Page",
lock: "Lock",
unlock: "Unlock",
make_private: "Make private",
make_public: "Make public",
archive: "Archive",
restore: "Restore",
add_to_favorites: "Add to favorites",
remove_from_favorites: "Remove from favorites",
copy_url: "Copy URL",
copy_url_toast_success: "Page URL copied to clipboard.",
copy_url_toast_error: "Some error occurred while copying the page URL to clipboard.",
},
},
creation_actions: {
create_work_item: "New work item",
create_page: "New page",
create_view: "New view",
create_cycle: "New cycle",
create_module: "New module",
create_project: "New project",
create_workspace: "New workspace",
},
navigation_actions: {
open_workspace: "Open a workspace",
nav_home: "Go to home",
nav_inbox: "Go to inbox",
nav_your_work: "Go to your work",
nav_account_settings: "Go to account settings",
open_project: "Open a project",
nav_projects_list: "Go to projects list",
nav_all_workspace_work_items: "Go to all work items",
nav_assigned_workspace_work_items: "Go to assigned work items",
nav_created_workspace_work_items: "Go to created work items",
nav_subscribed_workspace_work_items: "Go to subscribed work items",
nav_workspace_analytics: "Go to workspace analytics",
nav_workspace_drafts: "Go to workspace drafts",
nav_workspace_archives: "Go to workspace archives",
open_workspace_setting: "Open a workspace setting",
nav_workspace_settings: "Go to workspace settings",
nav_project_work_items: "Go to work items",
open_project_cycle: "Open a cycle",
nav_project_cycles: "Go to cycles",
open_project_module: "Open a module",
nav_project_modules: "Go to modules",
open_project_view: "Open a project view",
nav_project_views: "Go to project views",
nav_project_pages: "Go to pages",
nav_project_intake: "Go to intake",
nav_project_archives: "Go to project archives",
open_project_setting: "Open a project setting",
nav_project_settings: "Go to project settings",
},
account_actions: {
sign_out: "Sign out",
workspace_invites: "Workspace invites",
},
miscellaneous_actions: {
toggle_app_sidebar: "Toggle app sidebar",
copy_current_page_url: "Copy current page URL",
copy_current_page_url_toast_success: "Current page URL copied to clipboard.",
copy_current_page_url_toast_error: "Some error occurred while copying the current page URL to clipboard.",
},
preferences_actions: {
update_theme: "Change interface theme",
update_timezone: "Change timezone",
update_start_of_week: "Change first day of week",
update_language: "Change interface language",
toast: {
theme: {
success: "Theme updated successfully.",
error: "Failed to update theme. Please try again.",
},
timezone: {
success: "Timezone updated successfully.",
error: "Failed to update timezone. Please try again.",
},
generic: {
success: "Preferences updated successfully.",
error: "Failed to update preferences. Please try again.",
},
},
},
help_actions: {
open_keyboard_shortcuts: "Open keyboard shortcuts",
open_plane_documentation: "Open Plane documentation",
join_discord: "Join our Discord",
report_bug: "Report a bug",
chat_with_us: "Chat with us",
},
page_placeholders: {
default: "Type a command or search",
open_workspace: "Open a workspace",
open_project: "Open a project",
open_workspace_setting: "Open a workspace setting",
open_project_cycle: "Open a cycle",
open_project_module: "Open a module",
open_project_view: "Open a project view",
open_project_setting: "Open a project setting",
update_work_item_state: "Change state",
update_work_item_priority: "Change priority",
update_work_item_assignee: "Assign to",
update_work_item_estimate: "Change estimate",
update_work_item_cycle: "Add to cycle",
update_work_item_module: "Add to modules",
update_work_item_labels: "Add labels",
update_module_member: "Change members",
update_module_status: "Change status",
update_theme: "Change theme",
update_timezone: "Change timezone",
update_start_of_week: "Change first day of week",
update_language: "Change language",
},
search_menu: {
no_results: "No results found",
clear_search: "Clear search",
},
footer: {
workspace_level: "Workspace level",
},
group_titles: {
contextual: "Contextual",
navigation: "Navigate",
create: "Create",
general: "General",
settings: "Settings",
account: "Account",
miscellaneous: "Miscellaneous",
preferences: "Preferences",
help: "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 | 🔴 Critical

🧩 Analysis chain

Power‑K namespace looks solid

Well‑structured, consistent with existing i18n patterns. Safe additive change; no collisions observed.

Run this to ensure other locales include the new namespace and to surface phrasing we’re standardizing:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locales missing power_k:"
fd -t f 'translations.(ts|js|json)' packages/i18n/src/locales -E 'en/translations.ts' \
| while read -r f; do rg -q 'power_k\s*:' "$f" || echo "  - $f"; done

echo
echo "Occurrences of 'Un-assign' (should be 'Unassign'):"
rg -n --glob 'packages/i18n/src/locales/**' 'Un-assign'

echo
echo "Occurrences of 'first day of week' (should be 'first day of the week'):"
rg -n --glob 'packages/i18n/src/locales/**' 'first day of week'

Length of output: 1355


Add power_k namespace to all non-English locales

The new power_k namespace is missing from 18 locale files (cs, de, es, fr, id, it, ja, ko, pl, pt-BR, ro, ru, sk, tr-TR, ua, vi-VN, zh-CN, zh-TW). The feature will be unavailable for non-English users. Also fix two minor wording issues in en/translations.ts:

  • Line 2486: change "Un-assign from me" to "Unassign from me" (matches "Assign to me" style)
  • Lines 2592, 2636: change "Change first day of week" to "Change first day of the week"

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)
apps/web/core/components/power-k/projects-app-provider.tsx (1)

78-82: Duplicate: Array-to-string conversion issue already flagged.

The past review comment correctly identifies that projectId and workItemIdentifier can be arrays, and calling .toString() on arrays produces comma-separated values. Line 78 has the same issue with workspaceSlug.

My earlier comment on lines 29-43 provides a comprehensive fix that addresses all three parameters (workspaceSlug, projectId, workItemIdentifier) by normalizing them to string | undefined at the point of extraction from route params.

🧹 Nitpick comments (1)
apps/web/core/components/power-k/projects-app-provider.tsx (1)

40-40: Remove duplicate comment.

The comment "// derived values" appears twice (lines 36 and 40). Remove the duplicate on line 40.

-  // derived values
   const workItemId = workItemIdentifier ? getIssueIdByIdentifier(workItemIdentifier.toString()) : undefined;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53aae45 and 8a8de34.

📒 Files selected for processing (3)
  • apps/web/ce/components/command-palette/index.ts (0 hunks)
  • apps/web/core/components/power-k/projects-app-provider.tsx (1 hunks)
  • apps/web/ee/components/command-palette/modals/index.ts (0 hunks)
💤 Files with no reviewable changes (2)
  • apps/web/ee/components/command-palette/modals/index.ts
  • apps/web/ce/components/command-palette/index.ts
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/core/components/power-k/projects-app-provider.tsx (8)
apps/web/core/hooks/use-app-router.tsx (1)
  • useAppRouter (4-4)
apps/web/core/components/power-k/core/types.ts (2)
  • TPowerKCommandConfig (67-108)
  • TPowerKContext (11-27)
apps/web/core/hooks/store/use-power-k.ts (1)
  • usePowerK (7-11)
apps/web/core/components/power-k/config/commands.ts (1)
  • useProjectsAppPowerKCommands (11-29)
apps/web/core/components/power-k/global-shortcuts.tsx (1)
  • GlobalShortcutsProvider (24-62)
apps/web/ce/components/command-palette/modals/work-item-level.tsx (1)
  • WorkItemLevelModals (22-105)
apps/web/core/components/power-k/ui/modal/wrapper.tsx (1)
  • ProjectsAppPowerKModalWrapper (23-186)
apps/web/core/components/power-k/ui/modal/commands-list.tsx (1)
  • ProjectsAppPowerKCommandsList (16-49)
🔇 Additional comments (2)
apps/web/core/components/power-k/projects-app-provider.tsx (2)

1-25: LGTM: Imports and component setup are correct.

The "use client" directive, imports, and MobX observer wrapper are properly configured for a Next.js client component with reactive state management.


46-73: LGTM: Context construction and memoization are correct.

The useMemo dependencies are complete, and the context object properly combines router params, user state, and control handlers for the PowerK feature.

// router
const router = useAppRouter();
const params = useParams();
const { workspaceSlug, projectId: routerProjectId, workItem: workItemIdentifier } = params;
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

Handle potential array-valued route params before using .toString().

Next.js route params from useParams() can be string | string[] | undefined for catch-all or optional catch-all routes. Calling .toString() on an array produces comma-separated values (e.g., ["id1", "id2"] becomes "id1,id2"), which would break downstream function calls and API requests.

On line 41, workItemIdentifier.toString() is passed to getIssueIdByIdentifier(), and on line 43, projectId is explicitly typed to allow arrays, then used with .toString() on lines 80 and 82.

Apply this pattern to safely extract string values:

  const router = useAppRouter();
  const params = useParams();
- const { workspaceSlug, projectId: routerProjectId, workItem: workItemIdentifier } = params;
+ const { workspaceSlug: rawWorkspaceSlug, projectId: rawProjectId, workItem: rawWorkItem } = params;
+
+ // Normalize route params to strings
+ const workspaceSlug = typeof rawWorkspaceSlug === 'string' ? rawWorkspaceSlug : rawWorkspaceSlug?.[0];
+ const routerProjectId = typeof rawProjectId === 'string' ? rawProjectId : rawProjectId?.[0];
+ const workItemIdentifier = typeof rawWorkItem === 'string' ? rawWorkItem : rawWorkItem?.[0];

Then update line 43 to narrow the type:

- const projectId: string | string[] | undefined | null = routerProjectId ?? workItemDetails?.project_id;
+ const projectId: string | undefined = routerProjectId ?? workItemDetails?.project_id;

This ensures that workspaceSlug, routerProjectId, workItemIdentifier, and projectId are always string | undefined, making the .toString() calls on lines 78, 80, and 82 safe.

Also applies to: 41-43

cursor[bot]

This comment was marked as outdated.

return redirectProjectId
? `/${page?.workspace__slug}/projects/${redirectProjectId}/pages/${page?.id}`
: `/${page?.workspace__slug}/pages/${page?.id}`;
: `/${page?.workspace__slug}/wiki/${page?.id}`;
Copy link

Choose a reason for hiding this comment

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

Bug: Incorrect Routing for Global Pages

The page routing logic has been changed from /pages/{id} to /wiki/{id} for workspace-level pages (pages without a project association). This routing change appears to be unintentional or refers to a route that doesn't exist in the standard routing configuration. The workspace-level page route should remain as /{workspace}/pages/{id} based on the application's page hierarchy. The /wiki/ route is not a standard page routing pattern and this change could result in 404 errors when users try to navigate to global pages through the search functionality.

Fix in Cursor Fix in Web

if (!!projectId && page?.project_ids?.includes(projectId)) redirectProjectId = projectId;
return redirectProjectId
? `/${page?.workspace__slug}/projects/${redirectProjectId}/pages/${page?.id}`
: `/${page?.workspace__slug}/wiki/${page?.id}`;
Copy link

Choose a reason for hiding this comment

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

Bug: Incorrect Page Routing Causes Navigation Errors

The page routing logic has been changed from /pages/{id} to /wiki/{id} for workspace-level pages. This routing change appears to be unintentional or refers to a route that doesn't exist. The workspace-level page route should remain as /{workspace}/pages/{id} based on the application's page hierarchy. The /wiki/ route is not a standard page routing pattern and this change could result in 404 errors when users navigate to global pages through Power K search.

Fix in Cursor Fix in Web

@pushya22 pushya22 merged commit 350107d into preview Oct 29, 2025
7 of 8 checks passed
@pushya22 pushya22 deleted the feat/power-k-v2 branch October 29, 2025 10:08
ClarenceChen0627 pushed a commit to ClarenceChen0627/plane that referenced this pull request Dec 5, 2025
* feat: add project shortcut in command palette

* feat: global project switcher shortcut

* refactor: generalize command palette entity handling

* feat: extend command palette navigation

* feat: add issue shortcut to command palette

* feat: add modular project selection for cycle navigation

* chore: add reusable command palette utilities

* fix: update key sequence handling to use window methods for timeout management

* fix: build errors

* chore: minor ux copy improvements

* feat: implement a new command registry and renderer for enhanced command palette functionality

* feat: introduce new command palette components and enhance search functionality

* feat: enhance command palette components with improved initialization and loading indicators

* feat: Implement new command palette architecture with multi-step commands, context-aware filtering, and reusable components. Add comprehensive documentation and integration guides. Enhance command execution with a dedicated executor and context provider. Introduce new command types and improve existing command definitions for better usability and maintainability.

* refactor: hook arguments

* refactor: folder structure

* refactor: update import paths

* fix: context prop drilling

* refactor: update search components

* refactor: create actions

* chore: add type to pages

* chore: init contextual actions

* refactor: context based actions code split

* chore: module context-based actions

* refactor: streamline command execution flow and enhance multi-step handling in command palette

* refactor: remove placeholder management from command execution and implement centralized placeholder mapping

* chore: cycle context based actions

* refactor: simplify command execution by consolidating selection steps and adding page change handling

* chore: added more options to work item contextual actions

* chore: page context actions

* refactor: update step type definitions and enhance page mapping for command execution

* feat: implement Command Palette V2 with global shortcuts and enhanced context handling

* refactor: power k v2

* refactor: creation commands

* feat: add navigation utility for Power K context handling

* feat: implement comprehensive navigation commands for Power K

* refactor: work item contextual actions

* fix: build errors

* refactor: remaining contextual actions

* refactor: remove old code

* chore: update placeholder

* refactor: enhance command registry with observable properties and context-aware shortcut handling

* refactor: improve command filtering logic in CommandPaletteModal

* chore: context indicator

* chore: misc actions

* style: shortcut badge

* feat: add open entity actions and enhance navigation commands for Power K

* refactor: rename and reorganize Power K components for improved clarity and structure

* refactor: update CommandPalette components and streamline global shortcuts handling

* refactor: adjust debounce timing in CommandPaletteModal for improved responsiveness

* feat: implement shortcuts modal and enhance command registry for better shortcut management

* fix: search implemented

* refactor: search results code split

* refactor: search results code split

* feat: introduce creation and navigation command modules for Power K, enhancing command organization and functionality

* chore: update menu logos

* refactor: remove unused PowerKOpenEntityActionsExtended component from command palette

* refactor: search menu

* fix: clear context on backspace and manual clear

* refactor: rename creation command keys for consistency and clarity in Power K

* chore: added intake in global search

* chore: preferences menu

* chore: removed the empty serach params

* revert: command palette changes

* cleanup

* refactor: update command IDs to use underscores for consistency across Power K components

* refactor: extended context based actions

* chore: modal command item status props

* refactor: replace CommandPalette with CommandPaletteProvider in settings and profile layouts

* refactor: update settings menu to use translated labels instead of i18n labels

* refactor: update command titles to use translation keys for creation actions

* refactor: update navigation command titles to use translation keys for consistency

* chore: minor cleanup

* chore: misc commands added

* chore: code split for no search results command

* chore: state menu items for work item context based commands

* chore: add more props to no search results command

* chore: add more props to no search results command

* refactor: remove shortcut key for create workspace command

* Refactor command palette to use PowerK store

- Replaced instances of `useCommandPalette` with `usePowerK` across various components, including `AppSearch`, `CommandModal`, and `CommandPalette`.
- Introduced `PowerKStore` to manage modal states and commands, enhancing the command palette functionality.
- Updated modal handling to toggle `PowerKModal` and `ShortcutsListModal` instead of the previous command palette modals.
- Refactored related components to ensure compatibility with the new store structure and maintain functionality.

* Refactor PowerK command handling to remove context dependency

- Updated `usePowerKCommands` and `usePowerKCreationCommands` to eliminate the need for a context parameter, simplifying their usage.
- Adjusted related command records to utilize the new structure, ensuring consistent access to command configurations.
- Enhanced permission checks in creation commands to utilize user project roles for better access control.

* chore: add context indicator

* chore: update type import

* chore: migrate toast implementation from @plane/ui to @plane/propel/toast across multiple command files

* refactor: power k modal wrapper and provider

* fix: type imports

* chore: update creation command shortcuts

* fix: page context commands

* chore: update navigation and open command shortcuts

* fix: work item standalone page modals

* fix: context indicator visibility

* fix: potential error points

* fix: build errors

* fix: lint errors

* fix: import order

---------

Co-authored-by: Vihar Kurama <vihar.kurama@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
ClarenceChen0627 pushed a commit to ClarenceChen0627/plane that referenced this pull request Dec 5, 2025
* feat: add project shortcut in command palette

* feat: global project switcher shortcut

* refactor: generalize command palette entity handling

* feat: extend command palette navigation

* feat: add issue shortcut to command palette

* feat: add modular project selection for cycle navigation

* chore: add reusable command palette utilities

* fix: update key sequence handling to use window methods for timeout management

* fix: build errors

* chore: minor ux copy improvements

* feat: implement a new command registry and renderer for enhanced command palette functionality

* feat: introduce new command palette components and enhance search functionality

* feat: enhance command palette components with improved initialization and loading indicators

* feat: Implement new command palette architecture with multi-step commands, context-aware filtering, and reusable components. Add comprehensive documentation and integration guides. Enhance command execution with a dedicated executor and context provider. Introduce new command types and improve existing command definitions for better usability and maintainability.

* refactor: hook arguments

* refactor: folder structure

* refactor: update import paths

* fix: context prop drilling

* refactor: update search components

* refactor: create actions

* chore: add type to pages

* chore: init contextual actions

* refactor: context based actions code split

* chore: module context-based actions

* refactor: streamline command execution flow and enhance multi-step handling in command palette

* refactor: remove placeholder management from command execution and implement centralized placeholder mapping

* chore: cycle context based actions

* refactor: simplify command execution by consolidating selection steps and adding page change handling

* chore: added more options to work item contextual actions

* chore: page context actions

* refactor: update step type definitions and enhance page mapping for command execution

* feat: implement Command Palette V2 with global shortcuts and enhanced context handling

* refactor: power k v2

* refactor: creation commands

* feat: add navigation utility for Power K context handling

* feat: implement comprehensive navigation commands for Power K

* refactor: work item contextual actions

* fix: build errors

* refactor: remaining contextual actions

* refactor: remove old code

* chore: update placeholder

* refactor: enhance command registry with observable properties and context-aware shortcut handling

* refactor: improve command filtering logic in CommandPaletteModal

* chore: context indicator

* chore: misc actions

* style: shortcut badge

* feat: add open entity actions and enhance navigation commands for Power K

* refactor: rename and reorganize Power K components for improved clarity and structure

* refactor: update CommandPalette components and streamline global shortcuts handling

* refactor: adjust debounce timing in CommandPaletteModal for improved responsiveness

* feat: implement shortcuts modal and enhance command registry for better shortcut management

* fix: search implemented

* refactor: search results code split

* refactor: search results code split

* feat: introduce creation and navigation command modules for Power K, enhancing command organization and functionality

* chore: update menu logos

* refactor: remove unused PowerKOpenEntityActionsExtended component from command palette

* refactor: search menu

* fix: clear context on backspace and manual clear

* refactor: rename creation command keys for consistency and clarity in Power K

* chore: added intake in global search

* chore: preferences menu

* chore: removed the empty serach params

* revert: command palette changes

* cleanup

* refactor: update command IDs to use underscores for consistency across Power K components

* refactor: extended context based actions

* chore: modal command item status props

* refactor: replace CommandPalette with CommandPaletteProvider in settings and profile layouts

* refactor: update settings menu to use translated labels instead of i18n labels

* refactor: update command titles to use translation keys for creation actions

* refactor: update navigation command titles to use translation keys for consistency

* chore: minor cleanup

* chore: misc commands added

* chore: code split for no search results command

* chore: state menu items for work item context based commands

* chore: add more props to no search results command

* chore: add more props to no search results command

* refactor: remove shortcut key for create workspace command

* Refactor command palette to use PowerK store

- Replaced instances of `useCommandPalette` with `usePowerK` across various components, including `AppSearch`, `CommandModal`, and `CommandPalette`.
- Introduced `PowerKStore` to manage modal states and commands, enhancing the command palette functionality.
- Updated modal handling to toggle `PowerKModal` and `ShortcutsListModal` instead of the previous command palette modals.
- Refactored related components to ensure compatibility with the new store structure and maintain functionality.

* Refactor PowerK command handling to remove context dependency

- Updated `usePowerKCommands` and `usePowerKCreationCommands` to eliminate the need for a context parameter, simplifying their usage.
- Adjusted related command records to utilize the new structure, ensuring consistent access to command configurations.
- Enhanced permission checks in creation commands to utilize user project roles for better access control.

* chore: add context indicator

* chore: update type import

* chore: migrate toast implementation from @plane/ui to @plane/propel/toast across multiple command files

* refactor: power k modal wrapper and provider

* fix: type imports

* chore: update creation command shortcuts

* fix: page context commands

* chore: update navigation and open command shortcuts

* fix: work item standalone page modals

* fix: context indicator visibility

* fix: potential error points

* fix: build errors

* fix: lint errors

* fix: import order

---------

Co-authored-by: Vihar Kurama <vihar.kurama@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
ClarenceChen0627 pushed a commit to ClarenceChen0627/plane that referenced this pull request Dec 5, 2025
* feat: add project shortcut in command palette

* feat: global project switcher shortcut

* refactor: generalize command palette entity handling

* feat: extend command palette navigation

* feat: add issue shortcut to command palette

* feat: add modular project selection for cycle navigation

* chore: add reusable command palette utilities

* fix: update key sequence handling to use window methods for timeout management

* fix: build errors

* chore: minor ux copy improvements

* feat: implement a new command registry and renderer for enhanced command palette functionality

* feat: introduce new command palette components and enhance search functionality

* feat: enhance command palette components with improved initialization and loading indicators

* feat: Implement new command palette architecture with multi-step commands, context-aware filtering, and reusable components. Add comprehensive documentation and integration guides. Enhance command execution with a dedicated executor and context provider. Introduce new command types and improve existing command definitions for better usability and maintainability.

* refactor: hook arguments

* refactor: folder structure

* refactor: update import paths

* fix: context prop drilling

* refactor: update search components

* refactor: create actions

* chore: add type to pages

* chore: init contextual actions

* refactor: context based actions code split

* chore: module context-based actions

* refactor: streamline command execution flow and enhance multi-step handling in command palette

* refactor: remove placeholder management from command execution and implement centralized placeholder mapping

* chore: cycle context based actions

* refactor: simplify command execution by consolidating selection steps and adding page change handling

* chore: added more options to work item contextual actions

* chore: page context actions

* refactor: update step type definitions and enhance page mapping for command execution

* feat: implement Command Palette V2 with global shortcuts and enhanced context handling

* refactor: power k v2

* refactor: creation commands

* feat: add navigation utility for Power K context handling

* feat: implement comprehensive navigation commands for Power K

* refactor: work item contextual actions

* fix: build errors

* refactor: remaining contextual actions

* refactor: remove old code

* chore: update placeholder

* refactor: enhance command registry with observable properties and context-aware shortcut handling

* refactor: improve command filtering logic in CommandPaletteModal

* chore: context indicator

* chore: misc actions

* style: shortcut badge

* feat: add open entity actions and enhance navigation commands for Power K

* refactor: rename and reorganize Power K components for improved clarity and structure

* refactor: update CommandPalette components and streamline global shortcuts handling

* refactor: adjust debounce timing in CommandPaletteModal for improved responsiveness

* feat: implement shortcuts modal and enhance command registry for better shortcut management

* fix: search implemented

* refactor: search results code split

* refactor: search results code split

* feat: introduce creation and navigation command modules for Power K, enhancing command organization and functionality

* chore: update menu logos

* refactor: remove unused PowerKOpenEntityActionsExtended component from command palette

* refactor: search menu

* fix: clear context on backspace and manual clear

* refactor: rename creation command keys for consistency and clarity in Power K

* chore: added intake in global search

* chore: preferences menu

* chore: removed the empty serach params

* revert: command palette changes

* cleanup

* refactor: update command IDs to use underscores for consistency across Power K components

* refactor: extended context based actions

* chore: modal command item status props

* refactor: replace CommandPalette with CommandPaletteProvider in settings and profile layouts

* refactor: update settings menu to use translated labels instead of i18n labels

* refactor: update command titles to use translation keys for creation actions

* refactor: update navigation command titles to use translation keys for consistency

* chore: minor cleanup

* chore: misc commands added

* chore: code split for no search results command

* chore: state menu items for work item context based commands

* chore: add more props to no search results command

* chore: add more props to no search results command

* refactor: remove shortcut key for create workspace command

* Refactor command palette to use PowerK store

- Replaced instances of `useCommandPalette` with `usePowerK` across various components, including `AppSearch`, `CommandModal`, and `CommandPalette`.
- Introduced `PowerKStore` to manage modal states and commands, enhancing the command palette functionality.
- Updated modal handling to toggle `PowerKModal` and `ShortcutsListModal` instead of the previous command palette modals.
- Refactored related components to ensure compatibility with the new store structure and maintain functionality.

* Refactor PowerK command handling to remove context dependency

- Updated `usePowerKCommands` and `usePowerKCreationCommands` to eliminate the need for a context parameter, simplifying their usage.
- Adjusted related command records to utilize the new structure, ensuring consistent access to command configurations.
- Enhanced permission checks in creation commands to utilize user project roles for better access control.

* chore: add context indicator

* chore: update type import

* chore: migrate toast implementation from @plane/ui to @plane/propel/toast across multiple command files

* refactor: power k modal wrapper and provider

* fix: type imports

* chore: update creation command shortcuts

* fix: page context commands

* chore: update navigation and open command shortcuts

* fix: work item standalone page modals

* fix: context indicator visibility

* fix: potential error points

* fix: build errors

* fix: lint errors

* fix: import order

---------

Co-authored-by: Vihar Kurama <vihar.kurama@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
@coderabbitai coderabbitai bot mentioned this pull request Feb 10, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants