diff --git a/apps/api/src/lib/bg-effects.ts b/apps/api/src/lib/bg-effects.ts index 42ceca3f..12fd84f9 100644 --- a/apps/api/src/lib/bg-effects.ts +++ b/apps/api/src/lib/bg-effects.ts @@ -44,8 +44,8 @@ export async function blurBackground( */ export async function addDropShadow(subjectBuffer: Buffer, opacity: number): Promise { const meta = await sharp(subjectBuffer).metadata(); - const width = meta.width!; - const height = meta.height!; + if (!meta.width || !meta.height) throw new Error("Cannot read image dimensions"); + const { width, height } = meta; const normalizedOpacity = Math.max(0, Math.min(100, opacity)) / 100; // Shadow parameters @@ -121,6 +121,7 @@ export async function createGradientBackground( */ export async function compositeOnColor(subjectBuffer: Buffer, hexColor: string): Promise { const meta = await sharp(subjectBuffer).metadata(); + if (!meta.width || !meta.height) throw new Error("Cannot read image dimensions"); const hex = hexColor.replace("#", ""); const r = parseInt(hex.substring(0, 2), 16); const g = parseInt(hex.substring(2, 4), 16); @@ -128,8 +129,8 @@ export async function compositeOnColor(subjectBuffer: Buffer, hexColor: string): return sharp({ create: { - width: meta.width!, - height: meta.height!, + width: meta.width, + height: meta.height, channels: 4, background: { r, g, b, alpha: 255 }, }, @@ -148,8 +149,8 @@ export async function compositeOnImage( backgroundBuffer: Buffer, ): Promise { const meta = await sharp(subjectBuffer).metadata(); - const width = meta.width!; - const height = meta.height!; + if (!meta.width || !meta.height) throw new Error("Cannot read image dimensions"); + const { width, height } = meta; const resizedBg = await sharp(backgroundBuffer) .resize(width, height, { fit: "cover" }) @@ -184,8 +185,8 @@ export async function applyEffects( }, ): Promise { const meta = await sharp(subjectBuffer).metadata(); - const width = meta.width!; - const height = meta.height!; + if (!meta.width || !meta.height) throw new Error("Cannot read image dimensions"); + const { width, height } = meta; const bgType = settings.backgroundType || "transparent"; // Step 1: Add shadow to the subject (before background compositing) diff --git a/apps/api/src/routes/batch.ts b/apps/api/src/routes/batch.ts index ab8ae24f..8e9d8c40 100644 --- a/apps/api/src/routes/batch.ts +++ b/apps/api/src/routes/batch.ts @@ -153,7 +153,7 @@ export async function registerBatchRoutes(app: FastifyInstance): Promise { processBuffer = await decodeHeic(processBuffer); // Update extension to match decoded format (HEIC/HEIF → PNG) const ext = processFilename.match(/\.[^.]+$/)?.[0]; - if (ext) processFilename = processFilename.slice(0, -ext.length) + ".png"; + if (ext) processFilename = `${processFilename.slice(0, -ext.length)}.png`; } if (!skipPreprocess) { processBuffer = await autoOrient(processBuffer); diff --git a/apps/api/src/routes/docs.ts b/apps/api/src/routes/docs.ts index 0187dde8..040d31e4 100644 --- a/apps/api/src/routes/docs.ts +++ b/apps/api/src/routes/docs.ts @@ -81,7 +81,7 @@ function generateLlmsFullTxt(spec: OpenAPISpec): string { for (const [method, op] of Object.entries(methods)) { const tag = op.tags?.[0] || "Other"; if (!tagGroups.has(tag)) tagGroups.set(tag, []); - tagGroups.get(tag)!.push({ method: method.toUpperCase(), path, op }); + tagGroups.get(tag)?.push({ method: method.toUpperCase(), path, op }); } } diff --git a/apps/api/src/routes/pipeline.ts b/apps/api/src/routes/pipeline.ts index d57c42b2..9241a02c 100644 --- a/apps/api/src/routes/pipeline.ts +++ b/apps/api/src/routes/pipeline.ts @@ -106,7 +106,7 @@ export async function registerPipelineRoutes(app: FastifyInstance): Promise(app: FastifyInstance, config: ToolRouteConfig try { fileBuffer = await decodeHeic(fileBuffer); const ext = filename.match(/\.[^.]+$/)?.[0]; - if (ext) filename = filename.slice(0, -ext.length) + ".png"; + if (ext) filename = `${filename.slice(0, -ext.length)}.png`; } catch (err) { return reply.status(422).send({ error: "Failed to decode HEIC file. Ensure libheif-examples is installed.", diff --git a/apps/api/src/routes/tools/color-adjustments.ts b/apps/api/src/routes/tools/color-adjustments.ts index 74af43b1..5fa2e751 100644 --- a/apps/api/src/routes/tools/color-adjustments.ts +++ b/apps/api/src/routes/tools/color-adjustments.ts @@ -1,7 +1,6 @@ import { brightness as adjustBrightness, contrast as adjustContrast, - saturation as adjustSaturation, sharpen as adjustSharpen, colorChannels, grayscale, diff --git a/apps/api/src/routes/tools/content-aware-resize.ts b/apps/api/src/routes/tools/content-aware-resize.ts index 213d7d4c..a80359de 100644 --- a/apps/api/src/routes/tools/content-aware-resize.ts +++ b/apps/api/src/routes/tools/content-aware-resize.ts @@ -65,7 +65,7 @@ export function registerContentAwareResize(app: FastifyInstance) { try { fileBuffer = await decodeHeic(fileBuffer); const ext = filename.match(/\.[^.]+$/)?.[0]; - if (ext) filename = filename.slice(0, -ext.length) + ".png"; + if (ext) filename = `${filename.slice(0, -ext.length)}.png`; } catch (err) { return reply.status(422).send({ error: "Failed to decode HEIC/HEIF file", diff --git a/apps/api/src/routes/tools/image-enhancement.ts b/apps/api/src/routes/tools/image-enhancement.ts index 1d54dd77..8dfa802e 100644 --- a/apps/api/src/routes/tools/image-enhancement.ts +++ b/apps/api/src/routes/tools/image-enhancement.ts @@ -4,7 +4,6 @@ import sharp from "sharp"; import { z } from "zod"; import { autoOrient } from "../../lib/auto-orient.js"; import { validateImageBuffer } from "../../lib/file-validation.js"; -import { sanitizeFilename } from "../../lib/filename.js"; import { decodeHeic } from "../../lib/heic-converter.js"; import { resolveOutputFormat } from "../../lib/output-format.js"; import { createToolRoute } from "../tool-factory.js"; diff --git a/apps/api/src/routes/tools/passport-photo.ts b/apps/api/src/routes/tools/passport-photo.ts index 3c6ef4e4..179eaaa9 100644 --- a/apps/api/src/routes/tools/passport-photo.ts +++ b/apps/api/src/routes/tools/passport-photo.ts @@ -160,7 +160,7 @@ export function registerPassportPhoto(app: FastifyInstance) { if (validation.format === "heif") { fileBuffer = await decodeHeic(fileBuffer); const ext = filename.match(/\.[^.]+$/)?.[0]; - if (ext) filename = filename.slice(0, -ext.length) + ".png"; + if (ext) filename = `${filename.slice(0, -ext.length)}.png`; } // Auto-orient to fix EXIF rotation diff --git a/apps/api/src/routes/tools/remove-background.ts b/apps/api/src/routes/tools/remove-background.ts index 60f4f656..846c9bdd 100644 --- a/apps/api/src/routes/tools/remove-background.ts +++ b/apps/api/src/routes/tools/remove-background.ts @@ -83,7 +83,7 @@ export function registerRemoveBackground(app: FastifyInstance) { if (validation.format === "heif") { fileBuffer = await decodeHeic(fileBuffer); const ext = filename.match(/\.[^.]+$/)?.[0]; - if (ext) filename = filename.slice(0, -ext.length) + ".png"; + if (ext) filename = `${filename.slice(0, -ext.length)}.png`; } // Auto-orient to fix EXIF rotation diff --git a/apps/api/src/routes/tools/stitch.ts b/apps/api/src/routes/tools/stitch.ts index a0951471..025bec58 100644 --- a/apps/api/src/routes/tools/stitch.ts +++ b/apps/api/src/routes/tools/stitch.ts @@ -205,8 +205,9 @@ export function registerStitch(app: FastifyInstance) { if (settings.cornerRadius > 0) { const meta = await sharp(result).metadata(); - const w = meta.width!; - const h = meta.height!; + if (!meta.width || !meta.height) throw new Error("Cannot read image dimensions"); + const w = meta.width; + const h = meta.height; const r = Math.min(settings.cornerRadius, Math.floor(Math.min(w, h) / 2)); const mask = Buffer.from( diff --git a/apps/web/src/components/common/image-viewer.tsx b/apps/web/src/components/common/image-viewer.tsx index 4216d281..f6eb10e5 100644 --- a/apps/web/src/components/common/image-viewer.tsx +++ b/apps/web/src/components/common/image-viewer.tsx @@ -90,6 +90,7 @@ export function ImageViewer({ }, []); // Reset state on src change + // biome-ignore lint/correctness/useExhaustiveDependencies: src is a prop that triggers state reset useEffect(() => { setZoom(DEFAULT_ZOOM); setFitMode("fit"); diff --git a/apps/web/src/components/common/multi-image-viewer.tsx b/apps/web/src/components/common/multi-image-viewer.tsx index 30fdbcc7..7ad221bb 100644 --- a/apps/web/src/components/common/multi-image-viewer.tsx +++ b/apps/web/src/components/common/multi-image-viewer.tsx @@ -46,7 +46,10 @@ export function MultiImageViewer() { const hasNext = selectedIndex < entries.length - 1; const hasProcessed = !!currentEntry.processedUrl; - const isPreviewable = hasProcessed && canBrowserPreview(currentEntry.processedUrl!); + const isPreviewable = + hasProcessed && currentEntry.processedUrl + ? canBrowserPreview(currentEntry.processedUrl) + : false; const displayUrl = currentEntry.processedPreviewUrl ?? currentEntry.processedUrl; const processedFilename = currentEntry.processedUrl diff --git a/apps/web/src/components/tools/collage-preview.tsx b/apps/web/src/components/tools/collage-preview.tsx index 5cc5ea97..b1cb0eaa 100644 --- a/apps/web/src/components/tools/collage-preview.tsx +++ b/apps/web/src/components/tools/collage-preview.tsx @@ -149,7 +149,7 @@ function CollageCanvas({ template }: { template: CollageTemplate }) { return (
store.setSelectedCell(null)} onKeyDown={(e) => e.key === "Escape" && store.setSelectedCell(null)} > @@ -178,7 +178,7 @@ function CollageCanvas({ template }: { template: CollageTemplate }) { return ( - {rects.map((r, i) => ( + {rects.map((r) => (