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

Commit af38871

Browse files
authored
fix(loops): restore loops registry in personal space
Generated-By: PostHog Code Task-Id: 0b782cef-7a2c-4061-b4ac-3c2908a096ba
1 parent ce825d0 commit af38871

2 files changed

Lines changed: 94 additions & 8 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { render, screen } from "@testing-library/react";
2+
import { describe, expect, it, vi } from "vitest";
3+
4+
vi.mock("@posthog/ui/features/canvas/hooks/useChannels", () => ({
5+
useChannels: () => ({
6+
channels: [{ id: "personal-space", name: "me", path: "/me" }],
7+
isLoading: false,
8+
}),
9+
}));
10+
vi.mock("@posthog/ui/features/canvas/hooks/useChannelsLayout", () => ({
11+
useChannelsLayout: () => true,
12+
}));
13+
vi.mock("@posthog/ui/features/canvas/components/ChannelHeader", () => ({
14+
ChannelHeader: () => null,
15+
}));
16+
vi.mock("@posthog/ui/hooks/useSetHeaderContent", () => ({
17+
useSetHeaderContent: () => {},
18+
}));
19+
vi.mock("@posthog/ui/router/navigationBridge", () => ({
20+
navigateToNewLoop: vi.fn(),
21+
}));
22+
vi.mock("@posthog/ui/features/canvas/hooks/useOrgMembers", () => ({
23+
useOrgMembers: () => ({
24+
members: [],
25+
isLoading: false,
26+
isError: false,
27+
isComplete: true,
28+
}),
29+
}));
30+
vi.mock("@posthog/ui/features/loops/hooks/useLoops", () => ({
31+
useLoops: () => ({ data: [], isLoading: false, isError: false }),
32+
useLoopLimits: () => null,
33+
}));
34+
vi.mock("@posthog/ui/features/loops/components/LoopBuilderComposer", () => ({
35+
LoopBuilderComposer: () => null,
36+
}));
37+
vi.mock("@posthog/ui/features/loops/components/LoopFallbacks", () => ({
38+
LoopsEmptyNotice: () => null,
39+
LoopsSkeleton: () => null,
40+
}));
41+
vi.mock("@posthog/ui/features/loops/components/LoopRow", () => ({
42+
LoopRow: () => null,
43+
}));
44+
vi.mock("@posthog/ui/features/loops/components/LoopsEmptyState", () => ({
45+
LoopsEmptyState: () => null,
46+
}));
47+
vi.mock("@posthog/ui/features/loops/components/LoopTemplatesSection", () => ({
48+
LoopTemplatesSection: () => null,
49+
}));
50+
vi.mock("@posthog/ui/features/canvas/hooks/useTaskChannels", () => ({
51+
PERSONAL_CHANNEL_NAME: "me",
52+
}));
53+
vi.mock("@posthog/ui/features/loops/components/LoopsListView", () => ({
54+
LoopsListView: () => <div>Project loops registry</div>,
55+
}));
56+
57+
import { WebsiteChannelLoops } from "./WebsiteChannelLoops";
58+
59+
describe("WebsiteChannelLoops", () => {
60+
it("shows the project loops registry in the Personal space", () => {
61+
render(<WebsiteChannelLoops channelId="personal-space" />);
62+
63+
expect(screen.getByText("Project loops registry")).toBeInTheDocument();
64+
});
65+
});

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

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from "../../loops/components/LoopFallbacks";
2323
import { LoopRow } from "../../loops/components/LoopRow";
2424
import { LoopsEmptyState } from "../../loops/components/LoopsEmptyState";
25+
import { LoopsListView } from "../../loops/components/LoopsListView";
2526
import { LoopTemplatesSection } from "../../loops/components/LoopTemplatesSection";
2627
import { useLoopLimits, useLoops } from "../../loops/hooks/useLoops";
2728
import { useLoopDraftStore } from "../../loops/loopDraftStore";
@@ -56,17 +57,39 @@ function contextQuickStarts(name: string): { label: string; prompt: string }[] {
5657
* composer pinned at the bottom), but the build surface is tuned to automations that feed
5758
* this context. `channelId` is the desktop folder id, matching `context_target.folder_id`. */
5859
export function WebsiteChannelLoops({ channelId }: { channelId: string }) {
60+
const { channels } = useChannels();
61+
const channel = channels.find((candidate) => candidate.id === channelId);
62+
63+
// The Personal space is the project-level home for loops in the spaces
64+
// layout. API-created and other unattached loops have no context_target, so
65+
// rendering the space-scoped list here incorrectly produces the global
66+
// "Create your first loop" empty state while those loops already exist.
67+
if (channel?.name === PERSONAL_CHANNEL_NAME) {
68+
return <LoopsListView />;
69+
}
70+
71+
return (
72+
<SpaceAttachedLoops
73+
channelId={channelId}
74+
contextName={channel?.name ?? channelId}
75+
/>
76+
);
77+
}
78+
79+
function SpaceAttachedLoops({
80+
channelId,
81+
contextName,
82+
}: {
83+
channelId: string;
84+
contextName: string;
85+
}) {
5986
const { data: loops, isLoading, isError } = useLoops();
6087
const spacesLayout = useChannelsLayout();
6188
const limits = useLoopLimits();
6289
const limitReason =
6390
limits?.atLimit === true
6491
? `You've reached the limit of ${limits.max} loops for this project. Delete one to add another.`
6592
: null;
66-
const { channels } = useChannels();
67-
const channel = channels.find((c) => c.id === channelId);
68-
const contextName = channel?.name ?? channelId;
69-
const isPersonal = contextName === PERSONAL_CHANNEL_NAME;
7093

7194
useSetHeaderContent(
7295
useMemo(
@@ -113,7 +136,7 @@ export function WebsiteChannelLoops({ channelId }: { channelId: string }) {
113136
navigateToNewLoop();
114137
};
115138

116-
const title = isPersonal ? "Loops" : `Automate #${contextName}`;
139+
const title = `Automate #${contextName}`;
117140
const description =
118141
"Put your work on autopilot. Loops run on a schedule, on an API call, or when something happens on GitHub. You can finally close the laptop!";
119142
const createButton = (
@@ -214,9 +237,7 @@ export function WebsiteChannelLoops({ channelId }: { channelId: string }) {
214237
</Flex>
215238
</Flex>
216239
) : (
217-
<LoopsEmptyState
218-
contextName={isPersonal ? undefined : contextName}
219-
/>
240+
<LoopsEmptyState contextName={contextName} />
220241
)}
221242

222243
<LoopTemplatesSection onSelect={startFromTemplate} />

0 commit comments

Comments
 (0)