Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion scripts/design-system-audit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ const buttonStylingPattern =
/(?:^|[\s"'`(])(?:bg-(?!transparent\b)[a-z[]|text-(?:foreground|muted|primary|secondary|destructive|accent|current|white|black|surface|app|sidebar)|border-(?:input|border|destructive|primary|accent|current|surface)|hover:(?!opacity-100\b)|active:(?:bg|text|border|opacity)|focus-visible:(?:bg|text|border)|data-\[state=open\]:(?:bg|text)|aria-expanded:(?:bg|text)|shadow-(?!none)|opacity-(?!0\b|100\b)\d|backdrop-|ring-(?!offset|ring\b|\d))/;

const buttonStylingBaseline = new Set([
"src/features/agents/ui/AgentDetailPage.tsx",
"src/features/chat/ui/ChatInputToolbar.tsx",
"src/features/chat/ui/MessageBubbleActions.tsx",
"src/features/chat/ui/PersonaPicker.tsx",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ import {
useChatSessionStore,
type ChatSession,
} from "@/features/chat/stores/chatSessionStore";
import { setExperimentEnabled } from "@/features/experiments/experimentPreferences";
import { AVATAR_COLLECTION_PAGE_EXPERIMENT_ID } from "@/features/experiments/experimentDefinitions";
import type { AgentSourceEntry } from "@/shared/api/agents";

const existingAgentSource: AgentSourceEntry = {
Expand Down Expand Up @@ -119,7 +117,6 @@ describe("AgentBuilderCapability keep-save telemetry", () => {
);
apiMocks.listPersonas.mockResolvedValue([]);
resetAgentBuilderSourceLifecycleForTests();
setExperimentEnabled(AVATAR_COLLECTION_PAGE_EXPERIMENT_ID, false);
useAgentStore.setState({
personas: [],
personasLoading: false,
Expand Down
12 changes: 12 additions & 0 deletions src/features/agents/lib/getCachedAvatarMedia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { AvatarLibraryState } from "@/features/agents/hooks/useAvatarLibrary";

export function getCachedAvatarMedia(
cachedAvatarMediaById: AvatarLibraryState["cachedAvatarMediaById"],
catalogVersion: string | undefined,
avatarId: string,
) {
const cachedMediaEntry = cachedAvatarMediaById[avatarId];
return cachedMediaEntry?.catalogVersion === catalogVersion
? cachedMediaEntry.media
: undefined;
}
142 changes: 0 additions & 142 deletions src/features/agents/ui/AgentAvatarSection.tsx

This file was deleted.

123 changes: 7 additions & 116 deletions src/features/agents/ui/AgentBuilderRail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import {
import { useTranslation } from "react-i18next";
import {
IconAlertTriangle,
IconArrowLeft,
IconLayoutSidebarLeftExpand,
IconPhoto,
IconSparkles,
IconX,
} from "@tabler/icons-react";
import { avatarRef, parseAvatarRef } from "@/shared/avatars/catalog";
import { avatarRef } from "@/shared/avatars/catalog";
import { normalizeAvatarUrl } from "@/shared/lib/avatarUrl";
import { cn } from "@/shared/lib/cn";
import type { AgentSourceEntry } from "@/shared/api/agents";
Expand Down Expand Up @@ -45,10 +44,7 @@ import {
PLACEHOLDER_AGENT_DESCRIPTION,
promoteDraft,
} from "@/features/agents/lib/agentBuilderSession";
import { useExperiment } from "@/features/experiments/experimentPreferences";
import { AVATAR_COLLECTION_PAGE_EXPERIMENT_ID } from "@/features/experiments/experimentDefinitions";
import { AvatarCollectionOverlay } from "@/features/agents/ui/AvatarCollectionOverlay";
import { AvatarLibraryPicker } from "@/features/agents/ui/AvatarLibraryPicker";
import { ProviderModelFields } from "@/features/agents/ui/PersonaFields/ProviderModelFields";
import { FORM_FIELD_CLASS } from "@/shared/ui/form-field-tokens";

Expand Down Expand Up @@ -139,15 +135,6 @@ export function AgentBuilderRail({
const [failedMissingDraftRecoveryKey, setFailedMissingDraftRecoveryKey] =
useState<string | null>(null);
const avatarLibrary = useAvatarLibrary(true);
const avatarCollectionExperiment = useExperiment(
AVATAR_COLLECTION_PAGE_EXPERIMENT_ID,
);
// When on, "library" renders as the full-surface collection canvas overlay
// (portal over the whole app) instead of the inline picker. The chat +
// builder stay mounted underneath, so composer drafts, resize state, all survive the takeover.
const avatarCollectionOverlayEnabled = Boolean(
avatarCollectionExperiment?.enabled,
);
const isWaitingForDraftTarget = !targetAgentPath;
const missingDraftRecoveryKey = `${sessionId}:${targetAgentPath ?? "pending"}`;
const [previousMissingDraftRecoveryKey, setPreviousMissingDraftRecoveryKey] =
Expand Down Expand Up @@ -197,17 +184,6 @@ export function AgentBuilderRail({
typeof data?.properties?.avatar === "string" ? data.properties.avatar : "";
const trimmedAvatar = avatarRaw.trim();
const normalizedAvatar = normalizeAvatarUrl(trimmedAvatar);
const [selectedCollectionId, setSelectedCollectionId] = useState<
string | null
>(null);
const selectedCollection = useMemo(
() =>
avatarLibrary.catalog?.collections.find(
(collection) => collection.id === selectedCollectionId,
) ?? null,
[avatarLibrary.catalog, selectedCollectionId],
);

const provider = (data?.properties?.provider as string | undefined) ?? "";
const modelProviderId =
(data?.properties?.modelProviderId as string | undefined) ?? "";
Expand All @@ -229,9 +205,10 @@ export function AgentBuilderRail({

const onSelectAvatar = useCallback(
(avatarId: string) => {
// Avatar selection joins the same working buffer as every other field.
// Existing-agent edits stay local until Save; drafts keep their normal
// debounced durability rather than making this field a separate commit.
writeProperty("avatar", avatarRef(avatarId));
setSelectedCollectionId(null);
setAvatarPanel("closed");
},
[writeProperty],
);
Expand All @@ -258,11 +235,6 @@ export function AgentBuilderRail({
: null;
const effectiveAvatar =
normalizedAvatar ?? (defaultAvatarId ? avatarRef(defaultAvatarId) : null);
const selectedAvatarRefValue = effectiveAvatar
? parseAvatarRef(effectiveAvatar)
? effectiveAvatar
: null
: null;
const selectedAvatarMediaState = useAvatarMediaState(effectiveAvatar);

const onChangeProvider = useCallback(
Expand Down Expand Up @@ -522,55 +494,15 @@ export function AgentBuilderRail({
</aside>
);

// Rendered by both the compact rail and the full-page builder. Declared once
// so picker changes cannot be applied to one layout and silently missed in
// the other.
const avatarLibraryPickerNode = (
<AvatarLibraryPicker
library={avatarLibrary}
selectedAvatarRef={selectedAvatarRefValue}
onSelectAvatar={onSelectAvatar}
onPreviewError={() => {}}
selectedCollectionId={selectedCollectionId}
onSelectCollection={setSelectedCollectionId}
/>
);

const avatarCollectionOverlayNode =
avatarCollectionOverlayEnabled && avatarPanel === "library" ? (
avatarPanel === "library" ? (
<AvatarCollectionOverlay
library={avatarLibrary}
initialCollectionId={selectedCollectionId}
onSelectAvatar={onSelectAvatar}
onClose={() => {
setSelectedCollectionId(null);
setAvatarPanel("closed");
}}
onClose={() => setAvatarPanel("closed")}
/>
) : null;

const pickerHeaderNode = (
<div className={cn(STICKY_HEADER_CLASS, "flex items-center gap-2")}>
<Button
type="button"
variant="ghost"
size="icon-xs"
aria-label={t("builderRail.backToForm")}
onClick={() => {
if (selectedCollectionId) setSelectedCollectionId(null);
else setAvatarPanel("closed");
}}
>
<IconArrowLeft className="size-4" aria-hidden="true" />
</Button>
<h2 className="truncate text-sm font-normal text-foreground">
{selectedCollection
? selectedCollection.label
: t("builderRail.chooseAvatarTitle")}
</h2>
</div>
);

if (error === "parse") {
return shell(
headerNode,
Expand Down Expand Up @@ -666,17 +598,6 @@ export function AgentBuilderRail({
);
}

// With the collection canvas experiment on, the "library" panel renders as
// the full-surface overlay (mounted below) instead of swapping the rail
// body, so the form stays visible underneath the frosted glass.
if (
avatarPanel === "library" &&
!fullPage &&
!avatarCollectionOverlayEnabled
) {
return shell(pickerHeaderNode, avatarLibraryPickerNode);
}

const avatarNode = (
<section>
<button
Expand Down Expand Up @@ -822,37 +743,7 @@ export function AgentBuilderRail({
</>
);

const fullPageLeftColumn =
avatarPanel === "library" && !avatarCollectionOverlayEnabled ? (
<div className="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-8 py-6 xl:px-12 xl:py-8">
<div className="flex items-center gap-2 text-sm text-foreground">
<Button
type="button"
variant="ghost"
size="icon-xs"
className="-ml-1 shrink-0"
aria-label={t("builderRail.backToForm")}
onClick={() => {
if (selectedCollectionId) {
setSelectedCollectionId(null);
} else {
setAvatarPanel("closed");
}
}}
>
<IconArrowLeft className="size-4" aria-hidden="true" />
</Button>
<h3 className="truncate text-sm font-normal text-foreground">
{selectedCollection
? selectedCollection.label
: t("builderRail.chooseAvatarTitle")}
</h3>
</div>
{avatarLibraryPickerNode}
</div>
) : (
<div className="flex flex-col">{avatarNode}</div>
);
const fullPageLeftColumn = <div className="flex flex-col">{avatarNode}</div>;

if (fullPage) {
return (
Expand Down
Loading
Loading