diff --git a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx
index e64c65f6b..129779663 100644
--- a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx
+++ b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx
@@ -3,11 +3,12 @@
import {
act,
fireEvent,
- render,
+ render as testingLibraryRender,
screen,
waitFor,
within,
} from '@testing-library/react';
+import type { ReactElement, ReactNode } from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type {
@@ -17,6 +18,7 @@ import type {
} from '@/packages/shared/src/contracts';
import type { EditorAgentConversationClient } from '@/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts';
import { EDITOR_AGENT_PATIENCE_NOTICE_DELAY_MS } from '@/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts';
+import { ImageCanvasActionsProvider } from '@/src/components/image-editor/ImageCanvasActionsProvider.tsx';
import { useImageCanvasContextStore } from '@/src/components/image-editor/useImageCanvasContextStore.ts';
import { EditorAgentConversationPanelView } from './EditorAgentConversationPanelView.tsx';
@@ -24,6 +26,17 @@ import { EditorAgentConversationPanelView } from './EditorAgentConversationPanel
const createEditorProjectResourceMock = vi.hoisted(() => vi.fn());
const uploadEditorMediaAssetFileMock = vi.hoisted(() => vi.fn());
const probeImageFileDimensionsMock = vi.hoisted(() => vi.fn());
+const refreshCanvasMock = vi.fn();
+
+function render(ui: ReactElement) {
+ return testingLibraryRender(ui, {
+ wrapper: ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+}
vi.mock('@/src/services/image-editor/editorProjectClient.ts', async () => {
const actual = await vi.importActual<
@@ -1801,14 +1814,11 @@ describe('EditorAgentConversationPanelView', () => {
resolveConfirmation = resolve;
}),
);
- const onCanvasRefreshRequested = vi.fn();
-
render(
,
);
@@ -1850,7 +1860,7 @@ describe('EditorAgentConversationPanelView', () => {
'internal completed tool output that must stay hidden',
),
).toBeNull();
- expect(onCanvasRefreshRequested).not.toHaveBeenCalled();
+ expect(refreshCanvasMock).not.toHaveBeenCalled();
expect(screen.queryByRole('button', { name: '确认' })).toBeNull();
});
diff --git a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx
index 8d52c8c51..6df047e7b 100644
--- a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx
+++ b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx
@@ -42,7 +42,6 @@ type EditorAgentConversationPanelViewProps = {
onToggleOpen: () => void;
layers?: CanvasLayer[];
assets?: EditorAsset[];
- onCanvasRefreshRequested?: () => void;
// TODO refactor: move the task list update seperate
onConfirmSent?: () => void;
client?: EditorAgentConversationClient;
@@ -57,7 +56,6 @@ export function EditorAgentConversationPanelView({
onToggleOpen,
layers = [],
assets = [],
- onCanvasRefreshRequested,
onConfirmSent,
client,
}: EditorAgentConversationPanelViewProps) {
@@ -93,7 +91,6 @@ export function EditorAgentConversationPanelView({
} = useEditorAgentConversation({
projectId: effectiveProjectId,
client,
- onCanvasRefreshRequested,
onConfirmSent,
});
const [draftText, setDraftText] = useState('');
@@ -310,7 +307,6 @@ export function EditorAgentConversationPanelView({
onReferenceImage={referenceContextAsset}
onJobCompleted={() => {
void refreshActiveConversation();
- onCanvasRefreshRequested?.();
}}
/>
))}
diff --git a/src/components/image-editor/EditorAgentConversation/ToolCallView.test.tsx b/src/components/image-editor/EditorAgentConversation/ToolCallView.test.tsx
index 8ec8c461f..a9338a306 100644
--- a/src/components/image-editor/EditorAgentConversation/ToolCallView.test.tsx
+++ b/src/components/image-editor/EditorAgentConversation/ToolCallView.test.tsx
@@ -1,13 +1,16 @@
/* @vitest-environment jsdom */
import { act, render, screen, waitFor } from '@testing-library/react';
+import type { ReactElement, ReactNode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { EditorAgentToolCall } from '@/packages/shared/src/contracts';
+import { ImageCanvasActionsProvider } from '@/src/components/image-editor/ImageCanvasActionsProvider.tsx';
import ToolCallView from './ToolCallView.tsx';
const getExternalGenerationJobStatusMock = vi.hoisted(() => vi.fn());
+const refreshCanvasMock = vi.fn();
vi.mock('@/src/services/external-generation', () => ({
getExternalGenerationJobStatus: getExternalGenerationJobStatusMock,
@@ -54,9 +57,20 @@ function createCompletedJobResponse(jobId: string) {
};
}
+function renderWithCanvasActions(ui: ReactElement) {
+ return render(ui, {
+ wrapper: ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+}
+
describe('ToolCallView', () => {
beforeEach(() => {
getExternalGenerationJobStatusMock.mockReset();
+ refreshCanvasMock.mockReset();
});
it.each([
@@ -65,14 +79,13 @@ describe('ToolCallView', () => {
] as const)(
'keeps the server %s state when the pending response arrives late',
async (status, statusLabel, error) => {
- const pendingResponse = createDeferred<
- ReturnType
- >();
+ const pendingResponse =
+ createDeferred>();
getExternalGenerationJobStatusMock.mockReturnValueOnce(
pendingResponse.promise,
);
const onJobCompleted = vi.fn();
- const { rerender } = render(
+ const { rerender } = renderWithCanvasActions(
{
expect(screen.getByText(statusLabel)).toBeTruthy();
expect(screen.queryByText('已完成')).toBeNull();
expect(onJobCompleted).not.toHaveBeenCalled();
+ expect(refreshCanvasMock).not.toHaveBeenCalled();
},
);
it('ignores job A after switching to job B and completes job B once', async () => {
- const jobAResponse = createDeferred<
- ReturnType
- >();
- const jobBResponse = createDeferred<
- ReturnType
- >();
+ const jobAResponse =
+ createDeferred>();
+ const jobBResponse =
+ createDeferred>();
getExternalGenerationJobStatusMock.mockImplementation((jobId: string) =>
jobId === 'job-a' ? jobAResponse.promise : jobBResponse.promise,
);
const onJobCompleted = vi.fn();
- const { rerender } = render(
+ const { rerender } = renderWithCanvasActions(
{
expect(await screen.findByText('已完成')).toBeTruthy();
expect(onJobCompleted).toHaveBeenCalledTimes(1);
+ expect(refreshCanvasMock).toHaveBeenCalledTimes(1);
});
+
});
diff --git a/src/components/image-editor/EditorAgentConversation/ToolCallView.tsx b/src/components/image-editor/EditorAgentConversation/ToolCallView.tsx
index 65279fd36..9732d3b8b 100644
--- a/src/components/image-editor/EditorAgentConversation/ToolCallView.tsx
+++ b/src/components/image-editor/EditorAgentConversation/ToolCallView.tsx
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from 'react';
import type { EditorAgentToolCall } from '@/packages/shared/src/contracts';
import { editorAgentToolLabel } from '@/src/components/image-editor/EditorAgentConversation/toolCallPresentation.ts';
+import { useImageCanvasActions } from '@/src/components/image-editor/ImageCanvasActionsContext.ts';
import { ResolvedAssetAudio } from '@/src/components/ResolvedAssetAudio.tsx';
import { ResolvedAssetImage } from '@/src/components/ResolvedAssetImage.tsx';
import { ResolvedAssetVideo } from '@/src/components/ResolvedAssetVideo.tsx';
@@ -19,6 +20,7 @@ function ToolCallView({
onJobCompleted?: () => void;
onRightClickMenu?: RightClickMenuHandler;
}) {
+ const { refreshCanvas } = useImageCanvasActions();
const videos = toolCall.videos ?? [];
const audios = toolCall.audios ?? [];
const jobId = toolCall.externalJobId?.trim() || null;
@@ -37,9 +39,13 @@ function ToolCallView({
useState(initialDisplayError);
const terminalNotifiedRef = useRef(false);
const onJobCompletedRef = useRef(onJobCompleted);
+ const refreshCanvasRef = useRef(refreshCanvas);
useEffect(() => {
onJobCompletedRef.current = onJobCompleted;
}, [onJobCompleted]);
+ useEffect(() => {
+ refreshCanvasRef.current = refreshCanvas;
+ }, [refreshCanvas]);
useEffect(() => {
setDisplayStatus(initialDisplayStatus);
setDisplayError(initialDisplayError);
@@ -59,6 +65,7 @@ function ToolCallView({
setDisplayError(response.job.error ?? null);
if (!terminalNotifiedRef.current) {
terminalNotifiedRef.current = true;
+ refreshCanvasRef.current();
onJobCompletedRef.current?.();
}
return;
diff --git a/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.test.tsx b/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.test.tsx
index 03ff1b3c8..3f1aa8c3e 100644
--- a/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.test.tsx
+++ b/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.test.tsx
@@ -1,6 +1,13 @@
/* @vitest-environment jsdom */
-import { act, renderHook, waitFor } from '@testing-library/react';
+import {
+ act,
+ renderHook as testingLibraryRenderHook,
+ type RenderHookOptions,
+ type RenderHookResult,
+ waitFor,
+} from '@testing-library/react';
+import type { ReactNode } from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
@@ -9,12 +16,37 @@ import {
type EditorAgentMessage,
type EditorAgentMessageResponse,
} from '../../../../packages/shared/src/contracts/editorAgent.ts';
+import { ImageCanvasActionsProvider } from '../ImageCanvasActionsProvider.tsx';
import {
EDITOR_AGENT_PATIENCE_NOTICE_DELAY_MS,
type EditorAgentConversationClient,
useEditorAgentConversation,
} from './useEditorAgentConversation.ts';
+const refreshCanvasMock = vi.fn();
+
+function ImageCanvasActionsTestWrapper({
+ children,
+}: {
+ children: ReactNode;
+}) {
+ return (
+
+ {children}
+
+ );
+}
+
+function renderHook(
+ callback: (initialProps: Props) => Result,
+ options?: RenderHookOptions,
+): RenderHookResult {
+ return testingLibraryRenderHook(callback, {
+ ...options,
+ wrapper: ImageCanvasActionsTestWrapper,
+ });
+}
+
function createEditImageDisplayArgs(prompt: string) {
return {
stringArgs: [{ name: 'prompt', label: '修改要求', value: prompt }],
@@ -124,12 +156,10 @@ describe('useEditorAgentConversation', () => {
it('loads conversations and applies delta messages', async () => {
const client = createClient();
- const onCanvasRefreshRequested = vi.fn();
const { result } = renderHook(() =>
useEditorAgentConversation({
projectId: 'project-1',
client,
- onCanvasRefreshRequested,
}),
);
@@ -156,7 +186,7 @@ describe('useEditorAgentConversation', () => {
expect(result.current.activeConversation?.title).toBe(
'把这个角色改成像素风',
);
- expect(onCanvasRefreshRequested).toHaveBeenCalledTimes(1);
+ expect(refreshCanvasMock).toHaveBeenCalledTimes(1);
expect(result.current.messages.map((message) => message.text)).toEqual([
'把这个角色改成像素风',
'我来处理',
@@ -595,7 +625,6 @@ describe('useEditorAgentConversation', () => {
it('does not apply a completed message response after switching conversations', async () => {
const client = createClient();
- const onCanvasRefreshRequested = vi.fn();
let resolveSend!: (response: EditorAgentMessageResponse) => void;
vi.mocked(client.sendMessage).mockImplementation(
() =>
@@ -607,7 +636,6 @@ describe('useEditorAgentConversation', () => {
useEditorAgentConversation({
projectId: 'project-1',
client,
- onCanvasRefreshRequested,
}),
);
@@ -682,7 +710,7 @@ describe('useEditorAgentConversation', () => {
expect(result.current.activeConversationId).toBe('conversation-2');
expect(result.current.messages).toHaveLength(1);
expect(result.current.messages[0]?.text).toBe('第二个会话原有消息');
- expect(onCanvasRefreshRequested).not.toHaveBeenCalled();
+ expect(refreshCanvasMock).not.toHaveBeenCalled();
expect(
result.current.conversations.find(
(conversation) => conversation.conversationId === 'conversation-1',
@@ -931,13 +959,11 @@ describe('useEditorAgentConversation', () => {
createdAt: '2026-07-03T00:00:00.000Z',
};
vi.mocked(client.confirmToolCall).mockResolvedValue(undefined);
- const onCanvasRefreshRequested = vi.fn();
const onConfirmSent = vi.fn();
const { result } = renderHook(() =>
useEditorAgentConversation({
projectId: 'project-1',
client,
- onCanvasRefreshRequested,
onConfirmSent,
}),
);
@@ -981,7 +1007,7 @@ describe('useEditorAgentConversation', () => {
expect(result.current.messages[1]?.toolCall?.externalJobId).toBe(
'task-edit-1',
);
- expect(onCanvasRefreshRequested).not.toHaveBeenCalled();
+ expect(refreshCanvasMock).not.toHaveBeenCalled();
expect(onConfirmSent).toHaveBeenCalledTimes(1);
});
@@ -1109,12 +1135,10 @@ describe('useEditorAgentConversation', () => {
createdAt: '2026-07-03T00:00:00.000Z',
updatedAt: '2026-07-03T00:00:00.000Z',
});
- const onCanvasRefreshRequested = vi.fn();
const { result } = renderHook(() =>
useEditorAgentConversation({
projectId: 'project-1',
client,
- onCanvasRefreshRequested,
}),
);
@@ -1135,7 +1159,7 @@ describe('useEditorAgentConversation', () => {
);
expect(result.current.messages).toHaveLength(1);
expect(result.current.messages[0]?.toolCall?.status).toBe('cancelled');
- expect(onCanvasRefreshRequested).not.toHaveBeenCalled();
+ expect(refreshCanvasMock).not.toHaveBeenCalled();
});
it('rethrows fetch errors and rolls back the optimistic message', async () => {
diff --git a/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts b/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts
index e8f610dd5..42787b74d 100644
--- a/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts
+++ b/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts
@@ -9,6 +9,7 @@ import type {
EditorAgentMessageRequest,
EditorAgentMessageResponse,
} from '@/packages/shared/src/contracts';
+import { useImageCanvasActions } from '@/src/components/image-editor/ImageCanvasActionsContext.ts';
import {
cancelEditorAgentToolCall,
@@ -47,7 +48,6 @@ export type EditorAgentConversationClient = {
type UseEditorAgentConversationOptions = {
projectId?: string | null;
client?: EditorAgentConversationClient;
- onCanvasRefreshRequested?: () => void;
onConfirmSent?: () => void;
};
@@ -135,9 +135,9 @@ function upsertConversationSummary(
export function useEditorAgentConversation({
projectId,
client = defaultEditorAgentConversationClient,
- onCanvasRefreshRequested,
onConfirmSent,
}: UseEditorAgentConversationOptions) {
+ const { refreshCanvas } = useImageCanvasActions();
const normalizedProjectId = projectId?.trim() ?? '';
const [conversations, setConversations] = useState<
EditorAgentConversationSummary[]
@@ -424,10 +424,10 @@ export function useEditorAgentConversation({
);
})
) {
- onCanvasRefreshRequested?.();
+ refreshCanvas();
}
},
- [onCanvasRefreshRequested],
+ [refreshCanvas],
);
const applyDeltaMessages = useCallback(
diff --git a/src/components/image-editor/ImageCanvasActionsContext.test.tsx b/src/components/image-editor/ImageCanvasActionsContext.test.tsx
new file mode 100644
index 000000000..673acf789
--- /dev/null
+++ b/src/components/image-editor/ImageCanvasActionsContext.test.tsx
@@ -0,0 +1,35 @@
+/* @vitest-environment jsdom */
+
+import { act, renderHook } from '@testing-library/react';
+import type { ReactNode } from 'react';
+import { describe, expect, it, vi } from 'vitest';
+
+import {
+ useImageCanvasActions,
+} from './ImageCanvasActionsContext';
+import { ImageCanvasActionsProvider } from './ImageCanvasActionsProvider';
+
+describe('ImageCanvasActionsContext', () => {
+ it('exposes the editor-scoped canvas actions', () => {
+ const refreshCanvas = vi.fn();
+ const { result } = renderHook(useImageCanvasActions, {
+ wrapper: ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ act(() => {
+ result.current.refreshCanvas();
+ });
+
+ expect(refreshCanvas).toHaveBeenCalledTimes(1);
+ });
+
+ it('fails clearly when consumed outside the editor provider', () => {
+ expect(() => renderHook(useImageCanvasActions)).toThrow(
+ 'useImageCanvasActions must be used within ImageCanvasActionsProvider',
+ );
+ });
+});
diff --git a/src/components/image-editor/ImageCanvasActionsContext.ts b/src/components/image-editor/ImageCanvasActionsContext.ts
new file mode 100644
index 000000000..99d681712
--- /dev/null
+++ b/src/components/image-editor/ImageCanvasActionsContext.ts
@@ -0,0 +1,18 @@
+import { createContext, useContext } from 'react';
+
+export type ImageCanvasActions = {
+ refreshCanvas: () => void;
+};
+
+export const ImageCanvasActionsContext =
+ createContext(null);
+
+export function useImageCanvasActions() {
+ const actions = useContext(ImageCanvasActionsContext);
+ if (!actions) {
+ throw new Error(
+ 'useImageCanvasActions must be used within ImageCanvasActionsProvider',
+ );
+ }
+ return actions;
+}
diff --git a/src/components/image-editor/ImageCanvasActionsProvider.tsx b/src/components/image-editor/ImageCanvasActionsProvider.tsx
new file mode 100644
index 000000000..222d0a69e
--- /dev/null
+++ b/src/components/image-editor/ImageCanvasActionsProvider.tsx
@@ -0,0 +1,25 @@
+import { type ReactNode, useMemo } from 'react';
+
+import {
+ type ImageCanvasActions,
+ ImageCanvasActionsContext,
+} from './ImageCanvasActionsContext';
+
+export function ImageCanvasActionsProvider({
+ children,
+ refreshCanvas,
+}: {
+ children: ReactNode;
+ refreshCanvas: ImageCanvasActions['refreshCanvas'];
+}) {
+ const actions = useMemo(
+ () => ({ refreshCanvas }),
+ [refreshCanvas],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/image-editor/ImageCanvasEditorShellView.test.tsx b/src/components/image-editor/ImageCanvasEditorShellView.test.tsx
index 23f387937..bdf6f63fd 100644
--- a/src/components/image-editor/ImageCanvasEditorShellView.test.tsx
+++ b/src/components/image-editor/ImageCanvasEditorShellView.test.tsx
@@ -1,9 +1,15 @@
/* @vitest-environment jsdom */
-import { fireEvent, render, screen, within } from '@testing-library/react';
-import { createRef } from 'react';
+import {
+ fireEvent,
+ render as testingLibraryRender,
+ screen,
+ within,
+} from '@testing-library/react';
+import { createRef, type ReactElement, type ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
+import { ImageCanvasActionsProvider } from './ImageCanvasActionsProvider';
import { ImageCanvasEditorShellView } from './ImageCanvasEditorShellView';
import type { CanvasLayer } from './ImageCanvasEditorTypes';
import type { ImageCanvasMetadataModalViewProps } from './ImageCanvasMetadataModalView';
@@ -11,6 +17,16 @@ import type { ImageCanvasSidebarViewProps } from './ImageCanvasSidebarView';
import type { ImageCanvasStageViewProps } from './ImageCanvasStageView';
import type { ImageCanvasTopbarViewProps } from './ImageCanvasTopbarView';
+function render(ui: ReactElement) {
+ return testingLibraryRender(ui, {
+ wrapper: ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+}
+
function createLayer(overrides: Partial = {}): CanvasLayer {
return {
id: 'layer-1',
diff --git a/src/components/image-editor/ImageCanvasEditorView.tsx b/src/components/image-editor/ImageCanvasEditorView.tsx
index ce516ccd9..a8791887e 100644
--- a/src/components/image-editor/ImageCanvasEditorView.tsx
+++ b/src/components/image-editor/ImageCanvasEditorView.tsx
@@ -32,6 +32,7 @@ import {
PlatformRechargePaymentResultDialog,
} from '../platform-entry/PlatformRechargePaymentStatusDialogs';
import { usePlatformProfileCenterController } from '../platform-entry/usePlatformProfileCenterController';
+import { ImageCanvasActionsProvider } from './ImageCanvasActionsProvider';
import {
canvasAssetKindOrNull,
DEFAULT_CANVAS_BACKGROUND_COLOR,
@@ -1195,7 +1196,7 @@ export function ImageCanvasEditorView({
},
[applyProjectSnapshot, captureCanvasHistory, refreshAssetLibrary],
);
- const handleEditorAgentCanvasRefreshRequested = useCallback(() => {
+ const refreshCanvas = useCallback(() => {
if (!projectId) {
return;
}
@@ -1469,10 +1470,6 @@ export function ImageCanvasEditorView({
const handleEditorAgentConfirmSent = useCallback(() => {
generationSurface.refreshTaskList();
}, [generationSurface]);
- const handleEditorAgentJobCompleted = useCallback(() => {
- generationSurface.refreshTaskList();
- handleEditorAgentCanvasRefreshRequested();
- }, [generationSurface, handleEditorAgentCanvasRefreshRequested]);
const showGenerationWarning = generationSurface.showGenerationWarning;
const handleExternalGenerationTasksCompleted = useCallback(
(tasks: ExternalGenerationTaskRecord[]) => {
@@ -1486,12 +1483,8 @@ export function ImageCanvasEditorView({
showGenerationWarning(warning);
}
refreshEditorWalletState();
- void loadEditorProject(projectId)
- .then(applyGeneratedProjectSnapshot)
- .catch(() => undefined);
},
[
- applyGeneratedProjectSnapshot,
projectId,
refreshEditorWalletState,
showGenerationWarning,
@@ -2400,7 +2393,6 @@ export function ImageCanvasEditorView({
onActivateGenerationDialog: activateCanvasGenerationDialog,
onFocusExternalTask: focusExternalGenerationTask,
onExternalTasksCompleted: handleExternalGenerationTasksCompleted,
- onEditorAgentCanvasRefreshRequested: handleEditorAgentJobCompleted,
onEditorAgentConfirmSent: handleEditorAgentConfirmSent,
onToggleTaskSidebar: toggleTaskSidebar,
onToggleAgentConversation: toggleAgentConversation,
@@ -2465,7 +2457,7 @@ export function ImageCanvasEditorView({
};
return (
- <>
+
) : null}
- >
+
);
}
diff --git a/src/components/image-editor/ImageCanvasStageView.tsx b/src/components/image-editor/ImageCanvasStageView.tsx
index fee6cf878..1a9f6ae1b 100644
--- a/src/components/image-editor/ImageCanvasStageView.tsx
+++ b/src/components/image-editor/ImageCanvasStageView.tsx
@@ -134,7 +134,6 @@ export type ImageCanvasStageViewProps = {
onActivateGenerationDialog: (dialog: CanvasGenerationDialogState) => void;
onFocusExternalTask: (task: ExternalGenerationTaskRecord) => void;
onExternalTasksCompleted?: (tasks: ExternalGenerationTaskRecord[]) => void;
- onEditorAgentCanvasRefreshRequested?: () => void;
onEditorAgentConfirmSent?: () => void;
onToggleTaskSidebar: () => void;
onToggleAgentConversation: () => void;
@@ -271,7 +270,6 @@ export function ImageCanvasStageView({
onActivateGenerationDialog,
onFocusExternalTask,
onExternalTasksCompleted,
- onEditorAgentCanvasRefreshRequested,
onEditorAgentConfirmSent,
onToggleTaskSidebar,
onToggleAgentConversation,
@@ -512,7 +510,6 @@ export function ImageCanvasStageView({
onToggleOpen={onToggleAgentConversation}
layers={layers}
assets={editorAgentAssets}
- onCanvasRefreshRequested={onEditorAgentCanvasRefreshRequested}
onConfirmSent={onEditorAgentConfirmSent}
/>
) : null}
diff --git a/src/components/image-editor/ImageCanvasTaskSidebarView.test.tsx b/src/components/image-editor/ImageCanvasTaskSidebarView.test.tsx
index d13243333..e5479ba81 100644
--- a/src/components/image-editor/ImageCanvasTaskSidebarView.test.tsx
+++ b/src/components/image-editor/ImageCanvasTaskSidebarView.test.tsx
@@ -3,17 +3,31 @@
import {
act,
fireEvent,
- render,
+ render as testingLibraryRender,
screen,
waitFor,
} from '@testing-library/react';
+import type { ReactElement, ReactNode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration';
import { listExternalGenerationTasks } from '../../services/external-generation';
+import { ImageCanvasActionsProvider } from './ImageCanvasActionsProvider.tsx';
import { ImageCanvasTaskSidebarView } from './ImageCanvasTaskSidebarView';
import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts';
+const refreshCanvasMock = vi.fn();
+
+function render(ui: ReactElement) {
+ return testingLibraryRender(ui, {
+ wrapper: ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+}
+
vi.mock('../../services/external-generation', () => ({
listExternalGenerationTasks: vi.fn().mockResolvedValue({
overview: {
@@ -30,6 +44,7 @@ const listExternalGenerationTasksMock = vi.mocked(listExternalGenerationTasks);
beforeEach(() => {
useImageCanvasContextStore.getState().setProjectId('project-1');
+ refreshCanvasMock.mockReset();
listExternalGenerationTasksMock.mockClear();
listExternalGenerationTasksMock.mockResolvedValue({
overview: {
@@ -448,6 +463,7 @@ describe('ImageCanvasTaskSidebarView', () => {
}),
]);
expect(onExternalTasksCompleted).toHaveBeenCalledTimes(1);
+ expect(refreshCanvasMock).toHaveBeenCalledTimes(1);
} finally {
vi.useRealTimers();
}
diff --git a/src/components/image-editor/ImageCanvasTaskSidebarView.tsx b/src/components/image-editor/ImageCanvasTaskSidebarView.tsx
index 7dcbf6c7a..d987ce431 100644
--- a/src/components/image-editor/ImageCanvasTaskSidebarView.tsx
+++ b/src/components/image-editor/ImageCanvasTaskSidebarView.tsx
@@ -17,6 +17,7 @@ import {
import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration';
import { listExternalGenerationTasks } from '../../services/external-generation';
+import { useImageCanvasActions } from './ImageCanvasActionsContext.ts';
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
import type { CanvasTaskStatus } from './ImageCanvasEditorTypes';
import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts';
@@ -280,6 +281,7 @@ export function ImageCanvasTaskSidebarView({
onFocusExternalTask,
onExternalTasksCompleted,
}: ImageCanvasTaskSidebarViewProps) {
+ const { refreshCanvas } = useImageCanvasActions();
const projectId = useImageCanvasContextStore((state) => state.projectId);
const normalizedProjectId = projectId?.trim() ?? '';
const [activeTab, setActiveTab] = useState('active');
@@ -334,9 +336,10 @@ export function ImageCanvasTaskSidebarView({
for (const task of newlyCompletedTasks) {
completedRefreshNotifiedTaskIdsRef.current.add(task.jobId);
}
+ refreshCanvas();
onExternalTasksCompleted?.(newlyCompletedTasks);
},
- [onExternalTasksCompleted],
+ [onExternalTasksCompleted, refreshCanvas],
);
useEffect(() => {