From 0520fe0e862aaedca5f603252a8f75e4c4a44136 Mon Sep 17 00:00:00 2001
From: kenny lopez
Date: Thu, 23 Jul 2026 13:33:53 -0700
Subject: [PATCH] Publish catalog updates on save
---
.../agents/ui/AgentDefinitionDialog.tsx | 19 +++---
.../agents/ui/AgentDefinitionDialogFooter.tsx | 35 ++++-------
.../src/features/agents/ui/AgentDialog.tsx | 2 +-
desktop/src/features/agents/ui/AgentsView.tsx | 2 +-
desktop/tests/e2e/agents.spec.ts | 62 +++++++++++--------
5 files changed, 56 insertions(+), 64 deletions(-)
diff --git a/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx b/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx
index 81bc3948a..09b2a98a4 100644
--- a/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx
+++ b/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx
@@ -98,8 +98,8 @@ type AgentDefinitionDialogProps = {
input: CreatePersonaInput | UpdatePersonaInput,
options: AgentDefinitionSubmitOptions,
) => Promise;
- /** Offers publishing alongside Save after a shared agent has been edited. */
- showPublishUpdatesOption?: boolean;
+ /** Publishes saved changes when the edited agent is shared in the catalog. */
+ publishCatalogUpdatesOnSave?: boolean;
/** Rendered below the form fields in create mode only ("Where to run"). */
createRunSection?: React.ReactNode;
/** Extra create-mode submit gate (e.g. incomplete provider config). */
@@ -127,7 +127,7 @@ export function AgentDefinitionDialog({
runtimesLoading = false,
onOpenChange,
onSubmit,
- showPublishUpdatesOption = false,
+ publishCatalogUpdatesOnSave = false,
createRunSection,
createSubmitBlocked = false,
}: AgentDefinitionDialogProps) {
@@ -166,8 +166,6 @@ export function AgentDefinitionDialog({
const [isAvatarUploadPending, setIsAvatarUploadPending] =
React.useState(false);
const [hasUserChanges, setHasUserChanges] = React.useState(false);
- const [publishUpdatesChecked, setPublishUpdatesChecked] =
- React.useState(false);
const {
globalConfig,
inheritedDefaults: {
@@ -224,7 +222,6 @@ export function AgentDefinitionDialog({
setShowAdvancedFields(false);
setIsAvatarUploadPending(false);
setHasUserChanges(false);
- setPublishUpdatesChecked(false);
isRuntimeAutoSeededRef.current = false;
hasSeededForOpenRef.current = false;
}, [initialValues, open]);
@@ -271,7 +268,6 @@ export function AgentDefinitionDialog({
setShowAdvancedFields(false);
setIsAvatarUploadPending(false);
setHasUserChanges(false);
- setPublishUpdatesChecked(false);
// isRuntimeAutoSeededRef and hasSeededForOpenRef are NOT reset here — the
// [initialValues, open] effect resets both when the dialog re-opens.
}
@@ -329,8 +325,7 @@ export function AgentDefinitionDialog({
...baseInput,
},
{
- publishCatalogUpdates:
- showPublishUpdatesOption && hasUserChanges && publishUpdatesChecked,
+ publishCatalogUpdates: publishCatalogUpdatesOnSave && hasUserChanges,
},
);
return;
@@ -736,9 +731,9 @@ export function AgentDefinitionDialog({
isAvatarUploadPending={isAvatarUploadPending}
isPending={isPending}
onCancel={() => handleOpenChange(false)}
- onPublishUpdatesCheckedChange={setPublishUpdatesChecked}
- publishUpdatesChecked={publishUpdatesChecked}
- showPublishUpdates={showPublishUpdatesOption && hasUserChanges}
+ publishesCatalogUpdates={
+ publishCatalogUpdatesOnSave && hasUserChanges
+ }
submitBlockReason={displayName.trim() ? submitBlockReason : null}
submitLabel={submitLabel}
/>
diff --git a/desktop/src/features/agents/ui/AgentDefinitionDialogFooter.tsx b/desktop/src/features/agents/ui/AgentDefinitionDialogFooter.tsx
index 2c7adc741..92428ad95 100644
--- a/desktop/src/features/agents/ui/AgentDefinitionDialogFooter.tsx
+++ b/desktop/src/features/agents/ui/AgentDefinitionDialogFooter.tsx
@@ -1,14 +1,11 @@
import { Button } from "@/shared/ui/button";
-import { Checkbox } from "@/shared/ui/checkbox";
type AgentDefinitionDialogFooterProps = {
canSubmit: boolean;
isAvatarUploadPending: boolean;
isPending: boolean;
onCancel: () => void;
- onPublishUpdatesCheckedChange: (checked: boolean) => void;
- publishUpdatesChecked: boolean;
- showPublishUpdates: boolean;
+ publishesCatalogUpdates: boolean;
submitBlockReason: string | null;
submitLabel: string;
};
@@ -18,9 +15,7 @@ export function AgentDefinitionDialogFooter({
isAvatarUploadPending,
isPending,
onCancel,
- onPublishUpdatesCheckedChange,
- publishUpdatesChecked,
- showPublishUpdates,
+ publishesCatalogUpdates,
submitBlockReason,
submitLabel,
}: AgentDefinitionDialogFooterProps) {
@@ -35,22 +30,14 @@ export function AgentDefinitionDialogFooter({
{submitBlockReason}
) : null}
- {showPublishUpdates ? (
-
+ This agent is in the community catalog. Your changes will be
+ published when you save.
+
) : null}
@@ -73,7 +60,9 @@ export function AgentDefinitionDialogFooter({
? "Saving..."
: isAvatarUploadPending
? "Uploading..."
- : submitLabel}
+ : publishesCatalogUpdates
+ ? "Save and publish"
+ : submitLabel}
diff --git a/desktop/src/features/agents/ui/AgentDialog.tsx b/desktop/src/features/agents/ui/AgentDialog.tsx
index cf5deaed1..f5be3cc7e 100644
--- a/desktop/src/features/agents/ui/AgentDialog.tsx
+++ b/desktop/src/features/agents/ui/AgentDialog.tsx
@@ -69,7 +69,7 @@ type AgentDialogDefinitionEditProps = {
input: CreatePersonaInput | UpdatePersonaInput,
options: AgentDefinitionSubmitOptions,
) => Promise;
- showPublishUpdatesOption?: boolean;
+ publishCatalogUpdatesOnSave?: boolean;
};
type AgentDialogProps =
diff --git a/desktop/src/features/agents/ui/AgentsView.tsx b/desktop/src/features/agents/ui/AgentsView.tsx
index 30bb095f3..ce3f1ad07 100644
--- a/desktop/src/features/agents/ui/AgentsView.tsx
+++ b/desktop/src/features/agents/ui/AgentsView.tsx
@@ -332,7 +332,7 @@ export function AgentsView() {
)
}
open={personas.personaDialogState !== null}
- showPublishUpdatesOption={
+ publishCatalogUpdatesOnSave={
"id" in personas.personaDialogState.initialValues &&
personas.sharedCatalogPersonaIdSet.has(
personas.personaDialogState.initialValues.id,
diff --git a/desktop/tests/e2e/agents.spec.ts b/desktop/tests/e2e/agents.spec.ts
index 9ff6bd296..e7a0e073b 100644
--- a/desktop/tests/e2e/agents.spec.ts
+++ b/desktop/tests/e2e/agents.spec.ts
@@ -1399,29 +1399,48 @@ This deliberately long fenced-code example must not establish the minimum width
await page.getByLabel("Open actions for Catalog Analyst").click();
await page.getByRole("menuitem", { name: "Edit" }).click();
const editDialog = page.getByTestId("persona-dialog");
- const publishUpdatesCheckbox = editDialog.getByTestId(
- "persona-dialog-publish-updates",
+ const catalogPublishNotice = editDialog.getByTestId(
+ "persona-dialog-catalog-publish-notice",
);
- await expect(publishUpdatesCheckbox).toHaveCount(0);
+ await expect(catalogPublishNotice).toHaveCount(0);
+ await expect(
+ editDialog.getByRole("button", { name: "Save and publish" }),
+ ).toHaveCount(0);
+ await expect(
+ editDialog.getByRole("button", { name: "Save changes" }),
+ ).toBeVisible();
await editDialog
.getByLabel("Agent instructions")
.fill("Review the latest catalog changes.");
- await expect(publishUpdatesCheckbox).toBeVisible();
- await expect(publishUpdatesCheckbox).toHaveAttribute(
- "data-state",
- "unchecked",
+ await expect(catalogPublishNotice).toHaveText(
+ "This agent is in the community catalog. Your changes will be published when you save.",
);
- const [cancelButtonBox, publishUpdatesCheckboxBox] = await Promise.all([
- editDialog.getByRole("button", { name: "Cancel" }).boundingBox(),
- publishUpdatesCheckbox.boundingBox(),
- ]);
- expect(
- (publishUpdatesCheckboxBox?.x ?? 0) +
- (publishUpdatesCheckboxBox?.width ?? 0),
- ).toBeLessThan(cancelButtonBox?.x ?? 0);
- await editDialog.getByRole("button", { name: "Save changes" }).click();
+ await expect(
+ editDialog.getByRole("button", { name: "Save changes" }),
+ ).toHaveCount(0);
+ await editDialog.getByRole("button", { name: "Save and publish" }).click();
await expect(editDialog).toHaveCount(0);
+ await page.getByLabel("Open actions for Catalog Analyst").click();
+ await page.getByRole("menuitem", { name: "Share" }).click();
+ await expect(catalogAccess).toHaveText("Agent only");
+ await expect(publishCatalogUpdatesButton).toHaveCount(0);
+ await page
+ .getByTestId("persona-share-dialog")
+ .getByRole("button", { name: "Close" })
+ .click();
+
+ await page.evaluate((id) => {
+ const storageKey = "buzz-persona-catalog-published-versions-v1";
+ const publishedVersions = JSON.parse(
+ localStorage.getItem(storageKey) ?? "{}",
+ ) as Record;
+ publishedVersions[id] = "stale";
+ localStorage.setItem(storageKey, JSON.stringify(publishedVersions));
+ }, personaId);
+ await gotoApp(page);
+ await page.getByTestId("open-agents-view").click();
+
await page.getByLabel("Open actions for Catalog Analyst").click();
await page.getByRole("menuitem", { name: "Share" }).click();
await expect(catalogAccess).toHaveText("Agent only");
@@ -1442,17 +1461,6 @@ This deliberately long fenced-code example must not establish the minimum width
.getByRole("button", { name: "Close" })
.click();
- await page.getByLabel("Open actions for Catalog Analyst").click();
- await page.getByRole("menuitem", { name: "Edit" }).click();
- await editDialog
- .getByLabel("Agent instructions")
- .fill("Review and publish the latest catalog changes.");
- await expect(publishUpdatesCheckbox).toBeVisible();
- await publishUpdatesCheckbox.click();
- await expect(publishUpdatesCheckbox).toHaveAttribute("data-state", "checked");
- await editDialog.getByRole("button", { name: "Save changes" }).click();
- await expect(editDialog).toHaveCount(0);
-
await page.getByLabel("Open actions for Catalog Analyst").click();
await page.getByRole("menuitem", { name: "Share" }).click();
await expect(catalogAccess).toHaveText("Agent only");