diff --git a/ui/goose2/docs/superpowers/plans/2026-04-23-alex-redesign.md b/ui/goose2/docs/superpowers/plans/2026-04-23-alex-redesign.md index 4a75edd4e9..354f1bcd4c 100644 --- a/ui/goose2/docs/superpowers/plans/2026-04-23-alex-redesign.md +++ b/ui/goose2/docs/superpowers/plans/2026-04-23-alex-redesign.md @@ -1843,6 +1843,28 @@ Task 4.2 imports `src/assets/agents/figure.png` directly. ### Task 4.2: Replace `PersonaCard` body render +**Corrections applied during implementation (2026-04-27):** +- Plan called for `persona.description`; the actual data model has + `persona.systemPrompt` only. Used `systemPrompt` with `line-clamp-2` + to keep long prompts from breaking layout. Original card already + rendered this field as the body paragraph. +- Plan referenced `t("card.optionsAria", { name })` — that key does + not exist in `agents.json`. Kept existing `t("card.options")` aria + label; introducing new i18n strings is out of scope for the redesign. +- Preserved `onClick={(e) => e.stopPropagation()}` and + `onKeyDown={(e) => e.stopPropagation()}` on the kebab ` + + , + ); + return () => setTopBarActions(null); + }, [setTopBarActions, t, handleImportPicker, handleNewPersona]); + return (
-
- {/* Header */} -
-
-

- {t("view.title")} -

-

- {t("view.description")} -

-
-
- - -
-
- - {/* Search */} - + openPersonaEditor(p, "details")} + onEditPersona={(p) => openPersonaEditor(p, "edit")} + onDuplicatePersona={handleDuplicatePersona} + onDeletePersona={handleDeletePersona} + onExportPersona={handleExportPersona} + isLoading={personasLoading} + dropHandlers={dropHandlers} + isDragOver={isDragOver} /> - - {/* Personas section */} -
- openPersonaEditor(p, "details")} - onEditPersona={(p) => openPersonaEditor(p, "edit")} - onDuplicatePersona={handleDuplicatePersona} - onDeletePersona={handleDeletePersona} - onExportPersona={handleExportPersona} - onCreatePersona={() => openPersonaEditor()} - onImportFile={handleImportFileBytes} - validateImportFile={validateImportFile} - onImportError={handleImportError} - isLoading={personasLoading} - /> -
+ + {/* Bottom fade — same soft gradient mask as Skills page */} + {/* Persona editor modal */} diff --git a/ui/goose2/src/features/agents/ui/PersonaCard.tsx b/ui/goose2/src/features/agents/ui/PersonaCard.tsx index 83e2c9cb5f..c12238e198 100644 --- a/ui/goose2/src/features/agents/ui/PersonaCard.tsx +++ b/ui/goose2/src/features/agents/ui/PersonaCard.tsx @@ -1,9 +1,8 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { MoreVertical, Copy, Pencil, Trash2, Download } from "lucide-react"; +import figureUrl from "@/assets/agents/figure.png"; import { cn } from "@/shared/lib/cn"; -import { Avatar, AvatarImage, AvatarFallback } from "@/shared/ui/avatar"; -import { Badge } from "@/shared/ui/badge"; import { Button } from "@/shared/ui/button"; import { DropdownMenu, @@ -11,7 +10,6 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/shared/ui/dropdown-menu"; -import { useAvatarSrc } from "@/shared/hooks/useAvatarSrc"; import type { Persona } from "@/shared/types/agents"; import { getPersonaSource } from "@/features/agents/lib/personaPresentation"; @@ -37,14 +35,9 @@ export function PersonaCard({ const { t } = useTranslation(["agents", "common"]); const [menuOpen, setMenuOpen] = useState(false); - const initials = persona.displayName.charAt(0).toUpperCase(); - const avatarSrc = useAvatarSrc(persona.avatar); const personaSource = getPersonaSource(persona); const canEditPersona = personaSource === "custom"; const canDeletePersona = personaSource !== "builtin"; - const providerModelLabel = [persona.provider, persona.model] - .filter(Boolean) - .join(" / "); const handleCardKeyDown = (event: React.KeyboardEvent) => { if (event.target !== event.currentTarget || menuOpen) { @@ -65,14 +58,30 @@ export function PersonaCard({ onKeyDown={handleCardKeyDown} tabIndex={0} className={cn( - "group relative flex flex-col items-center gap-3 rounded-xl border p-5 cursor-pointer", - "bg-background transition-colors duration-200 motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-2", - "hover:bg-accent/50", - isActive ? "border-border ring-1 ring-ring" : "border-border", + "group relative flex flex-col items-center cursor-pointer px-3 py-4", + "transition-colors duration-200", + isActive && "bg-black/[0.03]", )} > - {/* Dropdown trigger */} -
+ {/* Single shared cutout figure — visual placeholder; real per-persona avatars deferred */} + + +
+ + + {persona.displayName} + + +

+ {persona.systemPrompt} +

+ +
@@ -101,10 +107,12 @@ export function PersonaCard({ {t("common:actions.duplicate")} - onExport?.(persona)}> - - {t("common:actions.export")} - + {onExport && ( + onExport(persona)}> + + {t("common:actions.export")} + + )} {canDeletePersona && (
- - {/* Avatar */} - - - - {initials} - - - - {/* Name */} -

- {persona.displayName} -

- - {/* Built-in badge */} - {personaSource === "builtin" && ( - - {t("common:labels.builtIn")} - - )} - {personaSource === "file" && ( - - {t("card.fileBacked")} - - )} - - {/* System prompt preview */} -

- {persona.systemPrompt} -

- - {/* Provider/model badge */} - {providerModelLabel && ( - - - {providerModelLabel} - - - )}
); } diff --git a/ui/goose2/src/features/agents/ui/PersonaGallery.tsx b/ui/goose2/src/features/agents/ui/PersonaGallery.tsx index b37863eca3..ae9484dcc8 100644 --- a/ui/goose2/src/features/agents/ui/PersonaGallery.tsx +++ b/ui/goose2/src/features/agents/ui/PersonaGallery.tsx @@ -1,12 +1,9 @@ import { useMemo } from "react"; import { useTranslation } from "react-i18next"; -import { Plus } from "lucide-react"; import { cn } from "@/shared/lib/cn"; -import { Button } from "@/shared/ui/button"; import { Skeleton } from "@/shared/ui/skeleton"; import type { Persona } from "@/shared/types/agents"; import { PersonaCard } from "@/features/agents/ui/PersonaCard"; -import { useFileImportZone } from "@/shared/hooks/useFileImportZone"; interface PersonaGalleryProps { personas: Persona[]; @@ -16,23 +13,19 @@ interface PersonaGalleryProps { onDuplicatePersona: (persona: Persona) => void; onDeletePersona: (persona: Persona) => void; onExportPersona?: (persona: Persona) => void; - onCreatePersona: () => void; - onImportFile?: (fileBytes: number[], fileName: string) => void; - validateImportFile?: (file: Pick) => string | null; - onImportError?: (message: string) => void; isLoading?: boolean; + dropHandlers?: React.HTMLAttributes; + isDragOver?: boolean; } function SkeletonCard() { return ( -