Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 2c044ef

Browse files
Merging cea3fce into trunk-temp/pr-3698/3fcf69ab-1155-4457-8dfd-77e7d5da9cce
2 parents 0305ec2 + cea3fce commit 2c044ef

3 files changed

Lines changed: 102 additions & 7 deletions

File tree

‎apps/code/snapshots.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ snapshots:
8585
channels-taskfeedrow--human-started--light:
8686
hash: v1.k4693efd2.84c26deb1a587fe061238b3982b555167575893bacc9cd2667d4d3f74646261f.abqc0Voe6FIQFflzDJTv1KewcZ4XxcDz1a0mFlJu7oM
8787
channels-taskfeedrow--long-prompt--dark:
88-
hash: v1.k4693efd2.2547e88d76889aa7290c5221ff5ecb674cdb1cd9f1ae5efd211fb648a88037b6.4c9RO1upuz3VijYp2xU2MTNrW_87zWQLX5kRKCYABq4
88+
hash: v1.k4693efd2.b79b9d4cc5eeeb06f77766f05d21fca4997a155912d6d71f23105a6e58cf5fe6.bqN_eRKXFN0qEz1lD1h_3qWDTUpDDUsIjpvtEL8zdko
8989
channels-taskfeedrow--long-prompt--light:
90-
hash: v1.k4693efd2.6b0f609e0155bc3b6e3149714b70d33ef83f7c2ca7b72e6d3a495b8db36828d0.eC5ZQDilDw7Y9EJqhfIdlk186GJjgGbYolZ9cm9Xtt0
90+
hash: v1.k4693efd2.d07cc4df0bf67388e83ff917dc8bbe73d862f10044c2ef201a2f62042f621271.HauxzRuUxumHY1wAtYSqVPdh3nFT8teGBWpYEOGC_FE
9191
channels-taskfeedrow--no-prompt--dark:
9292
hash: v1.k4693efd2.9fa967f1a9acdeba0c50a9e45ae649f118ee26938037dbefd1bb0577067b03d4.va1lGsscqLW86-5yCKc3H6pQ8Az7_HBItkgGTnTQiYU
9393
channels-taskfeedrow--no-prompt--light:
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { Task } from "@posthog/shared/domain-types";
2+
import { Theme } from "@radix-ui/themes";
3+
import { render, screen } from "@testing-library/react";
4+
import userEvent from "@testing-library/user-event";
5+
import { describe, expect, it, vi } from "vitest";
6+
import { TaskFeedRow } from "./ChannelFeedView";
7+
8+
const task = {
9+
id: "task-1",
10+
task_number: 1,
11+
slug: "task-1",
12+
title: "Investigate signup drop-off",
13+
description: "A long prompt that needs to be expanded in the channel feed",
14+
created_at: "2026-07-17T12:00:00.000Z",
15+
updated_at: "2026-07-17T12:00:00.000Z",
16+
origin_product: "user_created",
17+
created_by: {
18+
id: 1,
19+
uuid: "user-1",
20+
email: "person@example.com",
21+
first_name: "A",
22+
last_name: "Person",
23+
},
24+
} satisfies Task;
25+
26+
describe("TaskFeedRow", () => {
27+
it("expands a truncated prompt", async () => {
28+
vi.spyOn(HTMLElement.prototype, "scrollHeight", "get").mockReturnValue(60);
29+
vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockReturnValue(40);
30+
const user = userEvent.setup();
31+
render(
32+
<Theme>
33+
<TaskFeedRow task={task} />
34+
</Theme>,
35+
);
36+
37+
const prompt = screen.getByText(task.description);
38+
expect(prompt).toHaveClass("line-clamp-2");
39+
40+
await user.click(screen.getByRole("button", { name: "more" }));
41+
42+
expect(prompt).not.toHaveClass("line-clamp-2");
43+
expect(screen.getByRole("button", { name: "less" })).toBeInTheDocument();
44+
});
45+
});

‎packages/ui/src/features/canvas/components/ChannelFeedView.tsx‎

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,12 @@ import {
6161
Fragment,
6262
memo,
6363
type ReactNode,
64+
useCallback,
6465
useEffect,
6566
useLayoutEffect,
6667
useMemo,
6768
useRef,
69+
useState,
6870
} from "react";
6971

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

414+
function ExpandablePrompt({
415+
children,
416+
lines,
417+
}: {
418+
children: string;
419+
lines: 2 | 4;
420+
}) {
421+
const observerRef = useRef<ResizeObserver | null>(null);
422+
const [expanded, setExpanded] = useState(false);
423+
const [truncated, setTruncated] = useState(false);
424+
425+
const measureRef = useCallback(
426+
(body: HTMLDivElement | null) => {
427+
observerRef.current?.disconnect();
428+
observerRef.current = null;
429+
if (!body || expanded) return;
430+
const measure = () => setTruncated(body.scrollHeight > body.clientHeight);
431+
measure();
432+
const observer = new ResizeObserver(measure);
433+
observer.observe(body);
434+
observerRef.current = observer;
435+
},
436+
[expanded],
437+
);
438+
439+
return (
440+
<div>
441+
<ThreadItemBody
442+
ref={measureRef}
443+
className={cn(
444+
"wrap-break-word whitespace-pre-wrap",
445+
!expanded && (lines === 2 ? "line-clamp-2" : "line-clamp-4"),
446+
)}
447+
>
448+
{children}
449+
</ThreadItemBody>
450+
{(truncated || expanded) && (
451+
<button
452+
type="button"
453+
aria-expanded={expanded}
454+
className="text-muted-foreground text-xs underline underline-offset-2 hover:text-foreground"
455+
onClick={() => setExpanded((value) => !value)}
456+
>
457+
{expanded ? "less" : "more"}
458+
</button>
459+
)}
460+
</div>
461+
);
462+
}
463+
412464
export function TaskFeedRow({
413465
task,
414466
actions,
@@ -451,10 +503,10 @@ export function TaskFeedRow({
451503
</ThreadItemTimestamp>
452504
</ThreadItemHeader>
453505

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

459511
{children}
460512
</ThreadItemContent>
@@ -569,9 +621,7 @@ function PendingFeedRow({
569621
<ThreadItemAuthor>You</ThreadItemAuthor>
570622
<ThreadItemTimestamp dateTime={createdAt}>now</ThreadItemTimestamp>
571623
</ThreadItemHeader>
572-
<ThreadItemBody className="wrap-break-word line-clamp-4 whitespace-pre-wrap">
573-
{pending.prompt}
574-
</ThreadItemBody>
624+
<ExpandablePrompt lines={4}>{pending.prompt}</ExpandablePrompt>
575625
<Card
576626
size="sm"
577627
className="mt-1.5 w-full max-w-[820px] rounded-sm py-0"

0 commit comments

Comments
 (0)