From c43e496cc2ec77ff9b5fbef3975d5ff698530fce Mon Sep 17 00:00:00 2001 From: yyh Date: Fri, 27 Mar 2026 14:04:40 +0800 Subject: [PATCH] fix(skill): pin preview tabs when remote sync only dirties metadata --- .../__tests__/index.spec.tsx | 44 +++++++++++++++++++ .../use-file-content-controller.ts | 9 ++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/web/app/components/workflow/skill/skill-body/panels/file-content-panel/__tests__/index.spec.tsx b/web/app/components/workflow/skill/skill-body/panels/file-content-panel/__tests__/index.spec.tsx index 4efc8ff107..17a1b10769 100644 --- a/web/app/components/workflow/skill/skill-body/panels/file-content-panel/__tests__/index.spec.tsx +++ b/web/app/components/workflow/skill/skill-body/panels/file-content-panel/__tests__/index.spec.tsx @@ -682,6 +682,50 @@ describe('FileContentPanel', () => { expect(mocks.workflowActions.clearDraftContent).toHaveBeenCalledWith('file-1') expect(mocks.workflowActions.pinTab).not.toHaveBeenCalled() }) + + it('should pin the tab when remote collaboration sync only dirties metadata', async () => { + // Arrange + mocks.fileTypeInfo = { + isMarkdown: true, + isCodeOrText: false, + isImage: false, + isVideo: false, + isPdf: false, + isSQLite: false, + isEditable: true, + isPreviewable: true, + } + mocks.fileData.fileContent = { + content: `linked §[file].[app].[${FILE_REFERENCE_ID}]§`, + metadata: {}, + } + mocks.workflowState.fileMetadata = new Map>([ + ['file-1', {}], + ]) + mocks.nodeMapData = new Map([ + ['file-1', createNode({ name: 'prompt.md', extension: 'md' })], + [FILE_REFERENCE_ID, createNode({ id: FILE_REFERENCE_ID, name: 'kb.txt', extension: 'txt' })], + ]) + + // Act + render() + await screen.findByTestId('markdown-editor') + const firstCall = mocks.useSkillMarkdownCollaboration.mock.calls[0] + const args = firstCall?.[0] as UseSkillMarkdownCollaborationArgs | undefined + args?.onRemoteChange?.(`linked §[file].[app].[${FILE_REFERENCE_ID}]§`) + + // Assert + expect(mocks.workflowActions.clearDraftContent).toHaveBeenCalledWith('file-1') + expect(mocks.workflowActions.setDraftMetadata).toHaveBeenCalledWith( + 'file-1', + expect.objectContaining({ + files: expect.objectContaining({ + [FILE_REFERENCE_ID]: expect.objectContaining({ id: FILE_REFERENCE_ID }), + }), + }), + ) + expect(mocks.workflowActions.pinTab).toHaveBeenCalledWith('file-1') + }) }) describe('Preview modes', () => { diff --git a/web/app/components/workflow/skill/skill-body/panels/file-content-panel/use-file-content-controller.ts b/web/app/components/workflow/skill/skill-body/panels/file-content-panel/use-file-content-controller.ts index 4b06b8196c..54d72d21c2 100644 --- a/web/app/components/workflow/skill/skill-body/panels/file-content-panel/use-file-content-controller.ts +++ b/web/app/components/workflow/skill/skill-body/panels/file-content-panel/use-file-content-controller.ts @@ -86,7 +86,7 @@ export const useFileContentController = (): FileContentControllerState => { const updateFileReferenceMetadata = useCallback((content: string) => { if (!fileTabId) - return + return false const referenceIds = extractFileReferenceIds(content) const metadata = (currentMetadata || {}) as SkillFileMetadata @@ -108,9 +108,10 @@ export const useFileContentController = (): FileContentControllerState => { delete nextMetadata.files if (isDeepEqual(metadata, nextMetadata)) - return + return false storeApi.getState().setDraftMetadata(fileTabId, nextMetadata) + return true }, [currentMetadata, fileTabId, nodeMap, storeApi]) const applyContentChange = useCallback(( @@ -130,8 +131,8 @@ export const useFileContentController = (): FileContentControllerState => { else state.setDraftContent(fileTabId, nextValue) - updateFileReferenceMetadata(nextValue) - if (nextValue !== originalContent || options?.pinWhenContentMatchesOriginal) + const didUpdateMetadata = updateFileReferenceMetadata(nextValue) + if (nextValue !== originalContent || didUpdateMetadata || options?.pinWhenContentMatchesOriginal) state.pinTab(fileTabId) }, [fileTabId, isEditable, originalContent, storeApi, updateFileReferenceMetadata])