Hosted onhoraciohdev.dev.hyper.mediavia theHypermedia Protocol

Image Handling in Web Comment Editorhttps://linear.app/seedhypermedia/issue/SHM-1622/i-cant-paste-images-on-web-mobile-comment-box

Problem

When users insert images into the web comment editor, we currently use data URLs (base64 encoding) to store images in drafts. This allows unauthenticated users to create drafts with images that persist across sessions without uploading to IPFS until the comment is published. However, the current data URL approach has significant UX issues:

Browser Freezing: Base64 encoding blocks the main thread, causing the editor to freeze, especially with larger images (>1MB)

Performance: Data URLs are 33% larger than the original files, slowing down draft serialization and deserialization

Poor Mobile Experience: The encoding delay is even more noticeable on mobile devices

Why We Can't Just Upload Immediately

    Unauthenticated Access: Anyone can create comment drafts without an account

    Orphaned Uploads: If we upload images immediately, users who add images but never publish would create orphaned IPFS data

    Resource Abuse: Malicious users could paste many images and waste daemon resources

Solution

I asked AI as suggested and this is what it came up with:

Option 1: Blob URLs ❌ NOT GOOD for your use case

const displaySrc = URL.createObjectURL(img) // "blob:http://..."

Problems:

    Blob URLs are temporary - they die when the page reloads

    Can't be serialized to localStorage/IndexedDB

    Lost when user closes the tab

    Your cached drafts would lose images!

Verdict: Won't work for persistent drafts.

Option 2: IndexedDB with Blob Storage ✅ BEST for your use case

Store the actual File/Blob in IndexedDB, not the base64:

// Store original file in IndexedDB const imageId = `draft-img-${Date.now()}` await indexedDB.put('draft-images', img, imageId) // Create blob URL for display (fast!) const displaySrc = URL.createObjectURL(img) node.create({ displaySrc: displaySrc, // Temporary display (fast) imageId: imageId, // Reference to IndexedDB name: name, }) // On mount/reload: recreate blob URL from IndexedDB const storedBlob = await indexedDB.get('draft-images', imageId) if (storedBlob) { const displaySrc = URL.createObjectURL(storedBlob) // Update block with new blob URL }

Pros:

    Fast - no base64 encoding

    Persistent - survives page reload

    Secure - stays in user's browser, no server upload

    Efficient - stores binary, not base64

    Cleanup - can delete IndexedDB entries when draft is published/discarded

Cons:

    More code complexity

    Need to handle IndexedDB lifecycle

Option 3: Data URLs (Current approach) ⚠️ Works but slow

const dataUrl = await readAsDataURL(img) // "data:image/jpeg;base64,..."

Pros:

    Serializable (can store in localStorage)

    Persistent

    Simple

Cons:

    Very slow - base64 encoding blocks UI

    33% larger than original file

    Freezes browser for large images

Option 4: Upload Immediately ⚠️ Orphan problem

Cons:

    Creates orphaned IPFS uploads

    Wastes daemon resources

    Security issue - unauthenticated users can fill your IPFS

My Recommendation: IndexedDB (Option 2)

Here's why:

    Solves the freezing problem - no base64 encoding

    Keeps drafts secure - client-side only until published

    Persists across sessions - user can reload and continue

    Clean architecture - natural place to store draft data

    Easy cleanup - delete from IndexedDB when draft published/discarded

Do you like what you are reading? Subscribe to receive updates.

Unsubscribe anytime