postcards v2: design options, multi-image gallery, flip-card (Wendekarte) #2

Closed
opened 2026-06-15 16:13:40 +00:00 by mAi · 2 comments
Collaborator

Context

v1 is live at https://post.msbls.de and m likes it ("schon ganz gut"). This is the next iteration — all about the card's composition + rendering. The per-recipient /c/{token} model and unguessable-token scheme stay exactly as-is; don't touch the URL/persistence model beyond what these features need.

Mobile-first remains a hard requirement.

Asks (from m, via WhatsApp)

  1. More design options (Gestaltungsmöglichkeiten) — let the sender pick a layout/style when composing. Keep it a small, tasteful set, do not over-spec. A sensible starting set: a couple of clean themes/background styles + the layout choice below. Surface the choice in the compose flow and persist it with the card so the render matches.

  2. Multiple images per card — support more than one image (v1 already accepts multiple uploads server-side via images/MAX_IMAGES=8; the gap is the render). Provide a gallery / collage layout for multi-image cards, not just a single image. Mobile-friendly (swipeable or grid).

  3. Wendekarte (flip card) — m's most specific wish. A real-postcard feel: image on the FRONT, text on the BACK, with a flip interaction (tap/swipe to flip, mobile-friendly). Offer this as one of the layout options. Front = primary image (with the gallery underneath or on flip as fits), back = the message + sender/recipient.

Design guidance

  • Layout is a per-card choice made at compose time, stored with the postcard, and honored at render. Suggested enum: single (v1 behaviour) / gallery (multi-image) / flip (Wendekarte). Keep it extensible.
  • Tasteful and restrained — m values clean design. No gaudy themes. CSS-only flip (3D transform) preferred over heavy JS; progressive-enhancement so it degrades gracefully.
  • German UI with correct Umlaute, noindex on compose/card pages (as v1).
  • Stay renamable: nothing new should hardcode the domain (read from BASE_URL as v1 does).

DB / storage

  • Add a layout (or style) column to the postcards schema via a new idempotent migration (the app auto-migrates on boot). IMPORTANT — schema ownership: the prod app connects to msupabase as the postgres role via the pooler. When verifying against msupabase, connect as that same postgres role (NOT supabase_admin), otherwise new objects get owned by a role the prod app can't write to and the deploy crash-loops (this exact bug hit v1's first deploy). New migrations must be ownable/writable by postgres.
  • Multi-image already persists in postcard_images (ordered by position) — reuse it; render all images per the chosen layout.

Definition of done

  • Compose flow lets the sender choose a layout (single / gallery / flip) and it's stored + honored on the rendered card.
  • Multi-image cards render as a proper gallery/collage; flip cards flip (image front, text back) smoothly on mobile.
  • go build / go vet / go test green; verify e2e against msupabase as the postgres role, then clean up test data.
  • Mobile layout verified for all three layouts.
  • Commit on your branch + comment the commit link on this issue. otto-head merges to main and redeploys to post.msbls.de, then reports the live result to mai/otto-wa for m to try.

m is on vacation — async is fine, no rush messaging.

## Context v1 is live at https://post.msbls.de and m likes it ("schon ganz gut"). This is the next iteration — all about the **card's composition + rendering**. The per-recipient `/c/{token}` model and unguessable-token scheme stay exactly as-is; don't touch the URL/persistence model beyond what these features need. **Mobile-first remains a hard requirement.** ## Asks (from m, via WhatsApp) 1. **More design options** (Gestaltungsmöglichkeiten) — let the sender pick a layout/style when composing. Keep it a **small, tasteful set**, do not over-spec. A sensible starting set: a couple of clean themes/background styles + the layout choice below. Surface the choice in the compose flow and persist it with the card so the render matches. 2. **Multiple images per card** — support more than one image (v1 already accepts multiple uploads server-side via `images`/`MAX_IMAGES=8`; the gap is the *render*). Provide a **gallery / collage** layout for multi-image cards, not just a single image. Mobile-friendly (swipeable or grid). 3. **Wendekarte (flip card)** — m's most specific wish. A real-postcard feel: **image on the FRONT, text on the BACK**, with a **flip interaction** (tap/swipe to flip, mobile-friendly). Offer this as one of the layout options. Front = primary image (with the gallery underneath or on flip as fits), back = the message + sender/recipient. ## Design guidance - Layout is a **per-card choice** made at compose time, stored with the postcard, and honored at render. Suggested enum: `single` (v1 behaviour) / `gallery` (multi-image) / `flip` (Wendekarte). Keep it extensible. - Tasteful and restrained — m values clean design. No gaudy themes. CSS-only flip (3D transform) preferred over heavy JS; progressive-enhancement so it degrades gracefully. - German UI with correct Umlaute, `noindex` on compose/card pages (as v1). - Stay renamable: nothing new should hardcode the domain (read from `BASE_URL` as v1 does). ## DB / storage - Add a `layout` (or `style`) column to the `postcards` schema via a new idempotent migration (the app auto-migrates on boot). **IMPORTANT — schema ownership:** the prod app connects to msupabase as the **postgres** role via the pooler. When verifying against msupabase, connect as that same `postgres` role (NOT supabase_admin), otherwise new objects get owned by a role the prod app can't write to and the deploy crash-loops (this exact bug hit v1's first deploy). New migrations must be ownable/writable by `postgres`. - Multi-image already persists in `postcard_images` (ordered by `position`) — reuse it; render all images per the chosen layout. ## Definition of done - Compose flow lets the sender choose a layout (single / gallery / flip) and it's stored + honored on the rendered card. - Multi-image cards render as a proper gallery/collage; flip cards flip (image front, text back) smoothly on mobile. - `go build` / `go vet` / `go test` green; verify e2e against msupabase **as the postgres role**, then clean up test data. - Mobile layout verified for all three layouts. - Commit on your branch + comment the commit link on this issue. otto-head merges to main and redeploys to post.msbls.de, then reports the live result to mai/otto-wa for m to try. m is on vacation — async is fine, no rush messaging.
mAi self-assigned this 2026-06-15 16:13:40 +00:00
Author
Collaborator

Alles auf meinem Branch, gebaut/getestet und e2e gegen das echte msupabase (als postgres-Rolle) verifiziert.

Was neu ist

Gestaltungswahl im Compose-Flow — der Absender wählt jetzt beim Erstellen:

  • Layout: single (wie v1), gallery, flip (Wendekarte)
  • Farbwelt (theme): classic (Sandpapier, Default), sky (Himmel), dusk (Dämmerung)

Beide werden mit der Karte gespeichert und beim Rendern berücksichtigt. Auf der Compose-Seite zeigt eine Live-Vorschau die gewählte Farbwelt sofort am Hintergrund.

Die drei Layouts:

  • Klassisch — v1-Verhalten: Bild(er) oben, Text darunter. Bestehende Karten sehen unverändert aus.
  • Galerie — mehrere Fotos als wischbarer Scroll-Snap-Streifen (mobil swipebar, kein schweres JS) mit Hinweis „← wischen für mehr Fotos →“.
  • Wendekarte (flip) — m's Wunsch: Bild auf der Vorderseite, Nachricht + Absender/Empfänger auf der Rückseite. Tippen dreht die Karte. Reine CSS-Lösung (Checkbox-Hack + 3D-rotateY), funktioniert ohne JavaScript und degradiert sauber: wo keine 3D-Transforms unterstützt werden, stehen Vorder-/Rückseite einfach untereinander; prefers-reduced-motion schaltet die Animation ab. Bei mehreren Bildern zeigt die Vorderseite ein dezentes „🖼️ N“-Badge.

Technik

  • Migration migrations/002_layout_theme.sql: idempotente ALTER TABLE ... ADD COLUMN IF NOT EXISTS layout/theme text mit Defaults single/classic. ALTER behält den Tabellen-Owner (postgres) — keine neuen Owned-Objects, also kein Wiederholen des v1-Ownership-Crashs. Auto-Migrate beim Boot.
  • Werte werden beim Schreiben und beim Lesen normalisiert (NormalizeLayout/NormalizeTheme) → unbekannte/alte Werte fallen sicher auf v1-Verhalten zurück, der Renderer kann nie brechen.
  • Themes über [data-theme] + CSS-Custom-Properties — restrained, nur Paletten-Tausch, nichts Grelles.
  • Mobile-first durchgehend; noindex bleibt; Domain weiterhin aus BASE_URL (nichts hartkodiert). Korrekte Umlaute überall.
  • Multi-Image nutzt das bestehende postcard_images (nach position) — pro Layout gerendert.

Verifikation

  • go build / go vet / go test grün; gofmt sauber. Neue Tests: Normalisierung (Unit) + Template-Render pro Layout.
  • E2e gegen prod-msupabase als postgres-Rolle (genau wie der prod-App-Connect, 100.99.98.203:6789): Migration sauber angewandt, Spalten + Defaults bestätigt, je eine Karte pro Layout (2 Bilder, Theme sky) compose→persist→render — alle drei korrekt gerendert, Theme honoriert. Testdaten danach wieder gelöscht (DELETE 3, 0 Reste). Bild-Uploads liefen über Local-Storage, das prod-Bucket blieb unberührt.

Hinweis für otto-head (Merge/Deploy)

Die Migration wurde beim e2e-Test bereits auf der prod-DB angewandt (Spalten existieren mit sicheren Defaults) — der alte laufende prod-Binary liest sie noch nicht, also kein Effekt auf die Live-App bis zum Redeploy. Merge → Redeploy auf post.msbls.de ist unkritisch.

Commit: eef40031f5
Branch: mai/hermes/issue-2-postcards-v2

## v2 umgesetzt: Layouts (single / gallery / Wendekarte) + Farbwelten Alles auf meinem Branch, gebaut/getestet und e2e gegen das echte msupabase (als `postgres`-Rolle) verifiziert. ### Was neu ist **Gestaltungswahl im Compose-Flow** — der Absender wählt jetzt beim Erstellen: - **Layout**: `single` (wie v1), `gallery`, `flip` (Wendekarte) - **Farbwelt** (`theme`): `classic` (Sandpapier, Default), `sky` (Himmel), `dusk` (Dämmerung) Beide werden mit der Karte gespeichert und beim Rendern berücksichtigt. Auf der Compose-Seite zeigt eine Live-Vorschau die gewählte Farbwelt sofort am Hintergrund. **Die drei Layouts:** - **Klassisch** — v1-Verhalten: Bild(er) oben, Text darunter. Bestehende Karten sehen unverändert aus. - **Galerie** — mehrere Fotos als wischbarer Scroll-Snap-Streifen (mobil swipebar, kein schweres JS) mit Hinweis „← wischen für mehr Fotos →“. - **Wendekarte (flip)** — m's Wunsch: Bild auf der **Vorderseite**, Nachricht + Absender/Empfänger auf der **Rückseite**. Tippen dreht die Karte. Reine CSS-Lösung (Checkbox-Hack + 3D-`rotateY`), funktioniert **ohne JavaScript** und degradiert sauber: wo keine 3D-Transforms unterstützt werden, stehen Vorder-/Rückseite einfach untereinander; `prefers-reduced-motion` schaltet die Animation ab. Bei mehreren Bildern zeigt die Vorderseite ein dezentes „🖼️ N“-Badge. ### Technik - **Migration** `migrations/002_layout_theme.sql`: idempotente `ALTER TABLE ... ADD COLUMN IF NOT EXISTS layout/theme text` mit Defaults `single`/`classic`. ALTER behält den Tabellen-Owner (`postgres`) — keine neuen Owned-Objects, also kein Wiederholen des v1-Ownership-Crashs. Auto-Migrate beim Boot. - Werte werden **beim Schreiben und beim Lesen** normalisiert (`NormalizeLayout`/`NormalizeTheme`) → unbekannte/alte Werte fallen sicher auf v1-Verhalten zurück, der Renderer kann nie brechen. - Themes über `[data-theme]` + CSS-Custom-Properties — restrained, nur Paletten-Tausch, nichts Grelles. - Mobile-first durchgehend; `noindex` bleibt; Domain weiterhin aus `BASE_URL` (nichts hartkodiert). Korrekte Umlaute überall. - Multi-Image nutzt das bestehende `postcard_images` (nach `position`) — pro Layout gerendert. ### Verifikation - `go build` / `go vet` / `go test` grün; `gofmt` sauber. Neue Tests: Normalisierung (Unit) + Template-Render pro Layout. - **E2e gegen prod-msupabase als `postgres`-Rolle** (genau wie der prod-App-Connect, `100.99.98.203:6789`): Migration sauber angewandt, Spalten + Defaults bestätigt, je eine Karte pro Layout (2 Bilder, Theme `sky`) compose→persist→render — alle drei korrekt gerendert, Theme honoriert. **Testdaten danach wieder gelöscht** (`DELETE 3`, 0 Reste). Bild-Uploads liefen über Local-Storage, das prod-Bucket blieb unberührt. ### Hinweis für otto-head (Merge/Deploy) Die Migration wurde beim e2e-Test bereits auf der prod-DB angewandt (Spalten existieren mit sicheren Defaults) — der alte laufende prod-Binary liest sie noch nicht, also kein Effekt auf die Live-App bis zum Redeploy. Merge → Redeploy auf post.msbls.de ist unkritisch. **Commit:** https://mgit.msbls.de/m/postcards/commit/eef40031f51d4b03d62d0682349bbe11328103fc **Branch:** `mai/hermes/issue-2-postcards-v2`
Author
Collaborator

v2 merged & live on https://post.msbls.de

Merged commit eef4003 to main (merge 6921faa) and deployed.

Head review: go build/vet/test green from a clean checkout (new tests added for layout/render). Migration 002_layout_theme.sql is idempotent (ADD COLUMN IF NOT EXISTS, sane defaults) and applied cleanly — worker correctly heeded the schema-ownership note, no crash-loop this time.

Verified live e2e: created a flip (Wendekarte) card with 2 images on theme=dusk → renders with flip markup + both images from Supabase Storage, HTTP 200. layout/theme columns present in the DB. Test data cleaned up (m's own cards left untouched).

Deploy gotcha noted for the record: application.redeploy rebuilds the Docker image from Dokploy's existing git checkout — it does NOT pull. Use application.deploy to fetch the latest main first. (Cost me one stale rebuild before I switched.)

Not closing — m closes. Next: #3 (Shlink custom URLs) now unblocked, picking it up.

## ✅ v2 merged & live on https://post.msbls.de Merged commit `eef4003` to main (merge `6921faa`) and deployed. **Head review:** `go build`/`vet`/`test` green from a clean checkout (new tests added for layout/render). Migration `002_layout_theme.sql` is idempotent (`ADD COLUMN IF NOT EXISTS`, sane defaults) and applied cleanly — worker correctly heeded the schema-ownership note, no crash-loop this time. **Verified live e2e:** created a `flip` (Wendekarte) card with 2 images on `theme=dusk` → renders with flip markup + both images from Supabase Storage, HTTP 200. layout/theme columns present in the DB. Test data cleaned up (m's own cards left untouched). **Deploy gotcha noted for the record:** `application.redeploy` rebuilds the Docker image from Dokploy's *existing* git checkout — it does NOT pull. Use `application.deploy` to fetch the latest `main` first. (Cost me one stale rebuild before I switched.) Not closing — m closes. Next: #3 (Shlink custom URLs) now unblocked, picking it up.
mAi closed this issue 2026-06-23 15:48:01 +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#2
No description provided.