UX: unify media upload, clearer to/from, field order + equal buttons (compose polish) #12

Closed
opened 2026-06-16 17:47:27 +00:00 by mAi · 2 comments
Collaborator

What

m's UX feedback after using the live site (sendmy.cards). Compose-flow refinement.

1. Unified media upload (photo OR video in one element)

Currently photo (images, multiple) and video (video, single) are two separate file inputs. m wants one upload element — the user just picks files and the app auto-detects image vs video by MIME type and routes accordingly (images → gallery/postcard_images, video → postcard_videos). Keep the existing limits (multiple images, one video, size/format validation) but behind a single picker (accept="image/*,video/*", multiple). Server sorts the uploaded files by content-type.

2. Clearer / simpler to/from URL choice

The /to/ /from/ namespace selector for the custom short link isn't clear/easy enough. Redesign it so picking the prefix is obvious and simple (e.g. a clear segmented toggle sendmy.cards/[ to | from ]/<slug> with a live preview of the resulting URL). Keep the behaviour from #8 (multi-segment slug), just make the control intuitive.

3. 'An' and 'von' adjacent

The recipient field ('An …' / recipient_name) and the sender field ('von …' / sender_name) should sit directly one after the other in the form, not separated by other fields. Group them together.

4. Equal-size buttons on 'für wen' (audience)

The audience options ('an eine Person' / 'zum Teilen') render as buttons of unequal size. Make them equal width/height (consistent button sizing). Same for any other button-group (layout/occasion tiles) where sizing is ragged.

5. General polish

m is 'noch nicht soo zufrieden' with the UI overall. Beyond 1-4, do a tasteful mobile-first cleanup of the compose flow: consistent spacing, clear visual hierarchy/grouping (message → media → an/von → options → custom URL → password), aligned controls, equal-sized tappable tiles/buttons. Don't redesign the brand or change features — tighten the existing layout. Restrained, clean (m values clean design).

Constraints

  • Mobile-first, German UI + Umlaute. No feature regressions: layouts (single/gallery/flip), broadcast, occasions, /to//from slugs + lifecycle, password, lightbox all keep working.
  • Mostly web/templates/compose.html + web/static/style.css; the unified upload needs the multipart handler in internal/server/handlers.go to sort files by MIME (images vs video) instead of reading two named fields. No schema change expected.

DoD

  • One media picker; dropping images + a video together works (images→gallery, video→player); limits + validation intact.
  • to/from choice is obvious with a live URL preview.
  • An/von fields adjacent; 'für wen' (and other) buttons equal-sized; compose flow visibly tidier on mobile.
  • build/vet/test green; e2e verified (mixed image+video upload renders correctly; a /to/ slug still resolves); test data cleaned. Commit references this issue. otto-head merges + deploys.
## What m's UX feedback after using the live site (sendmy.cards). Compose-flow refinement. ### 1. Unified media upload (photo OR video in one element) Currently photo (`images`, multiple) and video (`video`, single) are **two separate file inputs**. m wants **one upload element** — the user just picks files and the app **auto-detects** image vs video by MIME type and routes accordingly (images → gallery/`postcard_images`, video → `postcard_videos`). Keep the existing limits (multiple images, one video, size/format validation) but behind a single picker (`accept="image/*,video/*"`, `multiple`). Server sorts the uploaded files by content-type. ### 2. Clearer / simpler to/from URL choice The `/to/` `/from/` namespace selector for the custom short link isn't clear/easy enough. Redesign it so picking the prefix is obvious and simple (e.g. a clear segmented toggle `sendmy.cards/[ to | from ]/<slug>` with a live preview of the resulting URL). Keep the behaviour from #8 (multi-segment slug), just make the control intuitive. ### 3. 'An' and 'von' adjacent The recipient field ('An …' / recipient_name) and the sender field ('von …' / sender_name) should sit **directly one after the other** in the form, not separated by other fields. Group them together. ### 4. Equal-size buttons on 'für wen' (audience) The audience options ('an eine Person' / 'zum Teilen') render as buttons of **unequal size**. Make them **equal width/height** (consistent button sizing). Same for any other button-group (layout/occasion tiles) where sizing is ragged. ### 5. General polish m is 'noch nicht soo zufrieden' with the UI overall. Beyond 1-4, do a tasteful mobile-first cleanup of the compose flow: consistent spacing, clear visual hierarchy/grouping (message → media → an/von → options → custom URL → password), aligned controls, equal-sized tappable tiles/buttons. Don't redesign the brand or change features — tighten the existing layout. Restrained, clean (m values clean design). ## Constraints - Mobile-first, German UI + Umlaute. No feature regressions: layouts (single/gallery/flip), broadcast, occasions, /to//from slugs + lifecycle, password, lightbox all keep working. - Mostly `web/templates/compose.html` + `web/static/style.css`; the unified upload needs the multipart handler in `internal/server/handlers.go` to sort files by MIME (images vs video) instead of reading two named fields. No schema change expected. ## DoD - One media picker; dropping images + a video together works (images→gallery, video→player); limits + validation intact. - to/from choice is obvious with a live URL preview. - An/von fields adjacent; 'für wen' (and other) buttons equal-sized; compose flow visibly tidier on mobile. - build/vet/test green; e2e verified (mixed image+video upload renders correctly; a /to/ slug still resolves); test data cleaned. Commit references this issue. otto-head merges + deploys.
mAi self-assigned this 2026-06-16 17:47:38 +00:00
Author
Collaborator

#12 done — compose UX polish (shift-1)

Branch mai/hermes/issue-12-ux-unify-media, commit 8e931d6. Not yet merged/deployed — otto-head does that.

1. Unified media upload

One picker now: <input name="media" accept="image/*,video/*" multiple>. The server sorts each uploaded part by sniffed content type (new partitionMedia in internal/server/handlers.go): video containers (mp4 ftyp box / webm EBML magic) → saveVideos/player, everything else → saveImages/gallery. The two named fields (images, video) are gone. Per-type limits + validation are unchanged (max images, max 1 video, size/format), they just run after the split. Client-side preview merged into one handler (thumbnails for images, inline player for the first video).

2. Clearer to/from choice

The –/to/from inline <select> is replaced by a segmented toggle (ohne · to/ · from/, each labelled privat / an wen / von wem) plus a live URL preview line that updates as you toggle and type — e.g. Dein Link: sendmy.cards/to/oma-erika, or the automatischer, privater Link hint when empty. Slug handling is byte-for-byte unchanged (still submits slug_prefix), so #8's multi-segment /to//from/ behaviour and lifecycle limits are preserved.

3. 'An' and 'von' adjacent

Recipient + sender are now one responsive name-pair placed right after the media block. Stacks on phones, side-by-side (equal width) once there's room; when the recipient is hidden for a broadcast card the lone Von field spans full width (auto-fit grid).

4. Equal-size buttons

All choice tiles (audience, layout, occasion, theme swatches) now stretch to the tallest in their grid row (height:100% on .choice/.choice-card/.swatch), so the für wen pair and every other button-group render as an even row regardless of label length.

5. Polish

Field order is now message → media → an/von → Gestaltung → Wunsch-Adresse → Passwort, with the segmented toggle and URL preview giving the custom-address block a clearer hierarchy. Mobile-first; brand/features untouched.

Verification

  • go build / go vet / go test -race ./... — all green.
  • New TestPartitionMedia (mixed jpeg+mp4+png+webm split, order preserved, empties dropped); updated TestComposeShowsMediaField and the prefill render test for the new markup.
  • e2e (throwaway Postgres + local storage): posted one card with 2 PNGs + 1 MP4 through the single picker → 303 → rendered card shows the swipe gallery (0.png,1.png) and a <video> (video/0.mp4); Umlauts intact. Guards re-checked live: 2 videos → „höchstens 1 Video(s)“; a text file → „Nur JPEG-… Bilder“. Compose form with Shlink enabled renders the segmented toggle + data-base + URL preview. Test container + temp files cleaned; no prod data touched.

Note: a full live /to/ Shlink round-trip wasn't re-run this shift — the slug code path is unchanged from #8 (only the form control changed), covered by the prefill render test asserting the chosen segment stays checked. Recommend otto-head smoke a /to/ card after deploy.

## #12 done — compose UX polish (shift-1) Branch `mai/hermes/issue-12-ux-unify-media`, commit [`8e931d6`](https://mgit.msbls.de/m/postcards/commit/8e931d6484ea1fbeb2b2f1a1b6d4185113c72f51). Not yet merged/deployed — otto-head does that. ### 1. Unified media upload One picker now: `<input name="media" accept="image/*,video/*" multiple>`. The server sorts each uploaded part by **sniffed content type** (new `partitionMedia` in `internal/server/handlers.go`): video containers (mp4 `ftyp` box / webm EBML magic) → `saveVideos`/player, everything else → `saveImages`/gallery. The two named fields (`images`, `video`) are gone. Per-type limits + validation are unchanged (max images, max 1 video, size/format), they just run after the split. Client-side preview merged into one handler (thumbnails for images, inline player for the first video). ### 2. Clearer to/from choice The `–/to/from` inline `<select>` is replaced by a **segmented toggle** (`ohne` · `to/` · `from/`, each labelled *privat / an wen / von wem*) plus a **live URL preview** line that updates as you toggle and type — e.g. `Dein Link: sendmy.cards/to/oma-erika`, or the *automatischer, privater Link* hint when empty. Slug handling is byte-for-byte unchanged (still submits `slug_prefix`), so #8's multi-segment `/to//from/` behaviour and lifecycle limits are preserved. ### 3. 'An' and 'von' adjacent Recipient + sender are now one responsive **name-pair** placed right after the media block. Stacks on phones, side-by-side (equal width) once there's room; when the recipient is hidden for a broadcast card the lone *Von* field spans full width (`auto-fit` grid). ### 4. Equal-size buttons All choice tiles (audience, layout, occasion, theme swatches) now stretch to the tallest in their grid row (`height:100%` on `.choice`/`.choice-card`/`.swatch`), so the *für wen* pair and every other button-group render as an even row regardless of label length. ### 5. Polish Field order is now message → media → an/von → Gestaltung → Wunsch-Adresse → Passwort, with the segmented toggle and URL preview giving the custom-address block a clearer hierarchy. Mobile-first; brand/features untouched. ### Verification - `go build` / `go vet` / `go test -race ./...` — all green. - New `TestPartitionMedia` (mixed jpeg+mp4+png+webm split, order preserved, empties dropped); updated `TestComposeShowsMediaField` and the prefill render test for the new markup. - **e2e** (throwaway Postgres + local storage): posted one card with 2 PNGs + 1 MP4 through the single picker → 303 → rendered card shows the swipe **gallery** (`0.png`,`1.png`) **and** a `<video>` (`video/0.mp4`); Umlauts intact. Guards re-checked live: 2 videos → *„höchstens 1 Video(s)“*; a text file → *„Nur JPEG-… Bilder“*. Compose form with Shlink enabled renders the segmented toggle + `data-base` + URL preview. Test container + temp files cleaned; no prod data touched. _Note: a full live `/to/` Shlink round-trip wasn't re-run this shift — the slug code path is unchanged from #8 (only the form control changed), covered by the prefill render test asserting the chosen segment stays checked. Recommend otto-head smoke a `/to/` card after deploy._
Author
Collaborator

Merged & deployed — compose UX polish live on sendmy.cards

Merged 8e931d6 (merge 9852264), deployed. Verified live:

  • Unified media picker: one field, 1 PNG + 1 MP4 uploaded together → gallery image + <video> both render (content-type sniffing splits them).
  • /to/ smoke (worker's recommended check): sendmy.cards/to/otto-smoke12 → 302 → card. slug path intact.
  • to/from segmented toggle + live URL preview, An/von adjacent, equal-size choice tiles, tidier field order — all shipped.

No schema change. build/vet/test -race green. Test data cleaned. Not closing — m closes.

## ✅ Merged & deployed — compose UX polish live on sendmy.cards Merged `8e931d6` (merge `9852264`), deployed. Verified live: - **Unified media picker**: one field, 1 PNG + 1 MP4 uploaded together → gallery image + `<video>` both render (content-type sniffing splits them). ✅ - **/to/ smoke** (worker's recommended check): `sendmy.cards/to/otto-smoke12` → 302 → card. ✅ slug path intact. - to/from segmented toggle + live URL preview, An/von adjacent, equal-size choice tiles, tidier field order — all shipped. No schema change. build/vet/test -race green. Test data cleaned. Not closing — m closes.
mAi closed this issue 2026-06-23 15:48:03 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/postcards#12
No description provided.