diff --git a/docs/sharing-plan.md b/docs/sharing-plan.md new file mode 100644 index 0000000..2e580b7 --- /dev/null +++ b/docs/sharing-plan.md @@ -0,0 +1,176 @@ +# Sharing plan: links + player landing + Motif export + +This document captures the implementation plan for **Share links** (Copy Link + Share to X), a minimal **player landing page**, and the next step: sharing **generated Motif variants** via **exported artifacts** (MIDI and/or audio). + +## Goals +- **Shareable outcome**: users can share something they heard (original MIDI preview, or a generated Motif variant). +- **One-click-to-hear**: shared links load quickly and are ready to play (but never true autoplay; iOS requires a user gesture). +- **Lightweight**: keep the landing page minimal and stable. +- **Safe**: shared URLs must not turn the backend into an open proxy. + +## MVP constraints (important) +- **iOS/Safari** blocks audio until a user gesture. The landing can preload, but playback needs a tap. +- **X/Twitter** shares are links + preview cards. No reliable in-post WebAudio playback. +- Search results can change over time, so **index-based sharing** is brittle. + +## UX spec + +### A) Main app: Share controls +Placement: near the “Selected MIDI”/result context (where users decide something is share-worthy). + +Controls: +- **Copy link**: copies a permalink to clipboard + shows a small “Copied” toast. +- **Share to X**: opens the tweet composer with prefilled text + URL. + +X intent URL format: +- `https://twitter.com/intent/tweet?text=&url=` + +Suggested default tweet text: +- `Listening to “{title}” in MOTIF — try it` + +### B) Share landing: `/play` +A minimal landing page that: +- reads parameters from the URL +- fetches the MIDI (or exported artifact) +- enables Play/Stop + volume +- shows iOS “Enable Audio” CTA if required +- includes a primary CTA: **Generate your own** → links back to the main app + +## URL design (heart of the plan) + +### v1: share the original MIDI preview (recommended MVP) +Use the MIDI URL as the stable identifier. + +Landing URL: +- `/play?u=&title=&song=` + +Notes: +- `u` is required for reproducibility. +- `title` is display-only (optional). +- `song` supports the CTA back to the main app (optional). + +Example: +- `/play?u=https%3A%2F%2Fbitmidi.com%2Fuploads%2F...mid&title=Hotel%20California&song=Hotel%20California` + +Main app CTA target: +- `/?song=` + +### Why not share “result index”? +Because search ranking shifts; index links rot. If needed, index can be a fallback only when `u` is missing. + +## `/play` landing behavior + +### Required inputs +- **Preview share**: `u` (MIDI source URL) +- **Export share**: `m` (motif midi id) or `a` (audio id) + +### State machine +- If `m` or `a` present → load exported artifact +- Else if `u` present → fetch + parse MIDI via backend proxy +- Else → show “Invalid link” + CTA to home + +### Playback +- Never autoplay. +- Enable Audio CTA shown until AudioContext is running. + +### Error cases +- Missing/invalid params → show “Invalid link” +- Backend fetch failure → show “Couldn’t load this MIDI” +- Parse failure → show “Unsupported or corrupted MIDI” + +## Export-based sharing (generated Motif variants) +Sharing a generated Motif variant needs an artifact the landing page can load reliably: +- **MIDI export**: smaller, fast, consistent with the project (music-as-structure) +- **Audio export**: most universal listening, but heavier and needs careful encoding/hosting + +### Recommended sequencing +1) **MIDI export first** (fast, small, easy to iterate)\n2) Add **audio export** once the flow proves value (and storage/limits are solved) + +### Proposed backend endpoints + +#### 1) Create export (returns a shareable play URL) +`POST /api/share/export` + +Input (example): +```json +{ + "u": "https://bitmidi.com/uploads/....mid", + "song": "Hotel California", + "title": "Hotel California - Eagles", + "preset": "dance|ambient|ominous|default", + "params": { "intensity": 0.5, "swing": 0.1 }, + "format": "midi|audio" +} +``` + +Output (example): +```json +{ + "id": "abc123", + "format": "midi", + "playUrl": "/play?m=abc123&title=Hotel%20California&song=Hotel%20California" +} +``` + +#### 2) Fetch export artifact +Option A (explicit): +- `GET /api/share/artifact?id=abc123` → returns bytes (MIDI or audio) + +Option B (format-specific): +- `GET /api/share/midi?id=abc123` +- `GET /api/share/audio?id=abc123` + +Landing params: +- MIDI artifact: `/play?m=&title=...&song=...` +- Audio artifact: `/play?a=&title=...&song=...` + +### Storage strategy (dev vs production) +- **Local dev**: filesystem under `server/cache/exports/` +- **Vercel production**: filesystem is not durable; prefer: + - blob storage (recommended), or + - KV/object store (if blob not available), or + - a small database row referencing a blob key + +### Security constraints (must-have) +- Validate `u` server-side: + - allow only `http`/`https` + - strongly consider allowlisting hosts (e.g. `bitmidi.com`) for v1 +- Enforce limits: + - max MIDI bytes + - max parsed duration + - max export size + - max export time/CPU +- Rate-limit export endpoints. +- Cache and dedupe exports: + - key by `(sourceMidiHash + preset + params + format)` to avoid repeated work + +## Files (expected) — implementation map +Frontend: +- `index.html`: Share UI elements +- `src/main.ts`: URL generation + clipboard + X intent +- `play.html` + `src/play.ts`: landing page UI + logic +- `vite.config.ts`: add `play.html` to multi-page inputs + +Backend: +- `server/src/server.ts`: route handlers for export endpoints +- `server/src/services/*`: reuse parsing/generation services as needed +- `server/cache/exports/`: local dev artifact cache + +Routing/deploy: +- `vercel.json`: add `/play` → `/play.html` similar to `/embed` + +## Test plan +- Desktop: + - Share from main app → open `/play?...` in new tab → loads → plays after click + - Copy link works; X intent opens with correct URL +- iOS Safari: + - `/play` shows Enable Audio when needed + - Tap Enable Audio → tap Play → sound +- Export flow: + - Export MIDI variant → receive `/play?m=...` → plays the exported MIDI reliably + - Export audio variant (if implemented) → `/play?a=...` → plays reliably + +## Success criteria +- A shared link reliably recreates “the thing” (preview or exported variant) and is playable with one tap. +- The system remains safe under abuse (no open proxy behavior, bounded cost). +