From d197d30eb0f5bb697e998ee2a4881210acf8a428 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 26 Mar 2026 10:36:07 +0100 Subject: [PATCH] fix(composant) : preserve skeleton selections on form validation error Shared module-level loading ref in useComposants caused structureDataLoading to toggle during submission, unmounting the skeleton assignment UI. On remount, watchers cleared selections not found in the limited local catalog. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/composables/useComponentCreate.ts | 2 +- .../useStructureAssignmentFetch.ts | 38 +++++++++++++------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/app/composables/useComponentCreate.ts b/app/composables/useComponentCreate.ts index 40ab58f..bec753b 100644 --- a/app/composables/useComponentCreate.ts +++ b/app/composables/useComponentCreate.ts @@ -106,7 +106,7 @@ export function useComponentCreate() { const availableProducts = computed(() => productCatalogRef.value ?? []) const availableComponents = computed(() => componentCatalogRef.value ?? []) const structureDataLoading = computed( - () => piecesLoading.value || componentsLoading.value || productsLoading.value, + () => !submitting.value && (piecesLoading.value || componentsLoading.value || productsLoading.value), ) const fetchedPieceTypeMap = ref>({}) diff --git a/app/composables/useStructureAssignmentFetch.ts b/app/composables/useStructureAssignmentFetch.ts index 26e442d..a1be483 100644 --- a/app/composables/useStructureAssignmentFetch.ts +++ b/app/composables/useStructureAssignmentFetch.ts @@ -279,6 +279,11 @@ export function useStructureAssignmentFetch(deps: StructureAssignmentFetchDeps) if (deps.isRoot()) { return } + // Only clear if we have loaded options (cache or catalog); skip when options are empty + // because the fetch may not have completed yet. + if (!options.length) { + return + } const hasMatch = options.some( (component) => component.id === deps.assignment.selectedComponentId, ) @@ -293,12 +298,18 @@ export function useStructureAssignmentFetch(deps: StructureAssignmentFetchDeps) () => [deps.pieces, deps.assignment.pieces], () => { for (const pieceAssignment of deps.assignment.pieces) { - const options = getPieceOptions(pieceAssignment) - if ( - pieceAssignment.selectedPieceId - && !options.some((piece) => piece.id === pieceAssignment.selectedPieceId) - ) { - pieceAssignment.selectedPieceId = '' + const hasCachedOptions = !!pieceOptionsByPath.value[pieceAssignment.path] + // Only clear selections when we have loaded options (cached or from catalog). + // When no cache exists, a fetch is about to fire — clearing now would lose + // user input before the real option list arrives. + if (hasCachedOptions) { + const options = getPieceOptions(pieceAssignment) + if ( + pieceAssignment.selectedPieceId + && !options.some((piece) => piece.id === pieceAssignment.selectedPieceId) + ) { + pieceAssignment.selectedPieceId = '' + } } if (!primedPiecePaths.has(pieceAssignment.path) && !pieceOptionsByPath.value[pieceAssignment.path]) { primedPiecePaths.add(pieceAssignment.path) @@ -313,12 +324,15 @@ export function useStructureAssignmentFetch(deps: StructureAssignmentFetchDeps) () => [deps.products, deps.assignment.products], () => { for (const productAssignment of deps.assignment.products) { - const options = getProductOptions(productAssignment) - if ( - productAssignment.selectedProductId - && !options.some((product) => product.id === productAssignment.selectedProductId) - ) { - productAssignment.selectedProductId = '' + const hasCachedOptions = !!productOptionsByPath.value[productAssignment.path] + if (hasCachedOptions) { + const options = getProductOptions(productAssignment) + if ( + productAssignment.selectedProductId + && !options.some((product) => product.id === productAssignment.selectedProductId) + ) { + productAssignment.selectedProductId = '' + } } if (!primedProductPaths.has(productAssignment.path) && !productOptionsByPath.value[productAssignment.path]) { primedProductPaths.add(productAssignment.path)