+
+
+
+ }
+ />
{pinnedTasks.length === 0 &&
flatTasks.length === 0 &&
diff --git a/apps/code/src/renderer/features/sidebar/components/items/HomeItem.tsx b/apps/code/src/renderer/features/sidebar/components/items/HomeItem.tsx
index 5fcef26b0..7ca165a1c 100644
--- a/apps/code/src/renderer/features/sidebar/components/items/HomeItem.tsx
+++ b/apps/code/src/renderer/features/sidebar/components/items/HomeItem.tsx
@@ -1,12 +1,9 @@
-import { Badge } from "@components/ui/Badge";
import { Tooltip } from "@components/ui/Tooltip";
import { EnvelopeSimple, Plus } from "@phosphor-icons/react";
-import type { ButtonProps } from "@posthog/quill";
-import {
- formatHotkey,
- SHORTCUTS,
-} from "@renderer/constants/keyboard-shortcuts";
+import { Badge, type ButtonProps } from "@posthog/quill";
+import { SHORTCUTS } from "@renderer/constants/keyboard-shortcuts";
import { SidebarItem } from "../SidebarItem";
+import { SidebarKbdHint } from "./SidebarKbdHint";
interface NewTaskItemProps {
isActive: boolean;
@@ -22,6 +19,7 @@ export function NewTaskItem({ isActive, onClick }: NewTaskItemProps) {
label="New task"
isActive={isActive}
onClick={onClick}
+ endContent={}
/>
);
}
@@ -45,7 +43,6 @@ export function InboxItem({ isActive, onClick, signalCount }: InboxItemProps) {
? `${signalCount} actionable report${signalCount === 1 ? "" : "s"} assigned to you`
: "No actionable reports assigned to you yet"
}
- shortcut={formatHotkey(SHORTCUTS.INBOX)}
side="right"
>
@@ -72,7 +69,12 @@ export function InboxItem({ isActive, onClick, signalCount }: InboxItemProps) {
}
isActive={isActive}
onClick={onClick}
- endContent={Alpha}
+ endContent={
+ <>
+ Alpha
+
+ >
+ }
/>
diff --git a/apps/code/src/renderer/features/sidebar/components/items/SearchItem.tsx b/apps/code/src/renderer/features/sidebar/components/items/SearchItem.tsx
new file mode 100644
index 000000000..99d68461b
--- /dev/null
+++ b/apps/code/src/renderer/features/sidebar/components/items/SearchItem.tsx
@@ -0,0 +1,20 @@
+import { MagnifyingGlass } from "@phosphor-icons/react";
+import { SHORTCUTS } from "@renderer/constants/keyboard-shortcuts";
+import { SidebarItem } from "../SidebarItem";
+import { SidebarKbdHint } from "./SidebarKbdHint";
+
+interface SearchItemProps {
+ onClick: () => void;
+}
+
+export function SearchItem({ onClick }: SearchItemProps) {
+ return (
+ }
+ label="Search"
+ onClick={onClick}
+ endContent={}
+ />
+ );
+}
diff --git a/apps/code/src/renderer/features/sidebar/components/items/SidebarKbdHint.tsx b/apps/code/src/renderer/features/sidebar/components/items/SidebarKbdHint.tsx
new file mode 100644
index 000000000..3a751d2ae
--- /dev/null
+++ b/apps/code/src/renderer/features/sidebar/components/items/SidebarKbdHint.tsx
@@ -0,0 +1,21 @@
+import { Kbd } from "@posthog/quill";
+import { formatHotkey } from "@renderer/constants/keyboard-shortcuts";
+
+interface SidebarKbdHintProps {
+ /** Raw shortcut string from SHORTCUTS, e.g. "mod+k". */
+ keys: string;
+}
+
+/**
+ * Keyboard shortcut hint for a sidebar nav item. Hidden until the parent
+ * SidebarItem (which carries the `group` class) is hovered. Toggled via
+ * `display` so it takes no space when idle and preceding `endContent` sits
+ * flush to the edge — no transition, to match the rest of the sidebar.
+ */
+export function SidebarKbdHint({ keys }: SidebarKbdHintProps) {
+ return (
+
+ {formatHotkey(keys)}
+
+ );
+}
diff --git a/apps/code/src/renderer/features/sidebar/components/items/TaskIcon.tsx b/apps/code/src/renderer/features/sidebar/components/items/TaskIcon.tsx
new file mode 100644
index 000000000..2d8ef6a02
--- /dev/null
+++ b/apps/code/src/renderer/features/sidebar/components/items/TaskIcon.tsx
@@ -0,0 +1,204 @@
+import { DotsCircleSpinner } from "@components/DotsCircleSpinner";
+import { Tooltip } from "@components/ui/Tooltip";
+import type { SidebarPrState } from "@features/sidebar/hooks/useTaskPrStatus";
+import type { WorkspaceMode } from "@main/services/workspace/schemas";
+import {
+ ChatCircle,
+ Circle,
+ Cloud as CloudIcon,
+ GitBranch,
+ GitMerge,
+ GitPullRequest,
+ HandPalm,
+ Pause,
+ PushPin,
+} from "@phosphor-icons/react";
+import { isTerminalStatus, type TaskRunStatus } from "@shared/types";
+
+export const ICON_SIZE = 12;
+
+// Colors are passed as the phosphor `color` prop (an SVG `fill` attribute)
+// rather than `text-*` classes: in the command palette, quill's
+// `[data-highlighted] *` rule resets every descendant CSS `color` for the
+// selected row, which turns a `currentColor` icon black on hover. An explicit
+// `fill` is immune, and renders identically in the sidebar.
+
+function CloudStatusIcon({ taskRunStatus }: { taskRunStatus?: TaskRunStatus }) {
+ if (taskRunStatus === "queued" || taskRunStatus === "in_progress") {
+ return (
+
+
+
+
+
+ );
+ }
+ if (taskRunStatus === "completed") {
+ return (
+
+
+
+
+
+ );
+ }
+ if (taskRunStatus === "failed" || taskRunStatus === "cancelled") {
+ const label =
+ taskRunStatus === "cancelled" ? "Cloud (cancelled)" : "Cloud (failed)";
+ return (
+
+
+
+
+
+ );
+ }
+ return (
+
+
+
+
+
+ );
+}
+
+function PrStatusIcon({
+ prState,
+ hasDiff,
+}: {
+ prState?: SidebarPrState;
+ hasDiff?: boolean;
+}) {
+ if (prState === "merged") {
+ return (
+
+
+
+
+
+ );
+ }
+ if (prState === "open") {
+ return (
+
+
+
+
+
+ );
+ }
+ if (prState === "draft") {
+ return (
+
+
+
+
+
+ );
+ }
+ if (prState === "closed") {
+ return (
+
+
+
+
+
+ );
+ }
+ if (hasDiff) {
+ return (
+
+
+
+
+
+ );
+ }
+ return null;
+}
+
+export interface TaskIconProps {
+ workspaceMode?: WorkspaceMode;
+ isGenerating?: boolean;
+ isUnread?: boolean;
+ isPinned?: boolean;
+ isSuspended?: boolean;
+ needsPermission?: boolean;
+ taskRunStatus?: TaskRunStatus;
+ prState?: SidebarPrState;
+ hasDiff?: boolean;
+}
+
+/**
+ * Status icon for a task, shared by the sidebar task list and the command
+ * palette so both render the exact same states (cloud run status, PR/branch
+ * status, generating, unread, etc.).
+ */
+export function TaskIcon({
+ workspaceMode,
+ isGenerating,
+ isUnread,
+ isPinned,
+ isSuspended,
+ needsPermission,
+ taskRunStatus,
+ prState,
+ hasDiff,
+}: TaskIconProps) {
+ const isCloudTask = workspaceMode === "cloud";
+ const isTerminalCloud = isCloudTask && isTerminalStatus(taskRunStatus);
+
+ if (needsPermission) {
+ return (
+
+
+
+
+
+ );
+ }
+ if (isTerminalCloud) {
+ return ;
+ }
+ if (isGenerating) {
+ return ;
+ }
+ if (isCloudTask) {
+ return ;
+ }
+ if (isSuspended) {
+ return (
+
+
+
+
+
+ );
+ }
+ if (isUnread) {
+ return (
+
+
+
+ );
+ }
+ if (prState || hasDiff) {
+ return ;
+ }
+ if (isPinned) {
+ return ;
+ }
+ return ;
+}
diff --git a/apps/code/src/renderer/features/sidebar/components/items/TaskItem.tsx b/apps/code/src/renderer/features/sidebar/components/items/TaskItem.tsx
index eb604baeb..16412e341 100644
--- a/apps/code/src/renderer/features/sidebar/components/items/TaskItem.tsx
+++ b/apps/code/src/renderer/features/sidebar/components/items/TaskItem.tsx
@@ -1,23 +1,12 @@
-import { DotsCircleSpinner } from "@components/DotsCircleSpinner";
import { Tooltip } from "@components/ui/Tooltip";
import type { SidebarPrState } from "@features/sidebar/hooks/useTaskPrStatus";
import type { WorkspaceMode } from "@main/services/workspace/schemas";
-import {
- Archive,
- ChatCircle,
- Circle,
- Cloud as CloudIcon,
- GitBranch,
- GitMerge,
- GitPullRequest,
- HandPalm,
- Pause,
- PushPin,
-} from "@phosphor-icons/react";
-import { isTerminalStatus, type TaskRunStatus } from "@shared/types";
+import { Archive, PushPin } from "@phosphor-icons/react";
+import type { TaskRunStatus } from "@shared/types";
import { formatRelativeTimeShort } from "@utils/time";
import { useCallback, useEffect, useRef, useState } from "react";
import { SidebarItem } from "../SidebarItem";
+import { TaskIcon } from "./TaskIcon";
interface TaskItemProps {
depth?: number;
@@ -110,119 +99,8 @@ function TaskHoverToolbar({
);
}
-const ICON_SIZE = 12;
const INDENT_SIZE = 8;
-function CloudStatusIcon({
- taskRunStatus,
-}: {
- taskRunStatus?: TaskItemProps["taskRunStatus"];
-}) {
- if (taskRunStatus === "queued" || taskRunStatus === "in_progress") {
- return (
-
-
-
-
-
- );
- }
- if (taskRunStatus === "completed") {
- return (
-
-
-
-
-
- );
- }
- if (taskRunStatus === "failed" || taskRunStatus === "cancelled") {
- const label =
- taskRunStatus === "cancelled" ? "Cloud (cancelled)" : "Cloud (failed)";
- return (
-
-
-
-
-
- );
- }
- return (
-
-
-
-
-
- );
-}
-
-function PrStatusIcon({
- prState,
- hasDiff,
-}: {
- prState?: SidebarPrState;
- hasDiff?: boolean;
-}) {
- if (prState === "merged") {
- return (
-
-
-
-
-
- );
- }
- if (prState === "open") {
- return (
-
-
-
-
-
- );
- }
- if (prState === "draft") {
- return (
-
-
-
-
-
- );
- }
- if (prState === "closed") {
- return (
-
-
-
-
-
- );
- }
- if (hasDiff) {
- return (
-
-
-
-
-
- );
- }
- return null;
-}
-
export function TaskItem({
depth = 0,
taskId,
@@ -247,37 +125,18 @@ export function TaskItem({
onEditSubmit,
onEditCancel,
}: TaskItemProps) {
- const isCloudTask = workspaceMode === "cloud";
- const isTerminalCloud = isCloudTask && isTerminalStatus(taskRunStatus);
-
- const icon = needsPermission ? (
-
-
-
-
-
- ) : isTerminalCloud ? (
-
- ) : isGenerating ? (
-
- ) : isCloudTask ? (
-
- ) : isSuspended ? (
-
-
-
-
-
- ) : isUnread ? (
-
-
-
- ) : prState || hasDiff ? (
-
- ) : isPinned ? (
-
- ) : (
-
+ const icon = (
+
);
const timestampNode = timestamp ? (
diff --git a/apps/code/src/renderer/features/sidebar/hooks/useTaskPrStatus.ts b/apps/code/src/renderer/features/sidebar/hooks/useTaskPrStatus.ts
index 22c7787f3..cf034e96c 100644
--- a/apps/code/src/renderer/features/sidebar/hooks/useTaskPrStatus.ts
+++ b/apps/code/src/renderer/features/sidebar/hooks/useTaskPrStatus.ts
@@ -12,7 +12,9 @@ export interface TaskPrStatus {
const SIDEBAR_STALE_TIME = 60_000;
const EMPTY: TaskPrStatus = { prState: null, hasDiff: false };
-export function useTaskPrStatus(task: TaskData): TaskPrStatus {
+export function useTaskPrStatus(
+ task: Pick,
+): TaskPrStatus {
const trpc = useTRPC();
const { data } = useQuery(
diff --git a/apps/code/src/shared/types/analytics.ts b/apps/code/src/shared/types/analytics.ts
index 17f6e439b..7250605aa 100644
--- a/apps/code/src/shared/types/analytics.ts
+++ b/apps/code/src/shared/types/analytics.ts
@@ -39,7 +39,8 @@ export type CommandMenuAction =
| "logout"
| "toggle-theme"
| "toggle-left-sidebar"
- | "open-review-panel";
+ | "open-review-panel"
+ | "open-task";
// Event property interfaces
export interface TaskListViewProperties {