diff --git a/packages/ui/src/features/canvas/components/ChannelFeedView.tsx b/packages/ui/src/features/canvas/components/ChannelFeedView.tsx index f2056a5e6c..fd5a38f871 100644 --- a/packages/ui/src/features/canvas/components/ChannelFeedView.tsx +++ b/packages/ui/src/features/canvas/components/ChannelFeedView.tsx @@ -3,7 +3,6 @@ import { ChatCircleIcon, GitBranchIcon, RobotIcon, - UserIcon, } from "@phosphor-icons/react"; import { Avatar, @@ -40,13 +39,10 @@ import type { Task, TaskRunStatus } from "@posthog/shared/domain-types"; import { isTerminalStatus } from "@posthog/shared/domain-types"; import { getUserInitials } from "@posthog/ui/features/auth/userInitials"; import { TaskTabIcon } from "@posthog/ui/features/browser-tabs/TaskTabIcon"; +import { mentionChipClass } from "@posthog/ui/features/canvas/components/MentionText"; import { useChannelTaskData } from "@posthog/ui/features/canvas/hooks/useChannelTaskData"; import { useTaskThread } from "@posthog/ui/features/canvas/hooks/useTaskThread"; import { userDisplayName } from "@posthog/ui/features/canvas/utils/userDisplay"; -import { xmlToPlainText } from "@posthog/ui/features/message-editor/content"; -import { CollapsibleMessageContent } from "@posthog/ui/features/sessions/components/session-update/CollapsibleMessageContent"; -import { extractChannelContext } from "@posthog/ui/features/sessions/components/session-update/channelContext"; -import { getOriginProductMeta } from "@posthog/ui/features/sidebar/components/items/TaskIcon"; import { type SidebarPrState, useTaskPrStatus, @@ -198,9 +194,8 @@ function useTaskStatusDisplay(task: Task): TaskStatusDisplay { base = statusBadge(status); } else { // Local, non-terminal: the run status is unreliable (the backend row stays - // "queued" while the agent runs on the creator's machine), and the - // environment already shows in the card's meta row ("· Local"), so we - // render no status badge here rather than a redundant "Local" pill. + // "queued" while the agent runs on the creator's machine), so we render no + // status badge rather than a misleading one. base = null; } @@ -242,59 +237,21 @@ function TaskStatusBadge({ display }: { display: TaskStatusDisplay }) { ); } -// The prompt as the user typed it: drop the channel CONTEXT.md block the saga -// prepended and flatten the editor XML back to plain text. -function promptText(task: Task): string { - const raw = - extractChannelContext(task.description)?.stripped ?? task.description; - try { - return xmlToPlainText(raw).trim() || task.title; - } catch { - return raw.trim() || task.title; - } -} - -// The card's context line, mirroring the storybook feed: who/what kicked the -// task off ("Requested by @Ann" for humans, the origin product otherwise). -function TaskCardOrigin({ task }: { task: Task }) { - const isUserCreated = task.origin_product === "user_created"; - const label = isUserCreated - ? `Requested by @${userDisplayName(task.created_by)}` - : (getOriginProductMeta(task.origin_product)?.label ?? task.origin_product); - return ( - - {isUserCreated ? : } - {label} - - ); -} - // The task the message kicked off, as a card everyone in the channel sees: -// origin + status up top, bold title, then run metadata. +// bold title + status up top, then run metadata. function TaskCard({ task, onOpen }: { task: Task; onOpen: () => void }) { const statusDisplay = useTaskStatusDisplay(task); const prUrl = typeof task.latest_run?.output?.pr_url === "string" ? task.latest_run.output.pr_url : undefined; - // The repository renders separately with its icon; `meta` is the plain-text - // remainder of the row. - const environment = task.latest_run?.environment; - const meta = [ - task.slug || null, - task.latest_run?.stage ?? null, - environment === "cloud" - ? "Cloud" - : environment === "local" - ? "Local" - : null, - ].filter(Boolean) as string[]; + const stage = task.latest_run?.stage; return ( void }) { >
- +
+ {/* Same live status icon as the code side nav, so the card and the + nav never disagree (generating spinner, needs-permission, cloud + status colors, PR state). */} + + + {task.title || "Untitled task"} + +
-
- {/* Same live status icon as the code side nav, so the card and the - nav never disagree (generating spinner, needs-permission, cloud - status colors, PR state). */} - - - {task.title || "Untitled task"} - -
- {(meta.length > 0 || task.repository || prUrl) && ( + {(stage || task.repository || prUrl) && (
{task.repository && ( @@ -323,9 +279,9 @@ function TaskCard({ task, onOpen }: { task: Task; onOpen: () => void }) { {task.repository} )} - {meta.length > 0 && ( + {stage && ( - {meta.join(" · ")} + {stage} )} {prUrl && ( @@ -422,29 +378,20 @@ const FeedItem = memo(function FeedItem({ onOpenTask: (task: Task) => void; onOpenThread: (task: Task) => void; }) { - const prompt = useMemo(() => promptText(task), [task]); - const isAgent = !task.created_by || task.origin_product !== "user_created"; - return ( - {isAgent && !task.created_by ? ( - - ) : ( - getUserInitials(task.created_by) - )} + - - {task.created_by ? userDisplayName(task.created_by) : "Agent"} - - {isAgent && Agent} + PostHog + Agent @@ -452,10 +399,21 @@ const FeedItem = memo(function FeedItem({ - - - {prompt} - + + {/* Only attribute channel-started tasks: other origins (Slack, + automations) carry a created_by who didn't start it here. */} + {task.origin_product === "user_created" && task.created_by ? ( + <> + {/* Mention-styled but rendered inert: the starter shouldn't be + notified about their own task. */} + + @{userDisplayName(task.created_by)} + {" "} + started a new task + + ) : ( + "A new task was started" + )} onOpenTask(task)} /> diff --git a/packages/ui/src/features/canvas/components/MentionText.tsx b/packages/ui/src/features/canvas/components/MentionText.tsx index ffdb7834ec..ce6c4bd26a 100644 --- a/packages/ui/src/features/canvas/components/MentionText.tsx +++ b/packages/ui/src/features/canvas/components/MentionText.tsx @@ -8,6 +8,12 @@ type RenderSegment = | { type: "link"; text: string; href: string } | { type: "mention"; name: string; email: string }; +// The plain (not-the-viewer) mention chip look, also used by surfaces that +// render a mention-styled name without real mention semantics (e.g. the +// channel feed's "started a new task" row). +export const mentionChipClass = + "rounded px-0.5 font-medium text-[var(--accent-11)]"; + /** * Thread message content with inline mention tokens rendered as highlighted * `@Name` chips (a mention of the viewer gets the stronger treatment) and @@ -52,11 +58,11 @@ export function MentionText({ return ( @{segment.name}