fix(rack-planner): resolve infinite re-render loop in ConnectionLayer and add null-safety for VLAN tooltips

This commit is contained in:
2026-03-22 15:01:35 -05:00
parent becb55d57c
commit e2c5cad8a3
3 changed files with 28 additions and 20 deletions

View File

@@ -40,11 +40,17 @@ export function ConnectionLayer() {
// Also re-calculate if the user scrolls (though ideally lines are pinned to the canvas) // Also re-calculate if the user scrolls (though ideally lines are pinned to the canvas)
// Actually, if SVG is INSIDE the scrollable container, we don't need scroll adjustment. // Actually, if SVG is INSIDE the scrollable container, we don't need scroll adjustment.
// We'll use a MutationObserver to detect DOM changes (like modules being added/moved) // Use a MutationObserver to detect DOM changes (like modules being added/moved)
const observer = new MutationObserver(updateCoords); const observer = new MutationObserver(() => {
// Small debounce or check if it was our OWN SVG that changed
updateCoords();
});
const canvas = document.querySelector('.rack-planner-canvas'); const canvas = document.querySelector('.rack-planner-canvas');
if (canvas) { if (canvas) {
observer.observe(canvas, { childList: true, subtree: true, attributes: true }); // DO NOT observe the entire subtree with attributes if it includes the ConnectionLayer
// Instead, just watch for module layout changes
observer.observe(canvas, { childList: true, subtree: true });
} }
return () => { return () => {

View File

@@ -184,7 +184,7 @@ export function ModuleBlock({ module }: ModuleBlockProps) {
onClick={(e) => handlePortClick(e, port.id)} onClick={(e) => handlePortClick(e, port.id)}
aria-label={`Port ${port.portNumber}`} aria-label={`Port ${port.portNumber}`}
title={`Port ${port.portNumber}${port.label ? ` · ${port.label}` : ''}${ title={`Port ${port.portNumber}${port.label ? ` · ${port.label}` : ''}${
hasVlan ? ` (VLAN ${port.vlans.map((v) => v.vlan.vlanId).join(',')})` : '' hasVlan ? ` (VLAN ${port.vlans.map((v) => v.vlan?.vlanId).filter(Boolean).join(',')})` : ''
}\nShift+Click for settings`} }\nShift+Click for settings`}
style={{ backgroundColor: vlanColor, borderColor: 'rgba(0,0,0,0.2)' }} style={{ backgroundColor: vlanColor, borderColor: 'rgba(0,0,0,0.2)' }}
className={cn( className={cn(

View File

@@ -258,6 +258,7 @@ export function RackPlanner() {
</div> </div>
</div> </div>
) : ( ) : (
<>
<SortableContext items={rackIds} strategy={horizontalListSortingStrategy}> <SortableContext items={rackIds} strategy={horizontalListSortingStrategy}>
<div <div
ref={canvasRef} ref={canvasRef}
@@ -271,9 +272,10 @@ export function RackPlanner() {
hoverSlot={hoverSlot} hoverSlot={hoverSlot}
/> />
))} ))}
<ConnectionLayer />
</div> </div>
</SortableContext> </SortableContext>
<ConnectionLayer />
</>
)} )}
</div> </div>
</div> </div>