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

Commit 3573683

Browse files
authored
fix(channels): decouple task board from removed home
Generated-By: PostHog Code Task-Id: 1fbd8776-9d5b-4e74-9629-a6bdb8d3b78f
1 parent 5c72958 commit 3573683

7 files changed

Lines changed: 29 additions & 108 deletions

File tree

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from "vitest";
2-
import { taskBoardStatus, taskBoardStatusFromSources } from "./taskBoardStatus";
2+
import { taskBoardStatus } from "./taskBoardStatus";
33

44
describe("taskBoardStatus", () => {
55
it.each([
@@ -17,15 +17,3 @@ describe("taskBoardStatus", () => {
1717
expect(taskBoardStatus(input)).toBe(expected);
1818
});
1919
});
20-
21-
it("prefers a directly resolved merged PR over a stale open Home snapshot", () => {
22-
expect(
23-
taskBoardStatusFromSources({
24-
resolvedPrState: "merged",
25-
prSnapshot: {
26-
state: "open",
27-
ciStatus: "failing",
28-
} as never,
29-
}),
30-
).toBe("done");
31-
});
Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { TaskRunStatus } from "@posthog/shared/domain-types";
2-
import type { PrSnapshot, PrSnapshotState } from "../home/prSnapshot";
2+
3+
export type TaskBoardPrState = "open" | "draft" | "merged" | "closed";
34

45
export const TASK_BOARD_STATUSES = [
56
"working",
@@ -15,7 +16,7 @@ export type TaskBoardStatus = (typeof TASK_BOARD_STATUSES)[number];
1516
*/
1617
export function taskBoardStatus(input: {
1718
runStatus?: TaskRunStatus | null;
18-
prState?: PrSnapshotState | null;
19+
prState?: TaskBoardPrState | null;
1920
}): TaskBoardStatus {
2021
if (input.prState === "merged") return "done";
2122
if (input.prState === "closed") return "cancelled";
@@ -27,15 +28,3 @@ export function taskBoardStatus(input: {
2728
if (input.runStatus === "completed") return "done";
2829
return "working";
2930
}
30-
31-
export function taskBoardStatusFromSources(input: {
32-
runStatus?: TaskRunStatus | null;
33-
resolvedPrState?: PrSnapshotState | null;
34-
prSnapshot?: PrSnapshot | null;
35-
}): TaskBoardStatus {
36-
return taskBoardStatus({
37-
runStatus: input.runStatus,
38-
// Direct PR resolution is fresher than the periodically rebuilt Home row.
39-
prState: input.resolvedPrState ?? input.prSnapshot?.state,
40-
});
41-
}

packages/ui/src/features/canvas/components/ChannelBoardView.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ import {
55
GitCommit,
66
XCircle,
77
} from "@phosphor-icons/react";
8-
import type { PrSnapshot } from "@posthog/core/home/prSnapshot";
98
import {
109
TASK_BOARD_STATUSES,
1110
type TaskBoardStatus,
12-
taskBoardStatusFromSources,
11+
taskBoardStatus,
1312
} from "@posthog/core/tasks/taskBoardStatus";
1413
import type { Task } from "@posthog/shared/domain-types";
1514
import { UserAvatar } from "@posthog/ui/features/auth/UserAvatar";
@@ -82,14 +81,12 @@ const STATUS_VISUAL: Record<
8281
export function ChannelBoardView({
8382
tasks,
8483
isLoading,
85-
prSnapshotByTaskId,
8684
taskPrStates,
8785
onOpenTask,
8886
onOpenThread,
8987
}: {
9088
tasks: Task[];
9189
isLoading: boolean;
92-
prSnapshotByTaskId: ReadonlyMap<string, PrSnapshot>;
9390
taskPrStates: ChannelTaskPrStates;
9491
onOpenTask: (task: Task) => void;
9592
onOpenThread: (task: Task) => void;
@@ -115,14 +112,12 @@ export function ChannelBoardView({
115112
// A task with a PR cannot be classified until its first PR response.
116113
// Omitting it temporarily avoids showing it as Working and then moving it.
117114
if (taskPrStates.pendingTaskIds.has(task.id)) continue;
118-
const snapshot = prSnapshotByTaskId.get(task.id);
119115
const resolvedPrState = taskPrStates.states.get(task.id);
120-
const status = taskBoardStatusFromSources({
116+
const status = taskBoardStatus({
121117
runStatus: task.latest_run?.status,
122-
resolvedPrState,
123-
prSnapshot: snapshot,
118+
prState: resolvedPrState,
124119
});
125-
const prState = resolvedPrState ?? snapshot?.state ?? null;
120+
const prState = resolvedPrState ?? null;
126121
grouped.get(status)?.push({
127122
task,
128123
status,
@@ -136,7 +131,7 @@ export function ChannelBoardView({
136131
...STATUS_VISUAL[status],
137132
items: grouped.get(status) ?? [],
138133
}));
139-
}, [prSnapshotByTaskId, taskPrStates, tasks]);
134+
}, [taskPrStates, tasks]);
140135

141136
if (isLoading) {
142137
return <div className="min-h-0 flex-1" />;
@@ -153,7 +148,7 @@ export function ChannelBoardView({
153148
status={status}
154149
prState={prState}
155150
badgePrState={badgePrState}
156-
prUrl={taskPrUrl(task, prSnapshotByTaskId) ?? undefined}
151+
prUrl={taskPrUrl(task) ?? undefined}
157152
onOpenTask={onOpenTask}
158153
onOpenThread={onOpenThread}
159154
/>

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ function dayLabel(iso: string, now: Date): string {
148148
return `${weekday}, ${month} ${day}${year}`;
149149
}
150150

151-
interface TaskStatusDisplay {
151+
export interface TaskStatusDisplay {
152152
// The run/environment badge ("Local", "Completed", "In progress", …).
153153
base: ReactNode;
154154
// The PR's GitHub state, shown alongside the run badge when a PR exists.
@@ -168,7 +168,7 @@ interface TaskStatusDisplay {
168168
// shipped task never reads "Ready + Merged" or a stale "In progress + PR
169169
// ready". A failed/cancelled run suppresses the PR badge instead — that is a
170170
// deliberate end state we should not soften with a PR.
171-
function useTaskStatusDisplay(task: Task): TaskStatusDisplay {
171+
export function useTaskStatusDisplay(task: Task): TaskStatusDisplay {
172172
const data = useChannelTaskData(task);
173173
const { prState } = useTaskPrStatus({
174174
id: task.id,
@@ -247,7 +247,7 @@ function PrStateBadge({ prState }: { prState: Exclude<SidebarPrState, null> }) {
247247
return <Badge variant={variant}>{label}</Badge>;
248248
}
249249

250-
function TaskStatusBadge({ display }: { display: TaskStatusDisplay }) {
250+
export function TaskStatusBadge({ display }: { display: TaskStatusDisplay }) {
251251
return (
252252
<div className="flex shrink-0 items-center gap-1">
253253
{display.base}

packages/ui/src/features/canvas/components/WebsiteChannelHome.tsx

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Kanban, ListBullets, Plus, User } from "@phosphor-icons/react";
2-
import type { PrSnapshot } from "@posthog/core/home/prSnapshot";
32
import { insertTaskDedup } from "@posthog/core/tasks/taskDelete";
43
import { Button } from "@posthog/quill";
54
import { CHANNEL_TASK_BOARD_FLAG } from "@posthog/shared";
@@ -50,7 +49,6 @@ import {
5049
import { useChannelHomeUiStore } from "@posthog/ui/features/canvas/stores/channelHomeUiStore";
5150
import { useThreadPanelStore } from "@posthog/ui/features/canvas/stores/threadPanelStore";
5251
import { useFeatureFlag } from "@posthog/ui/features/feature-flags/useFeatureFlag";
53-
import { useHomeSnapshot } from "@posthog/ui/features/home/hooks/useHomeSnapshot";
5452
import { SuggestedPromptCard } from "@posthog/ui/features/task-detail/components/SuggestedPromptCard";
5553
import { taskDetailQuery } from "@posthog/ui/features/tasks/queries";
5654
import { useSetHeaderContent } from "@posthog/ui/hooks/useSetHeaderContent";
@@ -79,7 +77,6 @@ export function WebsiteChannelHome({ channelId }: { channelId: string }) {
7977
const effectiveViewMode = boardEnabled ? viewMode : "feed";
8078
const client = useOptionalAuthenticatedClient();
8179
const { data: currentUser } = useCurrentUser({ client });
82-
const { snapshot: homeSnapshot } = useHomeSnapshot();
8380

8481
// Poll while empty so the intro's context.md card flips to "created" when
8582
// the agent publishes mid plan-session, without a manual reload.
@@ -132,22 +129,8 @@ export function WebsiteChannelHome({ channelId }: { channelId: string }) {
132129
: tasks,
133130
[boardEnabled, currentUser?.uuid, taskScope, tasks],
134131
);
135-
const prSnapshotByTaskId = useMemo(() => {
136-
const result = new Map<string, PrSnapshot>();
137-
for (const workstream of [
138-
...homeSnapshot.needsAttention,
139-
...homeSnapshot.inProgress,
140-
]) {
141-
if (!workstream.pr) continue;
142-
for (const task of workstream.tasks) {
143-
result.set(task.id, workstream.pr);
144-
}
145-
}
146-
return result;
147-
}, [homeSnapshot.inProgress, homeSnapshot.needsAttention]);
148132
const taskPrStates = useChannelTaskPrStates(
149133
boardEnabled && effectiveViewMode === "board" ? visibleTasks : [],
150-
prSnapshotByTaskId,
151134
);
152135

153136
const composerRef = useRef<ChannelHomeComposerHandle>(null);
@@ -250,14 +233,8 @@ export function WebsiteChannelHome({ channelId }: { channelId: string }) {
250233
);
251234
const handleOpenTask = useCallback((task: Task) => setPreviewTask(task), []);
252235
const handleOpenFeedTask = useCallback(
253-
(task: Task) => {
254-
if (boardEnabled) {
255-
setPreviewTask(task);
256-
} else {
257-
handleOpenFull(task.id);
258-
}
259-
},
260-
[boardEnabled, handleOpenFull],
236+
(task: Task) => handleOpenFull(task.id),
237+
[handleOpenFull],
261238
);
262239

263240
const handleOpenThread = useCallback(
@@ -398,7 +375,6 @@ export function WebsiteChannelHome({ channelId }: { channelId: string }) {
398375
<ChannelBoardView
399376
tasks={visibleTasks}
400377
isLoading={isLoading}
401-
prSnapshotByTaskId={prSnapshotByTaskId}
402378
taskPrStates={taskPrStates}
403379
onOpenTask={handleOpenTask}
404380
onOpenThread={handleOpenThread}
@@ -455,15 +431,11 @@ export function WebsiteChannelHome({ channelId }: { channelId: string }) {
455431
task={previewTask}
456432
channelId={channelId}
457433
prUrl={
458-
previewTask
459-
? (taskPrUrl(previewTask, prSnapshotByTaskId) ?? undefined)
460-
: undefined
434+
previewTask ? (taskPrUrl(previewTask) ?? undefined) : undefined
461435
}
462436
prState={
463437
previewTask
464-
? (taskPrStates.states.get(previewTask.id) ??
465-
prSnapshotByTaskId.get(previewTask.id)?.state ??
466-
null)
438+
? (taskPrStates.states.get(previewTask.id) ?? null)
467439
: null
468440
}
469441
onClose={() => setPreviewTask(null)}
Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { describe, expect, it } from "vitest";
21
import type { Task } from "@posthog/shared/domain-types";
2+
import { describe, expect, it } from "vitest";
33
import { prDetailsToState, taskPrUrl } from "./useChannelTaskPrStates";
44

55
describe("prDetailsToState", () => {
@@ -21,20 +21,7 @@ describe("taskPrUrl", () => {
2121
latest_run: { output: { pr_url: "https://github.com/o/r/pull/1" } },
2222
} as unknown as Task;
2323

24-
it("prefers the Home snapshot URL", () => {
25-
expect(
26-
taskPrUrl(
27-
task,
28-
new Map([
29-
["task-1", { url: "https://github.com/o/r/pull/2" }],
30-
]),
31-
),
32-
).toBe("https://github.com/o/r/pull/2");
33-
});
34-
35-
it("falls back to the latest run URL", () => {
36-
expect(taskPrUrl(task, new Map())).toBe(
37-
"https://github.com/o/r/pull/1",
38-
);
24+
it("reads the latest run URL", () => {
25+
expect(taskPrUrl(task)).toBe("https://github.com/o/r/pull/1");
3926
});
4027
});

packages/ui/src/features/canvas/hooks/useChannelTaskPrStates.ts

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { PrSnapshot } from "@posthog/core/home/prSnapshot";
21
import type { Task } from "@posthog/shared/domain-types";
32
import {
43
type PrStateDetails,
@@ -18,16 +17,10 @@ export function prDetailsToState(
1817
return null;
1918
}
2019

21-
export function taskPrUrl(
22-
task: Task,
23-
prSnapshotByTaskId: ReadonlyMap<string, Pick<PrSnapshot, "url">>,
24-
): string | null {
25-
return (
26-
prSnapshotByTaskId.get(task.id)?.url ??
27-
(typeof task.latest_run?.output?.pr_url === "string"
28-
? task.latest_run.output.pr_url
29-
: null)
30-
);
20+
export function taskPrUrl(task: Task): string | null {
21+
return typeof task.latest_run?.output?.pr_url === "string"
22+
? task.latest_run.output.pr_url
23+
: null;
3124
}
3225

3326
export interface ChannelTaskPrStates {
@@ -37,20 +30,17 @@ export interface ChannelTaskPrStates {
3730
isRefreshing: boolean;
3831
}
3932

40-
export function useChannelTaskPrStates(
41-
tasks: Task[],
42-
prSnapshotByTaskId: ReadonlyMap<string, PrSnapshot>,
43-
): ChannelTaskPrStates {
33+
export function useChannelTaskPrStates(tasks: Task[]): ChannelTaskPrStates {
4434
const prUrls = useMemo(
4535
() => [
4636
...new Set(
4737
tasks.flatMap((task) => {
48-
const prUrl = taskPrUrl(task, prSnapshotByTaskId);
38+
const prUrl = taskPrUrl(task);
4939
return prUrl ? [prUrl] : [];
5040
}),
5141
),
5242
],
53-
[prSnapshotByTaskId, tasks],
43+
[tasks],
5444
);
5545
const results = usePrDetailsQueries(prUrls);
5646

@@ -63,7 +53,7 @@ export function useChannelTaskPrStates(
6353
let isRefreshing = false;
6454

6555
for (const task of tasks) {
66-
const prUrl = taskPrUrl(task, prSnapshotByTaskId);
56+
const prUrl = taskPrUrl(task);
6757
const result = prUrl ? resultByUrl.get(prUrl) : undefined;
6858
if (prUrl && result && !result.data && result.isPending) {
6959
pendingTaskIds.add(task.id);
@@ -83,5 +73,5 @@ export function useChannelTaskPrStates(
8373
isResolving: pendingTaskIds.size > 0,
8474
isRefreshing,
8575
};
86-
}, [prSnapshotByTaskId, prUrls, results, tasks]);
76+
}, [prUrls, results, tasks]);
8777
}

0 commit comments

Comments
 (0)