Image messages (paste/drop/pick, upload proxy, inline render) #12

Open
opened 2026-05-14 23:17:16 +02:00 by arne · 0 comments
Owner

Parent

#2

What to build

Image attachment via file picker, clipboard paste, OR drag-and-drop — all three supported. Attaching swaps the composer to "image mode" (textarea hidden, image preview shown, send button reads "Send image"). On send, chat.posta.no streams the bytes through to posta-server's POST /api/v1/uploads, then POSTs a posta.link/v1 message referencing the returned URL. Inbound image messages render <img> inline in their bubble: loading="lazy", decoding="async", onerror handler swapping to .broken placeholder, wrapped in <a target="_blank" rel="noopener">. Bubble-width sizing with max-height: 60vh; object-fit: contain. alt from payload's alt (fallback to name, then "Image from ").

Acceptance criteria

  • File picker button in composer opens native picker; selecting an image attaches it
  • paste event on composer with an image on clipboard attaches it
  • Drop event on composer attaches the dropped image
  • Attaching swaps composer to image-mode (textarea hidden, preview shown, button label "Send image")
  • "Discard" affordance on the preview clears the attachment without uploading
  • On send: chat.posta.no streams bytes (no full-buffer) to posta-server /api/v1/uploads
  • Upload success triggers POST /api/v1/messages with posta.link/v1 payload
  • Upload failure aborts atomically — no posta.link/v1 message is sent
  • Outbound image bubble appears with status indicator; composer resets to text-mode and re-focuses textarea
  • Inbound image renders <img> inline with bubble-width sizing and 60vh max-height
  • Click on image opens the original URL in a new tab (target="_blank" rel="noopener")
  • Broken image renders .broken placeholder with "Image from " caption
  • alt populated from payload's alt (fallback name, fallback "Image from ")
  • Files > 10 MiB are rejected client-side before upload begins
  • Non-image clipboard content (text paste) doesn't trigger image-mode
  • Browser fetches image bytes directly from peer's posta-server (chat.posta.no does NOT proxy the read path)

Blocked by

## Parent #2 ## What to build Image attachment via file picker, clipboard paste, OR drag-and-drop — all three supported. Attaching swaps the composer to "image mode" (textarea hidden, image preview shown, send button reads "Send image"). On send, chat.posta.no streams the bytes through to posta-server's `POST /api/v1/uploads`, then POSTs a `posta.link/v1` message referencing the returned URL. Inbound image messages render `<img>` inline in their bubble: `loading="lazy"`, `decoding="async"`, `onerror` handler swapping to `.broken` placeholder, wrapped in `<a target="_blank" rel="noopener">`. Bubble-width sizing with `max-height: 60vh; object-fit: contain`. `alt` from payload's `alt` (fallback to `name`, then "Image from <host>"). ## Acceptance criteria - [ ] File picker button in composer opens native picker; selecting an image attaches it - [ ] `paste` event on composer with an image on clipboard attaches it - [ ] Drop event on composer attaches the dropped image - [ ] Attaching swaps composer to image-mode (textarea hidden, preview shown, button label "Send image") - [ ] "Discard" affordance on the preview clears the attachment without uploading - [ ] On send: chat.posta.no streams bytes (no full-buffer) to posta-server `/api/v1/uploads` - [ ] Upload success triggers `POST /api/v1/messages` with `posta.link/v1` payload - [ ] Upload failure aborts atomically — no `posta.link/v1` message is sent - [ ] Outbound image bubble appears with status indicator; composer resets to text-mode and re-focuses textarea - [ ] Inbound image renders `<img>` inline with bubble-width sizing and 60vh max-height - [ ] Click on image opens the original URL in a new tab (`target="_blank" rel="noopener"`) - [ ] Broken image renders `.broken` placeholder with "Image from <host>" caption - [ ] `alt` populated from payload's `alt` (fallback `name`, fallback "Image from <host>") - [ ] Files > 10 MiB are rejected client-side before upload begins - [ ] Non-image clipboard content (text paste) doesn't trigger image-mode - [ ] Browser fetches image bytes directly from peer's posta-server (chat.posta.no does NOT proxy the read path) ## Blocked by - #10
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
posta/chat#12
No description provided.