• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
stevekrouse

stevekrouse

beyond-text-3

Public
Starter template with client-side React & Hono server
Remix of templates/reactHonoStarter
Like
beyond-text-3
Home
Code
8
api
1
database
2
frontend
6
scripts
1
shared
2
IMAGE_UPLOAD.md
README.md
H
index.ts
Connections
Environment variables
Branches
3
Pull requests
Remixes
History
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
IMAGE_UPLOAD.md
Code
/
IMAGE_UPLOAD.md
Search
…
Viewing readonly version of add-image-upload branch: v16
View latest version
IMAGE_UPLOAD.md

Image Upload Feature (branch: add-image-upload)

Lets users take a selfie (or pick any image) on mobile and drop it into the chat. Designed to merge cleanly alongside concurrent work on main.

Merge strategy

To avoid stepping on another agent working on main, nearly all of the code lives in new files. Edits to existing files are kept intentionally tiny and non-overlapping with the message-rendering path.

New files

api/
  images.ts                         ← Hono sub-app: POST /api/images, GET /api/images/:id
database/
  images.ts                         ← chat_images_v1 table + blob storage helpers
shared/
  image-types.ts                    ← ChatImage type, upload limits (separate from types.ts)
frontend/
  components/CameraButton.tsx       ← Mobile camera button (icon + label)
  hooks/useImageUpload.ts           ← Capture → compress → upload flow

Edited files (minimal)

  • index.ts — +3 lines: import imagesRoutes, mount with app.route("/", imagesRoutes).
  • frontend/components/App.tsx — +1 import, +6 JSX lines: render <CameraButton /> above <MessageInput />. Does not touch MessageList, MessageInput, useMessages, db.ts, types.ts, or index.html.

How it works

Rendering mermaid diagram...
  1. Capture — a hidden <input type="file" accept="image/*" capture="user"> element opens the front-facing camera on iOS and Android.
  2. Compress in-browser — images are downscaled to max 1600px and re-encoded as JPEG at quality 0.82 before upload. Keeps uploads fast on cellular.
  3. Upload — POST /api/images with a multipart form. Server validates MIME type, stores bytes in std/blob under chat-images/v1/{id}, stores metadata in a new chat_images_v1 SQLite table.
  4. Post to chat — the server then calls the existing insertMessage() to append a text message of the form 📷 /api/images/{id}. This means the image is delivered to every other client through the existing SSE stream — zero changes to the message pipeline.
  5. View — GET /api/images/:id streams the bytes back with an immutable cache header.

Mobile UX notes

  • capture="user" → front camera on iPhone/Android.
  • accept="image/*" → also allows picking from the gallery if the user prefers.
  • font-size: 16px on any input (inherited from the app) avoids iOS zoom-on-focus.
  • Button is large (min-h-10, rounded pill) for easy thumb-tapping.
  • EXIF orientation handled via createImageBitmap({ imageOrientation: 'from-image' }).
  • Error state shows as a small inline toast above the button so it doesn't shift the layout.

Rendering images inline (future / optional)

Currently, images show up in the chat as a text line 📷 /api/images/123. To render them as real <img> tags, MessageList.tsx could be taught to detect this prefix and render accordingly. That change is intentionally not made here so this branch stays orthogonal to any concurrent work on MessageList.

A one-liner enhancement once merged:

// In MessageList.tsx, inside the message map: const imageMatch = msg.text.match(/^📷\s+(\/api\/images\/\d+)/); if (imageMatch) { return <img src={imageMatch[1]} class="max-w-xs rounded-lg" />; }

Storage

  • Bytes: std/blob at chat-images/v1/{id}
  • Metadata: SQLite table chat_images_v1 (brand new — no migration of existing tables)
  • Limits: 8 MB max upload (enforced on both client post-compression and server). MIME allowlist: jpeg, png, webp, heic, heif. application/octet-stream from iOS camera is normalized to image/jpeg.
FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
AboutAlternativesPricingBlogNewsletterCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.