From 7bd2e996aabbad0d05c926f6f3cf52d85497b269 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Thu, 12 Feb 2026 10:39:14 +1300 Subject: [PATCH] =?UTF-8?q?fix:=20PR=20review=20fixes=20=E2=80=94=20lazy-l?= =?UTF-8?q?oad=20three.js,=20blob=20URL=20cleanup,=20missing=20integration?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove unused `useLoader` import from GLBViewerNode - Add useEffect cleanup to revoke blob URL on node unmount (memory leak) - Replace alert() with useToast for file validation errors - Lazy-load GLBViewerNode via next/dynamic to avoid bundling three.js for all users - Add glbViewer to ConnectionDropMenu IMAGE_SOURCE_OPTIONS - Add glbViewer case to quickstart validation createDefaultNodeData (build fix) - Restore trailing newline in gemini.ts Co-Authored-By: Claude Opus 4.6 --- src/app/api/generate/providers/gemini.ts | 2 +- src/components/ConnectionDropMenu.tsx | 9 +++++++++ src/components/WorkflowCanvas.tsx | 5 ++++- src/components/nodes/GLBViewerNode.tsx | 17 ++++++++++++++--- src/lib/quickstart/validation.ts | 6 ++++++ 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/app/api/generate/providers/gemini.ts b/src/app/api/generate/providers/gemini.ts index baedcdf7..58bca493 100644 --- a/src/app/api/generate/providers/gemini.ts +++ b/src/app/api/generate/providers/gemini.ts @@ -180,4 +180,4 @@ export async function generateWithGemini( }, { status: 500 } ); -} \ No newline at end of file +} diff --git a/src/components/ConnectionDropMenu.tsx b/src/components/ConnectionDropMenu.tsx index 5127de91..d439616e 100644 --- a/src/components/ConnectionDropMenu.tsx +++ b/src/components/ConnectionDropMenu.tsx @@ -149,6 +149,15 @@ const IMAGE_SOURCE_OPTIONS: MenuOption[] = [ ), }, + { + type: "glbViewer", + label: "3D Viewer", + icon: ( + + + + ), + }, { type: "annotation", label: "Annotate", diff --git a/src/components/WorkflowCanvas.tsx b/src/components/WorkflowCanvas.tsx index decee069..9b8c4551 100644 --- a/src/components/WorkflowCanvas.tsx +++ b/src/components/WorkflowCanvas.tsx @@ -19,6 +19,7 @@ import "@xyflow/react/dist/style.css"; import { useWorkflowStore, WorkflowFile } from "@/store/workflowStore"; import { useToast } from "@/components/Toast"; +import dynamic from "next/dynamic"; import { ImageInputNode, AudioInputNode, @@ -34,8 +35,10 @@ import { ImageCompareNode, VideoStitchNode, EaseCurveNode, - GLBViewerNode, } from "./nodes"; + +// Lazy-load GLBViewerNode to avoid bundling three.js for users who don't use 3D nodes +const GLBViewerNode = dynamic(() => import("./nodes/GLBViewerNode").then(mod => ({ default: mod.GLBViewerNode })), { ssr: false }); import { EditableEdge, ReferenceEdge } from "./edges"; import { ConnectionDropMenu, MenuAction } from "./ConnectionDropMenu"; import { MultiSelectToolbar } from "./MultiSelectToolbar"; diff --git a/src/components/nodes/GLBViewerNode.tsx b/src/components/nodes/GLBViewerNode.tsx index bd9f79d3..a9a6051e 100644 --- a/src/components/nodes/GLBViewerNode.tsx +++ b/src/components/nodes/GLBViewerNode.tsx @@ -2,11 +2,12 @@ import { useCallback, useRef, useState, useEffect, Suspense } from "react"; import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react"; -import { Canvas, useThree, useFrame, useLoader } from "@react-three/fiber"; +import { Canvas, useThree, useFrame } from "@react-three/fiber"; import { OrbitControls } from "@react-three/drei"; import { BaseNode } from "./BaseNode"; import { useCommentNavigation } from "@/hooks/useCommentNavigation"; import { useWorkflowStore } from "@/store/workflowStore"; +import { useToast } from "@/components/Toast"; import { GLBViewerNodeData } from "@/types"; import * as THREE from "three"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; @@ -255,16 +256,26 @@ export function GLBViewerNode({ id, data, selected }: NodeProps { + return () => { + if (nodeData.glbUrl) { + URL.revokeObjectURL(nodeData.glbUrl); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- only revoke on unmount + }, []); + // Shared file processing logic for both click-to-upload and drag-and-drop const processFile = useCallback( (file: File) => { if (!file.name.toLowerCase().endsWith(".glb")) { - alert("Please upload a .GLB file."); + useToast.getState().show("Please upload a .GLB file", "warning"); return; } if (file.size > 100 * 1024 * 1024) { - alert("File too large. Maximum size is 100MB."); + useToast.getState().show("File too large. Maximum size is 100MB", "warning"); return; } diff --git a/src/lib/quickstart/validation.ts b/src/lib/quickstart/validation.ts index 189a2461..ecbada66 100644 --- a/src/lib/quickstart/validation.ts +++ b/src/lib/quickstart/validation.ts @@ -316,6 +316,12 @@ function createDefaultNodeData(type: NodeType): WorkflowNodeData { progress: 0, encoderSupported: null, }; + case "glbViewer": + return { + glbUrl: null, + filename: null, + capturedImage: null, + }; } }