diff --git a/packages/ui/src/features/loops/components/LoopsListView.test.tsx b/packages/ui/src/features/loops/components/LoopsListView.test.tsx
index f0157a4f04..62af735344 100644
--- a/packages/ui/src/features/loops/components/LoopsListView.test.tsx
+++ b/packages/ui/src/features/loops/components/LoopsListView.test.tsx
@@ -18,11 +18,13 @@ vi.mock("./LoopRow", () => ({
function loop(
id: string,
visibility: LoopSchemas.LoopVisibilityEnum,
+ createdById = 1,
): LoopSchemas.Loop {
return {
id,
name: `${visibility} loop`,
visibility,
+ created_by_id: createdById,
} as LoopSchemas.Loop;
}
@@ -34,22 +36,45 @@ function controlledPanel(tab: HTMLElement): HTMLElement {
}
describe("LoopsListViewPresentation", () => {
- it("shows only the selected ownership tab", async () => {
+ it("does not render ownership groups while identity is loading", () => {
render(
,
);
- const personalTab = screen.getByRole("tab", { name: "My loops (1)" });
+ expect(screen.queryByRole("tab")).not.toBeInTheDocument();
+ });
+
+ it("groups loops by ownership rather than visibility", async () => {
+ render(
+
+
+ ,
+ );
+
+ const personalTab = screen.getByRole("tab", { name: "My loops (2)" });
expect(
within(controlledPanel(personalTab)).getByText("personal loop"),
).toBeVisible();
- expect(screen.queryByText("team loop")).not.toBeInTheDocument();
+ expect(
+ within(controlledPanel(personalTab)).getByText("team loop"),
+ ).toBeVisible();
const teamTab = screen.getByRole("tab", { name: "Team loops (1)" });
await userEvent.click(teamTab);
diff --git a/packages/ui/src/features/loops/components/LoopsListView.tsx b/packages/ui/src/features/loops/components/LoopsListView.tsx
index 3dbbe2bfd5..8798e4a908 100644
--- a/packages/ui/src/features/loops/components/LoopsListView.tsx
+++ b/packages/ui/src/features/loops/components/LoopsListView.tsx
@@ -8,6 +8,8 @@ import type { LoopSchemas } from "@posthog/api-client/loops";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@posthog/quill";
import { ANALYTICS_EVENTS } from "@posthog/shared/analytics-events";
import type { UserBasic } from "@posthog/shared/domain-types";
+import { useOptionalAuthenticatedClient } from "@posthog/ui/features/auth/authClient";
+import { useCurrentUser } from "@posthog/ui/features/auth/useCurrentUser";
import { useOrgMembers } from "@posthog/ui/features/canvas/hooks/useOrgMembers";
import { StopCloudRunDialog } from "@posthog/ui/features/sessions/components/StopCloudRunDialog";
import { useSetHeaderContent } from "@posthog/ui/hooks/useSetHeaderContent";
@@ -67,9 +69,22 @@ function startLoopFromTemplate(template: LoopTemplate): void {
export function LoopsListView() {
const { data: loops, isLoading, isError, error } = useLoops();
+ const authenticatedClient = useOptionalAuthenticatedClient();
+ const {
+ data: currentUser,
+ isLoading: currentUserLoading,
+ isError: currentUserError,
+ error: currentUserQueryError,
+ } = useCurrentUser({ client: authenticatedClient });
const limits = useLoopLimits();
const limitReason =
limits?.atLimit === true ? loopLimitReason(limits.max) : null;
+ let listError: unknown = null;
+ if (isError) {
+ listError = error;
+ } else if (currentUserError) {
+ listError = currentUserQueryError;
+ }
const headerContent = useMemo(
() => (
@@ -134,8 +149,9 @@ export function LoopsListView() {
return (
loop.visibility === "personal");
- const teamLoops = loops.filter((loop) => loop.visibility === "team");
+ const personalLoops = loops.filter(
+ (loop) =>
+ loop.visibility === "personal" ||
+ (currentUserId !== null && loop.created_by_id === currentUserId),
+ );
+ const teamLoops = loops.filter(
+ (loop) =>
+ loop.visibility === "team" &&
+ (currentUserId === null || loop.created_by_id !== currentUserId),
+ );
return (
diff --git a/packages/ui/src/shell/HedgehogMode.test.tsx b/packages/ui/src/shell/HedgehogMode.test.tsx
index 1bcfada5f6..6efe6079da 100644
--- a/packages/ui/src/shell/HedgehogMode.test.tsx
+++ b/packages/ui/src/shell/HedgehogMode.test.tsx
@@ -100,6 +100,8 @@ describe("HedgehogMode", () => {
expect(mocks.mount).toHaveBeenCalledTimes(1);
expect(overlay.querySelector("canvas")).not.toBeNull();
expect(overlay.style.visibility).toBe("visible");
+ expect(overlay).toHaveClass("absolute");
+ expect(overlay).not.toHaveClass("fixed");
});
it("destroys the game and reports when the context loss callback fires", async () => {
diff --git a/packages/ui/src/shell/HedgehogMode.tsx b/packages/ui/src/shell/HedgehogMode.tsx
index 179825964c..e4c9d44b48 100644
--- a/packages/ui/src/shell/HedgehogMode.tsx
+++ b/packages/ui/src/shell/HedgehogMode.tsx
@@ -134,7 +134,7 @@ export function HedgehogMode() {
zIndex: 999998,
visibility: hedgehogMode && !gameDead ? "visible" : "hidden",
}}
- className="pointer-events-none fixed inset-0"
+ className="pointer-events-none absolute inset-0"
/>
);
}