Reliably embed failure screenshots in Nextcloud Talk

Screenshots often fell back to "too large to embed" text instead of the
actual proof image. A single fixed 800px PNG encode exceeds Nextcloud
Talk's 32000-char message limit once base64-expanded (~74KB).

Encode as JPEG (much smaller for UI screenshots) and step the width down
through a list of candidates until the embedded message fits, keeping the
largest size that does. Typical search-failure screenshots now embed at
full 800px width; even a 336KB screenshot embeds at 520px.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Change-Id: I32a71646c3e6c57a18e1230080c53abf952b3dd7
diff --git a/lib/utils.js b/lib/utils.js
index 145a689..5d1c20e 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -18,29 +18,41 @@
         let fullMessage = message;
         const MAX_MESSAGE_LENGTH = 32000; // Nextcloud Talk message size limit
         
-        // If a screenshot path is provided, try to embed it
+        // If a screenshot path is provided, try to embed it. The data URI is
+        // base64 (~33% overhead), so a PNG quickly blows past the message size
+        // limit. Use JPEG (much smaller for UI screenshots) and step the width
+        // down until the embedded message fits, keeping the largest that does.
         if (screenshotPath && fs.existsSync(screenshotPath)) {
             try {
-                // First, try to resize the image to reduce size
-                const resizedBuffer = await sharp(screenshotPath)
-                    .resize(800, null, { // Resize to max width of 800px, maintain aspect ratio
-                        withoutEnlargement: true,
-                        fit: 'inside'
-                    })
-                    .png({ quality: 80, compressionLevel: 9 })
-                    .toBuffer();
-                
-                const base64Image = resizedBuffer.toString('base64');
-                const dataUri = `data:image/png;base64,${base64Image}`;
-                const messageWithImage = `${message}\n\n![Screenshot](${dataUri})`;
-                
-                // Check if the message with image fits within the limit
-                if (messageWithImage.length <= MAX_MESSAGE_LENGTH) {
-                    fullMessage = messageWithImage;
-                    console.log(`Screenshot will be embedded (message size: ${messageWithImage.length} chars)`);
+                const candidateWidths = [800, 640, 520, 420, 320, 240];
+                let embedded = null;
+                let lastSize = 0;
+
+                for (const width of candidateWidths) {
+                    const resizedBuffer = await sharp(screenshotPath)
+                        .resize(width, null, { // maintain aspect ratio
+                            withoutEnlargement: true,
+                            fit: 'inside'
+                        })
+                        .jpeg({ quality: 72, mozjpeg: true })
+                        .toBuffer();
+
+                    const dataUri = `data:image/jpeg;base64,${resizedBuffer.toString('base64')}`;
+                    const messageWithImage = `${message}\n\n![Screenshot](${dataUri})`;
+                    lastSize = messageWithImage.length;
+
+                    if (messageWithImage.length <= MAX_MESSAGE_LENGTH) {
+                        embedded = messageWithImage;
+                        console.log(`Screenshot embedded at width ${width}px (message size: ${messageWithImage.length} chars)`);
+                        break;
+                    }
+                }
+
+                if (embedded) {
+                    fullMessage = embedded;
                 } else {
-                    console.log(`Screenshot too large (${messageWithImage.length} chars), sending text-only notification`);
-                    fullMessage = `${message}\n\n_Screenshot available locally but too large to embed (${Math.round(messageWithImage.length / 1024)}KB)_`;
+                    console.log(`Screenshot too large to embed even at smallest size (${lastSize} chars), sending text-only notification`);
+                    fullMessage = `${message}\n\n_Screenshot available locally but too large to embed_`;
                 }
             } catch (imageError) {
                 console.error('Failed to process screenshot for Nextcloud Talk:', imageError.message);