mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 04:49:26 +00:00
Rail: solo sessions get Progress back (empty team object misread as lead); drop Gallery entry point
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
// Settings ▸ Personas ▸ Gallery: the catalog lives in a screen-sized modal opened
|
||||
// from the Personas page (link → featured carousel + list → in-modal solo page →
|
||||
// informed install → Done lands back on Personas). Plus the page-level delete
|
||||
// affordance for non-builtin personas.
|
||||
// The Gallery entry point was removed from Settings ▸ Coworkers (owner 2026-08-21) —
|
||||
// coworkers install from GitHub / folder / zip. This file keeps the page-level
|
||||
// delete flow (now on the coworker detail page, UX-035).
|
||||
import { expect } from "@playwright/test";
|
||||
import { test } from "./fixtures";
|
||||
|
||||
@@ -10,91 +9,12 @@ async function openPersonas(page) {
|
||||
await page.getByTestId("account-row").click();
|
||||
await page.getByRole("button", { name: "Settings", exact: true }).click();
|
||||
await page.getByRole("button", { name: "Coworkers", exact: true }).click();
|
||||
await expect(page.getByTestId("gallery-link")).toBeVisible();
|
||||
}
|
||||
|
||||
async function openGallery(page) {
|
||||
test("the Gallery entry point is gone from the Coworkers page", async ({ page }) => {
|
||||
await openPersonas(page);
|
||||
await page.getByTestId("gallery-link").click();
|
||||
await expect(page.getByTestId("gallery-modal")).toBeVisible();
|
||||
}
|
||||
|
||||
test("slow cloud: skeleton shows while the gallery loads, never a blank body", async ({
|
||||
page,
|
||||
}) => {
|
||||
// The real gallery is a cloud round-trip (Lambda + Dynamo) that can take seconds;
|
||||
// delay the mocked endpoints to assert the skeleton bridges the gap.
|
||||
await page.route("**/v1/cloud/status", async (route) => {
|
||||
await new Promise((r) => setTimeout(r, 1200));
|
||||
await route.fulfill({ json: { ok: true, signed_in: false } });
|
||||
});
|
||||
await openGallery(page);
|
||||
await expect(page.getByTestId("gallery-loading")).toBeVisible();
|
||||
await expect(page.getByTestId("gallery-loading")).toContainText("Loading the gallery");
|
||||
// Resolves into the real body (signed-out prompt here) once the cloud answers.
|
||||
await expect(page.getByTestId("gallery-signin")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByTestId("gallery-loading")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("signed out: modal prompts for sign-in, manual install path unaffected", async ({ page }) => {
|
||||
await openGallery(page);
|
||||
const prompt = page.getByTestId("gallery-signin");
|
||||
await expect(prompt).toContainText("needs a (free) cloud sign-in");
|
||||
await expect(prompt).toContainText("always works without an account");
|
||||
await expect(prompt.getByRole("button", { name: "Sign in" })).toBeVisible();
|
||||
// Esc closes; the Personas page (with its dir/Git importer) is still there.
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(page.getByTestId("gallery-modal")).not.toBeVisible();
|
||||
// The manual installer (collapsed disclosure, UX-035) is still there.
|
||||
await page.getByTestId("install-disclosure").click();
|
||||
await expect(page.getByRole("button", { name: "Install", exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("signed in: featured carousel + list; solo page installs informed; Done returns", async ({
|
||||
page,
|
||||
}) => {
|
||||
await openGallery(page);
|
||||
await page.getByTestId("gallery-signin").getByRole("button", { name: "Sign in" }).click();
|
||||
|
||||
// Featured carousel holds the flagged persona; the list holds both.
|
||||
const featured = page.getByTestId("gallery-featured");
|
||||
await expect(featured).toBeVisible({ timeout: 10_000 });
|
||||
await expect(featured).toContainText("Sales Coworker");
|
||||
await expect(featured).not.toContainText("Recruiter");
|
||||
await expect(page.getByTestId("gallery-recruiter")).toContainText("View & install");
|
||||
await expect(page.getByTestId("gallery-team-teaser")).toContainText("coming soon");
|
||||
|
||||
// Search narrows the list.
|
||||
await page.getByPlaceholder("Search coworkers").fill("recruit");
|
||||
await expect(page.getByTestId("gallery-sales")).not.toBeVisible();
|
||||
await page.getByPlaceholder("Search coworkers").fill("");
|
||||
|
||||
// Solo page: pitch + manifest-derived capabilities BEFORE install.
|
||||
await page.getByTestId("gallery-sales").click();
|
||||
const detail = page.getByTestId("gallery-detail");
|
||||
await expect(detail).toContainText("Walk into every call already knowing the account");
|
||||
const caps = page.getByTestId("gallery-capabilities");
|
||||
await expect(caps).toContainText("verified from its manifest");
|
||||
await expect(caps).toContainText("files, search, todo");
|
||||
await expect(caps).toContainText("hubspot · core");
|
||||
await expect(caps).toContainText("read deals and contacts");
|
||||
|
||||
await detail.getByRole("button", { name: "Install" }).click();
|
||||
await expect(detail).toContainText("disabled until you approve and enable it");
|
||||
|
||||
// Done closes the modal, landing back on the Personas page.
|
||||
await detail.getByRole("button", { name: "Done" }).click();
|
||||
await expect(page.getByTestId("gallery-modal")).not.toBeVisible();
|
||||
await expect(page.getByTestId("gallery-link")).toBeVisible();
|
||||
});
|
||||
|
||||
test("back link returns from the solo page to the catalog", async ({ page }) => {
|
||||
await openGallery(page);
|
||||
await page.getByTestId("gallery-signin").getByRole("button", { name: "Sign in" }).click();
|
||||
await page.getByTestId("gallery-sales").click({ timeout: 10_000 });
|
||||
await expect(page.getByTestId("gallery-detail")).toBeVisible();
|
||||
await page.getByRole("button", { name: "← Gallery" }).click();
|
||||
await expect(page.getByTestId("gallery-cards")).toBeVisible();
|
||||
await expect(page.getByTestId("install-disclosure")).toBeVisible();
|
||||
await expect(page.getByTestId("gallery-link")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("delete: non-builtin personas removable after confirm; built-ins are not", async ({
|
||||
|
||||
@@ -2084,7 +2084,13 @@ export function App() {
|
||||
setBoardDetailId(id);
|
||||
setBoardOpen(true);
|
||||
}}
|
||||
isLead={teamMembers.length > 0 || (!!curSession?.team && curSession.team.role !== "worker")}
|
||||
/* team serializes as {} for plain sessions — lead-ness needs an actual
|
||||
role, else every solo session loses its Progress panel (owner-hit
|
||||
2026-08-21: the rail showed nothing but "More"). */
|
||||
isLead={
|
||||
teamMembers.length > 0 ||
|
||||
(curSession?.team?.role != null && curSession.team.role !== "worker")
|
||||
}
|
||||
teamMembers={teamMembers}
|
||||
teamChatEnabled={!!curSession?.team?.chat_enabled}
|
||||
teamChatUnread={curSession?.team?.chat_unread || 0}
|
||||
|
||||
@@ -27,14 +27,7 @@ const BTN_BORDERED =
|
||||
const QUIET_ROW =
|
||||
"w-full flex items-center gap-2 px-4 pt-2 mt-6 text-[12.5px] text-muted select-none";
|
||||
|
||||
export function PersonasTab({
|
||||
onOpenPersona,
|
||||
onMeta,
|
||||
}: {
|
||||
onOpenPersona?: (id: string) => void;
|
||||
// Lets the section gate internal-build affordances (the Gallery entry point).
|
||||
onMeta?: (meta: { internal: boolean }) => void;
|
||||
}) {
|
||||
export function PersonasTab({ onOpenPersona }: { onOpenPersona?: (id: string) => void }) {
|
||||
const [personas, setPersonas] = useState<Persona[]>([]);
|
||||
const [internal, setInternal] = useState(false);
|
||||
const [mode, setMode] = useState<"git" | "dir" | "zip">("git");
|
||||
@@ -68,7 +61,6 @@ export function PersonasTab({
|
||||
.then((r) => {
|
||||
setPersonas(r.personas);
|
||||
setInternal(r.internal);
|
||||
onMeta?.({ internal: r.internal });
|
||||
})
|
||||
.catch(() => {});
|
||||
const reloadSessions = () => getSessions().then(setSessions).catch(() => {});
|
||||
|
||||
@@ -40,7 +40,6 @@ import { Icon } from "./Icon";
|
||||
import { PanelHead } from "./IntegrationsView";
|
||||
import { ModelsTab } from "./ManageTabs";
|
||||
import { MemorySection } from "./MemorySection";
|
||||
import { GalleryModal } from "./GalleryModal";
|
||||
import { PersonasTab } from "./PersonasTab";
|
||||
import { SkillsTab } from "./SkillsTab";
|
||||
import { showPersonas } from "../flags";
|
||||
@@ -375,13 +374,10 @@ function VoiceInputSection() {
|
||||
// -- Personas: installed/enabled/delete management, the dir/Git importer, and the
|
||||
// entry point to the Persona Gallery (a screen-sized modal — installs finish back
|
||||
// here, disabled pending consent; a gallery install re-mounts the list in place).
|
||||
// The Gallery entry point is GONE (owner 2026-08-21) — coworkers install from
|
||||
// GitHub / folder / zip only. GalleryModal stays in the tree for the gallery's
|
||||
// possible return as a first-class distribution surface, but nothing mounts it.
|
||||
function PersonasSection({ onOpenPersona }: { onOpenPersona?: (id: string) => void }) {
|
||||
const [galleryBump, setGalleryBump] = useState(0);
|
||||
const [galleryOpen, setGalleryOpen] = useState(false);
|
||||
// The Gallery is OUR distribution channel during development — internal builds only
|
||||
// (owner 2026-08-21). Users install from GitHub / folder / zip.
|
||||
const [internal, setInternal] = useState(false);
|
||||
|
||||
return (
|
||||
<section>
|
||||
<PanelHead title="Coworkers" sub="Manage your coworkers and add new ones." />
|
||||
@@ -390,33 +386,7 @@ function PersonasSection({ onOpenPersona }: { onOpenPersona?: (id: string) => vo
|
||||
with the tools and skills to be successful in that role. Enabling a coworker lets
|
||||
you pick it when starting a conversation.
|
||||
</p>
|
||||
<PersonasTab
|
||||
key={galleryBump}
|
||||
onOpenPersona={onOpenPersona}
|
||||
onMeta={(m) => setInternal(m.internal)}
|
||||
/>
|
||||
{internal && (
|
||||
<button
|
||||
className="mt-6 w-full rounded-xl2 border border-line bg-panel px-4 py-3.5 flex items-center gap-3 text-left hover:border-lineStrong"
|
||||
data-testid="gallery-link"
|
||||
onClick={() => setGalleryOpen(true)}
|
||||
>
|
||||
<Icon name="sparkle" size={16} className="text-accent shrink-0" />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-[13.5px] font-medium">Browse the Coworker Gallery</span>
|
||||
<span className="block text-[12px] text-muted">
|
||||
Curated coworkers from the OpenWorker team — see what each can do before installing.
|
||||
</span>
|
||||
</span>
|
||||
<span className="text-[12.5px] text-accent shrink-0">Open →</span>
|
||||
</button>
|
||||
)}
|
||||
{galleryOpen && (
|
||||
<GalleryModal
|
||||
onClose={() => setGalleryOpen(false)}
|
||||
onInstalled={() => setGalleryBump((b) => b + 1)}
|
||||
/>
|
||||
)}
|
||||
<PersonasTab onOpenPersona={onOpenPersona} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user