mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): restore the agent trading-card mint button (#5900)
## Problem PR #5574's profile-panel redesign dropped `ProfileSummaryView`'s `onCreateCard` prop — the only caller of `setCardMintTarget` — so the entire Agent Trading Cards feature (#3278) became unreachable from the GUI while staying fully wired underneath: mint dialog, background job store, viewer, gallery, composer chip, and the Rust `mint_agent_card`/`save_agent_card` commands all survive at main. `git log -S 'setCardMintTarget('` shows exactly two commits: the feature and the accidental removal. ## Outcome The mint trigger returns as a management row in the agent profile's Info tab, directly under **Export agent**, gated `isBot && canManagePersona` exactly like Duplicate/Export. Target resolution is byte-for-byte the original logic: prefer the live instance pubkey, fall back to the persona/definition id, allow locking only when an instance keypair exists. ## Shape - `UserProfileAgentManagementRows`: new optional `onCreateCard` row (Sparkles icon, `user-profile-create-card-row`), placed after Export. - Prop threaded `UserProfilePanel` → `ProfileSummaryView` → `ProfileInfoTabContent` → management rows, mirroring `onExportAgent` at every layer. - The mint-target state + open callback move into a `useCardMint` hook in `UserProfilePersonaDialogs` (beside the `CardMintTarget` type it manages). This keeps `UserProfilePanel.tsx` at 999 lines — the file sits at the size-ratchet cap and may not grow. ## Validation - `pnpm check` green (biome, file-size ratchet, px-text, pubkey-truncation). - `pnpm typecheck` green. - Full desktop unit suite: **4888 passed, 0 failed**. - Profile e2e spec: **32 passed**, including the updated management-row-order assertion and a new click → mint-dialog-visible → Escape → closed exercise of the restored row. Verified at `bff3110a0aeb3d63683eac9ed3e587829f9436da`, one commit atop main `01f76ec97`. Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
ArchiveRestore,
|
||||
CopyPlus,
|
||||
Download,
|
||||
Sparkles,
|
||||
Trash2,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
@@ -30,6 +31,7 @@ export function UserProfileAgentManagementRows({
|
||||
canDeleteAgent,
|
||||
isDeletePending,
|
||||
managedAgent,
|
||||
onCreateCard,
|
||||
onDeleteAgent,
|
||||
onDuplicateAgent,
|
||||
onExportAgent,
|
||||
@@ -39,11 +41,14 @@ export function UserProfileAgentManagementRows({
|
||||
canDeleteAgent: boolean;
|
||||
isDeletePending: boolean;
|
||||
managedAgent?: ManagedAgent;
|
||||
/** Mint an agent trading card. Present only for owner-managed personas. */
|
||||
onCreateCard?: () => void;
|
||||
onDeleteAgent: () => void;
|
||||
onDuplicateAgent?: () => void;
|
||||
onExportAgent?: () => void;
|
||||
}) {
|
||||
if (
|
||||
!onCreateCard &&
|
||||
!onDuplicateAgent &&
|
||||
!onExportAgent &&
|
||||
!canArchiveAgent &&
|
||||
@@ -72,6 +77,15 @@ export function UserProfileAgentManagementRows({
|
||||
testId="user-profile-export-agent-row"
|
||||
/>
|
||||
) : null}
|
||||
{onCreateCard ? (
|
||||
<ProfileAgentActionRow
|
||||
disabled={isDeletePending}
|
||||
icon={Sparkles}
|
||||
label="Create trading card"
|
||||
onClick={onCreateCard}
|
||||
testId="user-profile-create-card-row"
|
||||
/>
|
||||
) : null}
|
||||
{canArchiveAgent ? (
|
||||
<ProfileArchiveAgentRow archiveActions={archiveActions} />
|
||||
) : null}
|
||||
|
||||
@@ -66,7 +66,7 @@ import { useProfileAgentDeletion } from "@/features/profile/ui/UserProfilePanelD
|
||||
import { useProfileFieldBuckets } from "@/features/profile/ui/UserProfilePanelFields";
|
||||
import { submitProfilePersonaDialog } from "@/features/profile/ui/UserProfilePanelPersonaSubmit";
|
||||
import {
|
||||
type CardMintTarget,
|
||||
useCardMint,
|
||||
UserProfilePersonaDialogs,
|
||||
} from "@/features/profile/ui/UserProfilePersonaDialogs";
|
||||
import {
|
||||
@@ -179,8 +179,6 @@ export function UserProfilePanel({
|
||||
React.useState<AgentPersona | null>(null);
|
||||
const [personaToExportSnapshot, setPersonaToExportSnapshot] =
|
||||
React.useState<AgentPersona | null>(null);
|
||||
const [cardMintTarget, setCardMintTarget] =
|
||||
React.useState<CardMintTarget | null>(null);
|
||||
const [requestedInstancePubkey, setRequestedInstancePubkey] = React.useState<
|
||||
string | null
|
||||
>(null);
|
||||
@@ -712,6 +710,7 @@ export function UserProfilePanel({
|
||||
resolvedPersona,
|
||||
);
|
||||
const canManagePersona = isOwner === true && resolvedPersona !== undefined;
|
||||
const cardMint = useCardMint(resolvedPersona, managedAgent);
|
||||
const canDeletePersona = canManagePersona && !resolvedPersona?.sourceTeam;
|
||||
const canDeleteProfileAgent =
|
||||
isBot &&
|
||||
@@ -822,6 +821,7 @@ export function UserProfilePanel({
|
||||
agentSettingsFields={agentSettingsFields}
|
||||
diagnosticsFields={diagnosticsFields}
|
||||
onAddToChannel={() => setAddToChannelOpen(true)}
|
||||
onCreateCard={isBot && canManagePersona ? cardMint.create : undefined}
|
||||
onDeleteAgent={handleDeleteProfileAgent}
|
||||
onDuplicateAgent={
|
||||
isBot && canManagePersona ? handleDuplicatePersona : undefined
|
||||
@@ -935,7 +935,7 @@ export function UserProfilePanel({
|
||||
const personaDialogs = (
|
||||
<>
|
||||
<UserProfilePersonaDialogs
|
||||
cardMintTarget={cardMintTarget}
|
||||
cardMintTarget={cardMint.target}
|
||||
createError={
|
||||
createPersonaMutation.error instanceof Error
|
||||
? createPersonaMutation.error
|
||||
@@ -961,7 +961,7 @@ export function UserProfilePanel({
|
||||
? updatePersonaMutation.error
|
||||
: null
|
||||
}
|
||||
onCloseCardMint={() => setCardMintTarget(null)}
|
||||
onCloseCardMint={cardMint.close}
|
||||
onCloseDelete={() => setPersonaToDelete(null)}
|
||||
onCloseDialog={() => setPersonaDialogState(null)}
|
||||
onCloseExportSnapshot={() => setPersonaToExportSnapshot(null)}
|
||||
|
||||
@@ -94,6 +94,8 @@ export type ProfileSummaryViewProps = {
|
||||
agentSettingsFields: ProfileField[];
|
||||
diagnosticsFields: ProfileField[];
|
||||
onAddToChannel: () => void;
|
||||
/** Mint an agent trading card. Present only for owner-managed personas. */
|
||||
onCreateCard?: () => void;
|
||||
onDeleteAgent: () => void;
|
||||
onDuplicateAgent?: () => void;
|
||||
onExportAgent?: () => void;
|
||||
@@ -168,6 +170,7 @@ export function ProfileSummaryView({
|
||||
agentSettingsFields,
|
||||
diagnosticsFields,
|
||||
onAddToChannel,
|
||||
onCreateCard,
|
||||
onDeleteAgent,
|
||||
onDuplicateAgent,
|
||||
onExportAgent,
|
||||
@@ -512,6 +515,7 @@ export function ProfileSummaryView({
|
||||
isDeleteAgentPending={isAgentActionPending}
|
||||
managedAgent={managedAgent}
|
||||
onEditAgent={handleEditAgent}
|
||||
onCreateCard={onCreateCard}
|
||||
onDeleteAgent={onDeleteAgent}
|
||||
onDuplicateAgent={onDuplicateAgent}
|
||||
onExportAgent={onExportAgent}
|
||||
|
||||
@@ -206,6 +206,7 @@ export function ProfileInfoTabContent({
|
||||
isArchived,
|
||||
isDeleteAgentPending,
|
||||
managedAgent,
|
||||
onCreateCard,
|
||||
onDeleteAgent,
|
||||
onDuplicateAgent,
|
||||
onExportAgent,
|
||||
@@ -225,6 +226,8 @@ export function ProfileInfoTabContent({
|
||||
isArchived: boolean;
|
||||
isDeleteAgentPending: boolean;
|
||||
managedAgent?: ManagedAgent;
|
||||
/** Mint an agent trading card. Present only for owner-managed personas. */
|
||||
onCreateCard?: () => void;
|
||||
onDeleteAgent: () => void;
|
||||
onDuplicateAgent?: () => void;
|
||||
onExportAgent?: () => void;
|
||||
@@ -257,6 +260,7 @@ export function ProfileInfoTabContent({
|
||||
!hasInfoFields &&
|
||||
!showArchiveAction &&
|
||||
!canDeleteAgent &&
|
||||
!onCreateCard &&
|
||||
!onDuplicateAgent &&
|
||||
!onExportAgent &&
|
||||
!showActivityIngress &&
|
||||
@@ -306,6 +310,7 @@ export function ProfileInfoTabContent({
|
||||
canDeleteAgent={canDeleteAgent}
|
||||
isDeletePending={isDeleteAgentPending}
|
||||
managedAgent={managedAgent}
|
||||
onCreateCard={onCreateCard}
|
||||
onDeleteAgent={onDeleteAgent}
|
||||
onDuplicateAgent={onDuplicateAgent}
|
||||
onExportAgent={onExportAgent}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import * as React from "react";
|
||||
|
||||
import type {
|
||||
AcpRuntimeCatalogEntry,
|
||||
AgentPersona,
|
||||
CreatePersonaInput,
|
||||
ManagedAgent,
|
||||
UpdatePersonaInput,
|
||||
} from "@/shared/api/types";
|
||||
import { AgentCardMintDialog } from "@/features/agents/ui/AgentCardMintDialog";
|
||||
@@ -17,6 +20,30 @@ export type CardMintTarget = {
|
||||
canLock: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Card-mint dialog state plus the callback that opens it. `create` is
|
||||
* undefined when no persona resolves; owner gating is the caller's job.
|
||||
*/
|
||||
export function useCardMint(
|
||||
persona: AgentPersona | undefined,
|
||||
managedAgent: ManagedAgent | undefined,
|
||||
) {
|
||||
const [target, setTarget] = React.useState<CardMintTarget | null>(null);
|
||||
const close = React.useCallback(() => setTarget(null), []);
|
||||
const create = persona
|
||||
? () =>
|
||||
setTarget({
|
||||
// Prefer the live instance pubkey; fall back to the
|
||||
// persona/definition id (same resolution as export).
|
||||
id: managedAgent?.pubkey ?? persona.id,
|
||||
name: persona.displayName,
|
||||
// Locking needs an instance keypair to encrypt to.
|
||||
canLock: Boolean(managedAgent?.pubkey),
|
||||
})
|
||||
: undefined;
|
||||
return { close, create, target };
|
||||
}
|
||||
|
||||
export function UserProfilePersonaDialogs({
|
||||
cardMintTarget,
|
||||
createError,
|
||||
|
||||
@@ -1466,6 +1466,7 @@ test("renders agent profile ingress subviews from the Playwright mock bridge", a
|
||||
expect(managementRowOrder).toEqual([
|
||||
"user-profile-duplicate-agent-row",
|
||||
"user-profile-export-agent-row",
|
||||
"user-profile-create-card-row",
|
||||
"user-profile-archive-agent-row",
|
||||
"user-profile-delete-agent-row",
|
||||
]);
|
||||
@@ -1486,6 +1487,11 @@ test("renders agent profile ingress subviews from the Playwright mock bridge", a
|
||||
const exportDialog = page.getByTestId("agent-snapshot-export-dialog");
|
||||
await expect(exportDialog).toBeVisible();
|
||||
await exportDialog.getByRole("button", { name: "Cancel" }).click();
|
||||
await page.getByTestId("user-profile-create-card-row").click();
|
||||
const cardMintDialog = page.getByTestId("agent-card-mint-dialog");
|
||||
await expect(cardMintDialog).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(cardMintDialog).toHaveCount(0);
|
||||
const archiveAgentRow = page.getByTestId("user-profile-archive-agent-row");
|
||||
await expect(archiveAgentRow).toHaveText(/Archive agent/);
|
||||
await archiveAgentRow.click();
|
||||
|
||||
Reference in New Issue
Block a user