From e556a7bb6e357cbef34c4bbacab9a72e546643a5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 18 Apr 2026 15:57:01 +0000 Subject: [PATCH] Fix studio CI regressions Co-authored-by: Luke The Dev --- src/features/studio-world/export/exportGlb.ts | 30 ++++++++++++++----- .../preview/StudioWorldPreview.tsx | 2 +- .../studio-world/preview/scene-utils.ts | 14 ++++----- src/lib/studio-world/store.ts | 29 +++++++++++++----- 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/features/studio-world/export/exportGlb.ts b/src/features/studio-world/export/exportGlb.ts index 9bf4600..38f2bf2 100644 --- a/src/features/studio-world/export/exportGlb.ts +++ b/src/features/studio-world/export/exportGlb.ts @@ -14,13 +14,29 @@ const downloadBlob = (filename: string, blob: Blob) => { window.setTimeout(() => URL.revokeObjectURL(url), 0); }; -const sanitizeFilename = (value: string) => - value - .trim() - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-+|-+$/g, "") - .slice(0, 48) || "claw3d-studio-world"; +const sanitizeFilename = (value: string) => { + const normalized = value.trim().toLowerCase(); + let result = ""; + let pendingDash = false; + for (const character of normalized) { + const isAlphaNumeric = + (character >= "a" && character <= "z") || + (character >= "0" && character <= "9"); + if (isAlphaNumeric) { + if (pendingDash && result.length > 0) { + result += "-"; + } + result += character; + pendingDash = false; + if (result.length >= 48) { + break; + } + continue; + } + pendingDash = result.length > 0; + } + return result || "claw3d-studio-world"; +}; export const exportStudioProjectGlb = async (project: StudioProjectRecord) => { const exporter = new GLTFExporter(); diff --git a/src/features/studio-world/preview/StudioWorldPreview.tsx b/src/features/studio-world/preview/StudioWorldPreview.tsx index 991ab66..8923a12 100644 --- a/src/features/studio-world/preview/StudioWorldPreview.tsx +++ b/src/features/studio-world/preview/StudioWorldPreview.tsx @@ -15,7 +15,7 @@ type AssetMeshProps = { const AssetMesh = ({ asset }: AssetMeshProps) => { const groupRef = useRef(null); - const geometry = useMemo(() => buildAssetGeometry(asset.kind, asset.scale), [asset.kind, asset.scale]); + const geometry = useMemo(() => buildAssetGeometry(asset.kind), [asset.kind]); const material = useMemo( () => buildAssetMaterial(asset.color, asset.emissive ?? null), [asset.color, asset.emissive], diff --git a/src/features/studio-world/preview/scene-utils.ts b/src/features/studio-world/preview/scene-utils.ts index c330833..fa1cd74 100644 --- a/src/features/studio-world/preview/scene-utils.ts +++ b/src/features/studio-world/preview/scene-utils.ts @@ -48,13 +48,13 @@ const createAssetMesh = (asset: StudioWorldAssetDraft) => { const material = buildAssetMaterial(asset.color, asset.emissive ?? null); if (asset.kind === "platform") { - const mesh = new THREE.Mesh(buildAssetGeometry(asset.kind, asset.scale), material); + const mesh = new THREE.Mesh(buildAssetGeometry(asset.kind), material); mesh.scale.set(asset.scale[0], asset.scale[1], asset.scale[2]); return mesh; } if (asset.kind === "tower") { - const mesh = new THREE.Mesh(buildAssetGeometry(asset.kind, asset.scale), material); + const mesh = new THREE.Mesh(buildAssetGeometry(asset.kind), material); mesh.scale.set(asset.scale[0], asset.scale[1], asset.scale[2]); return mesh; } @@ -84,7 +84,7 @@ const createAssetMesh = (asset: StudioWorldAssetDraft) => { metalness: 0.02, }), ); - const canopy = new THREE.Mesh(buildAssetGeometry(asset.kind, asset.scale), material); + const canopy = new THREE.Mesh(buildAssetGeometry(asset.kind), material); trunk.position.y = 0.5; canopy.position.y = 1.5; group.add(trunk, canopy); @@ -93,14 +93,14 @@ const createAssetMesh = (asset: StudioWorldAssetDraft) => { } if (asset.kind === "rock") { - const mesh = new THREE.Mesh(buildAssetGeometry(asset.kind, asset.scale), material); + const mesh = new THREE.Mesh(buildAssetGeometry(asset.kind), material); mesh.scale.set(asset.scale[0], asset.scale[1], asset.scale[2]); return mesh; } if (asset.kind === "beacon") { const group = new THREE.Group(); - const base = new THREE.Mesh(buildAssetGeometry(asset.kind, asset.scale), material); + const base = new THREE.Mesh(buildAssetGeometry(asset.kind), material); const cap = new THREE.Mesh( new THREE.SphereGeometry(0.34, 18, 18), new THREE.MeshStandardMaterial({ @@ -119,13 +119,13 @@ const createAssetMesh = (asset: StudioWorldAssetDraft) => { } if (asset.kind === "crate") { - const mesh = new THREE.Mesh(buildAssetGeometry(asset.kind, asset.scale), material); + const mesh = new THREE.Mesh(buildAssetGeometry(asset.kind), material); mesh.scale.set(asset.scale[0], asset.scale[1], asset.scale[2]); return mesh; } const ring = new THREE.Mesh( - buildAssetGeometry(asset.kind, asset.scale), + buildAssetGeometry(asset.kind), new THREE.MeshStandardMaterial({ color: asset.color, emissive: asset.emissive ?? asset.color, diff --git a/src/lib/studio-world/store.ts b/src/lib/studio-world/store.ts index dddf018..3ea82b9 100644 --- a/src/lib/studio-world/store.ts +++ b/src/lib/studio-world/store.ts @@ -115,13 +115,28 @@ const writeStore = (store: StudioProjectsStore) => { fs.writeFileSync(storePath, JSON.stringify(store, null, 2), "utf8"); }; -const slugify = (value: string) => - value - .trim() - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-+|-+$/g, "") - .slice(0, 48) || "studio-world"; +const slugify = (value: string) => { + const input = value.trim().toLowerCase(); + let result = ""; + let pendingDash = false; + for (const char of input) { + const isAlphaNumeric = + (char >= "a" && char <= "z") || (char >= "0" && char <= "9"); + if (isAlphaNumeric) { + if (pendingDash && result) { + result += "-"; + } + result += char; + if (result.length >= 48) { + break; + } + pendingDash = false; + continue; + } + pendingDash = result.length > 0; + } + return result || "studio-world"; +}; const createProjectId = (name: string) => `${slugify(name)}-${Date.now().toString(36)}`;