diff --git a/apps/code/src/renderer/features/command/components/CommandMenu.tsx b/apps/code/src/renderer/features/command/components/CommandMenu.tsx index 006dc619a..daf431fe5 100644 --- a/apps/code/src/renderer/features/command/components/CommandMenu.tsx +++ b/apps/code/src/renderer/features/command/components/CommandMenu.tsx @@ -64,10 +64,17 @@ function TaskCommandIcon({ task }: { task: Task }) { cloudPrUrl: null, taskRunEnvironment: task.latest_run?.environment, }); + const stateSlackThreadUrl = ( + task.latest_run?.state as { slack_thread_url?: unknown } | undefined + )?.slack_thread_url; + const slackThreadUrl = + typeof stateSlackThreadUrl === "string" ? stateSlackThreadUrl : undefined; return ( diff --git a/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx b/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx index b8cec1f3e..e12c42aec 100644 --- a/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx +++ b/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx @@ -120,6 +120,8 @@ function TaskRow({ isPinned={task.isPinned} needsPermission={task.needsPermission} taskRunStatus={task.taskRunStatus} + originProduct={task.originProduct} + slackThreadUrl={task.slackThreadUrl} prState={prState} hasDiff={hasDiff} timestamp={timestamp} diff --git a/apps/code/src/renderer/features/sidebar/components/items/TaskIcon.tsx b/apps/code/src/renderer/features/sidebar/components/items/TaskIcon.tsx index 866a5ed65..de44afcd4 100644 --- a/apps/code/src/renderer/features/sidebar/components/items/TaskIcon.tsx +++ b/apps/code/src/renderer/features/sidebar/components/items/TaskIcon.tsx @@ -12,7 +12,9 @@ import { HandPalm, Pause, PushPin, + SlackLogo, } from "@phosphor-icons/react"; +import { trpcClient } from "@renderer/trpc/client"; import { isTerminalStatus, type TaskRunStatus } from "@shared/types"; export const ICON_SIZE = 12; @@ -23,47 +25,145 @@ export const ICON_SIZE = 12; // selected row, which turns a `currentColor` icon black on hover. An explicit // `fill` is immune, and renders identically in the sidebar. +// Map origin_product values to the icon + label used to brand the task's +// status icon. Extend this when a new product (e.g. email, support) needs its +// own indicator. +type OriginProductMeta = { Icon: typeof SlackLogo; label: string }; +const ORIGIN_PRODUCT_META: Record = { + slack: { Icon: SlackLogo, label: "Slack" }, +}; + +function getOriginProductMeta( + originProduct?: string, +): OriginProductMeta | undefined { + return originProduct ? ORIGIN_PRODUCT_META[originProduct] : undefined; +} + +// Renders the icon inside a span. When `link` is set the span becomes +// clickable and opens the originating thread externally. SidebarItem renders +// the row as a `