From 49182839c1a5c72537a5555ca46a1fa1d0fe8ba5 Mon Sep 17 00:00:00 2001 From: Anton Pascal Date: Thu, 21 May 2026 04:06:20 +0000 Subject: [PATCH] fix: resolve Sentry errors BD and BE (null refs) --- .../tools/item/use-placement-coordinator.tsx | 31 ++++++++++++++++--- packages/nodes/src/wall/tool.tsx | 1 + 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/editor/src/components/tools/item/use-placement-coordinator.tsx b/packages/editor/src/components/tools/item/use-placement-coordinator.tsx index 38c845bf5..df0a1eb5b 100644 --- a/packages/editor/src/components/tools/item/use-placement-coordinator.tsx +++ b/packages/editor/src/components/tools/item/use-placement-coordinator.tsx @@ -466,7 +466,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea draftItem: draftNode.current, gridPosition: gridPosition.current, state: { ...placementState.current }, - currentCursorRotationY: cursorGroupRef.current.rotation.y, + currentCursorRotationY: cursorGroupRef.current?.rotation.y ?? 0, }) const getActiveValidators = () => @@ -496,6 +496,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } const applyTransition = (result: TransitionResult) => { + if (!cursorGroupRef.current) return Object.assign(placementState.current, result.stateUpdate) gridPosition.current.set(...result.gridPosition) @@ -512,6 +513,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } const ensureDraft = (result: TransitionResult) => { + if (!cursorGroupRef.current) return gridPosition.current.set(...result.gridPosition) const c = worldToBuildingLocal(...result.cursorPosition) cursorGroupRef.current.position.set(c.x, c.y, c.z) @@ -571,6 +573,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea has3DPointerDrivenMoveRef.current = true lastRawPos.current.set(event.localPosition[0], event.localPosition[1], event.localPosition[2]) + if (!cursorGroupRef.current) return const result = floorStrategy.move(getContext(), event) if (!result) return @@ -598,7 +601,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea if (draft) { useLiveTransforms.getState().set(draft.id, { position: result.gridPosition, - rotation: cursorGroupRef.current.rotation.y, + rotation: cursorGroupRef.current?.rotation.y ?? 0, }) } @@ -606,11 +609,12 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } const onGridClick = (event: GridEvent) => { + if (!cursorGroupRef.current) return const result = floorStrategy.click(getContext(), event, getActiveValidators()) if (!result) return // Preserve cursor rotation for the next draft - const currentRotation: [number, number, number] = [0, cursorGroupRef.current.rotation.y, 0] + const currentRotation: [number, number, number] = [0, cursorGroupRef.current?.rotation.y ?? 0, 0] // Clear live transform before commit if (draftNode.current) { @@ -628,6 +632,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea const onWallEnter = (event: WallEvent) => { has3DPointerDrivenMoveRef.current = true + if (!cursorGroupRef.current) return const nodes = useScene.getState().nodes const result = wallStrategy.enter( getContext(), @@ -654,6 +659,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea const onWallMove = (event: WallEvent) => { has3DPointerDrivenMoveRef.current = true + if (!cursorGroupRef.current) return const ctx = getContext() if (ctx.state.surface !== 'wall') { @@ -754,6 +760,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } const onWallClick = (event: WallEvent) => { + if (!cursorGroupRef.current) return const result = wallStrategy.click(getContext(), event, getActiveValidators()) if (!result) return @@ -785,6 +792,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } const onWallLeave = (event: WallEvent) => { + if (!cursorGroupRef.current) return const result = wallStrategy.leave(getContext()) if (!result) return @@ -843,6 +851,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } const onItemEnter = (event: ItemEvent) => { + if (!cursorGroupRef.current) return if (event.node.id === draftNode.current?.id) return has3DPointerDrivenMoveRef.current = true const result = itemSurfaceStrategy.enter(getContext(), event) @@ -860,6 +869,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } const onItemMove = (event: ItemEvent) => { + if (!cursorGroupRef.current) return if (event.node.id === draftNode.current?.id) return has3DPointerDrivenMoveRef.current = true const ctx = getContext() @@ -931,6 +941,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } const onItemLeave = (event: ItemEvent) => { + if (!cursorGroupRef.current) return if (event.node.id === draftNode.current?.id) return if (placementState.current.surface !== 'item-surface') return @@ -1040,7 +1051,8 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } return } - + if (!cursorGroupRef.current) return + if (event.node.id === draftNode.current?.id) return const result = itemSurfaceStrategy.click(getContext(), event) if (!result) return @@ -1066,6 +1078,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea const onCeilingEnter = (event: CeilingEvent) => { has3DPointerDrivenMoveRef.current = true + if (!cursorGroupRef.current) return const nodes = useScene.getState().nodes const result = ceilingStrategy.enter(getContext(), event, resolveLevelId, nodes) if (!result) return @@ -1086,6 +1099,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea const onCeilingMove = (event: CeilingEvent) => { has3DPointerDrivenMoveRef.current = true + if (!cursorGroupRef.current) return if (!draftNode.current && placementState.current.surface === 'ceiling') { const nodes = useScene.getState().nodes const setup = ceilingStrategy.enter(getContext(), event, resolveLevelId, nodes) @@ -1127,12 +1141,13 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea // Publish live transform for 2D floorplan useLiveTransforms.getState().set(draft.id, { position: result.cursorPosition, - rotation: cursorGroupRef.current.rotation.y, + rotation: cursorGroupRef.current?.rotation.y ?? 0, }) } } const onCeilingClick = (event: CeilingEvent) => { + if (!cursorGroupRef.current) return const result = ceilingStrategy.click(getContext(), event, getActiveValidators()) if (!result) return @@ -1155,6 +1170,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } const onCeilingLeave = (event: CeilingEvent) => { + if (!cursorGroupRef.current) return const result = ceilingStrategy.leave(getContext()) if (!result) return @@ -1282,6 +1298,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea const ROTATION_STEP = Math.PI / 2 const onKeyDown = (event: KeyboardEvent) => { + if (!cursorGroupRef.current) return if (event.key === 'Shift') { shiftFreeRef.current = true revalidate() @@ -1372,6 +1389,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } const onKeyUp = (event: KeyboardEvent) => { + if (!cursorGroupRef.current) return if (event.key === 'Shift') { shiftFreeRef.current = false revalidate() @@ -1383,6 +1401,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea // ---- tool:cancel (Escape / programmatic) ---- const onCancel = () => { + if (!cursorGroupRef.current) return if (configRef.current.onCancel) { configRef.current.onCancel() } @@ -1391,6 +1410,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea // ---- Right-click cancel ---- const onContextMenu = (event: MouseEvent) => { + if (!cursorGroupRef.current) return if (configRef.current.onCancel) { event.preventDefault() configRef.current.onCancel() @@ -1517,6 +1537,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea useFrame((_, delta) => { if (!asset) return + if (!cursorGroupRef.current) return if (!draftNode.current) return // The mesh-position lerp below only makes sense once this coordinator // owns the move via a 3D pointer event. Skip until then so that diff --git a/packages/nodes/src/wall/tool.tsx b/packages/nodes/src/wall/tool.tsx index c63c512a2..ffb5e98b9 100644 --- a/packages/nodes/src/wall/tool.tsx +++ b/packages/nodes/src/wall/tool.tsx @@ -201,6 +201,7 @@ export const WallTool: React.FC = () => { } const onGridClick = (event: GridEvent) => { + if (!wallPreviewRef.current) return const walls = getCurrentLevelWalls() const localClick: WallPlanPoint = [event.localPosition[0], event.localPosition[2]]