fix: Preserve avatar reset drafts

This commit is contained in:
Danny Avila 2026-05-18 10:14:18 -04:00
parent 018edee845
commit adc115c878
4 changed files with 64 additions and 5 deletions

View file

@ -320,16 +320,26 @@ export default function AgentPanel() {
}, [clearDraftsForAgentIds, reset, setCurrentAgentId]);
const shouldPersistDraft = hasPersistableDirtyFields(dirtyFields);
const shouldPersistAvatarResetDraft =
dirtyFields?.avatar_action === true && getValues('avatar_action') === 'reset';
const isDirtyPersistableDraftField = useCallback(
(name?: string): boolean => {
const [fieldName] = name?.split('.') ?? [];
if (fieldName === 'avatar_action') {
return (
getFieldState(name as FieldPath<AgentForm>).isDirty &&
getValues('avatar_action') === 'reset'
);
}
if (!isPersistableDraftField(name)) {
return false;
}
return getFieldState(name as FieldPath<AgentForm>).isDirty;
},
[getFieldState],
[getFieldState, getValues],
);
useEffect(() => {
@ -367,13 +377,13 @@ export default function AgentPanel() {
}, [hasDraft]);
useEffect(() => {
if (!shouldPersistDraft) {
if (!shouldPersistDraft && !shouldPersistAvatarResetDraft) {
return;
}
shouldPersistDraftRef.current = true;
persistAgentDraft(currentAgentIdRef.current, getValues());
}, [getValues, persistAgentDraft, shouldPersistDraft]);
}, [getValues, persistAgentDraft, shouldPersistAvatarResetDraft, shouldPersistDraft]);
useEffect(() => {
const subscription = watch((_, { name }) => {

View file

@ -23,6 +23,8 @@ const AGENTS_BUTTON_LABEL = 'Agents';
const FILES_BUTTON_LABEL = 'Files';
const FILES_PANEL_LABEL = 'Files panel';
const PROGRAMMATIC_UPDATE_LABEL = 'Programmatic agent update';
const AVATAR_ACTION_LABEL = 'Avatar action';
const RESET_AVATAR_LABEL = 'Reset avatar';
const MOCK_USER_ID = 'user-123';
type MockButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
@ -166,13 +168,20 @@ jest.mock('../AgentConfig', () => ({
const { useFormContext } = jest.requireActual(
'react-hook-form',
) as typeof import('react-hook-form');
const { register } = useFormContext();
const { register, setValue } = useFormContext<AgentForm>();
return (
<div>
<input aria-label="Draft name" {...register('name')} />
<textarea aria-label="Draft instructions" {...register('instructions')} />
<input aria-label="Draft model" {...register('model')} />
<input aria-label={AVATAR_ACTION_LABEL} {...register('avatar_action')} />
<button
type="button"
onClick={() => setValue('avatar_action', 'reset', { shouldDirty: true })}
>
{RESET_AVATAR_LABEL}
</button>
</div>
);
},
@ -376,4 +385,22 @@ describe('AgentPanel draft preservation', () => {
expect(getAgentDraft(undefined, 'user-456')).toBeUndefined();
expect(getAgentDraft(undefined, MOCK_USER_ID)?.name).toBe('First user draft');
});
it('restores avatar reset-only drafts after switching to Files and back', async () => {
render(<Harness />);
fireEvent.click(screen.getByRole('button', { name: RESET_AVATAR_LABEL }));
await waitFor(() => {
expect(getAgentDraft(undefined, MOCK_USER_ID)?.avatar_action).toBe('reset');
});
fireEvent.click(screen.getByRole('button', { name: FILES_BUTTON_LABEL }));
expect(screen.getByText(FILES_PANEL_LABEL)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: AGENTS_BUTTON_LABEL }));
await waitFor(() => {
expect(screen.getByLabelText(AVATAR_ACTION_LABEL)).toHaveValue('reset');
});
});
});

View file

@ -93,4 +93,21 @@ describe('agent drafts', () => {
expect(getAgentDraft('agent-1', 'user-a')?.name).toBe('User A saved-agent draft');
expect(getAgentDraft('agent-1', 'user-b')).toBeUndefined();
});
it('preserves avatar reset intent without upload files', () => {
saveAgentDraft(
undefined,
createForm({
avatar_action: 'reset',
avatar_file: null,
avatar_preview: '/images/avatar.png',
}),
);
const draft = getAgentDraft(undefined);
expect(draft?.avatar_action).toBe('reset');
expect(draft?.avatar_preview).toBe('');
expect(draft?.avatar_file).toBeUndefined();
});
});

View file

@ -21,10 +21,15 @@ export function sanitizeAgentDraft(values: AgentForm): AgentDraftValues {
const {
avatar_file: _avatarFile,
avatar_preview: _avatarPreview,
avatar_action: _avatarAction,
avatar_action: avatarAction,
...draft
} = values;
if (avatarAction === 'reset') {
draft.avatar_action = avatarAction;
draft.avatar_preview = '';
}
if (typeof draft.agent === 'object' && draft.agent != null) {
draft.agent = {
...draft.agent,