fix(web): + Starten for empty projects instead of fallback bot (#92)

* fix(web): show Starten for empty projects instead of fallback bot

When a project space had no group conversations, the sidebar fell back
to listing 1:1 bots (e.g. Chief from onboarding) under the project name.
Bootstrap also redirected /app to the first bot, making it look like an
existing conversation.

Only show project group chats in Projekte, render + Starten for empty
projects, and open the existing create flow (group or bot) on click.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): keep empty-project Starten view and route flows correctly

Rebased onto main. Guard the Hauptthread auto-redirect when the current
space is an empty project so the main Starten CTA stays visible. Split
project start into create-bot vs create-group flows (main vs sidebar) and
feed home-space bots into the group form when starting from a project.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): surface empty-project Starten on Projekte pillar

Route bare /app in empty projects to the Projekte view, keep /app on
stale bot/group URLs, and show the create-group side panel without an
active thread.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(e2e): scope RPC to selected space and tighten Starten specs

Pass x-rakazo-space-id from Playwright RPC helpers so archive/list hit
the active project space. Trim the group-create Starten test to the side
panel flow and navigate to /app after archiving the last bot.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* style(e2e): format empty-project-starten spec

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(e2e): remove project bots so empty spaces stay deletable

Archived bots still count as space content and hide the delete-space
menu. Use bots/remove in spaces.spec when the test needs a deletable
empty project space.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
This commit is contained in:
cynarAI 2026-09-25 20:54:06 +02:00 committed by GitHub
parent bc5f41ecb1
commit 23084b460b
6 changed files with 306 additions and 50 deletions

View File

@ -0,0 +1,73 @@
import { expect, test } from "@playwright/test";
import { completeOnboarding, rpc, signup, unterhaltungenOnly } from "./helpers";
test("empty project shows Starten instead of a fallback bot", async ({ page }) => {
const stamp = Date.now();
await signup(page, `empty-project-${stamp}@rakazo.test`, "password12", "Empty Project");
await completeOnboarding(page);
await page.goto("/app");
await page.waitForURL(/\/app\/[^/]+$/);
const projekte = page.locator(".sidebar-projekte");
await projekte.getByTestId("create-project-trigger").click();
const dialog = page.getByRole("dialog", { name: "New project" });
await dialog.getByLabel("Name").fill("Launch");
await dialog.getByRole("button", { name: "Create project", exact: true }).click();
await page.waitForURL(/\/app(?:\/)?$/);
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible();
await expect(page.getByTestId("project-start-main")).toBeVisible();
await page.getByTestId("project-start-main").click();
await expect(page.getByTestId("side-panel")).toHaveAttribute("data-panel", "create");
});
test("Starten in an onboarded project opens the group create flow", async ({ page }) => {
const stamp = Date.now();
await signup(page, `project-start-${stamp}@rakazo.test`, "password12", "Project Start");
await completeOnboarding(page);
const projekte = page.locator(".sidebar-projekte");
await projekte.getByTestId("create-project-trigger").click();
const dialog = page.getByRole("dialog", { name: "New project" });
await dialog.getByLabel("Name").fill("Website");
await dialog.getByRole("button", { name: "Create project", exact: true }).click();
await page.waitForURL(/\/app(?:\/)?$/);
await page.goto("/onboarding");
await completeOnboarding(page);
await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1);
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
await projekte.getByRole("button", { name: "Starten", exact: true }).click();
await expect(page.getByTestId("side-panel")).toHaveAttribute("data-panel", "create-group");
await expect(page.getByText("New group", { exact: true })).toBeVisible();
await expect(
page.getByTestId("side-panel").getByRole("button", { name: /^Chief/ }),
).toBeVisible();
});
test("archiving the only bot keeps Starten visible in the project sidebar", async ({ page }) => {
const stamp = Date.now();
await signup(page, `project-archive-${stamp}@rakazo.test`, "password12", "Project Archive");
await completeOnboarding(page);
const projekte = page.locator(".sidebar-projekte");
await projekte.getByTestId("create-project-trigger").click();
const dialog = page.getByRole("dialog", { name: "New project" });
await dialog.getByLabel("Name").fill("Side");
await dialog.getByRole("button", { name: "Create project", exact: true }).click();
await page.waitForURL(/\/app(?:\/)?$/);
await page.goto("/onboarding");
await completeOnboarding(page);
const [chief] = await rpc<Array<{ id: string; name: string }>>(page, "bots/list", {});
expect(chief?.name).toMatch(/^Chief/);
await rpc(page, "bots/archive", { botId: chief!.id });
await page.goto("/app");
await page.waitForURL(/\/app(?:\/)?$/);
await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible();
await expect(page.getByTestId("project-start-main")).toBeVisible();
});

View File

@ -23,7 +23,11 @@ export function activeBotId(page: Page) {
}
export async function rpc<T>(page: Page, procedure: string, body: unknown): Promise<T> {
const response = await page.request.post(`/rpc/${procedure}`, { data: { json: body } });
const spaceId = await page.evaluate(() => window.localStorage.getItem("rakazo:space-id"));
const response = await page.request.post(`/rpc/${procedure}`, {
data: { json: body },
headers: spaceId ? { "x-rakazo-space-id": spaceId } : undefined,
});
const parsed = (await response.json()) as { json?: T; error?: { message?: string } };
if (!response.ok() || parsed.error) {
throw new Error(`${procedure} ${response.status()}: ${parsed.error?.message ?? "failed"}`);

View File

@ -41,7 +41,8 @@ test("Projekte + opens new project dialog instead of group form", async ({ page
await expect(unterhaltungenOnly(page).getByText("Customer project", { exact: true })).toHaveCount(
0,
);
await expect(page.locator("main").getByRole("button", { name: "Create an Oo" })).toBeVisible();
await expect(page.getByTestId("project-start-main")).toBeVisible();
await expect(page.getByTestId("project-start-main")).toHaveText(/Starten/);
});
test("spaces stay invisible by default and chat creation requires approval", async ({
@ -93,16 +94,18 @@ test("spaces stay invisible by default and chat creation requires approval", asy
const supportSpaceGroup = await supportSpace.getAttribute("data-sidebar-group");
const supportSpaceId = supportSpaceGroup?.match(/^project-space:([^:]+)/)?.[1];
expect(supportSpaceId).toBeTruthy();
await supportSpace.getByRole("button", { name: "Open Customer support" }).click();
await page.waitForURL(/\/onboarding/);
await supportSpace.getByRole("button", { name: "Select Customer support" }).click();
await page.waitForURL(/\/app(?:\/)?$/);
await expect
.poll(() => page.evaluate(() => window.localStorage.getItem("rakazo:space-id")))
.toBe(supportSpaceId);
await page.goto("/onboarding");
await completeOnboarding(page);
await expect(projekte.getByText("Customer support", { exact: true })).toBeVisible();
await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1);
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(1);
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible();
await captureScreenshot(page, testInfo, "spaces-sidebar");
await page.evaluate((id) => {
@ -136,21 +139,19 @@ test("a new space auto-completes onboarding and can be deleted from its menu", a
const projekte = page.locator(".sidebar-projekte");
await expect(projekte.getByText("Temporary", { exact: true })).toBeVisible();
await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1);
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(1);
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible();
await captureScreenshot(page, testInfo, "new-space-after-onboarding");
// Spaces can only be deleted when empty — remove auto-created Chief first.
const temporaryChief = projekte.getByRole("button", { name: /^Chief/ });
await temporaryChief.click({ button: "right" });
await page.getByRole("menuitem", { name: "Delete" }).click();
const deleteBotDialog = page.getByRole("alertdialog", { name: /Delete Chief/ });
await expect(deleteBotDialog).toBeVisible();
await deleteBotDialog.getByRole("button", { name: "Delete", exact: true }).click();
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1);
const [temporaryChief] = await rpc<Array<{ id: string }>>(page, "bots/list", {});
expect(temporaryChief).toBeTruthy();
await rpc(page, "bots/remove", { botId: temporaryChief!.id, deleteMemories: false });
await page.goto("/app");
await page.waitForURL(/\/app(?:\/)?$/);
// The emptied space stays selectable instead of trapping the user.
await expect(projekte.getByRole("button", { name: "Open Temporary" }).first()).toBeVisible();
await expect(projekte.getByRole("button", { name: "Select Temporary" }).first()).toBeVisible();
// Deletion is discoverable through the visible Space actions menu.
await projectSpace(projekte, "Temporary")
@ -196,20 +197,14 @@ test("deleting the last bot in a space stays in the app after first use", async
// Delete the only bot in the new space: the app must stay put instead of
// forcing onboarding again.
const sideBot = projekte.getByRole("button", { name: /^Chief/ });
await sideBot.click({ button: "right" });
await page.getByRole("menuitem", { name: "Delete" }).click();
const deleteDialog = page.getByRole("alertdialog", { name: /Delete Chief/ });
await expect(deleteDialog).toBeVisible();
await deleteDialog.getByRole("button", { name: "Delete", exact: true }).click();
await expect(sideBot).toHaveCount(0);
const [sideChief] = await rpc<Array<{ id: string }>>(page, "bots/list", {});
expect(sideChief).toBeTruthy();
await rpc(page, "bots/remove", { botId: sideChief!.id, deleteMemories: false });
await expect(page).not.toHaveURL(/\/onboarding/);
await page.reload();
await page.goto("/app");
await expect(page).not.toHaveURL(/\/onboarding/);
await expect(projekte.getByRole("button", { name: "Open Side" }).first()).toBeVisible();
await expect(
page.locator("main").getByRole("button", { name: "Create new Bot", exact: true }),
).toBeVisible();
await expect(projekte.getByRole("button", { name: "Select Side" }).first()).toBeVisible();
await expect(page.getByTestId("project-start-main")).toBeVisible();
await projectSpace(projekte, "Side").getByRole("button", { name: "Actions for Side" }).click();
await expect(page.getByRole("menuitem", { name: "Delete space" })).toBeVisible();

View File

@ -0,0 +1,50 @@
import { describe, expect, it } from "vitest";
import {
isEmptyProjectSpace,
isHauptthreadGroup,
projectGroupChatsInSpace,
} from "./project-empty-state.js";
describe("isHauptthreadGroup", () => {
it("recognizes Hauptthread and Team Chat", () => {
expect(isHauptthreadGroup({ id: "1", name: "Hauptthread" })).toBe(true);
expect(isHauptthreadGroup({ id: "2", name: "Team Chat" })).toBe(true);
expect(isHauptthreadGroup({ id: "3", name: "Launch" })).toBe(false);
});
});
describe("projectGroupChatsInSpace", () => {
it("returns project groups in a space without Hauptthread", () => {
const groups = [
{ id: "g1", name: "Launch", spaceId: "space-a" },
{ id: "g2", name: "Hauptthread", spaceId: "space-a" },
{ id: "g3", name: "Ops", spaceId: "space-b" },
];
expect(projectGroupChatsInSpace(groups, "space-a").map((group) => group.id)).toEqual(["g1"]);
});
});
describe("isEmptyProjectSpace", () => {
const spaces = [
{ id: "home", isDefault: true },
{ id: "project", isDefault: false },
];
it("is false for the home space", () => {
expect(isEmptyProjectSpace("home", spaces, [], "home")).toBe(false);
});
it("is true for a project space without group chats", () => {
expect(isEmptyProjectSpace("project", spaces, [], "home")).toBe(true);
});
it("is false when the project space already has a group chat", () => {
const groups = [{ id: "g1", name: "Launch", spaceId: "project" }];
expect(isEmptyProjectSpace("project", spaces, groups, "home")).toBe(false);
});
it("ignores Hauptthread when deciding emptiness", () => {
const groups = [{ id: "g1", name: "Hauptthread", spaceId: "project" }];
expect(isEmptyProjectSpace("project", spaces, groups, "home")).toBe(true);
});
});

View File

@ -0,0 +1,26 @@
import type { Group, Space } from "@rakazo/contracts";
export function isHauptthreadGroup(group: Pick<Group, "id" | "name">): boolean {
return group.name === "Hauptthread" || group.name === "Team Chat";
}
/** Project-scoped group chats in a space (excludes Hauptthread). */
export function projectGroupChatsInSpace(
groups: readonly Pick<Group, "id" | "name" | "spaceId">[],
spaceId: string,
): readonly Pick<Group, "id">[] {
return groups.filter((group) => group.spaceId === spaceId && !isHauptthreadGroup(group));
}
/** Non-default project space with no project group conversations yet. */
export function isEmptyProjectSpace(
spaceId: string | null | undefined,
spaces: readonly Pick<Space, "id" | "isDefault">[],
groups: readonly Pick<Group, "id" | "name" | "spaceId">[],
homeSpaceId: string | undefined,
): boolean {
if (!spaceId || !homeSpaceId || spaceId === homeSpaceId) return false;
const space = spaces.find((item) => item.id === spaceId);
if (!space || space.isDefault) return false;
return projectGroupChatsInSpace(groups, spaceId).length === 0;
}

View File

@ -214,6 +214,7 @@ import {
revokePendingAttachmentPreviews,
} from "../lib/pending-attachments";
import { markAfterPaint, markOnce } from "../lib/performance";
import { isEmptyProjectSpace } from "../lib/project-empty-state";
import { readProjectSidebarOrder, writeProjectSidebarOrder } from "../lib/project-sidebar-order";
import { quoteDraftForSelection } from "../lib/quote-selection";
import { clearSpaceSelection, rpc, selectedSpaceId, selectSpace } from "../lib/rpc";
@ -851,7 +852,15 @@ export function ShellPage() {
const inGroup = Boolean(groupId);
const projectSessionKey = searchParams.get("s");
const voiceEntry = searchParams.get("voice") === "1";
const active = inGroup ? undefined : (bots.find((b) => b.id === botId) ?? bots[0]);
const currentSpaceId = selectedSpaceId() ?? bootstrapMe?.spaceId;
const homeSpaceId = spaces.find((space) => space.isDefault)?.id ?? bootstrapMe?.spaceId;
const emptyProjectSpace = useMemo(
() => isEmptyProjectSpace(currentSpaceId, spaces, groups, homeSpaceId),
[currentSpaceId, groups, homeSpaceId, spaces],
);
const active = inGroup
? undefined
: (bots.find((b) => b.id === botId) ?? (emptyProjectSpace && !botId ? undefined : bots[0]));
const activeGroup = groups.find((group) => group.id === groupId);
const activeProjectSection = activeGroup?.sectionId
? botSections.find((section) => section.id === activeGroup.sectionId)
@ -1039,13 +1048,28 @@ export function ShellPage() {
return;
}
const currentGroupId = routeGroupId.current;
const currentBotId = routeBotId.current;
const refreshHomeSpaceId =
navigation.spaces.find((space) => space.isDefault)?.id ?? selectedSpaceId() ?? undefined;
const refreshSpaceId = selectedSpaceId() ?? refreshHomeSpaceId;
const emptyProject = isEmptyProjectSpace(
refreshSpaceId,
navigation.spaces,
groupList,
refreshHomeSpaceId,
);
if (emptyProject) {
if (currentGroupId || (currentBotId && !list.some((bot) => bot.id === currentBotId))) {
navigate("/app", { replace: true });
}
return;
}
if (currentGroupId) {
if (!groupList.some((group) => group.id === currentGroupId)) {
navigate(firstThreadRoute(list, groupList), { replace: true });
}
return;
}
const currentBotId = routeBotId.current;
if (!currentBotId || !list.some((bot) => bot.id === currentBotId)) {
navigate(firstThreadRoute(list, groupList), { replace: true });
}
@ -1268,6 +1292,21 @@ export function ShellPage() {
navigate("/onboarding", { replace: true });
return;
}
const bootstrapHomeSpaceId =
bootstrap.spaces.find((space) => space.isDefault)?.id ?? bootstrap.me.spaceId;
const bootstrapSpaceId = selectedSpaceId() ?? bootstrap.me.spaceId;
const emptyProject = isEmptyProjectSpace(
bootstrapSpaceId,
bootstrap.spaces,
groupList,
bootstrapHomeSpaceId,
);
if (emptyProject) {
if (groupId || (botId && !bootstrap.bots.some((bot) => bot.id === botId))) {
navigate("/app", { replace: true });
}
return;
}
if (groupId) {
if (!groupList.some((group) => group.id === groupId)) {
navigate(firstThreadRoute(bootstrap.bots, groupList), { replace: true });
@ -1663,8 +1702,6 @@ export function ShellPage() {
[groups],
);
const homeSpaceId = spaces.find((space) => space.isDefault)?.id ?? bootstrapMe?.spaceId;
const homeSpaceBots = useMemo(() => {
if (!homeSpaceId) return bots;
const home = spaces.find((space) => space.id === homeSpaceId);
@ -1710,10 +1747,7 @@ export function ShellPage() {
)
: [],
);
const botsInSpace = sidebarGroups.flatMap((group) =>
group.spaceId === space.id ? group.bots.filter((item) => item.kind === "bot") : [],
);
const projectBots = dedupeSidebarBots(groupsInSpace.length > 0 ? groupsInSpace : botsInSpace);
const projectBots = dedupeSidebarBots(groupsInSpace);
entries.push({
key:
projectBots.length > 0 ? `project-space:${space.id}` : `project-space:${space.id}:empty`,
@ -1806,6 +1840,14 @@ export function ShellPage() {
[bootstrapMe?.spaceId, navigate],
);
const openProjectStart = useCallback(
(spaceId: string, flow: "create" | "group") => {
setActiveNavPillar("projekte");
openSpaceChat(spaceId, `/app?project=start&flow=${flow}`);
},
[openSpaceChat],
);
const openConversationBot = useCallback(
(botId: string) => {
setConversationPickerOpen(false);
@ -2031,6 +2073,42 @@ export function ShellPage() {
void jumpToMessage({ groupId, messageId: projectSessionKey });
}, [groupId, inGroup, projectSessionKey]);
useEffect(() => {
if (!botId && !groupId && emptyProjectSpace && initialBotsLoaded) {
setActiveNavPillar("projekte");
}
}, [botId, emptyProjectSpace, groupId, initialBotsLoaded]);
useEffect(() => {
if (searchParams.get("project") !== "start" || !emptyProjectSpace || !initialBotsLoaded) {
return;
}
setActiveNavPillar("projekte");
const flow = searchParams.get("flow");
setPanel(
flow === "create"
? "create"
: flow === "group" && homeSpaceBots.length > 0
? "create-group"
: flow === "group"
? "create"
: bots.length === 0
? "create"
: "create-group",
);
const next = new URLSearchParams(searchParams);
next.delete("project");
next.delete("flow");
setSearchParams(next, { replace: true });
}, [
bots.length,
emptyProjectSpace,
homeSpaceBots.length,
initialBotsLoaded,
searchParams,
setSearchParams,
]);
const activeSnapshot = inGroup
? snapshot?.groupId === groupId
? snapshot
@ -3213,7 +3291,7 @@ export function ShellPage() {
) : null;
useEffect(() => {
if (!active && !activeGroup && initialBotsLoaded) {
if (!active && !activeGroup && initialBotsLoaded && !emptyProjectSpace) {
const hauptthread = groups.find((g) => g.name === "Hauptthread" || g.name === "Team Chat");
if (hauptthread) {
navigate(`/app/g/${hauptthread.id}`);
@ -3227,7 +3305,7 @@ export function ShellPage() {
.catch(console.error);
}
}
}, [active, activeGroup, initialBotsLoaded, groups, bots, navigate]);
}, [active, activeGroup, emptyProjectSpace, initialBotsLoaded, groups, bots, navigate]);
const userName = session.data?.user.name ?? t`You`;
const initials = userName
@ -3842,13 +3920,7 @@ export function ShellPage() {
className="flex min-w-0 flex-1 items-center justify-between gap-2 rounded-lg pl-8 py-1 text-ui-label font-semibold tracking-wider uppercase text-muted-foreground/60 hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-ring"
onClick={() => {
if (group.emptySpaceId) {
const space = spaces.find(
(item) => item.id === group.emptySpaceId,
);
openSpaceChat(
group.emptySpaceId,
space?.hasContent ? "/app" : "/onboarding",
);
openSpaceChat(group.emptySpaceId, "/app");
return;
}
toggleSidebarSection(group.key);
@ -3888,7 +3960,7 @@ export function ShellPage() {
aria-expanded={group.emptySpaceId ? undefined : !collapsed}
aria-label={
group.emptySpaceId
? t`Open ${group.title}`
? t`Select ${group.title}`
: collapsed
? t`Expand ${group.title}`
: t`Collapse ${group.title}`
@ -3931,6 +4003,20 @@ export function ShellPage() {
) : null}
</div>
) : null}
{!collapsed && group.emptySpaceId ? (
<div className="px-3 pb-2">
<Button
variant="outline"
size="sm"
className="w-full text-ui-label"
data-testid={`project-start-${group.spaceId}`}
onClick={() => openProjectStart(group.spaceId, "group")}
>
<Plus className="mr-1 size-icon-sm" />
{t`Starten`}
</Button>
</div>
) : null}
{!collapsed &&
group.bots.map((item) => (
<button
@ -4394,7 +4480,17 @@ export function ShellPage() {
) : null}
</div>
</div>
{!active && !activeGroup && initialBotsLoaded ? (
{!active && !activeGroup && emptyProjectSpace && initialBotsLoaded ? (
<div className="grid flex-1 place-items-center">
<Button
data-testid="project-start-main"
onClick={() => currentSpaceId && openProjectStart(currentSpaceId, "create")}
>
<Plus aria-hidden="true" className="size-icon-md" />
{t`Starten`}
</Button>
</div>
) : !active && !activeGroup && initialBotsLoaded ? (
<div className="grid flex-1 place-items-center">
<Button onClick={() => setShowCreateBotModal(true)}>
<Plus aria-hidden="true" className="size-icon-md" />
@ -4563,12 +4659,24 @@ export function ShellPage() {
data-panel={panel ?? "closed"}
style={{
width:
panel && (active || activeGroup || panel === "create" || panel === "browser")
panel &&
(active ||
activeGroup ||
panel === "create" ||
panel === "create-group" ||
panel === "browser")
? rightSidebarWidth
: 0,
}}
className={`absolute inset-y-0 end-0 z-20 flex min-h-0 shrink-0 flex-col overflow-hidden bg-background md:rounded-2xl md:border md:border-white/10 md:bg-[#0A0A0C] md:shadow-2xl md:relative ${
panel && (active || activeGroup || panel === "create" || panel === "browser")
panel &&
(
active ||
activeGroup ||
panel === "create" ||
panel === "create-group" ||
panel === "browser"
)
? "max-w-[384px] md:max-w-none opacity-100"
: "pointer-events-none w-0 opacity-0 md:opacity-0 md:w-0"
} ${!isResizingRight ? "transition-[width,opacity] duration-300 ease-[cubic-bezier(0.16,1,0.3,1)]" : ""}`}
@ -4577,7 +4685,7 @@ export function ShellPage() {
onPointerDown={startResizingRight}
className="absolute -left-2 top-0 bottom-0 w-4 cursor-col-resize z-50 hidden md:block"
/>
{panel && (active || activeGroup || panel === "create") ? (
{panel && (active || activeGroup || panel === "create" || panel === "create-group") ? (
<div className="rk-scroll h-full w-full overflow-y-auto px-5 lg:px-7 2xl:px-9 py-[17px]">
{panel !== "routine" &&
panel !== "create" &&
@ -4798,7 +4906,7 @@ export function ShellPage() {
) : null}
{panel === "create-group" ? (
<CreateGroupForm
bots={bots}
bots={emptyProjectSpace ? (homeSpaceBots as Bot[]) : bots}
onCancel={() => setPanel(null)}
onCreate={(input) => createGroup(input)}
/>