Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/code/snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ snapshots:
channels-taskfeedrow--human-started--light:
hash: v1.k4693efd2.84c26deb1a587fe061238b3982b555167575893bacc9cd2667d4d3f74646261f.abqc0Voe6FIQFflzDJTv1KewcZ4XxcDz1a0mFlJu7oM
channels-taskfeedrow--long-prompt--dark:
hash: v1.k4693efd2.2547e88d76889aa7290c5221ff5ecb674cdb1cd9f1ae5efd211fb648a88037b6.4c9RO1upuz3VijYp2xU2MTNrW_87zWQLX5kRKCYABq4
hash: v1.k4693efd2.b79b9d4cc5eeeb06f77766f05d21fca4997a155912d6d71f23105a6e58cf5fe6.bqN_eRKXFN0qEz1lD1h_3qWDTUpDDUsIjpvtEL8zdko
channels-taskfeedrow--long-prompt--light:
hash: v1.k4693efd2.6b0f609e0155bc3b6e3149714b70d33ef83f7c2ca7b72e6d3a495b8db36828d0.eC5ZQDilDw7Y9EJqhfIdlk186GJjgGbYolZ9cm9Xtt0
hash: v1.k4693efd2.d07cc4df0bf67388e83ff917dc8bbe73d862f10044c2ef201a2f62042f621271.HauxzRuUxumHY1wAtYSqVPdh3nFT8teGBWpYEOGC_FE
channels-taskfeedrow--no-prompt--dark:
hash: v1.k4693efd2.9fa967f1a9acdeba0c50a9e45ae649f118ee26938037dbefd1bb0577067b03d4.va1lGsscqLW86-5yCKc3H6pQ8Az7_HBItkgGTnTQiYU
channels-taskfeedrow--no-prompt--light:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { Task } from "@posthog/shared/domain-types";
import { Theme } from "@radix-ui/themes";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { TaskFeedRow } from "./ChannelFeedView";

const task = {
id: "task-1",
task_number: 1,
slug: "task-1",
title: "Investigate signup drop-off",
description: "A long prompt that needs to be expanded in the channel feed",
created_at: "2026-07-17T12:00:00.000Z",
updated_at: "2026-07-17T12:00:00.000Z",
origin_product: "user_created",
created_by: {
id: 1,
uuid: "user-1",
email: "person@example.com",
first_name: "A",
last_name: "Person",
},
} satisfies Task;

describe("TaskFeedRow", () => {
it("expands a truncated prompt", async () => {
vi.spyOn(HTMLElement.prototype, "scrollHeight", "get").mockReturnValue(60);
vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockReturnValue(40);
const user = userEvent.setup();
render(
<Theme>
<TaskFeedRow task={task} />
</Theme>,
);

const prompt = screen.getByText(task.description);
expect(prompt).toHaveClass("line-clamp-2");

await user.click(screen.getByRole("button", { name: "more" }));

expect(prompt).not.toHaveClass("line-clamp-2");
expect(screen.getByRole("button", { name: "less" })).toBeInTheDocument();
});
});
60 changes: 55 additions & 5 deletions packages/ui/src/features/canvas/components/ChannelFeedView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ import {
Fragment,
memo,
type ReactNode,
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";

// Feed rows poll their reply counts slower than the open thread panel — the
Expand Down Expand Up @@ -409,6 +411,56 @@ function channelTaskStarter(task: Task): UserBasic | null {
: null;
}

function ExpandablePrompt({
children,
lines,
}: {
children: string;
lines: 2 | 4;
}) {
const observerRef = useRef<ResizeObserver | null>(null);
const [expanded, setExpanded] = useState(false);
const [truncated, setTruncated] = useState(false);

const measureRef = useCallback(
(body: HTMLDivElement | null) => {
observerRef.current?.disconnect();
observerRef.current = null;
if (!body || expanded) return;
const measure = () => setTruncated(body.scrollHeight > body.clientHeight);
measure();
const observer = new ResizeObserver(measure);
observer.observe(body);
observerRef.current = observer;
},
[expanded],
);

return (
<div>
<ThreadItemBody
ref={measureRef}
className={cn(
"wrap-break-word whitespace-pre-wrap",
!expanded && (lines === 2 ? "line-clamp-2" : "line-clamp-4"),
)}
>
{children}
</ThreadItemBody>
{(truncated || expanded) && (
<button
type="button"
aria-expanded={expanded}
className="text-muted-foreground text-xs underline underline-offset-2 hover:text-foreground"
onClick={() => setExpanded((value) => !value)}
>
Comment thread
k11kirky marked this conversation as resolved.
{expanded ? "less" : "more"}
</button>
)}
</div>
);
}

export function TaskFeedRow({
task,
actions,
Expand Down Expand Up @@ -451,10 +503,10 @@ export function TaskFeedRow({
</ThreadItemTimestamp>
</ThreadItemHeader>

<ThreadItemBody className="wrap-break-word line-clamp-2 whitespace-pre-wrap">
<ExpandablePrompt lines={2}>
{prompt ||
(starter ? "started a new task" : "A new task was started")}
</ThreadItemBody>
</ExpandablePrompt>

{children}
</ThreadItemContent>
Expand Down Expand Up @@ -569,9 +621,7 @@ function PendingFeedRow({
<ThreadItemAuthor>You</ThreadItemAuthor>
<ThreadItemTimestamp dateTime={createdAt}>now</ThreadItemTimestamp>
</ThreadItemHeader>
<ThreadItemBody className="wrap-break-word line-clamp-4 whitespace-pre-wrap">
{pending.prompt}
</ThreadItemBody>
<ExpandablePrompt lines={4}>{pending.prompt}</ExpandablePrompt>
<Card
size="sm"
className="mt-1.5 w-full max-w-[820px] rounded-sm py-0"
Expand Down
Loading