From 3b35598b079cc9b9cd9413ae69d0eeca50e79624 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Tue, 24 Mar 2026 09:02:36 +0100 Subject: [PATCH] fix(structure) : stabilize piece/component/product ordering in machines All findBy(['machine' => ...]) queries now sort by createdAt ASC. Without explicit ORDER BY, PostgreSQL returned rows in heap order which changed on every INSERT, causing the displayed order to shuffle. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Controller/MachineStructureController.php | 24 +++++++++---------- src/Mcp/Tool/Machine/CloneMachineTool.php | 6 ++--- src/Mcp/Tool/Machine/MachineStructureTool.php | 6 ++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Controller/MachineStructureController.php b/src/Controller/MachineStructureController.php index 1986d32..f9ed9c3 100644 --- a/src/Controller/MachineStructureController.php +++ b/src/Controller/MachineStructureController.php @@ -53,9 +53,9 @@ class MachineStructureController extends AbstractController return $this->json(['success' => false, 'error' => 'Machine not found.'], 404); } - $componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $machine]); - $pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $machine]); - $productLinks = $this->machineProductLinkRepository->findBy(['machine' => $machine]); + $componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']); + $pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']); + $productLinks = $this->machineProductLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']); return $this->json($this->normalizeStructureResponse( $machine, @@ -159,9 +159,9 @@ class MachineStructureController extends AbstractController $this->entityManager->flush(); - $componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $newMachine]); - $pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $newMachine]); - $productLinks = $this->machineProductLinkRepository->findBy(['machine' => $newMachine]); + $componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $newMachine], ['createdAt' => 'ASC']); + $pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $newMachine], ['createdAt' => 'ASC']); + $productLinks = $this->machineProductLinkRepository->findBy(['machine' => $newMachine], ['createdAt' => 'ASC']); return $this->json($this->normalizeStructureResponse( $newMachine, @@ -209,7 +209,7 @@ class MachineStructureController extends AbstractController */ private function cloneComponentLinks(Machine $source, Machine $target): array { - $sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source]); + $sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']); $linkMap = []; // First pass: create all links without parent relationships @@ -242,7 +242,7 @@ class MachineStructureController extends AbstractController */ private function clonePieceLinks(Machine $source, Machine $target, array $componentLinkMap): array { - $sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source]); + $sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']); $linkMap = []; foreach ($sourceLinks as $link) { @@ -276,7 +276,7 @@ class MachineStructureController extends AbstractController array $componentLinkMap, array $pieceLinkMap, ): void { - $sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source]); + $sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']); $linkMap = []; // First pass: create all links @@ -319,7 +319,7 @@ class MachineStructureController extends AbstractController private function applyComponentLinks(Machine $machine, array $payload): array|JsonResponse { - $existing = $this->indexLinksById($this->machineComponentLinkRepository->findBy(['machine' => $machine])); + $existing = $this->indexLinksById($this->machineComponentLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC'])); $keepIds = []; $pendingParents = []; $links = []; @@ -376,7 +376,7 @@ class MachineStructureController extends AbstractController private function applyPieceLinks(Machine $machine, array $payload, array $componentLinks): array|JsonResponse { - $existing = $this->indexLinksById($this->machinePieceLinkRepository->findBy(['machine' => $machine])); + $existing = $this->indexLinksById($this->machinePieceLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC'])); $componentIndex = $this->indexLinksById($componentLinks); $keepIds = []; $pendingParents = []; @@ -443,7 +443,7 @@ class MachineStructureController extends AbstractController array $componentLinks, array $pieceLinks, ): array|JsonResponse { - $existing = $this->indexLinksById($this->machineProductLinkRepository->findBy(['machine' => $machine])); + $existing = $this->indexLinksById($this->machineProductLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC'])); $componentIndex = $this->indexLinksById($componentLinks); $pieceIndex = $this->indexLinksById($pieceLinks); $keepIds = []; diff --git a/src/Mcp/Tool/Machine/CloneMachineTool.php b/src/Mcp/Tool/Machine/CloneMachineTool.php index 8533569..fe76a66 100644 --- a/src/Mcp/Tool/Machine/CloneMachineTool.php +++ b/src/Mcp/Tool/Machine/CloneMachineTool.php @@ -123,7 +123,7 @@ class CloneMachineTool */ private function cloneComponentLinks(Machine $source, Machine $target): array { - $sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source]); + $sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']); $linkMap = []; // First pass: create all links without parent relationships @@ -156,7 +156,7 @@ class CloneMachineTool */ private function clonePieceLinks(Machine $source, Machine $target, array $componentLinkMap): array { - $sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source]); + $sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']); $linkMap = []; foreach ($sourceLinks as $link) { @@ -190,7 +190,7 @@ class CloneMachineTool array $componentLinkMap, array $pieceLinkMap, ): void { - $sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source]); + $sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']); $linkMap = []; // First pass: create all links diff --git a/src/Mcp/Tool/Machine/MachineStructureTool.php b/src/Mcp/Tool/Machine/MachineStructureTool.php index 0cd0dbb..36d35af 100644 --- a/src/Mcp/Tool/Machine/MachineStructureTool.php +++ b/src/Mcp/Tool/Machine/MachineStructureTool.php @@ -46,9 +46,9 @@ class MachineStructureTool $this->mcpError('not_found', "Machine not found: {$machineId}"); } - $componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $machine]); - $pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $machine]); - $productLinks = $this->machineProductLinkRepository->findBy(['machine' => $machine]); + $componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']); + $pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']); + $productLinks = $this->machineProductLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']); return $this->jsonResponse($this->normalizeStructureResponse( $machine,